本周在閱讀redux源碼時宜岛,發(fā)現(xiàn)一個文章,由簡至深功舀,甚好萍倡,原文地址在這里,學習一下辟汰。
但是本篇內(nèi)容并不是介紹redux API的列敲,redux的使用可以參考阮一峰老師的博客阱佛,或者示例
目錄
- 前言
-
狀態(tài)管理器
- 簡單的狀態(tài)管理器
- 有計劃的狀態(tài)管理器
-
多文件協(xié)作
- reducer 的拆分和合并
- state 的拆分和合并
- 中間件 middleware
- 完整的 redux
-
最佳實踐
- 純函數(shù)
- 總結(jié)
前言
記得開始接觸 react 技術(shù)棧的時候,最難理解的地方就是 redux戴而。全是新名詞:reducer瘫絮、store、dispatch填硕、middleware 等等麦萤,我就理解 state 一個名詞。
網(wǎng)上找的 redux 文章扁眯,要不有一本書的厚度壮莹,要不很玄乎,晦澀難懂姻檀,越看越覺得難命满,越看越怕,信心都沒有了绣版!
花了很長時間熟悉 redux胶台,慢慢的發(fā)現(xiàn)它其實真的很簡單。本章不會把 redux 的各種概念杂抽,名詞解釋一遍诈唬,這樣和其他教程沒有任何區(qū)別,沒有太大意義缩麸。我會帶大家從零實現(xiàn)一個完整的 redux铸磅,讓大家知其然,知其所以然杭朱。
開始前阅仔,你必須知道一些事情:
- redux 和 react 沒有關(guān)系,redux 可以用在任何框架中弧械,忘掉 react八酒。
- connect 不屬于 redux,它其實屬于 react-redux刃唐,請先忘掉它羞迷,下一章節(jié),我們會介紹它唁桩。
- 請一定先忘記 reducer闭树、store耸棒、dispatch荒澡、middleware 等等這些名詞。
- redux 是一個狀態(tài)管理器与殃。
Let's Go单山!
狀態(tài)管理器
簡單的狀態(tài)管理器
redux 是一個狀態(tài)管理器碍现,那什么是狀態(tài)呢?狀態(tài)就是數(shù)據(jù)米奸,比如計數(shù)器中的 count昼接。
let state = {
count: 1
}
我們來使用下狀態(tài)
console.log(state.count);
我們來修改下狀態(tài)
state.count = 2;
好了,現(xiàn)在我們實現(xiàn)了狀態(tài)(計數(shù))的修改和使用了悴晰。
讀者:你當我傻嗎慢睡?你說的這個誰不知道?捶你<g-emoji class="g-emoji" alias="fist_oncoming" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f44a.png" style="box-sizing: border-box; font-family: "Apple Color Emoji", "Segoe UI", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.4em; font-weight: 400; line-height: 20px; vertical-align: middle; font-style: normal !important; margin-right: 0px;">??</g-emoji>铡溪!
筆者:哎哎哎漂辐,別打我!有話好好說棕硫!redux 核心就是這個呀髓涯!我們一步一步擴展開來嘛!
當然上面的有一個很明顯的問題:修改 count 之后哈扮,使用 count 的地方不能收到通知纬纪。我們可以使用發(fā)布-訂閱模式來解決這個問題。
/*------count 的發(fā)布訂閱者實踐------*/
let state = {
count: 1
};
let listeners = [];
/*訂閱*/
function subscribe(listener) {
listeners.push(listener);
}
function changeCount(count) {
state.count = count;
/*當 count 改變的時候滑肉,我們要去通知所有的訂閱者*/
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
}
我們來嘗試使用下這個簡單的計數(shù)狀態(tài)管理器包各。
/*來訂閱一下,當 count 改變的時候靶庙,我要實時輸出新的值*/
subscribe(() => {
console.log(state.count);
});
/*我們來修改下 state髓棋,當然我們不能直接去改 state 了,我們要通過 changeCount 來修改*/
changeCount(2);
changeCount(3);
changeCount(4);
現(xiàn)在我們可以看到惶洲,我們修改 count 的時候按声,會輸出相應(yīng)的 count 值。
現(xiàn)在有兩個新的問題擺在我們面前
- 這個狀態(tài)管理器只能管理 count恬吕,不通用
- 公共的代碼要封裝起來
我們嘗試來解決這個問題签则,把公共的代碼封裝起來
const createStore = function (initState) {
let state = initState;
let listeners = [];
/*訂閱*/
function subscribe(listener) {
listeners.push(listener);
}
function changeState(newState) {
state = newState;
/*通知*/
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
}
function getState() {
return state;
}
return {
subscribe,
changeState,
getState
}
}
我們來使用這個狀態(tài)管理器管理多個狀態(tài) counter 和 info 試試
let initState = {
counter: {
count: 0
},
info: {
name: '',
description: ''
}
}
let store = createStore(initState);
store.subscribe(() => {
let state = store.getState();
console.log(`${state.info.name}:${state.info.description}`);
});
store.subscribe(() => {
let state = store.getState();
console.log(state.counter.count);
});
store.changeState({
...store.getState(),
info: {
name: '前端九部',
description: '我們都是前端愛好者!'
}
});
store.changeState({
...store.getState(),
counter: {
count: 1
}
});
到這里我們完成了一個簡單的狀態(tài)管理器铐料。
這里需要理解的是 createStore
渐裂,提供了 changeState
,getState
钠惩,subscribe
三個能力柒凉。
本小節(jié)完整源碼見 demo-1
有計劃的狀態(tài)管理器
我們用上面的狀態(tài)管理器來實現(xiàn)一個自增,自減的計數(shù)器篓跛。
let initState = {
count: 0
}
let store = createStore(initState);
store.subscribe(() => {
let state = store.getState();
console.log(state.count);
});
/*自增*/
store.changeState({
count: store.getState().count + 1
});
/*自減*/
store.changeState({
count: store.getState().count - 1
});
/*我想隨便改*/
store.changeState({
count: 'abc'
});
你一定發(fā)現(xiàn)了問題膝捞,count 被改成了字符串 abc
,因為我們對 count 的修改沒有任何約束愧沟,任何地方蔬咬,任何人都可以修改鲤遥。
我們需要約束,不允許計劃外的 count 修改林艘,我們只允許 count 自增和自減兩種改變方式盖奈!
那我們分兩步來解決這個問題
- 制定一個 state 修改計劃,告訴 store狐援,我的修改計劃是什么钢坦。
- 修改 store.changeState 方法,告訴它修改 state 的時候啥酱,按照我們的計劃修改场钉。
我們來設(shè)置一個 plan 函數(shù),接收現(xiàn)在的 state懈涛,和一個 action逛万,返回經(jīng)過改變后的新的 state。
/*注意:action = {type:'',other:''}, action 必須有一個 type 屬性*/
function plan(state, action) {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1
}
case 'DECREMENT':
return {
...state,
count: state.count - 1
}
default:
return state;
}
}
我們把這個計劃告訴 store批钠,store.changeState 以后改變 state 要按照我的計劃來改宇植。
/*增加一個參數(shù) plan*/
const createStore = function (plan, initState) {
let state = initState;
let listeners = [];
function subscribe(listener) {
listeners.push(listener);
}
function changeState(action) {
/*請按照我的計劃修改 state*/
state = plan(state, action);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
}
function getState() {
return state;
}
return {
subscribe,
changeState,
getState
}
}
我們來嘗試使用下新的 createStore 來實現(xiàn)自增和自減
let initState = {
count: 0
}
/*把plan函數(shù)*/
let store = createStore(plan, initState);
store.subscribe(() => {
let state = store.getState();
console.log(state.count);
});
/*自增*/
store.changeState({
type: 'INCREMENT'
});
/*自減*/
store.changeState({
type: 'DECREMENT'
});
/*我想隨便改 計劃外的修改是無效的!*/
store.changeState({
count: 'abc'
});
到這里為止埋心,我們已經(jīng)實現(xiàn)了一個有計劃的狀態(tài)管理器指郁!
我們商量一下吧?我們給 plan 和 changeState 改下名字好不好拷呆?plan 改成 reducer闲坎,changeState 改成 dispatch!不管你同不同意茬斧,我都要換腰懂,因為新名字比較厲害(其實因為 redux 是這么叫的)!
本小節(jié)完整源碼見 demo-2
多文件協(xié)作
reducer 的拆分和合并
這一小節(jié)我們來處理下 reducer 的問題涛酗。啥問題揩尸?
我們知道 reducer 是一個計劃函數(shù),接收老的 state伦仍,按計劃返回新的 state娄蔼。那我們項目中怖喻,有大量的 state,每個 state 都需要計劃函數(shù)岁诉,如果全部寫在一起會是啥樣子呢锚沸?
所有的計劃寫在一個 reducer 函數(shù)里面,會導(dǎo)致 reducer 函數(shù)及其龐大復(fù)雜涕癣。按經(jīng)驗來說哗蜈,我們肯定會按組件維度來拆分出很多個 reducer 函數(shù),然后通過一個函數(shù)來把他們合并起來。
我們來管理兩個 state恬叹,一個 counter候生,一個 info同眯。
let state = {
counter: {
count: 0
},
info: {
name: '前端九部',
description: '我們都是前端愛好者绽昼!'
}
}
他們各自的 reducer
/*counterReducer, 一個子reducer*/
/*注意:counterReducer 接收的 state 是 state.counter*/
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return {
count: state.count + 1
}
case 'DECREMENT':
return {
...state,
count: state.count - 1
}
default:
return state;
}
}
/*InfoReducer,一個子reducer*/
/*注意:countReducer 接收的 state 是 state.info*/
function InfoReducer(state, action) {
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.name
}
case 'SET_DESCRIPTION':
return {
...state,
description: action.description
}
default:
return state;
}
}
那我們用 combineReducers 函數(shù)來把多個 reducer 函數(shù)合并成一個 reducer 函數(shù)须蜗。大概這樣用
const reducer = combineReducers({
counter: counterReducer,
info: InfoReducer
});
我們嘗試實現(xiàn)下 combineReducers 函數(shù)
function combineReducers(reducers) {
/* reducerKeys = ['counter', 'info']*/
const reducerKeys = Object.keys(reducers)
/*返回合并后的新的reducer函數(shù)*/
return function combination(state = {}, action) {
/*生成的新的state*/
const nextState = {}
/*遍歷執(zhí)行所有的reducers硅确,整合成為一個新的state*/
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
const reducer = reducers[key]
/*之前的 key 的 state*/
const previousStateForKey = state[key]
/*執(zhí)行 分 reducer,獲得新的state*/
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
}
return nextState;
}
}
我們來嘗試下 combineReducers 的威力吧
const reducer = combineReducers({
counter: counterReducer,
info: InfoReducer
});
let initState = {
counter: {
count: 0
},
info: {
name: '前端九部',
description: '我們都是前端愛好者明肮!'
}
}
let store = createStore(reducer, initState);
store.subscribe(() => {
let state = store.getState();
console.log(state.counter.count, state.info.name, state.info.description);
});
/*自增*/
store.dispatch({
type: 'INCREMENT'
});
/*修改 name*/
store.dispatch({
type: 'SET_NAME',
name: '前端九部2號'
});
本小節(jié)完整源碼見 demo-3
state 的拆分和合并
上一小節(jié)菱农,我們把 reducer 按組件維度拆分了,通過 combineReducers 合并了起來柿估。但是還有個問題循未, state 我們還是寫在一起的,這樣會造成 state 樹很龐大秫舌,不直觀的妖,很難維護。我們需要拆分足陨,一個 state嫂粟,一個 reducer 寫一塊。
這一小節(jié)比較簡單墨缘,我就不賣關(guān)子了星虹,用法大概是這樣(注意注釋)
/* counter 自己的 state 和 reducer 寫在一起*/
let initState = {
count: 0
}
function counterReducer(state, action) {
/*注意:如果 state 沒有初始值,那就給他初始值D魉稀宽涌!*/
if (!state) {
state = initState;
}
switch (action.type) {
case 'INCREMENT':
return {
count: state.count + 1
}
default:
return state;
}
}
我們修改下 createStore 函數(shù),增加一行 dispatch({ type: Symbol() })
const createStore = function (reducer, initState) {
let state = initState;
let listeners = [];
function subscribe(listener) {
listeners.push(listener);
}
function dispatch(action) {
state = reducer(state, action);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
}
function getState() {
return state;
}
/* 注意5濉;ぬ恰!只修改了這里嚼松,用一個不匹配任何計劃的 type嫡良,來獲取初始值 */
dispatch({ type: Symbol() })
return {
subscribe,
dispatch,
getState
}
}
我們思考下這行可以帶來什么效果?
- createStore 的時候献酗,用一個不匹配任何 type 的 action寝受,來觸發(fā)
state = reducer(state, action)
- 因為 action.type 不匹配,每個子 reducer 都會進到 default 項罕偎,返回自己初始化的 state很澄,這樣就獲得了初始化的 state 樹了。
你可以試試
/*這里沒有傳 initState 哦 */
const store = createStore(reducer);
/*這里看看初始化的 state 是什么*/
console.dir(store.getState());
本小節(jié)完整源碼見 demo-4
到這里為止,我們已經(jīng)實現(xiàn)了一個七七八八的 redux 啦甩苛!
中間件 middleware
中間件 middleware 是 redux 中最難理解的地方蹂楣。但是我挑戰(zhàn)一下用最通俗的語言來講明白它。如果你看完這一小節(jié)讯蒲,還沒明白中間件是什么痊土,不知道如何寫一個中間件,那就是我的鍋了墨林!
中間件是對 dispatch 的擴展赁酝,或者說重寫,增強 dispatch 的功能旭等!
記錄日志
我現(xiàn)在有一個需求酌呆,在每次修改 state 的時候,記錄下來 修改前的 state 搔耕,為什么修改了隙袁,以及修改后的 state。我們可以通過重寫 store.dispatch 來實現(xiàn)弃榨,直接看代碼
const store = createStore(reducer);
const next = store.dispatch;
/*重寫了store.dispatch*/
store.dispatch = (action) => {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
}
我們來使用下
store.dispatch({
type: 'INCREMENT'
});
日志輸出為
this state { counter: { count: 0 } }
action { type: 'INCREMENT' }
1
next state { counter: { count: 1 } }
現(xiàn)在我們已經(jīng)實現(xiàn)了一個完美的記錄 state 修改日志的功能菩收!
記錄異常
我又有一個需求,需要記錄每次數(shù)據(jù)出錯的原因惭墓,我們擴展下 dispatch
const store = createStore(reducer);
const next = store.dispatch;
store.dispatch = (action) => {
try {
next(action);
} catch (err) {
console.error('錯誤報告: ', err)
}
}
這樣每次 dispatch 出異常的時候坛梁,我們都會記錄下來。
多中間件的合作
我現(xiàn)在既需要記錄日志腊凶,又需要記錄異常划咐,怎么辦?當然很簡單了钧萍,兩個函數(shù)合起來唄褐缠!
store.dispatch = (action) => {
try {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
} catch (err) {
console.error('錯誤報告: ', err)
}
}
如果又來一個需求怎么辦?接著改 dispatch 函數(shù)风瘦?那再來10個需求呢队魏?到時候 dispatch 函數(shù)肯定龐大混亂到無法維護了!這個方式不可取呀万搔!
我們需要考慮如何實現(xiàn)擴展性很強的多中間件合作模式胡桨。
-
我們把 loggerMiddleware 提取出來
const store = createStore(reducer); const next = store.dispatch; const loggerMiddleware = (action) => { console.log('this state', store.getState()); console.log('action', action); next(action); console.log('next state', store.getState()); } store.dispatch = (action) => { try { loggerMiddleware(action); } catch (err) { console.error('錯誤報告: ', err) } }
-
我們把 exceptionMiddleware 提取出來
const exceptionMiddleware = (action) => { try { /*next(action)*/ loggerMiddleware(action); } catch (err) { console.error('錯誤報告: ', err) } } store.dispatch = exceptionMiddleware;
-
現(xiàn)在的代碼有一個很嚴重的問題,就是 exceptionMiddleware 里面寫死了 loggerMiddleware瞬雹,我們需要讓
next(action)
變成動態(tài)的昧谊,隨便哪個中間件都可以const exceptionMiddleware = (next) => (action) => { try { /*loggerMiddleware(action);*/ next(action); } catch (err) { console.error('錯誤報告: ', err) } } /*loggerMiddleware 變成參數(shù)傳進去*/ store.dispatch = exceptionMiddleware(loggerMiddleware);
-
同樣的道理,loggerMiddleware 里面的 next 現(xiàn)在恒等于 store.dispatch酗捌,導(dǎo)致 loggerMiddleware 里面無法擴展別的中間件了呢诬!我們也把 next 寫成動態(tài)的
const loggerMiddleware = (next) => (action) => { console.log('this state', store.getState()); console.log('action', action); next(action); console.log('next state', store.getState()); }
到這里為止涌哲,我們已經(jīng)探索出了一個擴展性很高的中間件合作模式!
const store = createStore(reducer);
const next = store.dispatch;
const loggerMiddleware = (next) => (action) => {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
}
const exceptionMiddleware = (next) => (action) => {
try {
next(action);
} catch (err) {
console.error('錯誤報告: ', err)
}
}
store.dispatch = exceptionMiddleware(loggerMiddleware(next));
這時候我們開開心心的新建了一個 loggerMiddleware.js
尚镰,一個exceptionMiddleware.js
文件阀圾,想把兩個中間件獨立到單獨的文件中去。會碰到什么問題嗎狗唉?
loggerMiddleware 中包含了外部變量 store初烘,導(dǎo)致我們無法把中間件獨立出去。那我們把 store 也作為一個參數(shù)傳進去好了~
const store = createStore(reducer);
const next = store.dispatch;
const loggerMiddleware = (store) => (next) => (action) => {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
}
const exceptionMiddleware = (store) => (next) => (action) => {
try {
next(action);
} catch (err) {
console.error('錯誤報告: ', err)
}
}
const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
store.dispatch = exception(logger(next));
到這里為止敞曹,我們真正的實現(xiàn)了兩個可以獨立的中間件啦账月!
現(xiàn)在我有一個需求综膀,在打印日志之前輸出當前的時間戳澳迫。用中間件來實現(xiàn)!
const timeMiddleware = (store) => (next) => (action) => {
console.log('time', new Date().getTime());
next(action);
}
...
const time = timeMiddleware(store);
store.dispatch = exception(time(logger(next)));
本小節(jié)完整源碼見 demo-6
中間件使用方式優(yōu)化
上一節(jié)我們已經(jīng)完全實現(xiàn)了正確的中間件剧劝!但是中間件的使用方式不是很友好
import loggerMiddleware from './middlewares/loggerMiddleware';
import exceptionMiddleware from './middlewares/exceptionMiddleware';
import timeMiddleware from './middlewares/timeMiddleware';
...
const store = createStore(reducer);
const next = store.dispatch;
const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
const time = timeMiddleware(store);
store.dispatch = exception(time(logger(next)));
其實我們只需要知道三個中間件橄登,剩下的細節(jié)都可以封裝起來!我們通過擴展 createStore 來實現(xiàn)讥此!
先來看看期望的用法
/*接收舊的 createStore拢锹,返回新的 createStore*/
const newCreateStore = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware)(createStore);
/*返回了一個 dispatch 被重寫過的 store*/
const store = newCreateStore(reducer);
實現(xiàn) applyMiddleware
const applyMiddleware = function (...middlewares) {
/*返回一個重寫createStore的方法*/
return function rewriteCreateStoreFunc(oldCreateStore) {
/*返回重寫后新的 createStore*/
return function newCreateStore(reducer, initState) {
/*1\. 生成store*/
const store = oldCreateStore(reducer, initState);
/*給每個 middleware 傳下store,相當于 const logger = loggerMiddleware(store);*/
/* const chain = [exception, time, logger]*/
const chain = middlewares.map(middleware => middleware(store));
let dispatch = store.dispatch;
/* 實現(xiàn) exception(time((logger(dispatch))))*/
chain.reverse().map(middleware => {
dispatch = middleware(dispatch);
});
/*2\. 重寫 dispatch*/
store.dispatch = dispatch;
return store;
}
}
}
讓用戶體驗美好
現(xiàn)在還有個小問題萄喳,我們有兩種 createStore 了
/*沒有中間件的 createStore*/
import { createStore } from './redux';
const store = createStore(reducer, initState);
/*有中間件的 createStore*/
const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
const newCreateStore = rewriteCreateStoreFunc(createStore);
const store = newCreateStore(reducer, initState);
為了讓用戶用起來統(tǒng)一一些卒稳,我們可以很簡單的使他們的使用方式一致,我們修改下 createStore 方法
const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
/*如果有 rewriteCreateStoreFunc他巨,那就采用新的 createStore */
if(rewriteCreateStoreFunc){
const newCreateStore = rewriteCreateStoreFunc(createStore);
return newCreateStore(reducer, initState);
}
/*否則按照正常的流程走*/
...
}
最終的用法
const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);
const store = createStore(reducer, initState, rewriteCreateStoreFunc);
本小節(jié)完整源碼見 demo-7
完整的 redux
退訂
不能退訂的訂閱都是耍流浪充坑!我們修改下 store.subscribe 方法,增加退訂功能
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
使用
const unsubscribe = store.subscribe(() => {
let state = store.getState();
console.log(state.counter.count);
});
/*退訂*/
unsubscribe();
中間件拿到的store
現(xiàn)在的中間件拿到了完整的 store染突,他甚至可以修改我們的 subscribe 方法捻爷,按照最小開放策略,我們只用把 getState 給中間件就可以了份企!因為我們只允許你用 getState 方法也榄!
修改下 applyMiddleware 中給中間件傳的 store
/*const chain = middlewares.map(middleware => middleware(store));*/
const simpleStore = { getState: store.getState };
const chain = middlewares.map(middleware => middleware(simpleStore));
compose
我們的 applyMiddleware 中,把 [A, B, C] 轉(zhuǎn)換成 A(B(C(next)))司志,是這樣實現(xiàn)的
const chain = [A, B, C];
let dispatch = store.dispatch;
chain.reverse().map(middleware => {
dispatch = middleware(dispatch);
});
redux 提供了一個 compose 方式甜紫,可以幫我們做這個事情
const chain = [A, B, C];
dispatch = compose(...chain)(store.dispatch)
看下他是如何實現(xiàn)的
export default function compose(...funcs) {
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
當然 compose 函數(shù)對于新人來說可能比較難理解,你只需要他是做什么的就行啦骂远!
省略initState
有時候我們創(chuàng)建 store 的時候不傳 initState囚霸,我們怎么用?
const store = createStore(reducer, {}, rewriteCreateStoreFunc);
redux 允許我們這樣寫
const store = createStore(reducer, rewriteCreateStoreFunc);
我們僅需要改下 createStore 函數(shù)吧史,如果第二個參數(shù)是一個object邮辽,我們認為他是 initState唠雕,如果是 function,我們就認為他是 rewriteCreateStoreFunc吨述。
function craeteStore(reducer, initState, rewriteCreateStoreFunc){
if (typeof initState === 'function'){
rewriteCreateStoreFunc = initState;
initState = undefined;
}
...
}
2 行代碼的 replaceReducer
reducer 拆分后岩睁,和組件是一一對應(yīng)的。我們就希望在做按需加載的時候揣云,reducer也可以跟著組件在必要的時候再加載捕儒,然后用新的 reducer 替換老的 reducer。
const createStore = function (reducer, initState) {
...
function replaceReducer(nextReducer) {
reducer = nextReducer
/*刷新一遍 state 的值邓夕,新來的 reducer 把自己的默認狀態(tài)放到 state 樹上去*/
dispatch({ type: Symbol() })
}
...
return {
...
replaceReducer
}
}
我們來嘗試使用下
const reducer = combineReducers({
counter: counterReducer
});
const store = createStore(reducer);
/*生成新的reducer*/
const nextReducer = combineReducers({
counter: counterReducer,
info: infoReducer
});
/*replaceReducer*/
store.replaceReducer(nextReducer);
replaceReducer 示例源碼見 demo-5
bindActionCreators
bindActionCreators 我們很少很少用到刘莹,一般只有在 react-redux 的 connect 實現(xiàn)中用到。
他是做什么的焚刚?他通過閉包点弯,把 dispatch 和 actionCreator 隱藏起來,讓其他地方感知不到 redux 的存在矿咕。
我們通過普通的方式來 隱藏 dispatch 和 actionCreator 試試抢肛,注意最后兩行代碼
const reducer = combineReducers({
counter: counterReducer,
info: infoReducer
});
const store = createStore(reducer);
/*返回 action 的函數(shù)就叫 actionCreator*/
function increment() {
return {
type: 'INCREMENT'
}
}
function setName(name) {
return {
type: 'SET_NAME',
name: name
}
}
const actions = {
increment: function () {
return store.dispatch(increment.apply(this, arguments))
},
setName: function () {
return store.dispatch(setName.apply(this, arguments))
}
}
/*注意:我們可以把 actions 傳到任何地方去*/
/*其他地方在實現(xiàn)自增的時候,根本不知道 dispatch碳柱,actionCreator等細節(jié)*/
actions.increment(); /*自增*/
actions.setName('九部威武'); /*修改 info.name*/
我眼睛一看捡絮,這個 actions 生成的時候,好多公共代碼莲镣,提取一下
const actions = bindActionCreators({ increment, setName }, store.dispatch);
來看一下 bindActionCreators 的源碼福稳,超級簡單(就是生成了剛才的 actions)
/*核心的代碼在這里,通過閉包隱藏了 actionCreator 和 dispatch*/
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments))
}
}
/* actionCreators 必須是 function 或者 object */
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error()
}
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
bindActionCreators 示例源碼見 demo-8
大功告成
完整的示例源碼見 demo-9瑞侮,你可以和 redux 源碼做一下對比的圆,你會發(fā)現(xiàn),我們已經(jīng)實現(xiàn)了 redux 所有的功能了区岗。
當然略板,為了保證代碼的理解性,我們少了一些參數(shù)驗證慈缔。比如 createStore(reducer)
的參數(shù) reducer 必須是 function 等等叮称。
最佳實踐
純函數(shù)
什么是純函數(shù)?
純函數(shù)是這樣一種函數(shù)藐鹤,即相同的輸入瓤檐,永遠會得到相同的輸出,而且沒有任何可觀察的副作用娱节。
通俗來講挠蛉,就兩個要素
- 相同的輸入,一定會得到相同的輸出
- 不會有 “觸發(fā)事件”肄满,更改輸入?yún)?shù)谴古,依賴外部參數(shù)质涛,打印 log 等等副作用
/*不是純函數(shù),因為同樣的輸入掰担,輸出結(jié)果不一致*/
function a( count ){
return count + Math.random();
}
/*不是純函數(shù)汇陆,因為外部的 arr 被修改了*/
function b( arr ){
return arr.push(1);
}
let arr = [1, 2, 3];
b(arr);
console.log(arr); //[1, 2, 3, 1]
/*不是純函數(shù),以為依賴了外部的 x*/
let x = 1;
function c( count ){
return count + x;
}
我們的 reducer 計劃函數(shù)带饱,就必須是一個純函數(shù)毡代!
只要傳入?yún)?shù)相同,返回計算得到的下一個 state 就一定相同勺疼。沒有特殊情況教寂、沒有副作用,沒有 API 請求执庐、沒有變量修改酪耕,單純執(zhí)行計算。
總結(jié)
到了最后耕肩,我想把 redux 中關(guān)鍵的名詞列出來因妇,你每個都知道是干啥的嗎问潭?
createStore
創(chuàng)建 store 對象猿诸,包含 getState, dispatch, subscribe, replaceReducerreducer
reducer 是一個計劃函數(shù),接收舊的 state 和 action狡忙,生成新的 stateaction
action 是一個對象梳虽,必須包含 type 字段dispatch
dispatch( action )
觸發(fā) action,生成新的 statesubscribe
實現(xiàn)訂閱功能灾茁,每次觸發(fā) dispatch 的時候窜觉,會執(zhí)行訂閱函數(shù)combineReducers
多 reducer 合并成一個 reducerreplaceReducer
替換 reducer 函數(shù)middleware
擴展 dispatch 函數(shù)!
你再看 redux 流程圖北专,是不是大徹大悟了禀挫?