核心:合理高效的管理咱们应用的状态;javascript
这些管理方式会带来什么样的麻烦呢?html
// reducer
const counter = (state = 0, action) => { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } } // 建立store const store = createStore(counter) const rootEl = document.getElementById('root') const render = () => ReactDOM.render( <Counter value={store.getState()} onIncrement={() => store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, rootEl ) render() // 注册监听器 store.subscribe(render) 复制代码
Redux的源码实际上比较少,核心可能就两百行,比较适合初读源码的小伙伴们,下面咱们一块儿经过源码来探究如下Redux的原理。前端
createStore这个方法接受三个参数:java
返回了一个store对象,这个对象包含了四个值mysql
export default function createStore< S, A extends Action, Ext = {}, StateExt = never >( // 接受一个函数(这个函数接受两个参数:state,action,返回新的state树) reducer: Reducer<S, A>, // 初始时的 state。 在同构应用中,你能够决定是否把服务端传来的 state 水合(hydrate)后传给它, // 或者从以前保存的用户会话中恢复一个传给它。若是你使用 combineReducers 建立 reducer,它必须是一个普通对象,与传入的 keys 保持一样的结构 preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>, // 扩展store的功能 enhancer?: StoreEnhancer<Ext, StateExt> ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext { // 若是存在enhancer且是一个函数(高阶函数) if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)( reducer, preloadedState as PreloadedState<S> ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext } // 存储的是咱们reducer函数 let currentReducer = reducer // 目前的state let currentState = preloadedState as S // 存储监听器 let currentListeners: (() => void)[] | null = [] // 这里存储一份Listener是防止有人会在dispatch的过程当中进行subscribe/unsubscribe的骚操做 let nextListeners = currentListeners // 是否处于dispatch中 let isDispatching = false function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { // slice: 该方法并不会修改数组,而是返回一个子数组。 nextListeners = currentListeners.slice() } } // 获取当前的state function getState(): S { if (isDispatching) { throw new Error('简化') } return currentState as S } // 注册监听:传入listener 存入nextListeners; 返回unsubscribe方法 function subscribe(listener: () => void) { if (isDispatching) { throw new Error('简化') } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } if (isDispatching) { throw new Error('简化') } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) currentListeners = null } } // 接受一个plainobject的action function dispatch(action: A) { if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true // 经过传入的Reducer计算出本次action以后的新state树 currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 此时:nextListeners = currentListeners = listeners const listeners = (currentListeners = nextListeners) // 遍历执行监听器 for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } // 返回当前传入的action return action } // 替换 store 当前用来计算 state 的 reducer。currentReducer =》 newReducer function replaceReducer<NewState, NewActions extends A>( nextReducer: Reducer<NewState, NewActions> ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } // TODO: do this more elegantly ;((currentReducer as unknown) as Reducer< NewState, NewActions >) = nextReducer dispatch({ type: ActionTypes.REPLACE } as A) // change the type of the store by casting it to the new store return (store as unknown) as Store< ExtendState<NewState, StateExt>, NewActions, StateExt, Ext > & Ext } // store建立自动dispatch的初始化action,建立咱们的初始化state树 dispatch({ type: ActionTypes.INIT } as A) const store = ({ dispatch: dispatch as Dispatch<A>, subscribe, getState, replaceReducer } as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext return store } 复制代码
此方法就接收一个参数,这参数是一个reducerMap对象,相似{routing: routingReducer};react
export default function combineReducers(reducers: ReducersMapObject) {
// store tree上的key const reducerKeys = Object.keys(reducers) const finalReducers: ReducersMapObject = {} // 遍历拷贝传入的reducersMap到finalReducers for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } // finalReducerKeys也就是reducersMap的key的集合数组 const finalReducerKeys = Object.keys(finalReducers) let shapeAssertionError: Error try { // 这里去判断了一下reducers的对应的reducer的合理性 assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } // 返回了一个函数:也就是咱们传入到createStore方法中的第一个参数,传入state,action,返回新的state树 return function combination( state: StateFromReducersMapObject<typeof reducers> = {}, action: AnyAction ) { if (shapeAssertionError) { throw shapeAssertionError } // 是否须要返回新state的标识 let hasChanged = false const nextState: StateFromReducersMapObject<typeof reducers> = {} // 遍历去执行每一个reducer,根据对应的key,生成新的nextState for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] // 这里previousStateForKey存储的是对于变化前的store树上key的state const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } // nextState:新store tree nextState[key] = nextStateForKey // 若是有一个store tree上对应一个key的value改变了,那hasChanged就置为true hasChanged = hasChanged || nextStateForKey !== previousStateForKey } // 若是reducersMap keys不等于 state的keys 的length 也一样 hasChanged 为 true // 也就是说默认的state没有传,第一次的时候就会return nextState;nextState的value也就是reducers的默认值 hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length return hasChanged ? nextState : state } } 复制代码
这个方法很重要,咱们经过传入的中间件来达到「加强咱们store.dispatch方法」的目的;git
applyMiddleware方法接受一个数组做为参数: middlewares-传入数组<中间件>;github
redux做者为啥不直接传入多个参数,还非要将这个函数分解为多个单参数函数进行调用呢?这就是函数式编程的理念,你品,你细品;web
export default function applyMiddleware( ...middlewares: Middleware[] ): StoreEnhancer<any> { // 典型的函数式编程写法 return (createStore: StoreCreator) => <S, A extends AnyAction>( reducer: Reducer<S, A>, ...args: any[] ) => { // 生成store const store = createStore(reducer, ...args) let dispatch: Dispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) } const middlewareAPI: MiddlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) } // 遍历去执行middleware的方法(传入参数getState、dispatch) // middleware返回一个接受next参数的函数 // 因此chain其实是存储了不少函数的数组 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 对正常建立的store.dispatch进行加强 dispatch = compose<typeof dispatch>(...chain)(store.dispatch) return { ...store, dispatch } } } 复制代码
// 这是咱们的一个用来打印action和 dispatch后的state的middleware
function logger({ getState }) { return (next) => (action) => { console.log('will dispatch', action) // 调用 middleware 链中下一个 middleware 的 dispatch。 let returnValue = next(action) console.log('state after dispatch', getState()) // 通常会是 action 自己,除非 // 后面的 middleware 修改了它。 return returnValue } } 复制代码
「compose函数精髓所在:funs = [a,b,c,d,e] => return (...args) => a(b(c(d(e(...args)))))」sql
这个方法在koa2的源码中也有体现,具体可看:Koa源码分析(中间件执行机制、Koa2与Koa1比较)
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) { // infer the argument type so it is usable in inference down the line return <T>(arg: T) => arg } if (funcs.length === 1) { return funcs[0] } // compose函数精髓所在:funs = [a,b,c,d,e] => return (...args) => a(b(c(d(e(...args))))) return funcs.reduce((a, b) => (...args: any) => a(b(...args))) } 复制代码
Store
实例提供的
dispatch
函数。
export default function bindActionCreators( actionCreators: ActionCreator<any> | ActionCreatorsMapObject, dispatch: Dispatch ) { // 若是传入的actionCreateors就是一个函数直接返回一个dispatch这个action的包装函数 if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } // Map: key:ActionCreator的函数名,value: 一个dispatch这个action的包装函数 const boundActionCreators: ActionCreatorsMapObject = {} for (const key in actionCreators) { const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators } // 传入dispatch 和 actionCreator 返回dispatch(action)的包装函数 function bindActionCreator<A extends AnyAction = AnyAction>( actionCreator: ActionCreator<A>, dispatch: Dispatch ) { return function (this: any, ...args: any[]) { return dispatch(actionCreator.apply(this, args)) } } 复制代码
// 代码片断:具体代码可查看https://www.redux.org.cn/docs/api/bindActionCreators.html
this.boundActionCreators = bindActionCreators(TodoActionCreators, dispatch); console.log(this.boundActionCreators); // { // addTodo: Function, // removeTodo: Function // } let action = TodoActionCreators.addTodo('Use Redux'); dispatch(action); 复制代码
redux默认是同步action,可是做者也提供了一个能够进行异步操做的中间件;
// redux-thunk源码很简单
// 让咱们action能够成为一个函数,这个函数能够访问到dispatch, getState这两个方法, // 最后返回一个plainobject action就行了 function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk; 复制代码
是函数式编程里面的特有名词,主要用于calculation delay,也就是延迟计算。
function wrapper(arg) {
// 内部返回的函数就叫`thunk` return function thunk() { console.log('thunk running, arg: ', arg) } } // 咱们经过调用wrapper来得到thunk const thunk = wrapper('wrapper arg') // 而后在须要的地方再去执行thunk thunk() 复制代码
这个中间件应用的仍是很普遍的,好比说阿里的dva
都是集成咱们的redux-saga; 核心是经过监听咱们的action操做,执行对应的side effects的task,结合咱们ES6的generator
语法,配合saga提供的工具函数,能够很是方便的处理咱们的异步问题;
Redux虽然带来了比较高级的函数式编程思想以及Immutable
数据不可变的特性,可是咱们普通开发者去很好的践行这些缺很累,为何累? 咱们通常使用react-redux的时候,都会定义不少的样板代码,特色的繁琐,好比说你的组织目录,须要有actions、reducers等等,简单的一个数据传递扭转,你须要修改多处的代码,并且这些代码均可能是差差很少的。
你可能还会带来心智负担,什么心智负担呢?
什么样的数据须要放在咱们的store中呢,是否我全部的状态都须要集中管理呢?答案固然是否认的,咱们须要将咱们须要全局共享的状态放入store中,组件内的状态不须要共享的固然仍是组件内的状态使用的好。(这里牵涉到一个性能问题,咱们通常在react项目中都会集成react-redux这个库,若是你几乎每一个组件都去connect订阅store中的数据,你会得到不少不须要的rerender,性能会大大下降。)
目前社区中为了解决咱们Redux的这些弊端,诞生了不少优秀的类库,固然,它们的核心仍是redux;
除了上面说的dva
、rematch
,再推荐几个个类库辅助咱们进行项目开发:
帮助你在使用react-redux作的过程当中,mapStateToProps
方法不须要每次都重复计算state,会帮助你缓存计算的结果(固然,这是在你确实不须要从新计算的状况下,才会使用你缓存的结果);
均可以让你操做javascript的复杂数据类型时,没有心智负担,这里的心智负是指引用类型的反作用,你以前可能会经过深拷贝避免副做; 在redux的reducer中使用Object.assign
或者ES6的...
运算符保持咱们的reducer函数是纯的,这两个类库均可以方便的让你安全的操做state; 这两个库都是很是优秀的,可是immer是后起之秀,两者实现的原理也有区别,就不展开讨论了。
咱们要学会合理的运用社区中优秀的方案解决咱们的项目开发中的问题。没有最好的,只有适合的
。
本文使用 mdnice 排版