Redux 所有 API 实现详解
本文讲解并实现 Redux 所有 API,包括 createStore 、 combineReducers 、 applyMiddleware 、 compose 、 bindactioncreators 。所有实现为了简明去除了很多非必要的逻辑,但保持了主要功能完整。本文中的源码可在 这里 查看。
createStore
使用示例:
export function counterReducer(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } }; const { dispatch, subscribe, getState } = createStore(counterReducer); const unsubscribe = subscribe(() => console.log(getState())); dispatch({ type: 'INCREMENT' }); unsubscribe();
实现:
这里本身没什么难度, createStore
返回 store
,其中包含以下四个方法。
getState()
:返回应用当前的 state。dispatch(action)
:分发 action,这是触发 state 变化的惟一途径。subscribe(listener)
:添加一个变化监听器。replaceReducer(nextReducer)
:替换 store 当前用来计算 state 的 reducer。
// 保证不会与用户定义的 actionType 重名即可 const ACTION_TYPE_INIT = `@@redux/INIT${Math.random()}`; const ACTION_TYPE_REPLACE = `@@redux/REPLACE${Math.random()}`; function createStore(reducer, preloadedState) { let currentReducer = reducer; const listeners = []; let state = preloadedState; const getState = () => { return state; }; const subscribe = listener => { listeners.push(listener); // 用于取消订阅 const unsubscribe = () => { const index = listeners.indexOf(listener); listeners.splice(index, 1); }; return unsubscribe; } const dispatch = action => { state = currentReducer(state, action); listeners.forEach(listener => listener()); }; function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer; dispatch({ type: ActionTypes.REPLACE } ); // 替换 reducer 后重新初始化 state return store; } // reducer 都设置了默认的 state,调用 dispatch 完成 state 初始化 dispatch({ type: ACTION_TYPE_INIT }); const store = { dispatch, subscribe, getState, replaceReducer }; return store; };
combineReducers(reducers)
应用较大时需要将 reducer 拆成多个 reducer 函数,以实现每个 reducer 负责独立管理 state 的其中一部分。而 combineReducers
的作用是允许你把拆分出来的多个 reducer 函数合并成一个最终的 reducer 函数供 createStore
使用。
使用示例:
// counter.js export default function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } } // todos.js export default function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return state.concat([action.text]) default: return state } } const rootReducer = combineReducers({ todoList: todos, count: counter }); const store = createStore(rootReducer); store.getState(); // { todoList: [], count: 0 }
实现:
实现的思路就是最终要返回一个合并后的 reducer 函数(即 combination
)。这个 combination
函数接受到 action
后会调用拆分出来的所有 reducer 函数,得到最新的状态( nextState
)。
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers); return function combination(state = {}, action) { const nextState = {}; for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i]; const reducer = reducers[key]; const previousStateForKey = state[key]; const nextStateForKey = reducer(previousStateForKey, action); nextState[key] = nextStateForKey; } return nextState; }; };
applyMiddleware(...middleware)
middleware(中间件)
Redux 中间件的本质扩展 dispatch
的功能,并且返回被增强后的 dispatch
。我们先修改 createStore
中的 dispatch
方法,方便查看中间件的运行顺序。
const dispatch = action => { console.log('原始的 dispatch 开始工作了') state = reducer(state, action); listeners.forEach(listener => listener()); console.log('原始的 dispatch 结束工作了') };
先自定义两个中间件并使用:
function middleware1 (store) { return next => action => { console.log('Middleware 1 号增强的 dispatch 开始工作了'); const returnValue = next(action); console.log('Middleware 1 号增强的 dispatch 结束工作了'); return returnValue; }; } function middleware2 (store) { return next => action => { console.log('Middleware 2 号增强的 dispatch 开始工作了'); const returnValue = next(action); console.log('Middleware 2 号增强的 dispatch 结束工作了'); return returnValue; }; } const store = createStore(todosReducer, {}, applyMiddleware(middleware1, middleware2));
当我们发起一个 store.dispatch(action)
,可以看到这样的日志:
Middleware 1 号增强的 dispatch 开始工作了
Middleware 2 号增强的 dispatch 开始工作了
原始的 dispatch 开始工作了
原始的 dispatch 结束工作了
Middleware 2 号增强的 dispatch 结束工作了
Middleware 1 号增强的 dispatch 结束工作了
可以看出多个中间件情况下, next
就是被下一个中间件增加后的 dispatch
,最后一个中间件调用了的即使最原始的 dispatch
。原始的 dispatch
被从右往左的中间件一个个增强。
所以如果只有一个中间件的情况下, next
就是最原始的 store.dispatch
。所以上面代码等效于:
const store = createStore(todosReducer, {}); const next = store.dispatch; store.dispatch = action => { console.log('Middleware 1 号增强的 dispatch 开始工作了'); next(action); console.log('Middleware 1 号增强的 dispatch 结束工作了'); }
如果存在两个中间件:
const store = createStore(todosReducer, {}); const next = store.dispatch; const next2 = action => { console.log('Middleware 2 号增强的 dispatch 开始工作了'); next(action); // 原始的 store.dispatch console.log('Middleware 2 号增强的 dispatch 结束工作了'); } store.dispatch = action => { console.log('Middleware 1 号增强的 dispatch 开始工作了'); next2(action); console.log('Middleware 1 号增强的 dispatch 结束工作了'); }
上面虽然实现了自定义中间的功能,但是不够灵活,因为中间件可能不止 2 个,顺序需要自由的调换。所以我们就需要增加一个参数支持传入被下一个中间件增强的 dispatch
:
const next = store.dispatch; const middleware2 = next => action => { console.log('Middleware 2 号增强的 dispatch 开始工作了'); const returnValue next(action); console.log('Middleware 2 号增强的 dispatch 结束工作了'); return returnValue; } const middleware1 = next => action => { console.log('Middleware 1 号增强的 dispatch 开始工作了'); const returnValue = next(action); console.log('Middleware 1 号增强的 dispatch 结束工作了'); return returnValue; } store.dispatch = middleware1(middleware2(next));
又考虑到每个中间内部不仅仅是执行下一个 next
,比如一个 logger 中间件需要通过 store.getState
方法获取 state,一些支持异步场景的中间件内部还需要调用 store.dispatch
...
const logger = next => action => { return next => action => { console.log('即将 dispatch: ', action) const returnValue = next(action) console.log('dispatch 后的 state: ', store.getState()) return returnValue } }
这些都来自 store
,所以每个中间件还需要增加一个参数 mAPI
以接收这些 API。
const next = store.dispatch; const middleware2 = mAPI => next => action => { console.log('Middleware 2 号增强的 dispatch 开始工作了'); const returnValue = next(action); console.log('Middleware 2 号增强的 dispatch 结束工作了'); return returnValue; } const middleware1 = mAPI => next => action => { console.log('Middleware 1 号增强的 dispatch 开始工作了'); const returnValue = next(action); console.log('Middleware 1 号增强的 dispatch 结束工作了'); return returnValue; } const mAPI = store; const m2 = middleware2(mAPI); const m1 = middleware1(mAPI); store.dispatch = m1(m2(next));
Redux 中规定中间件中可使用的 API 只有 getState
和 dispatch
。所以改进之后:
const next = store.dispatch; // ... let dispatch = next; const mAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) } const chain = [middleware1, middleware2].map(m => m(mAPI)); // [m1, m2] // 实现:store.dispatch = m1(m2(next)) dispatch = chain.reverse().forEach(m => { dispatch = m(dispatch); }); store.dispatch = dispatch;
applyMiddleware
上面已经实现了中间件的功能,但是还不够优雅,接下来要做的就是把上面的过程收拢到一个 applyMiddleware
函数内部,期望的使用方式是:
const store = createStore(reducer, preloadedState, applyMiddleware(...middlewares));
applyMiddleware(...middlewares)
的目的是增强 createStore
返回的 dispatch
方法。所以可以理解为 applyMiddleware(...middlewares)
返回 createStore
的增强器 enhancer
,将 createStore
传入增强器后返回 createStore
的升级版。
const enhancer = applyMiddleware(...middlewares); const createStore2 = enhancer(createStore);
createStore
中增加一段逻辑,当传入 enhancer
时返回增强版的 createStore
:
// const enhancer = applyMiddleware(middleware1, middleware2); function createStore(reducer, preloadedState, enhancer) { if (typeof applyMiddleware === 'function') { const createStore2 = enhancer(createStore); return createStore2(reducer, preloadedState); } // ... }
实现 applyMiddleware
:
把处理中间件,增加 dispatch
的逻辑收拢到 applyMiddleware
返回的 createStore
增强器中。
function applyMiddleware(...middlewares) { const enhancer = createStore => (reducer, preloadedState) => { const store = createStore(reducer, preloadedState); const next = store.dispatch; let dispatch = next; const mAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args), } const chain = middlewares.map(middleware => middleware(mAPI)); chain.reverse().forEach(m => { dispatch = m(dispatch); }); return { ...store, dispatch } }; return enhancer; }
compose(...functions)
compose
的作用是从右到左把接收到的函数合成为一个最终函数,右边函数的返回值将作为一个参数提供给它左边的函数。即 compose(funcA, funcB, funcC)
变为 compose(funcA(funcB(funcC())))
。
function compose(...funcs) { if (funcs.length === 0) return arg => arg; if (funcs.length === 1) return funcs[0]; return funcs.reduce((a, b) => { retrun (...args) => a(b(...args)); }); }
applyMiddleware
处理中间件即可复用此逻辑, store.dispatch = m1(m2(store.dispatch))
,所以 applyMiddleware
可以使用 compose
修改为:
function applyMiddleware(...middlewares) { const enhancer = createStore => (reducer, preloadedState) => { const store = createStore(reducer, preloadedState); let dispatch; const mAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args), } const chain = middlewares.map(middleware => middleware(mAPI)); dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch } }; return enhancer; }
bindActionCreators(actionCreators, dispatch)
actionCreator
即创建 action 的函数,比如下面的两个,actionCreator
的好处是复用创建 action 的逻辑,使用 dispatch
时只需 dispatch(actionCreator(value))
。
export function addTodo(text) { return { type: 'ADD_TODO', text } } export function removeTodo(id) { return { type: 'REMOVE_TODO', id } }
bindActionCreators
的唯一使用场景就是当你需要把 actionCreator 传给子组件,但却不想让子组件感觉到 Redux 的存在。
import { addTodo, removeTodo } from './TodoActionCreators' class TodoListContainer extends Component { constructor(props) { super(props) const { dispatch } = props; // 由 react-redux 注入的 dispatch // 对 actionCreator 绑定 dispatch 方法 this.boundActionCreators = bindActionCreators({ addTodo, removeTodo, }, dispatch); console.log(this.boundActionCreators); // { addTodo: Function, removeTodo: Function } } render() { // 由 react-redux 注入的 todos: let { todos } = this.props return <TodoList todos={todos} {...this.boundActionCreators} /> // 不使用 bindActionCreators 时的做法: // 直接把 dispatch 当作 prop 传递给子组件,但这时子组件需要引入 actionCreator // return <TodoList todos={todos} dispatch={dispatch} />; } }
实现 bindActionCreators
:
bindActionCreators
的实现很简单,如果我们不想感知到 dispatch
和 actionCreator
,只需要利用闭包实现隐藏:
function bindActionCreator(actionCreator, dispatch) { return function (this, ...args) { return dispatch(actionCreator.apply(this, args)) } } // 使用: const addTodo = bindActionCreator(addTodo, store.dispatch); addTodo('Use Redux');
如果有多个 actionCreator
则返回一个 boundActionCreators
对象:
export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error('bindActionCreators expected an object or a function.') } const boundActionCreators = {} for (const key in actionCreators) { const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators } // 使用: const boundActionCreators = bindActionCreator({ addTodo, removeTodo, }, dispatch); boundActionCreators.addTodo('Use Redux');
参考
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: React SSR 应用的诸多细节
下一篇: 谈谈自己对于 AOP 的了解
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论