做为前端状态管理器,这个比较跨时代的工具库redux有不少实现和思想值得咱们思考。在深刻源码以前,咱们能够相关注下一些常见问题,这样带着问题去看实现,也能更加清晰的了解。
html
大概看了下主要有这么几个:前端
有关acton,reducer相关的部分能够看我前面的文章。咱们主要关注针对store和中间件相关的部分来解读。react
做为维护和管理数据的部分,store在redux中的做用十分重要。在action发出指令,reduxer进行数据更新以后,监听数据变化和同步数据更新的操做都要借助store来实现。redux
首先看下createStore的使用,即常见的就是接受通过combineReducers处理以后的reducer和初始的state数组
import reducer from './reducers' const store = createStore(reducer,initialState)
此外还能够接受第三个参数enhancer(加强器,通常就是applyMiddleware)app
/** * 建立管理state 树的Redux store * 应用中只能存在一个store,为了区分不一样action对应的reducer, * 能够经过combineReducers来关联不一样的reducer * @param {Function} reducer combineReducers关联以后的reducer * @param {Object} preloadedState 初始state * @param {Function} enhancer 能够加强store功能的函数,例如中间件等。惟一适合 * @returns 返回一个Store 以维护state和监听变化 */ export default function createStore(reducer, preloadedState, enhancer) { // 若是第二个参数为func,redux认为忽略了初始state,而是 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { // enhancer加强剂,即利用中间件等来加强redux能力 enhancer = preloadedState preloadedState = undefined } // 返回具备dispatch等属性的对象 即store return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
按照通常的执行顺序,咱们先看下对于参数的处理(平时你们也是同样,一个函数,执行以前尽可能判断入参是否符合预期,避免直接处理形成的错误)异步
对于三个参数,后两个是非必填的,但若是第二个参数是function,reduxe认为其实encher,否则初始状态是个函数不符合redux的预期,只能这样处理了。函数
// 若是第二个参数为func,redux认为忽略了初始state,而是 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { // enhancer加强剂,即利用中间件等来加强redux能力 enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // 对于存在的enhancer,高阶函数函数的用法, // 接受createStore返回一个增长功能以后的函数,而后再传入相关reducer获得store。 return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } // 一切符合预期,没有 enhancer,那么初始赋值 let currentReducer = reducer let currentState = preloadedState let currentListeners = [] // 监听队列 let nextListeners = currentListeners // dispatch标识 let isDispatching = false // 初始状态更新以后,声明init状态完成。 dispatch({ type: ActionTypes.INIT })
dispatch的做用就是根据action,执行对应reducer以更新state。并执行监听队列。
下面就来看dispatch的用法和实现。
常见使用:工具
// redux要求 参数必须为纯对象 dispatch({ type: ActionTypes.INIT })
那么对于纯对象,redux作了些什么呢post
/** * 通知方法,参数为纯的js对象,标明更新内容 * @param {Object} action */ function dispatch(action) { // 是否知足纯净对象 if (!isPlainObject(action)) { throw new Error( '省略' ) } // 必须的type是否存在 if (typeof action.type === 'undefined') { throw new Error( '省略' ) } // 判断是否处于某个action的dispatch中,你们一块儿dispatch可能死循环 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { // 开始dispatch,加锁,标明状态 isDispatching = true // 将当前状态和更新action,传给当前reducer处理 // 这里能够回想下咱们reducer中的两个参数,state和action 对应的是什么 /** * const todos = (state = [], action) => { */ currentState = currentReducer(currentState, action) } finally { // 有异常,锁置为false,不影响别的dispatch isDispatching = false } // 执行dispatch,而且更新当前监听队列为 最新队列 const listeners = (currentListeners = nextListeners) // 依次执行,监听器 for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
createStore初始化完成以后会执行dispatch({ type: ActionTypes.INIT }),此时执行初始化操做。
咱们要关注下currentState的计算,
将currentState,action传给reducer处理,而后更新currentState。
针对初始化来讲currentState其实就是initState:
// 初始化状态 let currentState = preloadedState /****省略***/ // 这里能够回想下咱们reducer中的两个参数,state和action对应的值 currentState = currentReducer(currentState, action)
reducer示例:
const todos = (state = [], action) => { switch (action.type) { case 'ADD_TODO': return [ ...state, { id: action.id, text: action.text, completed: false } ] }
getState就是得到store的state。这个比较简单。当结合react-redux使用时,其会帮咱们进行操做。咱们就不用自行调用这个方法了,因此不要疑惑从哪里获取的state。
/** * 返回应用当前状态 * 不过须要看下当前是否有更新正在进行,是的话则提示 */ function getState() { // 判断是否isDispatching 中 if (isDispatching) { throw new Error('省略') } return currentState }
subscribe是比较重要的一个方法,用来供咱们监听状态的变化,以执行相关操做。
例如react-redux中的handleChange 会对是否pure组件及state进行对比,以提高渲染效率。
示例:
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
实现:
返回的是一个函数,能够进行unsubscribe操做。
/** * 订阅通知 */ function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } if (isDispatching) { throw new Error( '省略' ) } // 是否已经监听过 let isSubscribed = true // 监听队列是否相同,区分开,操做nextListeners 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) } }
这个开发比较少用,用于热更新
// 用于reducer的热替换,开发中通常不会直接使用 function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer // 更新值以后,进行dispatch。 dispatch({ type: ActionTypes.REPLACE }) }
到这里createStore已经解析完成了,你们应该了解该方法到底作了些什么操做吧。
简单归纳一下就是:接收reducer和initState,返回一个store 对象。该对象提供了监听、分发等功能,以实现数据的更新。
通过上面的解读以后,对于redux的常规应用应该有所了解了。不过实际使用中可能会遇到些问题。
例如action要求是纯对象,而咱们获取数据通常是异步的,这就须要借助redux-thunk这个中间件了。
actionCreater返回一个函数。以下:
export function func1() { return dispatch => { dispatch({ type:'test', data:'a' }) } }
在了解如何实现以前,须要先看下redux中间件的原理。
由于reducer更多的关注的是数据的操做,对于一些公共的方法,须要抽离出来,不过这些方法在什么时候使用呢,redux为咱们提供了中间件来知足需求。
redux 借鉴了 Koa里 middleware 的思想,即鼎鼎大名的洋葱模型。
不过这里请求对应的是dispatch的过程。
每次dispatch的过程当中,都要依次将中间件执行一遍。
遇到阻塞或者操做完成,执行下个中间件,直到执行完成,以便咱们事先日志,监控、异常上报等功能。
那么redux 又是如何支持中间件的呢。这就离不开applyMiddleware了。
这里前面的
实现思想比较简单,经过科里化和compose,为符合规范的中间件分配访问dispatch和store的途径,以便在不一样阶段来自定义数据更新。
例如异步操做,返回的不是对象,那么就执行返回的函数,而后调用下一个中间件。等异步请求结束,再次dispatch 对应的action。
export default 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.` ) } // 赋予每一个中间件访问store的能力。 const middlewareAPI = { getState: store.getState, // 箭头函数保存dispatch,保证其的同步更新 dispatch: (...args) => dispatch(...args) } // 串联中间件,并赋予每一个中间件访问dispatch的能力。 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 关联dispatch与中间件,组合调用以后获得相似下面的新对象 // dispatch = f1(f2(f3(store.dispatch)))); dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
这样执行以后返回的,对象就是加强以后的store了。
redux中compose是柯里化函数的一个示例,目的是将函数串联起来。
/** * 函数组合,科里化的串联 */ export default 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))) }
redux-thunk源码,实现也很优雅,对于返回的function,将dispatch等参数传递进去,而后执行,等待回调异步完成再dispatch。对于正常对象则进行下一步。
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { // 每次dispatch的时候都会进行判断,若是是个函数,那就执行函数,再也不进行下一步吧,这样就避免了,函数不知足action要求的问题 if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
那么实际使用时,在createStore时加入该中间件便可:
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk'; const store = createStore( reducer, applyMiddleware({ ...middleware, thunk}) )
那么到这里对于redux的中间件 也就是问题2,我想你们也比较清楚了。
对于常见中间件能够参考
redux中文文档
深刻React技术栈
加上重读redux源码一和带着问题看 react-redux 源码实现总算将redux及react-redux重读了一遍。可能有人会说道这些源码,看完也会忘,有这个必要吗。我感受分状况来看,若是咱们只是使用,那么看官方文档就能够了,当遇到某些疑问好像找不到贴切解释的时候,不放一看。 此外也是学习大佬们的设计思路和实现方式,有的放矢才能开卷有益。