三個(gè)原則
1.store是數(shù)據(jù)的唯一來源
2.state是只讀的
3.只能通過純函數(shù)改變state烈拒。
基于上述三點(diǎn)仆抵,下面來分析下這三個(gè)特點(diǎn)給我們編程過程中帶來的便利和麻煩架曹。
1. store是數(shù)據(jù)的唯一來源
便利:類比于觀察者模式莫杈。項(xiàng)目的各個(gè)部分只需要監(jiān)聽store 的變化诱鞠,就可以獲得相關(guān)信息挎挖。
麻煩:react中,數(shù)據(jù)流是自上而下單向的從父節(jié)點(diǎn)到子節(jié)點(diǎn)航夺。如果子節(jié)點(diǎn)要監(jiān)聽store的變化蕉朵,就需要從頂級(jí)父元素一層層傳遞到對(duì)應(yīng)的子節(jié)點(diǎn),增加了代碼的復(fù)雜性阳掐,降低了可讀性始衅。
解決方案:React—Redux里的Provider組件解決了這個(gè)問題。Provider組件使得子元素都可以獲得store缭保。connect組件將React組件和Redux store連接在一起汛闸。
2.state是只讀的
具體來說,只有通過發(fā)送action艺骂,才能改變state诸老,這樣將state的改變途徑限定了。而且action是一個(gè)純粹的對(duì)象彻亲,可以打印孕锄,或者存儲(chǔ)起來。
3.只能通過純函數(shù)改變state
首先說明下純函數(shù)的定義苞尝,純函數(shù)的返回值只會(huì)由輸入值決定。一樣的輸入宦芦,一樣的輸出宙址,state 的變化成為可預(yù)見的。并且輸入結(jié)果不會(huì)帶來其他變化调卑,例如抡砂,輸入值是一個(gè)對(duì)象大咱,純函數(shù)會(huì)返回一個(gè)新對(duì)象,而不會(huì)改變輸入的對(duì)象注益。
好處:可實(shí)現(xiàn)時(shí)間旅行碴巾。
解決方案:可以利用spread語(yǔ)法,或者Immutable庫(kù)來返回新對(duì)象丑搔。
關(guān)鍵概念
- store
- state
- action
- dispatch
store
store 保存應(yīng)用的state厦瓢,并且提供了四個(gè)方法。
- store.getState()獲得state啤月。
- store.dispatch(action)更新state煮仇。
- store.subscribe() 訂閱監(jiān)聽事件的發(fā)生。
- store.replaceReducer 替換reducer谎仲。
action
action 必須是plain object浙垫, 并且type是必傳項(xiàng)
// https://github.com/reduxjs/redux/blob/master/src/createStore.js
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
dispatch
// https://github.com/reduxjs/redux/blob/master/src/createStore.js
currentState = currentReducer(currentState, action)
// https://github.com/reduxjs/redux/blob/master/src/combineReducers.js
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
dispatch 一個(gè)action時(shí),所有的reducer都會(huì)執(zhí)行一次郑诺,然后reducer內(nèi)部有判斷action對(duì)應(yīng)的是否是當(dāng)前reducer夹姥。
例子
// action.js
export const ADD_PROJECT = 'ADD_PROJECT'
export const DELETE_PROJECT = 'DELETE_PROJECT'
export function addProject(data) {
return { type: ADD_PROJECT, data } // action 是 a plain object
}
export function deleteProject(index) {
return { type: DELETE_PROJECT, index }
}
// reducers.js
import {
ADD_PROJECT,
DELETE_PROJECT,
} from './actions'
function projects(state = [], action) {
switch (action.type) {
case ADD_PROJECT:
return [
...state,
{
id: state.length,
data: action.data,
}
]
case DELETE_PROJECT:
return state.filter((project) => {
if (project.id !== action.index) {
return project
}
return ''
}).filter(v=>v)
default:
return state
}
}
const rootReducers = combineReducers({
projects
})
export default rootReducers
//store.js
import { createStore } from 'redux'
import rootReducers from './reducers'
const store = createStore(rootReducers)
優(yōu)化
一個(gè)項(xiàng)目只有一個(gè)store。隨著項(xiàng)目越來越大辙诞,reducers越來越多辙售,可以參考flux的命名空間的方式,將reducers按照功能分組倘要。 reducers可以分為兩級(jí)圾亏,第一級(jí)判斷是否是當(dāng)前命名空間的方法,第二級(jí)在當(dāng)前命名空間下的具體的數(shù)據(jù)處理方法封拧。
實(shí)踐經(jīng)驗(yàn)
在SPA應(yīng)用中志鹃,在不刷新頁(yè)面的前提下,各個(gè)路徑所對(duì)應(yīng)的頁(yè)面會(huì)共享store泽西。這樣在實(shí)際開發(fā)過程中曹铃,可能會(huì)導(dǎo)致無法準(zhǔn)確判斷,當(dāng)前頁(yè)面對(duì)數(shù)據(jù)請(qǐng)求是否正常捧杉。
例如陕见,a路徑請(qǐng)求了template相關(guān)數(shù)據(jù),直接跳轉(zhuǎn)到b頁(yè)面時(shí)味抖,這部分?jǐn)?shù)據(jù)已經(jīng)存在评甜,可以直接使用。但是如果用戶直接訪問b頁(yè)面仔涩,那么b頁(yè)面自己本身需要去請(qǐng)求template相關(guān)數(shù)據(jù)忍坷。所以為了避免這個(gè)盲區(qū),在自測(cè)階段,可以刷新這個(gè)頁(yè)面佩研,來確保每個(gè)頁(yè)面保證了數(shù)據(jù)獲取的正確性柑肴。