React 技術棧學習,redux 知識點
如何合理地設計 state
把整個應用的狀態按照領域(Domain)分成若干子 state,子 state 之間不能儲存重複的資料。
state 以鍵值對的結構儲存資料,以記錄的 key/ID 作為記錄的索引,記錄中的其他欄位都依賴於索引。
state 中不能儲存可以通過已有資料計算而來的資料,即 state 中的欄位不互相依賴。
設計 state 總結
設計 Redux State 的關鍵在於,像設計資料庫一樣設計 state。把 state 看作應用在記憶體中的一個數據庫,action、reducer 等看作操作這個資料庫的 SQL 語句。
state 的 key
- createStore 的引數 reducer 的 key 即為 store 樹上的 key,類似表名,在 reducer 裡僅能操作 reducer 的 key 對應的 store,不能操作別的 key 下的資料,所以,也不用給字首。
- 而在 mapStateToProps 函式裡,是需要寫 key,即需要指定“表名”的。
redux 的三大原則
單一資料來源
整個應用的 state 被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個 store 中。
這讓同構應用開發變得非常容易。來自服務端的 state 可以在無需編寫更多程式碼的情況下被序列化並注入到客戶端中。由於是單一的 state tree ,除錯也變得非常容易。在開發中,你可以把應用的 state 儲存在本地,從而加快開發速度。此外,受益於單一的 state tree ,以前難以實現的如“撤銷/重做”這類功能也變得輕而易舉。
state 是隻讀的
唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通物件。
這樣確保了檢視和網路請求都不能直接修改 state,相反它們只能表達想要修改的意圖。因為所有的修改都被集中化處理,且嚴格按照一個接一個的順序執行,因此不用擔心 race condition 的出現。 Action 就是普通物件而已,因此它們可以被日誌列印、序列化、儲存、後期除錯或測試時回放出來。
使用純函式來執行修改
為了描述 action 如何改變 state tree ,你需要編寫 reducers。
Reducer 只是一些純函式,它接收先前的 state 和 action,並返回新的 state。剛開始你可以只有一個 reducer,隨著應用變大,你可以把它拆成多個小的 reducers,分別獨立地操作 state tree 的不同部分,因為 reducer 只是函式,你可以控制它們被呼叫的順序,傳入附加資料,甚至編寫可複用的 reducer 來處理一些通用任務,如分頁器。
Redux 三個基本概念
action
- action 是把資料從應用層傳遞到 store 的有效載體,它是 store 資料的唯一來源。
- action 本質上是 JavaScript 普通物件。
- 我們約定,action 內必須使用一個字串型別的 type 欄位來表示將要執行的動作。
- action 僅僅表示某物件發生了什麼行為,我們應該儘量減少在 action 中傳遞的資料。
reducer
- action 只是描述了有事情發生了這一事實,並沒有指明應用如何更新 state。
- 而這正是 reducer 要做的事情。reducer 就是一個純函式,接收當前 state 和 action,返回新的 state。函式形式:
(previousState, action) => newState
- 只要傳入引數相同,返回的 newState 就一定相同。沒有特殊情況、沒有副作用,沒有 API 請求、沒有變數修改,單純執行計算。
- 保持 reducer 純淨非常重要。永遠不要在 reducer 裡做這些操作:
- 修改傳入引數;
- 執行有副作用的操作,如 API 請求和路由跳轉;
- 呼叫非純函式,如 Date.now() 或 Math.random()。
- 記得不要修改 previousState 的值,建立一個新的物件返回給 newState。
store
使用 reducers 來根據 action 更新 state, 儲存在 store 中。store 把之前建立的 action 和 reducer 聯絡在一起。
一個 Redux 應用中只有一個 store,store 儲存了唯一資料來源。
store 的職責有:
- 持有應用的 state;
- 提供 getState() 方法獲取 state;
- 提供 dispatch(action) 方法更新 state;
- 通過 subscribe(listener) 註冊監聽器;
- 通過 subscribe(listener) 返回的函式登出監聽器。
let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();
redux 資料流
redux 架構使用嚴格的單向資料流動方式,其生命週期分為以下四步:
- 應用呼叫 store.dispatch(action) 傳送 Action
- redux 根據傳入的 action 呼叫對應的 reducer 方法
- 根 reducer 把子 reducer 的結果合併成一顆 state 樹
- redux store 儲存根 reducer 生成的 state 樹
得到的 state 樹即為當前應用的下一個 state,所有訂閱 store.subscribe(listener) 的監聽器都將被呼叫。監聽器可以呼叫 store.getState() 獲得當前 state。
React-Redux 元件
- 如果一個元件既有 UI 又有業務邏輯,那怎麼辦?回答是,將它拆分成:外面是一個容器元件,裡面包了一個 UI 元件。前者負責與外部的通訊,將資料傳給後者,由後者渲染出檢視。
- React-Redux 規定,所有的 UI 元件都由使用者提供,容器元件則是由 React-Redux 自動生成。也就是說,使用者負責視覺層,狀態管理則是全部交給它。
UI 元件(presentational component)
UI 元件負責 UI 的呈現
- 只負責 UI 的呈現,不帶有任何業務邏輯
- 沒有狀態(即不使用 this.state 這個變數)
- 所有資料都由引數(this.props)提供
- 不使用任何 Redux 的 API
容器元件(container component)
負責管理資料和邏輯
- 負責管理資料和業務邏輯,不負責 UI 的呈現
- 帶有內部狀態
- 使用 Redux 的 API
react-redux
react-redux 提供了一個 connect 函式,用於把 react 元件和 redux 的 store 連線起來,生成一個容器元件,負責資料管理和業務邏輯,其簽名如下:
connect(mapStateToProps, mapDispatchToProps)(componentName)
mapStateToProps
輸入邏輯:外部的資料(即state物件)如何轉換為 UI 元件的引數。
- mapStateToProps 是一個函式。它的作用就是像它的名字那樣,建立一個從(外部的)state 物件到(UI 元件的)props 物件的對映關係。
- 作為函式,mapStateToProps 執行後應該返回一個物件,裡面的每一個鍵值對就是一個對映。
- mapStateToProps 會訂閱 store,每當 state 更新的時候,就會自動執行,重新計算 UI 元件的引數,從而觸發 UI 元件的重新渲染。
- mapStateToProps 的第一個引數總是 state 物件,還可以使用第二個引數(假如引數名為:ownProps),代表容器元件的 props 物件。
- 使用 ownProps 作為引數後,如果容器元件的引數發生變化,也會引發 UI 元件重新渲染。
- connect 方法可以省略 mapStateToProps 引數,那樣的話,UI 元件就不會訂閱 store,就是說 store 的更新不會引起 UI 元件的更新。
mapDispatchToProps
輸出邏輯:使用者發出的動作如何變為 Action 物件,繼而從 UI 元件傳出去。
- mapDispatchToProps 是 connect 函式的第二個引數,用來建立 UI 元件的引數到 store.dispatch 方法的對映。
- 換一種說法,它負責把需要用到的 action 對映到展示元件的 props 上。
- 也就是說,它定義了哪些使用者的操作應該當作 Action,傳給 store。
- mapDispatchToProps 可以是一個函式,也可以是一個物件。
- 如果 mapDispatchToProps 是一個函式,會得到 dispatch 和 ownProps(容器元件的 props 物件)兩個引數。
- mapDispatchToProps 作為函式,應該返回一個物件,該物件的每個鍵值對都是一個對映,定義了 UI 元件的引數怎樣發出 Action。
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
- 如果 mapDispatchToProps 是一個物件,它的每個鍵名也是對應 UI 元件的同名引數,鍵值應該是一個函式,會被當作 Action creator,返回的 Action 會由 Redux 自動發出。
- 舉例來說,上面的 mapDispatchToProps 寫成物件就是下面這樣。
const mapDispatchToProps = {
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
參考
- 有關 mapStateToProps 與 mapDispatchToProps,還可以參考《深入淺出React和Redux》P71-73,【3.2.5 React-Redux】章節。
- 阮一峰的網路日誌 -> Redux 入門教程(三):React-Redux 的用法
有對 connect(mapStateToProps, mapDispatchToProps)(componentName) 的詳細解讀,上面有關 mapStateToProps 與 mapDispatchToProps 的解讀文字,均來自這篇文章(略有修改)。
- Redux 官方文件中文翻譯
- Redux 文件 -> 英文原版
- 前端手記 TodoMVC 之 Redux 篇