前言
記得開始接觸 react 技術(shù)棧的時(shí)候,最難理解的地方就是 redux黎做。全是新名詞:reducer叉跛、store、dispatch蒸殿、middleware 等等筷厘,我就理解 state 一個(gè)名詞。
網(wǎng)上找的 redux 文章宏所,要不有一本書的厚度酥艳,要不很玄乎,晦澀難懂楣铁,越看越覺得難玖雁,越看越怕,信心都沒有了盖腕!
花了很長(zhǎng)時(shí)間熟悉 redux赫冬,慢慢的發(fā)現(xiàn)它其實(shí)真的很簡(jiǎn)單浓镜。本章不會(huì)把 redux 的各種概念,名詞解釋一遍劲厌,這樣和其他教程沒有任何區(qū)別膛薛,沒有太大意義。我會(huì)帶大家從零實(shí)現(xiàn)一個(gè)完整的 redux补鼻,讓大家知其然哄啄,知其所以然。
開始前风范,你必須知道一些事情:
redux 和 react 沒有關(guān)系咨跌,redux 可以用在任何框架中,忘掉 react硼婿。
connect 不屬于 redux锌半,它其實(shí)屬于 react-redux,請(qǐng)先忘掉它寇漫,下一章節(jié)刊殉,我們會(huì)介紹它。
請(qǐng)一定先忘記 reducer州胳、store记焊、dispatch、middleware 等等這些名詞栓撞。
redux 是一個(gè)狀態(tài)管理器遍膜。
Let's Go!
狀態(tài)管理器
簡(jiǎn)單的狀態(tài)管理器
redux 是一個(gè)狀態(tài)管理器腐缤,那什么是狀態(tài)呢捌归?狀態(tài)就是數(shù)據(jù),比如計(jì)數(shù)器中的 count岭粤。
letstate?=?{
count:?1
}
我們來(lái)使用下狀態(tài)
console.log(state.count);
我們來(lái)修改下狀態(tài)
state.count = 2;
好了惜索,現(xiàn)在我們實(shí)現(xiàn)了狀態(tài)(計(jì)數(shù))的修改和使用了。
讀者:你當(dāng)我傻嗎剃浇?你說(shuō)的這個(gè)誰(shuí)不知道巾兆?捶你??!
筆者:哎哎哎虎囚,別打我角塑!有話好好說(shuō)!redux 核心就是這個(gè)呀淘讥!我們一步一步擴(kuò)展開來(lái)嘛圃伶!
當(dāng)然上面的有一個(gè)很明顯的問(wèn)題:修改 count 之后,使用 count 的地方不能收到通知。我們可以使用發(fā)布-訂閱模式來(lái)解決這個(gè)問(wèn)題窒朋。
/*------count?的發(fā)布訂閱者實(shí)踐------*/
letstate?=?{
count:1
};
letlisteners?=?[];
/*訂閱*/
functionsubscribe(listener){
listeners.push(listener);
}
functionchangeCount(count){
state.count?=?count;
/*當(dāng)?count?改變的時(shí)候搀罢,我們要去通知所有的訂閱者*/
for(leti?=0;?i?<?listeners.length;?i++)?{
constlistener?=?listeners[i];
listener();
}
}
我們來(lái)嘗試使用下這個(gè)簡(jiǎn)單的計(jì)數(shù)狀態(tài)管理器。
/*來(lái)訂閱一下侥猩,當(dāng)?count?改變的時(shí)候榔至,我要實(shí)時(shí)輸出新的值*/
subscribe(()=>{
console.log(state.count);
});
/*我們來(lái)修改下?state,當(dāng)然我們不能直接去改?state?了欺劳,我們要通過(guò)?changeCount?來(lái)修改*/
changeCount(2);
changeCount(3);
changeCount(4);
現(xiàn)在我們可以看到唧取,我們修改 count 的時(shí)候,會(huì)輸出相應(yīng)的 count 值划提。
現(xiàn)在有兩個(gè)新的問(wèn)題擺在我們面前
這個(gè)狀態(tài)管理器只能管理 count枫弟,不通用
公共的代碼要封裝起來(lái)
我們嘗試來(lái)解決這個(gè)問(wèn)題,把公共的代碼封裝起來(lái)
constcreateStore?=function(initState){
letstate?=?initState;
letlisteners?=?[];
/*訂閱*/
functionsubscribe(listener){
listeners.push(listener);
}
functionchangeState(newState){
state?=?newState;
/*通知*/
for(leti?=0;?i?<?listeners.length;?i++)?{
constlistener?=?listeners[i];
listener();
}
}
functiongetState(){
returnstate;
}
return{
subscribe,
changeState,
getState
}
}
我們來(lái)使用這個(gè)狀態(tài)管理器管理多個(gè)狀態(tài) counter 和 info 試試
letinitState?=?{
counter:?{
count:0
},
info:?{
name:'',
description:''
}
}
letstore?=?createStore(initState);
store.subscribe(()=>{
letstate?=?store.getState();
console.log(`${state.info.name}:${state.info.description}`);
});
store.subscribe(()=>{
letstate?=?store.getState();
console.log(state.counter.count);
});
store.changeState({
...store.getState(),
info:?{
name:'前端九部',
description:'我們都是前端愛好者鹏往!'
}
});
store.changeState({
...store.getState(),
counter:?{
count:1
}
});
到這里我們完成了一個(gè)簡(jiǎn)單的狀態(tài)管理器媒区。
這里需要理解的是 createStore,提供了 changeState掸犬,getState,subscribe 三個(gè)能力绪爸。
本小節(jié)完整源碼見 demo-1
有計(jì)劃的狀態(tài)管理器
我們用上面的狀態(tài)管理器來(lái)實(shí)現(xiàn)一個(gè)自增湾碎,自減的計(jì)數(shù)器。
letinitState?=?{
count:0
}
letstore?=?createStore(initState);
store.subscribe(()=>{
letstate?=?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)了問(wèn)題奠货,count 被改成了字符串 abc介褥,因?yàn)槲覀儗?duì) count 的修改沒有任何約束,任何地方递惋,任何人都可以修改柔滔。
我們需要約束,不允許計(jì)劃外的 count 修改萍虽,我們只允許 count 自增和自減兩種改變方式睛廊!
那我們分兩步來(lái)解決這個(gè)問(wèn)題
制定一個(gè) state 修改計(jì)劃,告訴 store杉编,我的修改計(jì)劃是什么超全。
修改 store.changeState 方法,告訴它修改 state 的時(shí)候邓馒,按照我們的計(jì)劃修改嘶朱。
我們來(lái)設(shè)置一個(gè) plan 函數(shù),接收現(xiàn)在的 state光酣,和一個(gè) action疏遏,返回經(jīng)過(guò)改變后的新的 state。
/*注意:action =?{type:'',other:''}, action 必須有一個(gè) type 屬性*/
functionplan(state,?action){
switch(action.type)?{
case'INCREMENT':
return{
...state,
count:?state.count?+1
}
case'DECREMENT':
return{
...state,
count:?state.count?-1
}
default:
returnstate;
}
}
我們把這個(gè)計(jì)劃告訴 store,store.changeState 以后改變 state 要按照我的計(jì)劃來(lái)改财异。
/*增加一個(gè)參數(shù)?plan*/
constcreateStore?=function(plan,?initState){
letstate?=?initState;
letlisteners?=?[];
functionsubscribe(listener){
listeners.push(listener);
}
functionchangeState(action){
/*請(qǐng)按照我的計(jì)劃修改?state*/
state?=?plan(state,?action);
for(leti?=0;?i?<?listeners.length;?i++)?{
constlistener?=?listeners[i];
listener();
}
}
functiongetState(){
returnstate;
}
return{
subscribe,
changeState,
getState
}
}
我們來(lái)嘗試使用下新的 createStore 來(lái)實(shí)現(xiàn)自增和自減
letinitState?=?{
count:0
}
/*把plan函數(shù)*/
letstore?=?createStore(plan,?initState);
store.subscribe(()=>{
letstate?=?store.getState();
console.log(state.count);
});
/*自增*/
store.changeState({
type:'INCREMENT'
});
/*自減*/
store.changeState({
type:'DECREMENT'
});
/*我想隨便改?計(jì)劃外的修改是無(wú)效的倘零!*/
store.changeState({
count:'abc'
});
到這里為止,我們已經(jīng)實(shí)現(xiàn)了一個(gè)有計(jì)劃的狀態(tài)管理器宝当!
我們商量一下吧视事?我們給 plan 和 changeState 改下名字好不好?plan 改成 reducer庆揩,changeState 改成 dispatch俐东!不管你同不同意,我都要換订晌,因?yàn)樾旅直容^厲害(其實(shí)因?yàn)?redux 是這么叫的)!
本小節(jié)完整源碼見 demo-2
多文件協(xié)作
reducer 的拆分和合并
這一小節(jié)我們來(lái)處理下 reducer 的問(wèn)題虏辫。啥問(wèn)題?
我們知道 reducer 是一個(gè)計(jì)劃函數(shù)锈拨,接收老的 state砌庄,按計(jì)劃返回新的 state。那我們項(xiàng)目中奕枢,有大量的 state娄昆,每個(gè) state 都需要計(jì)劃函數(shù),如果全部寫在一起會(huì)是啥樣子呢缝彬?
所有的計(jì)劃寫在一個(gè) reducer 函數(shù)里面萌焰,會(huì)導(dǎo)致 reducer 函數(shù)及其龐大復(fù)雜。按經(jīng)驗(yàn)來(lái)說(shuō)谷浅,我們肯定會(huì)按組件維度來(lái)拆分出很多個(gè) reducer 函數(shù)扒俯,然后通過(guò)一個(gè)函數(shù)來(lái)把他們合并起來(lái)。
我們來(lái)管理兩個(gè) state一疯,一個(gè) counter撼玄,一個(gè) info。
letstate?=?{
counter:?{
count:?0
},
info:?{
name:'前端九部',
description:'我們都是前端愛好者墩邀!'
}
}
他們各自的 reducer
/*counterReducer,?一個(gè)子reducer*/
/*注意:counterReducer 接收的 state 是 state.counter*/
functioncounterReducer(state,?action){
switch(action.type)?{
case'INCREMENT':
return{
count:?state.count?+1
}
case'DECREMENT':
return{
...state,
count:?state.count?-1
}
default:
returnstate;
}
}
/*InfoReducer掌猛,一個(gè)子reducer*/
/*注意:InfoReducer 接收的 state 是 state.info*/
functionInfoReducer(state,?action){
switch(action.type)?{
case'SET_NAME':
return{
...state,
name:?action.name
}
case'SET_DESCRIPTION':
return{
...state,
description:?action.description
}
default:
returnstate;
}
}
那我們用 combineReducers 函數(shù)來(lái)把多個(gè) reducer 函數(shù)合并成一個(gè) reducer 函數(shù)。大概這樣用
constreducer?=?combineReducers({
counter:?counterReducer,
info:?InfoReducer
});
我們嘗試實(shí)現(xiàn)下?combineReducers?函數(shù)
functioncombineReducers(reducers){
/*?reducerKeys?=?['counter',?'info']*/
constreducerKeys?=Object.keys(reducers)
/*返回合并后的新的reducer函數(shù)*/
returnfunctioncombination(state?=?{},?action){
/*生成的新的state*/
constnextState?=?{}
/*遍歷執(zhí)行所有的reducers眉睹,整合成為一個(gè)新的state*/
for(leti?=0;?i?<?reducerKeys.length;?i++)?{
constkey?=?reducerKeys[i]
constreducer?=?reducers[key]
/*之前的?key?的?state*/
constpreviousStateForKey?=?state[key]
/*執(zhí)行?分?reducer留潦,獲得新的state*/
constnextStateForKey?=?reducer(previousStateForKey,?action)
nextState[key]?=?nextStateForKey
}
returnnextState;
}
}
我們來(lái)嘗試下 combineReducers 的威力吧
constreducer?=?combineReducers({
counter:?counterReducer,
info:?InfoReducer
});
letinitState?=?{
counter:?{
count:0
},
info:?{
name:'前端九部',
description:'我們都是前端愛好者!'
}
}
letstore?=?createStore(reducer,?initState);
store.subscribe(()=>{
letstate?=?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號(hào)'
});
本小節(jié)完整源碼見 demo-3
state 的拆分和合并
上一小節(jié)辣往,我們把 reducer 按組件維度拆分了兔院,通過(guò) combineReducers 合并了起來(lái)。但是還有個(gè)問(wèn)題站削, state 我們還是寫在一起的坊萝,這樣會(huì)造成 state 樹很龐大,不直觀,很難維護(hù)十偶。我們需要拆分菩鲜,一個(gè) state,一個(gè) reducer 寫一塊惦积。
這一小節(jié)比較簡(jiǎn)單接校,我就不賣關(guān)子了,用法大概是這樣(注意注釋)
/*?counter?自己的?state?和?reducer?寫在一起*/
letinitState?=?{
count:0
}
functioncounterReducer(state,?action){
/*注意:如果 state 沒有初始值狮崩,那就給他初始值V朊恪!*/
if(!state)?{
state?=?initState;
}
switch(action.type)?{
case'INCREMENT':
return{
count:?state.count?+1
}
default:
returnstate;
}
}
我們修改下 createStore 函數(shù)睦柴,增加一行 dispatch({ type: Symbol() })
constcreateStore?=function(reducer,?initState){
letstate?=?initState;
letlisteners?=?[];
functionsubscribe(listener){
listeners.push(listener);
}
functiondispatch(action){
state?=?reducer(state,?action);
for(leti?=0;?i?<?listeners.length;?i++)?{
constlistener?=?listeners[i];
listener();
}
}
functiongetState(){
returnstate;
}
/*?注意7塘琛!坦敌!只修改了這里侣诵,用一個(gè)不匹配任何計(jì)劃的 type,來(lái)獲取初始值?*/
dispatch({type:Symbol()?})
return{
subscribe,
dispatch,
getState
}
}
我們思考下這行可以帶來(lái)什么效果狱窘?
createStore 的時(shí)候杜顺,用一個(gè)不匹配任何 type 的 action,來(lái)觸發(fā) state = reducer(state, action)
因?yàn)?action.type 不匹配蘸炸,每個(gè)子 reducer 都會(huì)進(jìn)到 default 項(xiàng)哑舒,返回自己初始化的 state,這樣就獲得了初始化的 state 樹了幻馁。
你可以試試
/*這里沒有傳?initState?哦?*/
conststore?=?createStore(reducer);
/*這里看看初始化的?state?是什么*/
console.dir(store.getState());
本小節(jié)完整源碼見 demo-4
到這里為止,我們已經(jīng)實(shí)現(xiàn)了一個(gè)七七八八的 redux 啦越锈!
中間件 middleware
中間件 middleware 是 redux 中最難理解的地方仗嗦。但是我挑戰(zhàn)一下用最通俗的語(yǔ)言來(lái)講明白它。如果你看完這一小節(jié)甘凭,還沒明白中間件是什么稀拐,不知道如何寫一個(gè)中間件,那就是我的鍋了丹弱!
中間件是對(duì) dispatch 的擴(kuò)展德撬,或者說(shuō)重寫,增強(qiáng) dispatch 的功能躲胳!
記錄日志
我現(xiàn)在有一個(gè)需求蜓洪,在每次修改 state 的時(shí)候,記錄下來(lái) 修改前的 state 坯苹,為什么修改了隆檀,以及修改后的 state。我們可以通過(guò)重寫?store.dispatch?來(lái)實(shí)現(xiàn),直接看代碼
conststore?=?createStore(reducer);
constnext?=?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());
}
我們來(lái)使用下
store.dispatch({
type:'INCREMENT'
});
日志輸出為
thisstate{counter:?{?count:0}?}
action{type:'INCREMENT'}
1
nextstate{counter:?{?count:1}?}
現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了一個(gè)完美的記錄 state 修改日志的功能恐仑!
記錄異常
我又有一個(gè)需求泉坐,需要記錄每次數(shù)據(jù)出錯(cuò)的原因,我們擴(kuò)展下 dispatch
conststore?=?createStore(reducer);
constnext?=?store.dispatch;
store.dispatch?=(action)?=>{
try{
next(action);
}catch(err)?{
console.error('錯(cuò)誤報(bào)告:?',?err)
}
}
這樣每次?dispatch?出異常的時(shí)候裳仆,我們都會(huì)記錄下來(lái)腕让。
多中間件的合作
我現(xiàn)在既需要記錄日志,又需要記錄異常歧斟,怎么辦纯丸?當(dāng)然很簡(jiǎn)單了,兩個(gè)函數(shù)合起來(lái)唄构捡!
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('錯(cuò)誤報(bào)告:?',?err)
}
}
如果又來(lái)一個(gè)需求怎么辦液南?接著改?dispatch?函數(shù)?那再來(lái)10個(gè)需求呢勾徽?到時(shí)候 dispatch 函數(shù)肯定龐大混亂到無(wú)法維護(hù)了滑凉!這個(gè)方式不可取呀!
我們需要考慮如何實(shí)現(xiàn)擴(kuò)展性很強(qiáng)的多中間件合作模式喘帚。
1畅姊、我們把?loggerMiddleware?提取出來(lái)
conststore?=?createStore(reducer);
constnext?=?store.dispatch;
constloggerMiddleware?=(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('錯(cuò)誤報(bào)告:?',?err)
}
}
2、我們把?exceptionMiddleware?提取出來(lái)
constexceptionMiddleware?=(action)?=>{
try{
/*next(action)*/
loggerMiddleware(action);
}catch(err)?{
console.error('錯(cuò)誤報(bào)告:?',?err)
}
}
store.dispatch?=?exceptionMiddleware;
3吹由、現(xiàn)在的代碼有一個(gè)很嚴(yán)重的問(wèn)題若未,就是 exceptionMiddleware 里面寫死了loggerMiddleware,我們需要讓?next(action)變成動(dòng)態(tài)的倾鲫,隨便哪個(gè)中間件都可以
constexceptionMiddleware?=(next)?=>(action)?=>?{
try{
/*loggerMiddleware(action);*/
next(action);
}catch(err)?{
console.error('錯(cuò)誤報(bào)告:?',?err)
}
}
/*loggerMiddleware?變成參數(shù)傳進(jìn)去*/
store.dispatch?=?exceptionMiddleware(loggerMiddleware);
4粗合、同樣的道理,loggerMiddleware?里面的?next?現(xiàn)在恒等于?store.dispatch乌昔,導(dǎo)致?loggerMiddleware?里面無(wú)法擴(kuò)展別的中間件了隙疚!我們也把 next 寫成動(dòng)態(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)探索出了一個(gè)擴(kuò)展性很高的中間件合作模式磕道!
conststore?=?createStore(reducer);
constnext?=?store.dispatch;
constloggerMiddleware?=(next)?=>(action)?=>?{
console.log('this?state',?store.getState());
console.log('action',?action);
next(action);
console.log('next?state',?store.getState());
}
constexceptionMiddleware?=(next)?=>(action)?=>?{
try{
next(action);
}catch(err)?{
console.error('錯(cuò)誤報(bào)告:?',?err)
}
}
store.dispatch?=?exceptionMiddleware(loggerMiddleware(next));
這時(shí)候我們開開心心的新建了一個(gè)?loggerMiddleware.js供屉,一個(gè)exceptionMiddleware.js文件,想把兩個(gè)中間件獨(dú)立到單獨(dú)的文件中去溺蕉。會(huì)碰到什么問(wèn)題嗎伶丐?
loggerMiddleware?中包含了外部變量?store,導(dǎo)致我們無(wú)法把中間件獨(dú)立出去疯特。那我們把 store 也作為一個(gè)參數(shù)傳進(jìn)去好了~
conststore?=?createStore(reducer);
constnext??=?store.dispatch;
constloggerMiddleware?=(store)?=>(next)?=>(action)?=>{
console.log('this?state',?store.getState());
console.log('action',?action);
next(action);
console.log('next?state',?store.getState());
}
constexceptionMiddleware?=(store)?=>(next)?=>(action)?=>{
try{
next(action);
}catch(err)?{
console.error('錯(cuò)誤報(bào)告:?',?err)
}
}
constlogger?=?loggerMiddleware(store);
constexception?=?exceptionMiddleware(store);
store.dispatch?=?exception(logger(next));
到這里為止哗魂,我們真正的實(shí)現(xiàn)了兩個(gè)可以獨(dú)立的中間件啦!
現(xiàn)在我有一個(gè)需求漓雅,在打印日志之前輸出當(dāng)前的時(shí)間戳啡彬。用中間件來(lái)實(shí)現(xiàn)羹与!
consttimeMiddleware?=(store)?=>(next)?=>(action)?=>{
console.log('time',newDate().getTime());
next(action);
}
...
const?time?=?timeMiddleware(store);
store.dispatch?=?exception(time(logger(next)));
本小節(jié)完整源碼見 demo-6
中間件使用方式優(yōu)化
上一節(jié)我們已經(jīng)完全實(shí)現(xiàn)了正確的中間件!但是中間件的使用方式不是很友好
importloggerMiddlewarefrom'./middlewares/loggerMiddleware';
importexceptionMiddlewarefrom'./middlewares/exceptionMiddleware';
importtimeMiddlewarefrom'./middlewares/timeMiddleware';
...
const?store?=?createStore(reducer);
constnext?=?store.dispatch;
constlogger?=?loggerMiddleware(store);
constexception?=?exceptionMiddleware(store);
consttime?=?timeMiddleware(store);
store.dispatch?=?exception(time(logger(next)));
其實(shí)我們只需要知道三個(gè)中間件庶灿,剩下的細(xì)節(jié)都可以封裝起來(lái)纵搁!我們通過(guò)擴(kuò)展 createStore 來(lái)實(shí)現(xiàn)!
先來(lái)看看期望的用法
/*接收舊的?createStore往踢,返回新的?createStore*/
constnewCreateStore?=?applyMiddleware(exceptionMiddleware,?timeMiddleware,?loggerMiddleware)(createStore);
/*返回了一個(gè)?dispatch?被重寫過(guò)的?store*/
conststore?=?newCreateStore(reducer);
實(shí)現(xiàn)?applyMiddleware
constapplyMiddleware?=function(...middlewares){
/*返回一個(gè)重寫createStore的方法*/
returnfunctionrewriteCreateStoreFunc(oldCreateStore){
/*返回重寫后新的?createStore*/
returnfunctionnewCreateStore(reducer,?initState){
/*1.?生成store*/
conststore?=?oldCreateStore(reducer,?initState);
/*給每個(gè)?middleware?傳下store腾誉,相當(dāng)于?const?logger?=?loggerMiddleware(store);*/
/*?const?chain?=?[exception,?time,?logger]*/
constchain?=?middlewares.map(middleware=>middleware(store));
letdispatch?=?store.dispatch;
/*?實(shí)現(xiàn)?exception(time((logger(dispatch))))*/
chain.reverse().map(middleware=>{
dispatch?=?middleware(dispatch);
});
/*2.?重寫?dispatch*/
store.dispatch?=?dispatch;
returnstore;
}
}
}
讓用戶體驗(yàn)美好
現(xiàn)在還有個(gè)小問(wèn)題,我們有兩種?createStore?了
/*沒有中間件的?createStore*/
import{?createStore?}from'./redux';
conststore?=?createStore(reducer,?initState);
/*有中間件的?createStore*/
constrewriteCreateStoreFunc?=?applyMiddleware(exceptionMiddleware,?timeMiddleware,?loggerMiddleware);
constnewCreateStore?=?rewriteCreateStoreFunc(createStore);
conststore?=?newCreateStore(reducer,?initState);
為了讓用戶用起來(lái)統(tǒng)一一些峻呕,我們可以很簡(jiǎn)單的使他們的使用方式一致利职,我們修改下createStore?方法
constcreateStore?=(reducer,?initState,?rewriteCreateStoreFunc)?=>{
/*如果有?rewriteCreateStoreFunc,那就采用新的?createStore?*/
if(rewriteCreateStoreFunc){
constnewCreateStore?=??rewriteCreateStoreFunc(createStore);
returnnewCreateStore(reducer,?initState);
}
/*否則按照正常的流程走*/
...
}
最終的用法
constrewriteCreateStoreFunc?=?applyMiddleware(exceptionMiddleware,?timeMiddleware,?loggerMiddleware);
conststore?=?createStore(reducer,?initState,?rewriteCreateStoreFunc);
本小節(jié)完整源碼見 demo-7
完整的 redux
退訂
不能退訂的訂閱都是耍流浪瘦癌!我們修改下 store.subscribe 方法猪贪,增加退訂功能
functionsubscribe(listener){
listeners.push(listener);
returnfunctionunsubscribe(){
constindex?=?listeners.indexOf(listener)
listeners.splice(index,1)
}
}
使用
constunsubscribe?=?store.subscribe(()=>{
letstate?=?store.getState();
console.log(state.counter.count);
});
/*退訂*/
unsubscribe();
中間件拿到的store
現(xiàn)在的中間件拿到了完整的?store,他甚至可以修改我們的?subscribe?方法讯私,按照最小開放策略热押,我們只用把?getState?給中間件就可以了!因?yàn)槲覀冎辉试S你用getState?方法斤寇!
修改下?applyMiddleware?中給中間件傳的?store
/*const?chain?=?middlewares.map(middleware?=>?middleware(store));*/
constsimpleStore?=?{getState:?store.getState?};
constchain?=?middlewares.map(middleware=>middleware(simpleStore));
compose
我們的?applyMiddleware?中桶癣,把?[A, B, C]?轉(zhuǎn)換成?A(B(C(next))),是這樣實(shí)現(xiàn)的
constchain?=?[A,?B,?C];
letdispatch?=?store.dispatch;
chain.reverse().map(middleware=>{
dispatch?=?middleware(dispatch);
});
redux 提供了一個(gè)?compose?方式娘锁,可以幫我們做這個(gè)事情
const?chain?=?[A,?B,?C];
dispatch?=?compose(...chain)(store.dispatch)
看下他是如何實(shí)現(xiàn)的
exportdefaultfunctioncompose(...funcs){
if(funcs.length?===1)?{
returnfuncs[0]
}
returnfuncs.reduce((a,?b)?=>(...args)?=>?a(b(...args)))
}
當(dāng)然?compose?函數(shù)對(duì)于新人來(lái)說(shuō)可能比較難理解牙寞,你只需要他是做什么的就行啦!
省略initState
有時(shí)候我們創(chuàng)建?store?的時(shí)候不傳?initState莫秆,我們?cè)趺从茫?/p>
conststore?=?createStore(reducer,?{},?rewriteCreateStoreFunc);
redux 允許我們這樣寫
conststore?=?createStore(reducer,?rewriteCreateStoreFunc);
我們僅需要改下?createStore?函數(shù)间雀,如果第二個(gè)參數(shù)是一個(gè)object,我們認(rèn)為他是?initState镊屎,如果是?function惹挟,我們就認(rèn)為他是?rewriteCreateStoreFunc。
functioncraeteStore(reducer,?initState,?rewriteCreateStoreFunc){
if(typeofinitState?==='function'){
rewriteCreateStoreFunc?=?initState;
initState?=undefined;
}
...
}
2 行代碼的 replaceReducer
reducer?拆分后杯道,和組件是一一對(duì)應(yīng)的。我們就希望在做按需加載的時(shí)候责蝠,reducer也可以跟著組件在必要的時(shí)候再加載党巾,然后用新的?reducer?替換老的?reducer。
constcreateStore?=function(reducer,?initState){
...
functionreplaceReducer(nextReducer){
reducer?=?nextReducer
/*刷新一遍?state?的值霜医,新來(lái)的?reducer?把自己的默認(rèn)狀態(tài)放到?state?樹上去*/
dispatch({?type:?Symbol()?})
}
...
return{
...
replaceReducer
}
}
我們來(lái)嘗試使用下
constreducer?=?combineReducers({
counter:?counterReducer
});
conststore?=?createStore(reducer);
/*生成新的reducer*/
constnextReducer?=?combineReducers({
counter:?counterReducer,
info:?infoReducer
});
/*replaceReducer*/
store.replaceReducer(nextReducer);
replaceReducer?示例源碼見 demo-5
bindActionCreators
bindActionCreators 我們很少很少用到齿拂,一般只有在 react-redux 的 connect 實(shí)現(xiàn)中用到。
他是做什么的肴敛?他通過(guò)閉包署海,把 dispatch 和 actionCreator 隱藏起來(lái)吗购,讓其他地方感知不到 redux 的存在。
我們通過(guò)普通的方式來(lái) 隱藏 dispatch 和 actionCreator 試試砸狞,注意最后兩行代碼
constreducer?=?combineReducers({
counter:?counterReducer,
info:?infoReducer
});
conststore?=?createStore(reducer);
/*返回?action?的函數(shù)就叫?actionCreator*/
functionincrement(){
return{
type:'INCREMENT'
}
}
functionsetName(name){
return{
type:'SET_NAME',
name:?name
}
}
constactions?=?{
increment:function(){
returnstore.dispatch(increment.apply(this,arguments))
},
setName:function(){
returnstore.dispatch(setName.apply(this,arguments))
}
}
/*注意:我們可以把 actions 傳到任何地方去*/
/*其他地方在實(shí)現(xiàn)自增的時(shí)候捻勉,根本不知道?dispatch,actionCreator等細(xì)節(jié)*/
actions.increment();/*自增*/
actions.setName('九部威武');/*修改?info.name*/
我眼睛一看刀森,這個(gè)?actions?生成的時(shí)候踱启,好多公共代碼,提取一下
constactions?=?bindActionCreators({?increment,?setName?},?store.dispatch);
來(lái)看一下?bindActionCreators?的源碼研底,超級(jí)簡(jiǎn)單(就是生成了剛才的 actions)
/*核心的代碼在這里埠偿,通過(guò)閉包隱藏了?actionCreator?和?dispatch*/
functionbindActionCreator(actionCreator,?dispatch){
returnfunction(){
returndispatch(actionCreator.apply(this,arguments))
}
}
/*?actionCreators?必須是?function?或者?object?*/
exportdefaultfunctionbindActionCreators(actionCreators,?dispatch){
if(typeofactionCreators?==='function')?{
returnbindActionCreator(actionCreators,?dispatch)
}
if(typeofactionCreators?!=='object'||?actionCreators?===null)?{
thrownewError()
}
constkeys?=Object.keys(actionCreators)
constboundActionCreators?=?{}
for(leti?=0;?i?<?keys.length;?i++)?{
constkey?=?keys[i]
constactionCreator?=?actionCreators[key]
if(typeofactionCreator?==='function')?{
boundActionCreators[key]?=?bindActionCreator(actionCreator,?dispatch)
}
}
returnboundActionCreators
}
bindActionCreators?示例源碼見 demo-8
大功告成
完整的示例源碼見 demo-9,你可以和 redux 源碼做一下對(duì)比榜晦,你會(huì)發(fā)現(xiàn)冠蒋,我們已經(jīng)實(shí)現(xiàn)了 redux 所有的功能了。
當(dāng)然乾胶,為了保證代碼的理解性抖剿,我們少了一些參數(shù)驗(yàn)證。比如 createStore(reducer)的參數(shù) reducer 必須是 function 等等胚吁。
最佳實(shí)踐
純函數(shù)
什么是純函數(shù)牙躺?
純函數(shù)是這樣一種函數(shù),即相同的輸入腕扶,永遠(yuǎn)會(huì)得到相同的輸出孽拷,而且沒有任何可觀察的副作用。
通俗來(lái)講半抱,就兩個(gè)要素
相同的輸入脓恕,一定會(huì)得到相同的輸出
不會(huì)有 “觸發(fā)事件”,更改輸入?yún)?shù)窿侈,依賴外部參數(shù)炼幔,打印 log 等等副作用
/*不是純函數(shù),因?yàn)橥瑯拥妮斎胧芳颍敵鼋Y(jié)果不一致*/
functiona(?count?){
returncount?+Math.random();
}
/*不是純函數(shù)乃秀,因?yàn)橥獠康?arr?被修改了*/
functionb(?arr?){
returnarr.push(1);
}
letarr?=?[1,2,3];
b(arr);
console.log(arr);//[1,?2,?3,?1]
/*不是純函數(shù),以為依賴了外部的?x*/
letx?=1;
functionc(?count?){
returncount?+?x;
}
我們的?reducer?計(jì)劃函數(shù)圆兵,就必須是一個(gè)純函數(shù)跺讯!
只要傳入?yún)?shù)相同,返回計(jì)算得到的下一個(gè) state 就一定相同殉农。沒有特殊情況刀脏、沒有副作用,沒有 API 請(qǐng)求超凳、沒有變量修改愈污,單純執(zhí)行計(jì)算耀态。
總結(jié)
到了最后,我想把 redux 中關(guān)鍵的名詞列出來(lái)暂雹,你每個(gè)都知道是干啥的嗎首装?
createStore
創(chuàng)建 store 對(duì)象,包含 getState, dispatch, subscribe, replaceReducer
reducer
reducer 是一個(gè)計(jì)劃函數(shù)擎析,接收舊的 state 和 action簿盅,生成新的 state
action
action 是一個(gè)對(duì)象,必須包含 type 字段
dispatch
dispatch( action ) 觸發(fā) action揍魂,生成新的 state
subscribe
實(shí)現(xiàn)訂閱功能桨醋,每次觸發(fā) dispatch 的時(shí)候,會(huì)執(zhí)行訂閱函數(shù)
combineReducers
多 reducer 合并成一個(gè) reducer
replaceReducer
替換 reducer 函數(shù)
middleware
擴(kuò)展 dispatch 函數(shù)现斋!
你再看 redux 流程圖喜最,是不是大徹大悟了?