React只是一個視圖層的框架,負責把數據映射成DOM元素。但應用程序每每涉及到大量的數據交互和網絡請求,修改數據的頻率會很高,因此須要一種規範來約束對數據的更新,使得任何修改均可以被追蹤,這樣纔不懼應用程序的複雜性,實現良好的調試性能和可擴展能力。Redux是一個數據流和數據容器管理工具。這篇文章中有一組生動的對比圖看有無Redux時數據的處理方式。javascript
下面咱們看一個簡單的 todo-list 的例子。 html
//TodoApp.js
const [todos, setTodos] = useState([])
const addTodo = () => {}
const removeTodo = () => {}
const toggleTodo = () => {}
<AddTodo addTodo={addTodo} />
<TodoList removeTodo={removeTodo} toggleTodo={toggleTodo} todos={todos} /> 複製代碼
//TodoList.js
{
todos.map(todo => (
<TodoItem removeTodo={removeTodo} toggleTodo={toggleTodo} /> )) } 複製代碼
{
type: 'add',
payload: todo
}
複製代碼
咱們稱它爲Action,每次對todos進行操做時都發出這樣一個Action,就能夠很清楚的看到在對todos進行了什麼操做,此次操做攜帶的數據是什麼。直接把這樣一個Action對象丟給todos,todos是不知道該怎麼辦的,因此todos須要一個管家(dispatch)幫它處理而後把處理結果告訴它。下面讓咱們用代碼來實現一下dispatch。java
const dispatch = (action) => {
const { type, payload } = action
swicth(type) {
case 'set':
//set的邏輯
break;
case 'add':
//add的邏輯
break;
case 'remove':
//remove的邏輯
break;
case 'toggle':
//toggle的邏輯
break;
}
}
複製代碼
有了dispatch這個管家,如今處理addTodo的業務邏輯就很簡單了,只須要segmentfault
dispatch({
type: 'add',
payload: todo
})
複製代碼
由於每一次操做都是一個Action,而每個Action都只有兩個參數(type, payload),當操做頻繁時每次都寫上面的代碼會很麻煩,因此咱們考慮構建一個創造Action的函數actionCreator,這樣咱們就不用每次都手動生成Action了。由於有不少個Action,對應就會有不少個actionCreator,因此咱們考慮把全部的actionCreator放在一個單獨的文件actionCreators.js裏bash
//actionCreators.js
export const add = payload => ({
type: 'add',
payload
})
export const remove = payload => ({
type: 'remove',
payload
})
export const toggle = payload => ({
type: 'toggle',
payload
})
複製代碼
而後把actionCreators.js引入到TodoList.js中,如今咱們處理addTodo就只須要網絡
//TodoList.js
import * as actionCreators from './actionCreators';
dispatch(actionCreators.add(payload))
複製代碼
仔細看看每次操做都須要dispatch來派發Action,咱們能夠考慮再封裝一次,把dispatch也隱藏起來。框架
const addTodo = payload => dispatch(actionCreators.add(payload))
複製代碼
這樣咱們每次處理addTodo就只須要調用addTodo函數便可。這樣的封裝操做有不少個,咱們能夠批量實現一下,咱們但願獲得下面這樣的結果異步
{
addTodo: payload => dispatch(actionCreators.add(payload)),
removeTodo: payload => dispatch(actionCreators.remove(payload)),
toggleTodo: payload => dispatch(actionCreators.toggle(payload))
}
複製代碼
因而咱們編寫一個bindActionCreators函數來批量封裝獲得咱們想要的結果函數
function bindActionCreators(actionCreators, dispatch) {
const ret = {}
for(let key in actionCreators) {
ret[key] = function(...args) {
const actionCreator = actionCreators[key]
const action = actionCreator(...args)
dispatch(action)
}
}
return ret
}
複製代碼
如今咱們能夠這樣實現一個addTodo的操做工具
const {
add: addTodo,
remove: removeTodo,
toggle: toggleTodo
} = bindActionCreators(actionCreators, dispatch)
addTodo(payload)
複製代碼
由於TodoList的邏輯很簡單,因此咱們這樣改造完沒有看到很明顯的優點,因此讓咱們改造一下項目,讓項目變得稍微複雜一點,咱們新添加一個incrementCount變量,每次新添加一個todo,incrementCount就會加一。
const [todos, setTodos] = useState([])
const [incrementCount, setIncrementCount] = useState(0)
const dispatch = (action) => {
const { type, payload } = action
swicth(type) {
case 'set':
//set的邏輯
setIncrementCount(c => c + 1)
break;
case 'add':
//add的邏輯
setIncrementCount(c => c + 1)
break;
case 'remove':
//remove的邏輯
break;
case 'toggle':
//toggle的邏輯
break;
}
}
複製代碼
如今咱們看代碼能夠發現多個Action有一樣的邏輯,須要重複編碼實現,這是由於咱們是從Action的維度來執行的數據更新邏輯,可是這些Action操做都是爲了更新數據,爲了更加清晰,咱們能夠考慮從數據的維度來整理數據的更新邏輯,咱們但願有這樣的一個reducer,它接收state和action而後返回更新後的state數據,每個數據有本身單獨的reducer,而後返回合併後的多個reducer,如今讓咱們用代碼來實現一下。
//reducers.js
const reducers = {
todos(state, action) {
const { type, payload } = action
switch(type) {
case 'set':
return //set的邏輯
case 'add':
return //add的邏輯
case 'remove':
return //remove的邏輯
case 'toggle':
return //stoggle的邏輯
}
return state
},
incrementCount(state, action) {
const { type } = action
switch(type) {
case 'set':
return state + 1
case 'add':
return state + 1
}
return state
}
}
function combineReducers(reducers) {
return function reducer(state, action) {
const changed = {}
for(let key in reducers) {
changed[key] = reducers[key](state[key], action)
}
return {
...state,
...changed
}
}
}
export default combineReducers(reducers)
複製代碼
如今在TodoList.js中引入reducers.js,而後改寫dispatch函數
const dispatch = (action) => {
const state = {
todo,
incrementCount
}
const setters = {
todos: setTodos,
incrementCount: setIncrementCount
}
const newState = reducer(state, action)
for(let key in newState) {
setters[key](newState[key])
}
}
複製代碼
reducer的意義在於可以從數據字段的維度來處理action。
上面說了這麼多咱們都是在處理同步的Action,如今讓咱們思考一下如何處理異步的Action。最直接的想法就是咱們先處理異步的邏輯,異步結束後再派發一次Action,下面讓咱們用代碼來實現一下
//異步的Action
export const add = text => (dispatch, state) => {
setTimeout(() => {
const { todos } = state
if(!todos.find(todo => todo.text === text)) {
dispatch({
type: 'add',
payload: {
id: Date.now(),
text,
complete: false
}
})
}
}, 3000)
}
複製代碼
如今咱們的dispatch只能處理對象,不能處理異步Action的函數,因此讓咱們改寫一下dispatch讓它能夠支持對函數的處理
const dispatch = (action) => {
const state = {
todo,
incrementCount
}
const setters = {
todos: setTodos,
incrementCount: setIncrementCount
}
if('function' === typeof action) {
action(dispatch, state)
return
}
const newState = reducer(state, action)
for(let key in newState) {
setters[key](newState[key])
}
}
複製代碼
這樣咱們就實現了一個異步的Action,咱們但願增長todo時先判斷原有的todo列表中是否包含新添加todo的內容,若是是再也不添加,若是不是再添加。這時咱們進行調試若是在3s以前刪掉了重複的Action,咱們會發現3s後這個重複的Action仍是被添加到了todo的列表中。這是由於add函數拿到的數據是3s前的數據,爲了不這種狀況的出現,咱們會考慮用函數動態的獲取state裏面的數據,例如
addTodo(dispatch, () => state)
複製代碼
可是state這個對象老是在異步action發起以前臨時構成的,若是在3s內作了一些操做,那麼數據其實已經發生改變,異步Action內獲取到的仍是舊的數據。在每次渲染週期state都會改變,因此咱們能夠在組件以外建立一個store來存儲全部的state
let store = {
todo: [],
incrementCount: 0
}
//TodoList組件內同步數據
useEffect(() => {
Object.assign(store, {
todos,
incrementCount
})
}, [todos, incrementCount])
複製代碼
如今讓咱們改寫dispatch
const dispatch = (action) => {
const setters = {
todos: setTodos,
incrementCount: setIncrementCount
}
if('function' === typeof action) {
action(dispatch, () => store)
return
}
const newState = reducer(store, action)
for(let key in newState) {
setters[key](newState[key])
}
}
複製代碼
改寫異步Action
//異步的Action
export const add = text => (dispatch, getState) => {
setTimeout(() => {
const { todos } = getState()
if(!todos.find(todo => todo.text === text)) {
dispatch({
type: 'add',
payload: {
id: Date.now(),
text,
complete: false
}
})
}
}, 3000)
}
複製代碼
咱們能夠用actionCreators來生成一次操做的Action,用dispatch來派發這個Action,用reducer來更新數據,用bindActionCreators封裝多個Action的派發操做,用combineReducers將多個reducer合併成一個。
實際上Redux也只有最基本的功能,它自己不具有對異步Action的處理,可是在Reudx的整個流程中,在Action被dispatch派發到達reducer以前能夠通過多箇中間件的處理,這些中間件能夠加強dispatch的功能,好比Redux-thunk中間件就可讓dispatch具有處理異步Action的能力。若是想要對 Redux Store 進行更深層次的加強定製,就須要使用 Store Enhancer,利用 Store Enhancer 能夠加強 Redux Store 的 各個 方面。
Action -> dispatch -> 各類中間件 -> reducer -> store
複製代碼
我寫文章比較少,因此邏輯可能不是很清晰,若是有問題歡迎你們在評論區中提出,咱們一塊兒學習討論。本文是學習React勁爆新特性Hooks 重構去哪兒網火車票PWA這門課後,將老師講的內容加上一點點本身的理解寫成的。順便安利一下這門課,老師講的超級棒!!!再推薦一本書,程墨的《深刻淺出React和Redux》,裏面對Redux的原理也講解的十分清晰。