最近比較空閑脚乡,就想看點(diǎn)源碼蜒滩,學(xué)習(xí)記錄一下。如果自己收獲能給他人些幫助奶稠,是一件開心的事也會更有動力俯艰。今天的主角是Redux。
先嘮叨幾句耳熟能詳?shù)脑?/p>
Redux 是 JavaScript 狀態(tài)容器锌订,提供可預(yù)測化的狀態(tài)管理竹握。
Redux三大原則:
- 單一數(shù)據(jù)源
- State 只能通過觸發(fā)Action
- 使用純函數(shù)來執(zhí)行修改
Redux的源碼很少,5個核心文件辆飘。
1. 首先來看createStore.js啦辐,一切的開始。
const store = createStore(reducer, preloadedState, enhancer);
這個函數(shù)默認(rèn)接收三個參數(shù)(改變狀態(tài)的方法蜈项,狀態(tài)的初始值芹关,增強(qiáng)器(增強(qiáng)后的store)),返回的store對象中包含dispatch紧卒,getState侥衬,subscribe等方法。(筆者只列出在工作中常用的)跑芳。
這個時候轴总,代碼應(yīng)該是這樣的
function createStore(reducer, preloadedState, enhancer) {
function dispatch() {}
function getState() {}
function subscribe() {}
return {
dispatch,
getState,
subscribe
};
}
接下來,就要對傳入?yún)?shù)做判斷博个,按規(guī)矩辦事怀樟。這里就直接貼源碼了,都是條件判斷坡倔。
重點(diǎn)注意下漂佩,紅框中的部分。至于這里為什么這么寫罪塔,后面細(xì)說投蝉。不耽誤理解createStore這個函數(shù)。
下面將這個三個函數(shù)分別簡單實(shí)現(xiàn)
- getState 從簡單入手,將初始的state或者改變后的state返回征堪。代碼變成這樣
function createStore(reducer, preloadedState, enhancer) {
let currentState = preloadedState;
function getState() {
return currentState;
}
return { getState };
}
- subscribe 使用發(fā)布訂閱模式瘩缆,收集每一個訂閱state的函數(shù)。
function createStore(reducer, preloadedState, enhancer) {
let currentListeners = []
let nextListeners = currentListeners
function subscribe(listener) {
// ensureCanMutateNextListeners() 這個函數(shù)在官方的解釋為佃蚜,進(jìn)行淺拷貝庸娱,防止分發(fā)過程中着绊,增加/取消訂閱事件,而引發(fā)出問題熟尉。先去掉
nextListeners.push(listener)
return function unsubscribe() {
// nextListeners.splice(index, 1)
}
}
return { subscribe };
- dispatch 在dispatch的時候归露,會改變state,reducer返回新的state斤儿,所以dispatch內(nèi)部調(diào)用reducer剧包。訂閱者能收到消息,說明subscribe訂閱的函數(shù)會執(zhí)行往果。
function createStore(reducer, preloadedState, enhancer) {
let currentState = preloadedState;
let currentReducer = reducer;
let nextListeners = currentListeners
function dispatch(action) {
currentState = currentReducer(action);
for (let i = 0; i < nextListeners .length; i++) {
const listener = listeners[i]
listener()
}
return action;
}
// 默認(rèn)會執(zhí)行一次 dispatch,簡單寫
// dispatch();
return {
getState,
dispatch,
subscribe
};
}
不傳入增強(qiáng)器enhancer時疆液,代碼大致是這樣
function createStore(reducer, preloadedState, enhancer) {
let currentState = preloadedState;
let currentReducer = reducer;
let currentListeners = []
let nextListeners = currentListeners
function getState() {
return currentState;
}
function dispatch(action) {
currentState = currentReducer(action);
for (let i = 0; i < nextListeners .length; i++) {
const listener = listeners[i]
listener();
}
return action;
}
function subscribe(listener) {
// ensureCanMutateNextListeners();
nextListeners.push(listener)
return function unsubscribe() {}
}
return { getState, dispatch, subscribe };
}
現(xiàn)在陕贮,我們來看傳入enhancer的情況堕油。這里要用到applyMiddleware.js中的applyMiddleware函數(shù),這個函數(shù)返回增強(qiáng)后的store肮之,將dispatch方法增強(qiáng)掉缺。讓原來一步操作的dispatch(action),變成執(zhí)行func1->func2->...->store.dispatch(action)局骤。
// 上面createStore.js -> enhancer(createStore)(reducer, preloadedState) => { getState, dispatch, subscribe } 這個是調(diào)用applyMiddleware()函數(shù)后的返回函數(shù)enhancer攀圈,enhancer執(zhí)行2次最后返回對象store,由此推測代碼應(yīng)該是個樣子的
function applyMiddleware(...middlewares) {
return () => () => ({ getState, dispatch, subscribe })
}
第一步要有store峦甩,才能增強(qiáng)其dispatch方法赘来,獲取store的唯一途徑是調(diào)用createStore函數(shù),將reducer和initialState傳入】粒現(xiàn)在改成這樣
// applyMiddleware(...middlewares) -> enhancer(createStore)(reducer, preloadedState)
// enhancer(createStore) -> (...args) => { const store = createStore(...args); return store; }
// enhancer(createStore)(reducer, preloadedState) => store;
function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args);
return store;
}
}
下面犬辰,把中間件引進(jìn)來,我們知道中間件是層層包裹的冰单,經(jīng)過中間件后幌缝,返回的dispatch再調(diào)用dispatch時,會把外層函數(shù)都執(zhí)行了诫欠,最后再執(zhí)行dispatch(action)涵卵。可能是這樣
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = compose(...middlewares)(store.dispatch);
return { ...store, dispatch };
}
}
compose.js ,返回一個函數(shù)
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
個人猜測這里中間件如果這樣寫compose(...middlewares)(store.dispatch)荒叼,中間件的只能操作dispatch轿偎,將這個store整體傳入又不安全。如果給每個中間件都注入一個getState被廓,dispatch坏晦。就能做更多的事情。代碼可能變成這樣
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
// 在redux 3.x的版本源碼,都是這樣的 dispatch: (...args) => (store.dispatch)(...args); 這好理解每個中間件通過作用域鏈拿到dispatch昆婿,接下來就按照3.x的說球碉。
// 4.x版本的寫法,自己沒想明白仓蛆,上面的英文解釋說睁冬,防止在構(gòu)建時調(diào)用dispatch,我自己還沒有領(lǐng)會到精髓多律,了解的朋友請幫忙指點(diǎn)迷津
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
let dispatch = compose(...chain)(store.dispatch);
return { ...store, dispatch };
}
}
這樣痴突,就可以在每次調(diào)用dispatch時,由中間件控制dispatch何時調(diào)用狼荞。
最后,再來看一下中間件的寫法帮碰。({getState, dispatch}) => next => action => {},格式固定相味。
以redux-thunk為例,簡單說一下殉挽。
// redux-thunk
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware(); // 這里執(zhí)行了一次
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
最簡單的使用方式,當(dāng)參數(shù)是函數(shù)時丰涉,就執(zhí)行這個函數(shù)并將dispatch、getState作為參數(shù)傳入斯碌。
store.dispatch(
dispatch => {
setTimeout( () => {
dispatch(action)
}一死,1000)
}
);
第一次寫博客,有點(diǎn)慌張語言的組織不太好傻唾,有些地方還有點(diǎn)啰嗦投慈。如果個人理解偏差,請大家不吝賜教冠骄,共同進(jìn)步伪煤。