快速入門
本文為Redux中文文件基礎部分閱讀總結,建議有時間的去詳盡的閱讀一遍 原文
介紹
-
Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。
-
動機:
隨著 JavaScript 單頁應用開發日趨複雜,JavaScript 需要管理比任何時候都要多的
state
(狀態)。 這些state
可能包括伺服器響應、快取資料、本地生成尚未持久化到伺服器的資料,也包括 UI 狀態,如啟用的路由,被選中的標籤,是否顯示載入動效或者分頁器等等。管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那麼當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。state 在什麼時候,由於什麼原因,如何變化已然不受控制。
通過限制更新發生的時間和方式,Redux 試圖讓 state 的變化變得可預測 。
-
核心概念
用一個普通物件來描述應用的
state
,當你想要更新state
中的資料時,你需要發起一個action
。Action
就是一個普通 JavaScript 物件(注意到沒,這兒沒有任何魔法?)用來描述發生了什麼 (或者說Action
是一個指示器,用來指示針對state
的更新,提出要幹什麼)。Reducer
是為了接受state
和action
,並 返回新的state
的函式 (或者說Reducer
是針對Action
提出的需求,來具體實現這一改動的函式)。總體流程就是:
state
要改變,發起一個Action
;Action
針對state
的改變,提出需要幹什麼 ;Reducer
根據Action
提出的內容,具體去完成這一改變,並且返回新的state
。 -
三大原則
Redux 可以用這三個基本原則來描述:
-
單一資料來源
整個應用的
state
被儲存在一棵 object tree 中,並且這個 object tree 只存在於 唯一一個store
中。 -
State 是隻讀的
唯一改變 state 的方法就是觸發
action
,action
是一個用於描述已發生事件的普通物件。 -
使用純函式來執行修改
為了描述
action
如何改變 state tree ,你需要編寫reducers
。reducer
只是一些 純函式 ,它接收先前的state
和action
,並返回新的state
。
-
基礎
Action
-
Action
是把資料從應用(譯者注:這裡之所以不叫 view 是因為這些資料有可能是伺服器響應,使用者輸入或其它非 view 的資料 )傳到 store 的有效載荷。它 是 store 資料的唯一來源 。一般來說你會通過store.dispatch()
將 action 傳到 store。例如,新增新 todo 任務的 action 是這樣的:
const ADD_TODO = 'ADD_TODO' 複製程式碼
{ type: ADD_TODO, text: 'Build my first Redux app' } 複製程式碼
Action 本質上是 JavaScript 普通物件。
我們約定,action 內必須使用一個字串型別的
type
欄位來表示將要執行的動作。多數情況下,type
會被定義成字串常量。當應用規模越來越大時,建議使用單獨的模組或檔案來存放 action。例如:
import { ADD_TODO, REMOVE_TODO } from '../actionTypes' 複製程式碼
樣板檔案使用提醒
使用單獨的模組或檔案來定義 action type 常量並不是必須的,甚至根本不需要定義。對於小應用來說,使用字串做 action type 更方便些。不過,在大型應用中把它們顯式地定義成常量還是利大於弊的。
我們應該儘量減少在 action 中傳遞的資料。
Reducer
-
Reducers 指定了 應用狀態的變化如何響應 actions 併傳送到 store 的,記住 actions 只是描述了有事情發生了這一事實,並沒有描述應用如何更新 state。
reducer 就是一個純函式,接收舊的 state 和 action,返回新的 state。
通俗的講:Reducer 就是給 Store 生產新 State 的地方。
(previousState, action) => newState 複製程式碼
reducer 是一個純函式,保持 reducer 純淨非常重要。永遠不要在 reducer 裡做這些操作:
- 修改傳入引數;
- 執行有副作用的操作,如 API 請求和路由跳轉;
- 呼叫非純函式,如
Date.now()
或Math.random()
。
需要謹記 reducer 一定要保持純淨。
只要傳入引數相同,返回計算得到的下一個 state 就一定相同。沒有特殊情況、沒有副作用,沒有 API 請求、沒有變數修改,單純執行計算。
-
combineReducers(reducers)
注意每個 reducer 只負責管理全域性 state 中它負責的一部分。每個 reducer 的 state 引數都不同,分別對應它管理的那部分 state 資料。
隨著應用的膨脹,我們還可以將拆分後的 reducer 放到不同的檔案中, 以保持其獨立性並用於專門處理不同的資料域。為此,Redux提供了
combineReducers()
工具類,將多個 reducer 管理起來,這樣可以消除一些樣本模板程式碼。例如:
import { combineReducers } from 'redux' const todoApp = combineReducers({ visibilityFilter, todos }) export default todoApp 複製程式碼
注意上面的寫法和下面完全等價:
export default function todoApp(state = {}, action) { return { visibilityFilter: visibilityFilter(state.visibilityFilter, action), todos: todos(state.todos, action) } } 複製程式碼
combineReducers()
所做的只是生成一個函式,這個函式來呼叫你的一系列 reducer,每個 reducer 根據它們的 key 來篩選出 state 中的一部分資料並處理,然後這個生成的函式再將所有 reducer 的結果合併成一個大的物件。如果combineReducers()
中包含的所有 reducers 都沒有更改 state,那麼也就不會建立一個新的物件。
Store
-
我們已經知道了 action 用來描述“發生了什麼”;reducers 根據 action 更新 state 。Store 就是把它們聯絡到一起的物件。
Store 有以下職責:
- 維持應用的 state;
- 提供
getState()
方法獲取 state; - 提供
dispatch(action)
方法更新 state; - 通過
subscribe(listener)
註冊監聽器; - 通過
subscribe(listener)
返回的函式登出監聽器。
再次強調一下 Redux 應用只有一個單一的 store。當需要拆分資料處理邏輯時,你應該使用 reducer 組合 而不是建立多個 store。
建立store:
根據已有的 reducer 來建立 store 是非常容易的。前面我們使用
combineReducers()
將多個 reducer 合併成為一個reducer(todoApp)。現在我們將其匯入,並傳遞createStore()
。import { createStore } from 'redux' import todoApp from './reducers' let store = createStore(todoApp) 複製程式碼
資料在 Redux 應用中的流動
-
嚴格的單向資料流是 Redux 架構的設計核心。 這意味著應用中所有的資料都遵循相同的生命週期,這樣可以讓應用變得更加可預測且容易理解。
Redux 應用中資料的生命週期遵循下面 4 個步驟:
-
呼叫
store.dispatch(action)
。Action 就是一個描述“發生了什麼”的普通物件。比如:
{ type: 'LIKE_ARTICLE', articleId: 42 } { type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } } { type: 'ADD_TODO', text: 'Read the Redux docs.' } 複製程式碼
你可以在任何地方呼叫
store.dispatch(action)
,包括元件中、XHR 回撥中、甚至定時器中。 -
Redux store 呼叫傳入的 reducer 函式。
Store 會把兩個引數傳入 reducer: 當前的 state 樹和 action。例如,在這個 todo 應用中,根 reducer 可能接收這樣的資料:
// 當前應用的 state(todos 列表和選中的過濾器) let previousState = { visibleTodoFilter: 'SHOW_ALL', todos: [ { text: 'Read the docs.', complete: false } ] } // 將要執行的 action(新增一個 todo) let action = { type: 'ADD_TODO', text: 'Understand the flow.' } // reducer 返回處理後的應用狀態 let nextState = todoApp(previousState, action) //當前state,action 複製程式碼
-
根 reducer 應該把多個子 reducer 輸出合併成一個單一的 state 樹。
Redux 原生提供
combineReducers()
輔助函式,來把根 reducer 拆分成多個函式,用於分別處理 state 樹的一個分支。例如:function todos(state = [], action) { // 省略處理邏輯... return nextState } function visibleTodoFilter(state = 'SHOW_ALL', action) { // 省略處理邏輯... return nextState } let todoApp = combineReducers({ //combineReducers()將多個reducer整合 todos, visibleTodoFilter }) 複製程式碼
-
Redux store 儲存了根 reducer 返回的完整 state 樹。
這個新的樹就是應用的下一個 state!所有訂閱
store.subscribe(listener)
的監聽器都將被呼叫;監聽器裡可以呼叫store.getState()
獲得當前 state。現在,可以應用新的 state 來更新 UI。如果你使用了 React Redux 這類的繫結庫,這時就應該呼叫
component.setState(newState)
來更新。 -
總結:
在寫程式設計中,這個Store一般是見不到它的身影。更多的是和 reducer,state,dispach action打交道。所以 React-Redux 的工作流程 就變成了這樣:
reducer ----> state ---> store ----> Component ----> UI ----> action ----> dipatch(action) ---> store -----> reducer 複製程式碼
由由於 store 不常見,所以可以簡單粗暴理解為:
reducer ----> state ----> Component ----> UI ----> action ----> dipatch(action) -----> reducer 複製程式碼
-