什么是Redux?
管理整個(gè)前端項(xiàng)目(單頁應(yīng)用)所有的狀態(tài)數(shù)據(jù)灯萍,統(tǒng)一把整個(gè)應(yīng)用的狀態(tài)存到一個(gè)地方(store),保存成一個(gè)狀態(tài)樹寸谜,修改數(shù)據(jù)需要派發(fā)(dispatch)一個(gè)動作(action)通知store修改竟稳。組件通過訂閱(subscribe)修改事件属桦,獲取最新數(shù)據(jù)來修改自身狀態(tài)熊痴。
三大原則
- 整個(gè)應(yīng)用的state存儲在store中,有且只存在一個(gè)store聂宾。
- store里面的state是只讀的果善,唯一改變state的方法就是派發(fā)(dispatch)一個(gè)動作(action)。
- 純函數(shù)(reducer)修改state系谐,每次返回一個(gè)新的state巾陕,不能直接修改原對象。
為什么要使用Redux(應(yīng)用場景)
- 單頁應(yīng)用復(fù)雜纪他,管理不斷變化的state非常困難鄙煤。
- 非父子關(guān)系組件通信。
- 所有頁面的公用狀態(tài)茶袒。
從源碼出發(fā)梯刚,整個(gè)redux源碼,總共導(dǎo)出5個(gè)函數(shù)薪寓,和一個(gè)__DO_NOT_USE__ActionTypes
(默認(rèn)的action,所有action類型不能重復(fù)亡资,下面會細(xì)撩這個(gè)點(diǎn)),那么下面就來細(xì)細(xì)的撩一下5個(gè)函數(shù)向叉。
1.createStore(reducer, preloadedState, enhancer)
想要使用redux锥腻,首先就是創(chuàng)建一個(gè)全局的store(當(dāng)然是唯一的一個(gè)),就得調(diào)用這個(gè)函數(shù)(createStore)母谎。該函數(shù)接收三個(gè)參數(shù)瘦黑。store里面保存了所有的數(shù)據(jù),可以看做一個(gè)容器奇唤。
傳入reducer
和initState
創(chuàng)建store幸斥。
store返回給鑰匙
,修改器
,電話
。
有了鑰匙就能隨時(shí)取數(shù)據(jù)冻记,如果需要修改數(shù)據(jù)就得通過修改器睡毒,如需要需要知道數(shù)據(jù)什么時(shí)候修改了,就打一個(gè)電話給store冗栗,告訴它演顾,數(shù)據(jù)修改好了給我說供搀。
先來看看它返回的函數(shù):
getState() => currentState
返回當(dāng)前的store狀態(tài)樹,包含所有state钠至。這里在讀源碼的時(shí)候發(fā)現(xiàn)一個(gè)疑問葛虐。
這里返回的是原本的對象,那么外部拿到這個(gè)state棉钧,不是可以直接修改嗎屿脐?這樣違背了只讀。為什么不返回一個(gè)新引用對象宪卿,防止此操作的诵。
帶著這個(gè)疑問,給Redux提了一個(gè)PR. 得到回復(fù):
Yes - getState() just returns whatever the current state value is. It's up to you to avoid accidentally mutating it.
(譯文)是的佑钾,getState()只返回當(dāng)前狀態(tài)值西疤。你要避免不小心把它弄亂。
也就是說在這里需要注意一下休溶,getState()返回的值不能直接修改代赁,否則你將陷入深淵
subscribe(listener) => unsubscribe()
傳入一個(gè)函數(shù)用來監(jiān)聽store發(fā)生變化,一旦store發(fā)生了變化兽掰,將循環(huán)執(zhí)行所有的監(jiān)聽函數(shù)芭碍。調(diào)用該函數(shù)還返回了一個(gè)取消監(jiān)聽的函數(shù)。調(diào)用返回的函數(shù)孽尽,則取消該listener監(jiān)聽窖壕。
dispatch(action) => action
dispatch接收一個(gè)action。在函數(shù)內(nèi)部會把所有的reducer執(zhí)行一遍并把當(dāng)前的state和action傳入reducer泻云,然后再執(zhí)行所有的監(jiān)聽函數(shù)艇拍。從源碼中截了一段出來:
const dispatch = (action) => {
currentState = currentReducer(currentState, action);
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
action
一個(gè)對象,必須包含一個(gè)type宠纯,表示所要發(fā)生的動作卸夕,其他屬性自由設(shè)置,社區(qū)中有一個(gè)規(guī)范婆瓜,把其他所有數(shù)據(jù)放入payload中快集,一般開發(fā)中會寫一個(gè)函數(shù)來生成action。
function login(){
retrun {
type: 'LOGIN',
payload: {
username: 'xxx',
passworld: 'xxxx'
}
}
}
replaceReducer(nextReducer)
傳入新的reducer廉白,替換之前的reducer个初,并且派發(fā)一個(gè)ActionType為REPLACE的action。
再來看看它接收的三個(gè)參數(shù)
reducer
一個(gè)純函數(shù)猴蹂,接收當(dāng)前的state和action做為參數(shù)院溺,返回新的state。
Store 收到 Action 以后磅轻,會調(diào)用reducer珍逸,必須給出一個(gè)新的 State逐虚,這樣 Store里的數(shù)據(jù)才會發(fā)生變化。
編寫一個(gè)簡單的reducer
const reducer = function (state, action) {
switch(action.type){
case 'TYPE':
return {...state, newState}
default:
return state;
}
};
preloadedState
源碼里面的參數(shù)名叫這個(gè)谆膳,其實(shí)我更愿意叫它initState叭爱,初始化的狀態(tài),為什么源碼不叫initState呢漱病?因?yàn)椴粶?zhǔn)確买雾,在源碼里會默認(rèn)發(fā)送一個(gè)type為init的dispatch(),這個(gè)時(shí)候走到reducer里面(看看上面的reducer代碼)杨帽,state如果在這個(gè)時(shí)候設(shè)置一個(gè)默認(rèn)值漓穿,比如:
const reducer = function (state = {list: []}, action) {
switch(action.type){
case 'TYPE':
return {...state, newState}
default:
return state;
}
};
這個(gè)時(shí)候就默認(rèn)返回了{(lán)list: []},就給出了一個(gè)真正的initState睦尽。
enhancer
用來配合快速創(chuàng)建中間件的器净。
上面提到的__DO_NOT_USE__ActionTypes
型雳,就是2個(gè)actionType:
-
@@redux/INIT
: 用來內(nèi)部發(fā)送一個(gè)默認(rèn)的dispatch -
@@redux/REPLACE
: 用來替換reducer
2.bindActionCreators(actionCreators, dispatch) => boundActionCreators
遍歷所有生成action函數(shù)并執(zhí)行当凡,返回被dispatch包裹的函數(shù),可供直接調(diào)用派發(fā)一個(gè)請求纠俭。
actionCreators
該參數(shù)為一個(gè)對象沿量,包含生成action的函數(shù),例如:
const actionCreators = {
login: () => {
return {
type: 'LOGIN',
payload: {
username: 'xxx',
passworld: 'xxxx'
}
}
},
logout: () => {
retrun {
type: 'LOGOUT'
}
}
}
如果傳入一個(gè)函數(shù)冤荆,則執(zhí)行函數(shù)得到action朴则,返回一個(gè)dispatch(action)。
dispatch
這里就是createStore所返回的dispatch
該函數(shù)返回對象或函數(shù)(根據(jù)傳入的actionCreators來決定)钓简,可以直接調(diào)用xx.login()去派發(fā)一個(gè)登陸乌妒。
3.combineReducers(reducers)
在項(xiàng)目開發(fā)中,需要分模塊寫reducer外邓,利用該函數(shù)合并多個(gè)reducer模塊撤蚊。傳入一個(gè)reducer集合。
a.js
export (state = {list: []}, action) => {
switch (action.type) {
case "LIST_CHANGE":
return { ...state, ...action.payload };
default:
return state;
}
}
b.js
export (state = {list: []}, action) => {
switch (action.type) {
case "LIST":
return { ...state, ...action.payload };
default:
return state;
}
}
combineReducers.js
import a from './a';
import b from './b';
combineReducers({
a: a,
b: b
})
a
和b
都有l(wèi)ist這個(gè)state损话,但是他們并不相關(guān)侦啸,要把他們分開使用,就得用combineReducers去合并丧枪。
下面簡單實(shí)現(xiàn)了該函數(shù):
function combineReducers(reducers) {
return (state = {}, action) => {
return Object.keys(reducers).reduce((currentState, key) => {
currentState[key] = reducers[key](state[key], action);
return currentState;
}, {})
};
}
4.compose
可以說是js中函數(shù)式中很重要的方法光涂,把一堆函數(shù)串聯(lián)起來執(zhí)行,從右至左執(zhí)行(也就是倒序)拧烦,函數(shù)的參數(shù)是上一個(gè)函數(shù)的結(jié)果忘闻。看一個(gè)使用例子:
const fn1 = (val) => val + 'fn1';
const fn2 = (val) => val + 'fn2';
const fn3 = (val) => val + 'fn3';
const dispatch = compose(fn1, fn2, fn3);
console.log(dispatch('test'));
最終輸出結(jié)果testfn3fn2fn1
test傳給fn3當(dāng)參數(shù)恋博,fn3的返回值給了fn2....
compose.js
function compose(...fns){
if(fns.length==1) return fns[0];
return fns.reduce((a,b)=>(...args)=>a(b(...args)));
}
5.applyMiddleware
該函數(shù)是用來添加中間件齐佳,在修改數(shù)據(jù)的時(shí)候做一些其他操作葵蒂,redux通過改造dispatch來實(shí)現(xiàn)中間件.
使用中間件
const middleware = applyMiddleware(logger);
需要傳入中間件函數(shù),可以是多個(gè)函數(shù)重虑,依次傳入践付,在applyMiddleware里面用到了compose,所以我們傳入的函數(shù)的從右至左依次執(zhí)行的缺厉,這里需要注意一下永高。
const createStoreWithMiddleware = middleware(createStore);
因?yàn)橹虚g件是通過改造dispatch來實(shí)現(xiàn),所以需要把創(chuàng)建store的方法傳進(jìn)去提针。
const store = createStoreWithMiddleware(reducer, preloadedState)
這里再傳入createStore需要接收的參數(shù)命爬,返回store對象。
實(shí)現(xiàn)一個(gè)logger中間件
const logger = function({dispatch, getState}){
return function(next){
return function(action){
console.log('oldState',getState());
next(action); // 真實(shí)派發(fā)動作
console.log('newState',getState());
}
}
}
首先middleware會把未改造的dispatch
和getState
傳入進(jìn)來辐脖,這里的next
相當(dāng)于dispatch饲宛,去派發(fā)一個(gè)真正的修改數(shù)據(jù)動作。
源碼貼上:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
middlewareAPI
是存儲未改造的方法嗜价,compose
上面講過艇抠,第一個(gè)參數(shù)傳入的就是dispatch,返回的一個(gè)改造后dispatch就是通過compose過后的一個(gè)函數(shù)久锥,會依次執(zhí)行家淤。
最后
一些學(xué)習(xí)心得,如有不對歡迎指正
我的github
謝謝你閱讀我的文章