開篇
- 本文內容是自己在學習Redux時候,結合 Redux中文文檔做的一次總結,若有不妥之處,忘加以斧正。
介紹
Redux是JavaScript狀態管理器,是一個可以提供可預測化的狀態管理(個人理解:可預測化的狀態管理可以理解爲根據已知的輸入條件、能夠得到固定的結果),Redux能提供一個最簡化的API來使用,同時又可以做到行爲的完全可預測;它體小精悍(只有2kB,包括依賴),不僅僅用於React框架、還能支持其他界面庫;
核心概念
-
動機
隨着 JavaScript 單頁應用開發日趨複雜,JavaScript 需要管理比任何時候都要多的 state (狀態),這些 state 可能包括服務器響應、緩存數據、本地生成尚未持久化到服務器的數據,也包括 UI 狀態,如激活的的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那麼當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在什麼時候,由於什麼原因,如何變化已然不受控制。 當系統變得錯綜複雜的時候,想重現問題或者添加新功能就會變得舉步維艱。
-
三大原則
-
1.單一數據源
整個應用的 state 被儲存在一棵 object tree 中,(請認真且嚴肅的記住這一點,真正需要操作的也是state、瓶頸也會在這裏),並且這個 object tree 只存在於唯一一個 store 中。
State (也稱爲 state tree) 是一個寬泛的概念,但是在 Redux API 中,通常是指一個唯一的 state 值,由 store 管理且由 getState() 方法獲得。它表示了 Redux 應用的全部狀態,通常爲一個多層嵌套的對象.
有一個約定成俗的習慣就是:頂層的state爲一個對象,可以是任意的數據類型;
type State = any
const initialState = { allIds: [], byIds: {} };
2.state只讀
唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。
這樣確保了視圖和網絡請求都不能直接修改 state;所有的修改都需要集中化處理;Action 是一個普通對象,用來表示即將改變 state 的意圖。它是將數據放入 store 的唯一途徑。無論是從 UI 事件、網絡回調,還是其他諸如 WebSocket 之類的數據源所獲得的數據,最終都會被 dispatch 成 action
有一個約定成俗的習慣:Action必須擁有一個Type域(通俗的講就是每一個Action在type鍵上會定義他的對象名稱,可以定義爲常量,也可以是module導入),它直接告訴需要被執行的是哪一個Action,除Action之外、其他的對象有自己定義;
type Action = Object
action案例
export const addTodo = content => ({ type: SHOW_ALL, filter }); export function activeError () { return { type: ACTIVE_ERROR } }
3.使用純函數進行修改
爲了描述 action 如何改變 state tree ,你需要編寫 Reducers。
Reducer 只是一些純函數,它接收自身的state 和 action,並且返回新的state,而且是必須的需要返回;如果有多個Reducers,你可以通過Redux提供的API方法 combineReducers,將多個Reducers進行合併;
Reducer (也稱爲 reducing function) 函數接受兩個參數:之前累積運算的結果和當前被累積的值,返回的是一個新的累積結果。該函數把一個集合歸併成一個單值,如同JavaScript中的
Array.prototype.reduce()
方法一樣;
type Reducer<S, A> = (state: S, action: A) => S
Reducer案例:
// 定義Reducer,接受一個自身的state和action(這裏的state默認值爲”SHOW_ALL“) function visibilityFilter(state = 'SHOW_ALL', action) { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter default: return state } } function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return [ ...state, { text: action.text, completed: false } ] case 'COMPLETE_TODO': return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: true }) } return todo }) default: return state } } import { combineReducers, createStore } from 'redux' let reducer = combineReducers({ visibilityFilter, todos }) let store = createStore(reducer)
-
生態系統(我這裏只列舉和React相關的)
-
1.不同框架綁定
- react-redux — React
2. 中間件
- redux-thunk — 用最簡單的方式搭建異步 action 構造器
- redux-promise — 遵從 FSA 標準的 promise 中間件
- redux-axios-middleware — 使用 axios HTTP 客戶端獲取數據的 Redux 中間件
- redux-observable — Redux 的 RxJS 中間件
- redux-rx — 給 Redux 用的 RxJS 工具,包括觀察變量的中間件
- redux-logger — 記錄所有 Redux action 和下一次 state 的日誌
- redux-immutable-state-invariant — 開發中的狀態變更提醒
- redux-unhandled-action — 開發過程中,若 Action 未使 State 發生變化則發出警告
- redux-analytics — Redux middleware 分析
- redux-gen — Redux middleware 生成器
- redux-saga — Redux 應用的另一種副作用 model
- redux-action-tree — Redux 的可組合性 Cerebral-style 信號
- apollo-client — 針對 GraphQL 服務器及基於 Redux 的 UI 框架的緩存客戶端
3. 更多生態系統:
- 訪問網址:https://www.redux.org.cn/docs/introduction/Ecosystem.html**
基礎介紹
Action
這裏的Action僅僅代表給Action下定義,還不涉及到調用使用
Action
是把數據從應用(譯者注:這裏之所以不叫 view 是因爲這些數據有可能是服務器響應,用戶輸入或其它非 view 的數據 )傳到 store 的有效載荷。它是 store 數據的唯一來源。一般來說你會通過 store.dispatch() 將 action 傳到 store。
本質上是一個JavaScript普通對象,由一個Type約定表示需要執行的動作
//Action type 常量
const ADD_TODO = 'ADD_TODO'
// 定義對象
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
// 引用
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
Action 創建函數(這裏的纔是生成action的方法,提供給Reduces調用)
注意“action” 和 “action 創建函數” 這兩個概念很容易混在一起,使用時最好注意區分。
// 定義addTodoAction函數;
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
通常我們在使用的時候、如何觸發這個action呢,在Redux中只需把 action 創建函數的結果傳給 dispatch() 方法即可發起一次 dispatch 過程;
dispatch(addTodo(text))
dispatch(completeTodo(index))
或者創建一個 被綁定的 action 創建函數 來自動 dispatch:
const boundAddTodo = text => dispatch(addTodo(text))
const boundCompleteTodo = index => dispatch(completeTodo(index))
然後直接調用
boundAddTodo(text);
boundCompleteTodo(index);
雖然說 store 裏能直接通過 store.dispatch() 調用 dispatch() 方法,但是多數情況下你會使用 react-redux 提供的 connect() 幫助器來調用。bindActionCreators() 可以自動把多個 action 創建函數 綁定到 dispatch() 方法上。
####案例(action.js):
/*
* action 類型
*/
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
/*
* 其它的常量
*/
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
/*
* action 創建函數
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
Reducer
Reducers 指定了應用狀態的變化如何響應 actions 併發送到 store 的,記住 actions 只是描述了有事情發生了這一事實,並沒有描述應用如何更新 state,Reducer就是用來處理如何更新的。
-
在設計Reducer之前,需要確定state對象返回的結構(重要,最終所有的state都是這裏返回的);
因爲Reducer處理完之後是需要返回一個新的對象結果所以我們需要用最簡單的形式把應用的state用對象描述出來;
-
Action處理
在確定好state對象的結構之後,就可以開發reducer了。reducer 就是一個純函數,接收舊的 state 和 action,返回新的 state
(previousState, action) => newState
之所以將這樣的函數稱之爲reducer,是因爲這種函數與被傳入 Array.prototype.reduce(reducer, ?initialValue) 裏的回調函數屬於相同的類型。
-
保持 reducer 純淨非常重要。永遠不要在 reducer 裏做這些操作:
- 修改傳入參數;
- 執行有副作用的操作,如 API 請求和路由跳轉;
- 調用非純函數,如 Date.now() 或 Math.random()。
在以下案例中會出現幾個函數,在這裏提前說明以下;
-
combineReducers(Redux API)
調用redux提供的combineReducers函數,該函數把多個reducer函數組合在一次,每個 reducer 根據它們的 key 來篩選出 state 中的一部分數據並處理;然後這個生成的新的函數再將所有 reducer 的結果合併成一個大的對象
-
不要修改 state。 使用 Object.assign() 新建了一個副本。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter }),因爲它會改變第一個參數的值。你必須把第一個參數設置爲空對象。你也可以開啓對ES7提案對象展開運算符的支持, 從而使用 { …state, …newState } 達到相同的目的。
-
在 default 情況下返回舊的 state。遇到未知的 action 時,一定要返回舊的 state。
-
注意每個 reducer 只負責管理全局 state 中它負責的一部分。每個 reducer 的 state 參數都不同,分別對應它管理的那部分 state 數據。
案例(reducers.js)
// 調用combineReducers,組合多個reducers
import { combineReducers } from 'redux'
// 引入action.js
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
// 定義 visibilityFilter的 reducer函數,這裏的states寫入默認值初始值,
// Redux 首次執行時,state 爲 undefined,此時我們可藉機設置並返回應用的初始 state
function visibilityFilter(state = SHOW_ALL/** es6寫法,提供默認值*/, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
// 定義 todos 的reducer函數,管理自己的state 默認爲【】
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state, // 數組對象的展開運算符,結果會合並,通Object.assgin({},state,{text:**,completed:***})
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
// 合併2個reducer函數
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
Store
在上面已經使用了action來描述發生了什麼、使用reducers來根據action更新state的用法;
Store 就是把它們聯繫到一起的對象。Store 有以下職責:
- 維持應用的 state;
- 提供 getState() 方法獲取 state;
- 提供 dispatch(action) 方法更新 state;
- 通過 subscribe(listener) 註冊監聽器;
- 通過 subscribe(listener) 返回的函數註銷監聽器。
再次強調一下 Redux 應用只有一個單一的 store。當需要拆分數據處理邏輯時,你應該使用 reducer 組合 而不是創建多個 store。
import { createStore } from 'redux'
// combineReducers() 合併之後
import todoApp from './reducers'
let store = createStore(todoApp)
// 第二個參數是可選的,用於設置 state 初始狀態
// let store = createStore(todoApp, window.STATE_FROM_SERVER)
總結
以上就是對Redux的學習記錄;總結如下;
-
在Redux中有三個概念 State、Action、Reducer
State
- state 爲應用的狀態;所有的狀態最後都會存儲在一顆object tree中。這個object tree存在store中;
- state 是隻讀;在操作的時候千萬不要改變其狀態,唯一能夠改變state的只有action(個人考慮可能是由於滿足單一數據源、單項數據流)
Action
- action唯一可以更改state,他是store唯一的數據來源,只是描述了有事情發生了這一事實,並沒有描述應用如何更新 state。並且通過action函數把數據從應用傳到store 的有效載荷。:
Reducer
reducers 指定了應用狀態的變化如何響應 actions 併發送到 store 的;其中每個reducer函數只負責管理全局 state 中它負責的一部分,其實也就是自己定義的部分,
需要注意幾點:
- 不要修改傳入參數;
- 不要執行有副作用的操作,如 API 請求和路由跳轉;
- 不要調用非純函數,如 Date.now() 或 Math.random()
- 在 default 情況下返回舊的 state
使用總結:
應用通過store.dispatch 分發action函數(“action函數”(就是生成action方法,與“action定義“是兩個不同的概念)” 可以傳入參數,可能在後面reducer中使用到),然後調用定義在reducer的函數(最後合併所有的reducer函數,返回一個大的集合對象,其實就是邏輯處理部分,這部分是不可見的),根據對應的action對象中的type選擇不同的處理程序;最後返回自己負責的state對象,這些state會修改store中的state