使用 React、Redux 和 Bootstrap 實現 Alert
今天,我們來學習使用 React、Redux 和 Bootstrap 實現Alert。
例子
這個例子實現了彈出不同類型信息的功能,這些信息默認會在5秒后消失,你也可以手動點擊使其消失。如果你在服務端有信息要提示,還可以通過 Redux 的單一數據源傳到客戶端在渲染頁面時顯示出來。
源代碼:
https://github.com/lewis617/react-redux-tutorial/tree/master/r2-bs-alert
安裝:
npm i
開發環境下運行:
npm start
生產環境下構建:
npm run build
測試:
npm test
為何使用 Redux ?
React 有自己的局部狀態(Local State),可以幫助我們在不同狀態下渲染不同的界面。那么實現 Alert 為何要使用 Redux ?眾所周知,Alert 通常都是要在程序中全局使用的:
- 用戶操作可能會發出 Alert。
- 網絡請求等異步事件的處理器(Event Handler)可能會發出 Alert。
- 甚至服務器渲染頁面時,也可能會給客戶端帶來一個Alert(比如,你提交表單出錯了,服務器重定向到表單頁面,並顯示錯誤提示)。
知道了 Alert 要全局使用,我們再來看 Redux。Redux 有一個全局單一的數據源,這個數據源可以通過 react-redux 連接到程序的任意一個組件。不但如此,更新這個數據源的 action,也可以全局使用:
- 用戶操作可以發起 action。
- 網絡請求等異步事件的處理器(Event Handler)可能會發起 action。
- 甚至在服務器端也可以發起 action,然后將單一數據源傳給客戶端繼續使用。
Redux 牛逼的設計讓處理全局狀態變得特別方便,實現 Universal 渲染(有些人喜歡叫SSR,但我覺得不准確)也變得非常容易。這與實現 Alert 的需求非常吻合,因此,本文通過 Redux 來輔助實現 Alert。
從另一個方面來說,一些不在全局使用的組件和功能,一般使用React的局部狀態就可以了,切記不要什么功能都用 Redux 實現。
了解了為何要使用 Redux,我們就開始動工吧!
快速創建項目
搭建一個 React APP 的成本是很高的,你需要:
- 使用 Webpack 打包。
- 使用 Babel 編譯。
- 搭建開發服務器。
- 使用 ESLint 進行語法檢查。
- 使用 Mocha 或 Jest 進行測試。
- ……
很多人詬病這一點,不過這些東西都是重復性的體力活,社區早就造好了輪子,來提高生產力。本文就使用了 create-react-app 來快速搭建項目。All you need is these command:
npm install -g create-react-app create-react-app my-app cd my-app/ npm start
然后上述所有東西就都有了!
注意,要將 npm 設為淘寶源或你自己公司的私有 npm 源(只要快就行),否則速度會非常慢,甚至可能導致安裝失敗。
接下來,我們就開始編寫代碼。
設計編寫 Redux
在一個 React 與 Redux 中的程序中,React 負責程序界面,而 Redux 負責程序功能。由於本例界面比較容易,所以我們先來設計 Redux。
我們期望的Alert 的功能包括:
- 顯示一條信息。
- 隱藏一條信息。
- 顯示一條信息,過幾秒自動隱藏。
- 如果服務器傳來有信息,在頁面渲染完,過幾秒也自動隱藏。
功能明確了,讓我們把它們寫成函數:
- alertShow
- alertHide
- alertMessage
- hideAllAlert
src/alert/redux.js
export const ALERT_SHOW = 'ALERT_SHOW'; export const ALERT_HIDE = 'ALERT_HIDE'; export function alertShow(messageText, messageType, key) { return { type: ALERT_SHOW, payload: { messageText, messageType, key } }; } export function alertHide(key) { return { type: ALERT_HIDE, payload: { key } }; } export function alertMessage(messageText, messageType, delay = 5000) { return (dispatch, getState) => { if (typeof messageText === 'string' && ['success', 'warning', 'danger', 'info'].indexOf(messageType) > -1) { const key = getState().alerts.lastKey + 1; dispatch(alertShow(messageText, messageType, key)); setTimeout(() => dispatch(alertHide(key)), delay); } else { console.error('messageText must be string and messageType must be success, warning, danger, info'); } }; } export function hideAllAlert(delay = 5000) { return (dispatch, getState) => { getState().alerts.items.forEach((item) => { setTimeout(() => { dispatch(alertHide(item.key)); }, delay); }); }; }
盡管部分邏輯有點復雜,但都封裝在 action 創建函數中了,多么清晰和模塊化!接下來,我們編寫 reducer 來根據這些 action,進行 state 的更新。
src/alert/redux.js
export default function (state = { lastKey: -1, items: [] }, action) { switch (action.type) { case ALERT_SHOW: return { ...state, items: [...state.items, action.payload], lastKey: state.lastKey + 1 }; case ALERT_HIDE: return { ...state, items: state.items.filter(item => (item.key !== action.payload.key)) }; default: return state; } }
這里使用了解構賦值和重寫的語法來保證 state 的不可變(Immutable)。
Redux 的 state 要求是不可變數據,這么做的原因是方便進行快速變更檢查,進而有利於React組件判斷是否需要重新渲染(re-render)。另外,不可變數據還有利於進行狀態回退,錯誤追蹤。不可變數據是函數式編程中一個常用的概念。關於不可變以及函數式編程在 React 與 Redux 中的應用,《React與Redux開發實例精解》 這本書中有非常詳細的介紹,推薦閱讀參考。
至此,Redux的編寫就完成了。它的輸出有四個:
- reducer函數,用於創建store。
- alertHide 函數用於隱藏指定信息。
- alertMessage 函數用於顯示一條信息,並在幾秒后隱藏。
- hideAllAlert 函數用於在渲染完頁面后,過幾秒隱藏服務器傳來的信息。
接下來,我們來編寫 React 組件。
設計編寫 React 組件
React 組件的功能包括三個:
- 渲染要顯示的信息,並根據類型渲染成不同顏色。
- 為每條信息渲染一個按鈕,使用戶可以通過點擊按鈕隱藏該信息。
- 在第一次渲染后,過幾秒隱藏來自服務器的信息。
為此,我們做了以下幾件事:
- 首先,使用 react-redux 的 connect 將 Redux 的 state 和 action 創建函數傳給組件。
- 然后在組件中遍歷渲染出所有信息(使用了 react-bootstrap 提供的 Alert 組件)。
- 最后,將 alertHide 函數綁在按鈕的點擊事件上,將 hideAllAlert 函數綁在組件渲染后的生命周期鈎子上。
組件功能就實現了!
src/alert/AlertList.js import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import Alert from 'react-bootstrap/lib/Alert'; import { hideAllAlert, alertHide } from './redux'; class AlertList extends Component { static propTypes = { alerts: PropTypes.array.isRequired, hideAllAlert: PropTypes.func.isRequired, alertHide: PropTypes.func.isRequired }; componentDidMount() { this.props.hideAllAlert(); } render() { const { alerts, alertHide } = this.props; return ( <div> {alerts.map((item, i) => ( <Alert key={i} bsStyle={item.messageType} onDismiss={() => alertHide(item.key)} > {item.messageText} </Alert> ))} </div> ); } } export default connect( state => ({ alerts: state.alerts.items }), { hideAllAlert, alertHide } )(AlertList);
為了讓 connect 可以獲取到 Redux 的 state 和 dispatch 方法,我們還需要在組件頂部提供store。
src/index.js
// 三個參數分別為 reducer、initialState 和 enhancer const store = createStore( combineReducers({ alerts: alertsReducer }), {}, applyMiddleware(thunk) ); // 在渲染之前發起action,用於模擬從服務器傳來的信息 store.dispatch(alertMessage('message from server', 'info')); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
為了使用 bootstrap,還需要安裝 bootstrap,並引用它的樣式文件。
npm i bootstrap@3 --save
src/index.js
import 'bootstrap/dist/css/bootstrap.css';
至此,所有功能就都實現了!在后續的文章中,我們將介紹如何測試本例編寫的 Redux 函數以及 React 組件。要知道,寫測試是一個工程師走向成熟的必經之路,而且在 React 與 Redux 的應用中編寫測試簡直太方便了!
總結
- Redux 適合實現全局性的組件和功能,一些局部使用的功能使用 React 的局部狀態即可。
- 推薦使用 create-react-app 來快速搭建React應用。
- Redux 的 action 創建函數與要實現的功能一一對應。
- Redux 的 state 為不可變數據。
- 使用 react-redux 的 connect 將 Redux 的 state 和 action 創建函數連接到組件,進而渲染界面,綁定事件。
教程源代碼及目錄
如果您覺得本博客教程幫到了您,就賞顆星吧!
https://github.com/lewis617/react-redux-tutorial