1. 程式人生 > >React 技術棧學習,redux 知識點

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

  1. action 是把資料從應用層傳遞到 store 的有效載體,它是 store 資料的唯一來源。
  2. action 本質上是 JavaScript 普通物件。
  3. 我們約定,action 內必須使用一個字串型別的 type 欄位來表示將要執行的動作。
  4. action 僅僅表示某物件發生了什麼行為,我們應該儘量減少在 action 中傳遞的資料。

reducer

  1. action 只是描述了有事情發生了這一事實,並沒有指明應用如何更新 state。
  2. 而這正是 reducer 要做的事情。reducer 就是一個純函式,接收當前 state 和 action,返回新的 state。函式形式:(previousState, action) => newState
  3. 只要傳入引數相同,返回的 newState 就一定相同。沒有特殊情況、沒有副作用,沒有 API 請求、沒有變數修改,單純執行計算。
  4. 保持 reducer 純淨非常重要。永遠不要在 reducer 裡做這些操作:
    • 修改傳入引數;
    • 執行有副作用的操作,如 API 請求和路由跳轉;
    • 呼叫非純函式,如 Date.now() 或 Math.random()。
  5. 記得不要修改 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 架構使用嚴格的單向資料流動方式,其生命週期分為以下四步:

  1. 應用呼叫 store.dispatch(action) 傳送 Action
  2. redux 根據傳入的 action 呼叫對應的 reducer 方法
  3. 根 reducer 把子 reducer 的結果合併成一顆 state 樹
  4. 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
  };
}

參考

  1. 有關 mapStateToProps 與 mapDispatchToProps,還可以參考《深入淺出React和Redux》P71-73,【3.2.5 React-Redux】章節。
  2. 阮一峰的網路日誌 -> Redux 入門教程(三):React-Redux 的用法

    有對 connect(mapStateToProps, mapDispatchToProps)(componentName) 的詳細解讀,上面有關 mapStateToProps 與 mapDispatchToProps 的解讀文字,均來自這篇文章(略有修改)。

  3. Redux 官方文件中文翻譯
  4. Redux 文件 -> 英文原版
  5. 前端手記 TodoMVC 之 Redux 篇

擴充套件閱讀

  1. 劉一奇 -> React 與 Redux 系列教程,一共八篇文章
    1. 官方例子解讀–> React 與 Redux 教程(一)connect、applyMiddleware、thunk、webpackHotMiddleware
    2. React 與 Redux 教程(二)Redux的單一狀態樹完全替代了React的狀態機?
  2. 理解Redux應用架構——(一)Redux結構概覽
  3. 理解Redux應用架構——(二)建立store的createStore
  4. 理解Redux應用架構——(三)讓你愛上的Redux middleware