核心逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function createStore (reducer, preloadedState ) { let currentState = preloadedState; let currentListeners = [] function getState ( ) { return currentState } function dispatch (action ) { currentState = reducer(currentState, action) for (let i = 0 ; i < currentListeners.length; i++) { const listener = currentListeners[i] listener() } } function subscribe (listener ) { currentListeners.push(listener) } return { getState, dispatch, subscribe } }
计数器案例测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <button id ="btn1" > +1</button > <span id ="count" > 0</span > <button id ="btn2" > -1</button > <script src ="./myRedux.js" > </script > <script > function reducer (state, action ) { switch (action.type) { case 'increment' : return state + 1 case 'decrement' : return state - 1 default : return state } } const store = createStore(reducer, 0 ) console .log(store.getState()) document .getElementById('btn1' ).onclick = () => { store.dispatch({type : 'increment' }) } document .getElementById('btn2' ).onclick = () => { store.dispatch({type : 'decrement' }) } store.subscribe(() => { document .getElementById('count' ).innerText = store.getState() }) </script >
参数类型约束 reducer 必须是函数; action 必须是对象,并且必须有 type 属性; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 function createStore (reducer, preloadedState ) { if (typeof reducer !== 'function' ) throw new TypeError ('reducer must be a function!' ) let currentState = preloadedState; let currentListeners = [] function getState ( ) { return currentState } function dispatch (action ) { if (!isPlainObject(action)) throw new TypeError ('action must be an object' ) if (typeof action.type === 'undefined' ) throw new Error ('action must have type character' ) currentState = reducer(currentState, action) for (let i = 0 ; i < currentListeners.length; i++) { const listener = currentListeners[i] listener() } } function subscribe (listener ) { currentListeners.push(listener) } return { getState, dispatch, subscribe } } function isPlainObject (obj ) { if (typeof obj !== 'object' || obj === null ) return false let proto = obj while (Object .getPrototypeOf(proto) !== null ) { proto = Object .getPrototypeOf(proto) } return Object .getPrototypeOf(obj) === proto }
Enhancer enhancer 的作用是,让 createStore 方法的调用者对返回的 store 进行功能上的增强;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 function createStore (reducer, preloadedState, enhancer ) { if (typeof reducer !== 'function' ) throw new TypeError ('reducer must be a function!' ) if (typeof enhancer !== 'undefined' ) { if (typeof enhancer !== 'function' ) throw new TypeError ('enhancer must be a function!' ) return enhancer(createStore)(reducer, preloadedState) } let currentState = preloadedState; let currentListeners = [] function getState ( ) { return currentState } function dispatch (action ) { if (!isPlainObject(action)) throw new TypeError ('action must be an object' ) if (typeof action.type === 'undefined' ) throw new Error ('action must have type character' ) currentState = reducer(currentState, action) for (let i = 0 ; i < currentListeners.length; i++) { const listener = currentListeners[i] listener() } } function subscribe (listener ) { currentListeners.push(listener) } return { getState, dispatch, subscribe } } function isPlainObject (obj ) { if (typeof obj !== 'object' || obj === null ) return false let proto = obj while (Object .getPrototypeOf(proto) !== null ) { proto = Object .getPrototypeOf(proto) } return Object .getPrototypeOf(obj) === proto }
计数器案例测试增强后异步操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <button id ="btn1" > +1</button > <span id ="count" > 0</span > <button id ="btn2" > -1</button > <script src ="./myRedux.js" > </script > <script > function enhancer (createStore ) { return function (reducer, preloadedState ) { let store = createStore(reducer, preloadedState) console .log(store) let dispatch = store.dispatch function _dispatch (action ) { if (typeof action === 'function' ){ return action(dispatch) } dispatch(action) } return { ...store, dispatch : _dispatch } } } function reducer (state, action ) { switch (action.type) { case 'increment' : return state + 1 case 'decrement' : return state - 1 default : return state } } const store = createStore(reducer, 0 , enhancer) console .log(store.getState()) document .getElementById('btn1' ).onclick = () => { store.dispatch(dispatch => { setTimeout (() => { dispatch({type : 'increment' }) }, 1000 ) }) } document .getElementById('btn2' ).onclick = () => { store.dispatch({type : 'decrement' }) } store.subscribe(() => { document .getElementById('count' ).innerText = store.getState() }) </script >
applyMiddleware 将 enhancer 升级为 applyMiddleware 加强 dispatch 的功能;
新建两个中间件函数 logger.js 和 thunk.js ;
1 2 3 4 5 6 7 8 function logger (store ) { return function (next ) { return function (action ) { console .log('logger' ) next(action) } } }
1 2 3 4 5 6 7 8 function thunk (store ) { return function (next ) { return function (action ) { console .log('thunk' ) next(action) } } }
myRedux.js 修改,新增 applyMiddleware 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 function createStore (reducer, preloadedState, enhancer ) { if (typeof reducer !== 'function' ) throw new TypeError ('reducer must be a function!' ) if (typeof enhancer !== 'undefined' ) { if (typeof enhancer !== 'function' ) throw new TypeError ('enhancer must be a function!' ) return enhancer(createStore)(reducer, preloadedState) } let currentState = preloadedState; let currentListeners = [] function getState ( ) { return currentState } function dispatch (action ) { if (!isPlainObject(action)) throw new TypeError ('action must be an object' ) if (typeof action.type === 'undefined' ) throw new Error ('action must have type character' ) currentState = reducer(currentState, action) for (let i = 0 ; i < currentListeners.length; i++) { const listener = currentListeners[i] listener() } } function subscribe (listener ) { currentListeners.push(listener) } return { getState, dispatch, subscribe } } function isPlainObject (obj ) { if (typeof obj !== 'object' || obj === null ) return false let proto = obj while (Object .getPrototypeOf(proto) !== null ) { proto = Object .getPrototypeOf(proto) } return Object .getPrototypeOf(obj) === proto } function applyMiddleware (...middlewares ) { return function (createStore ) { return function (reducer, preloadedState ) { let store = createStore(reducer, preloadedState) let middleWareAPI = { getState : store.getState, dispatch : store.dispatch } let chain = middlewares.map(middleWare => middleWare(middleWareAPI)) let dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } } function compose ( ) { let funcs = [...arguments] return function (dispatch ) { for (let i = funcs.length - 1 ; i >= 0 ; i--) { dispatch = funcs[i](dispatch) } return dispatch } }
案例测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <button id ="btn1" > +1</button > <span id ="count" > 0</span > <button id ="btn2" > -1</button > <script src ="./myRedux.js" > </script > <script src ="./middlewares/logger.js" > </script > <script src ="./middlewares/thunk.js" > </script > <script > function enhancer (createStore ) { return function (reducer, preloadedState ) { let store = createStore(reducer, preloadedState) console .log(store) let dispatch = store.dispatch function _dispatch (action ) { if (typeof action === 'function' ){ return action(dispatch) } dispatch(action) } return { ...store, dispatch : _dispatch } } } function reducer (state, action ) { switch (action.type) { case 'increment' : return state + 1 case 'decrement' : return state - 1 default : return state } } const store = createStore(reducer, 0 , applyMiddleware(logger, thunk)) console .log(store.getState()) document .getElementById('btn1' ).onclick = () => { store.dispatch({type : 'increment' }) } document .getElementById('btn2' ).onclick = () => { store.dispatch({type : 'decrement' }) } store.subscribe(() => { document .getElementById('count' ).innerText = store.getState() }) </script >
注册中间件后,每次执行 action 前,都会按顺序先执行中间件;
bindActionCreators bindActionCreators 的作用是将 actionCreators 函数转换为可以出发 action 的函数;
myRedux.js
1 2 3 4 5 6 7 8 9 10 11 12 ... function bindActionCreators (actionCreators, dispatch ) { let boundActionCreators = {} for (let key in actionCreators) { boundActionCreators[key] = function ( ) { dispatch(actionCreators[key]()) } } return boundActionCreators }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <span id ="count" > 0</span > <button id ="btn2" > -1</button > <script src ="./myRedux.js" > </script > <script src ="./middlewares/logger.js" > </script > <script src ="./middlewares/thunk.js" > </script > <script > function enhancer (createStore ) { return function (reducer, preloadedState ) { let store = createStore(reducer, preloadedState) console .log(store) let dispatch = store.dispatch function _dispatch (action ) { if (typeof action === 'function' ){ return action(dispatch) } dispatch(action) } return { ...store, dispatch : _dispatch } } } function reducer (state, action ) { switch (action.type) { case 'increment' : return state + 1 case 'decrement' : return state - 1 default : return state } } const store = createStore(reducer, 0 , applyMiddleware(logger, thunk)) console .log(store.getState()) function increment ( ) { return {type : 'increment' } } function decrement ( ) { return {type : 'decrement' } } let actions = bindActionCreators({increment, decrement}, store.dispatch) document .getElementById('btn1' ).onclick = () => { actions.increment() } document .getElementById('btn2' ).onclick = () => { actions.decrement() } store.subscribe(() => { document .getElementById('count' ).innerText = store.getState() }) </script >
combineReducers combineReducers 将拆分的 reducer 合并为一个;
myRedux.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ... function combineReducers (reducers ) { let reducerKeys = Object .keys(reducers) for (let i = 0 ; i < reducerKeys.length; i++) { let key = reducerKeys[i] if (typeof reducers[key] !== 'function' ) throw new TypeError ('reducer must be a function!' ) } return function (state, action ) { let nextState = {} for (let i = 0 ; i < reducerKeys.length; i++) { let key = reducerKeys[i] let reducer = reducers[key] let prevState = state[key] nextState[key] = reducer(prevState, action) } return nextState } }
测试,注意在 subscribe 时是获取 store.getState().counter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <button id ="btn1" > +1</button > <span id ="count" > 0</span > <button id ="btn2" > -1</button > <script src ="./myRedux.js" > </script > <script src ="./middlewares/logger.js" > </script > <script src ="./middlewares/thunk.js" > </script > <script > function enhancer (createStore ) { return function (reducer, preloadedState ) { let store = createStore(reducer, preloadedState) console .log(store) let dispatch = store.dispatch function _dispatch (action ) { if (typeof action === 'function' ){ return action(dispatch) } dispatch(action) } return { ...store, dispatch : _dispatch } } } function counterReducer (state, action ) { switch (action.type) { case 'increment' : return state + 1 case 'decrement' : return state - 1 default : return state } } let rootReducer = combineReducers({counter : counterReducer}) const store = createStore(rootReducer, {counter : 100 }, applyMiddleware(logger, thunk)) console .log(store.getState()) function increment ( ) { return {type : 'increment' } } function decrement ( ) { return {type : 'decrement' } } let actions = bindActionCreators({increment, decrement}, store.dispatch) document .getElementById('btn1' ).onclick = () => { actions.increment() } document .getElementById('btn2' ).onclick = () => { actions.decrement() } store.subscribe(() => { document .getElementById('count' ).innerText = store.getState().counter }) </script >