博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
redux源码分析
阅读量:6092 次
发布时间:2019-06-20

本文共 7925 字,大约阅读时间需要 26 分钟。

Redux is a predictable state container for JavaScript apps. 官网第一句就很全面的介绍了redux。一个可预测的状态管理工具。redux 是如何做到的呢?

  • 单一的数据源 (states)
  • states是只读且不可变化的 (每次改变返回新值)
  • 通过纯函数改变states (reducer, 无副作用, 相同的输入就有相同的输出)
  • 改变states方式具有可描述性且单一 (action)

数据流

这个大家都很熟悉了吧,就不再讲了

源码解读

createStore

func createStore(reducer, preloadedState, enhancer) -> ({ dispatch, subscribe, getState, replaceReducer,[$$observable]: observable })

export default function createStore(reducer, preloadedState, enhancer) {  // preloadedState 可以不传,确定真实的参数  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {    enhancer = preloadedState    preloadedState = undefined  }  if (typeof enhancer !== 'undefined') {    return enhancer(createStore)(reducer, preloadedState)  }  let currentReducer = reducer  let currentState = preloadedState  let currentListeners = []  let nextListeners = currentListeners  let isDispatching = false  function ensureCanMutateNextListeners() {    if (nextListeners === currentListeners) {      nextListeners = currentListeners.slice()    }  }  function getState() {    return currentState  }  function subscribe(listener) {    let isSubscribed = true    ensureCanMutateNextListeners()    nextListeners.push(listener)    return function unsubscribe() {      isSubscribed = false      ensureCanMutateNextListeners()      const index = nextListeners.indexOf(listener)      nextListeners.splice(index, 1)    }  }  function dispatch(action) {    try {      isDispatching = true      currentState = currentReducer(currentState, action)    } finally {      isDispatching = false    }    const listeners = (currentListeners = nextListeners)    for (let i = 0; i < listeners.length; i++) {      const listener = listeners[i]      listener()    }    return action  }  function replaceReducer(nextReducer) {    currentReducer = nextReducer    dispatch({ type: ActionTypes.REPLACE })  }  function observable() {    //...  }  dispatch({ type: ActionTypes.INIT })  return {    dispatch,    subscribe,    getState,    replaceReducer,    [$$observable]: observable,  }}复制代码

为了篇幅减少些,上面的代码我删掉了部分错误检查。其实都很好理解,就有一点需要注意一下,为什么要用两个变量(currentListeners,nextListeners)来储存listener。这是因为redux允许在subscirbe中执行unsubscribe。 例如:

const unsubscribe1 = store.subscribe(() => {  unsubscribe1()})const unsubscribe2 = store.subscribe(() => {  unsubscribe2()})dispatch(unknownAction);复制代码

如果不缓存dispatch的listener的话 那么在dispatch里循环listeners时就会有问题。 另外也可以发现,如果你绑定了多个subscribe函数,即使在第一个subscription里执行了所有的unSubscribe,subscription还是会全部执行一遍 另外 observable 是为了和其他一些observable库配合使用,当目前为止还没用过。

applyMiddleware

用法
const logger = ({ getState }) => next => action => {  console.log('will dispatch logger1', action)  const returnValue = next(action)  console.log('state after dispatch logger1', getState())}const logger2 = ({ getState }) => next => action => {  console.log('will dispatch logger2', action)  const returnValue = next(action)  console.log('state after dispatch logger2', getState())}const store = createStore(  todos,  preload,  applyMiddleware(logger1, logger2),);// will dispatch logger1// will dispatch logger2// state after dispatch logger2// state after dispatch logger1复制代码
源码
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.`      )    }    const middlewareAPI = {      getState: store.getState,      dispatch: (...args) => dispatch(...args)    }        // chainItem:next => action => {...}    const chain = middlewares.map(middleware => middleware(middlewareAPI))    dispatch = compose(...chain)(store.dispatch)    return {      ...store,      dispatch    }  }}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)))}// compose(logger1, logger2) => (...arg) => logger1Next(logger2Next(...arg))// 合成之后的dispatch就成了这个样子:const dispatch = (action) => logger1Next(logger2Next(store.dispatch))(action);// 假如还有logger3, 那么应该是这个样子const dispatch = (action) => logger1Next(logger2Next(logger3Next(store.dispatch)))(action);复制代码

可以compose原因是middle模式统一:store => next => action => {} 在执行完 const chain = middlewares.map(middleware => middleware(middlewareAPI))之后 chainItem: next => action => {} 本质是 接收一个dispatch,再返回一个合成的dispatch

bindActionCreators

用法

bindActionCreators({ actionCreator1, actionCreator2, ...}, dispatch) => ({ boundAction1, boundAction2, ... })

源码
// 返回 boundActionfunction bindActionCreator(actionCreator, dispatch) {  return function() {    return dispatch(actionCreator.apply(this, arguments))  }}export default function bindActionCreators(actionCreators, dispatch) {  // 仅仅传入一个actionCreator  if (typeof actionCreators === 'function') {    return bindActionCreator(actionCreators, dispatch)  }  if (typeof actionCreators !== 'object' || actionCreators === null) {    throw new Error()  }  // 传入一个对象时,绑定所有key,并返回一个对象  const keys = Object.keys(actionCreators)  const boundActionCreators = {}  for (let i = 0; i < keys.length; i++) {    const key = keys[i]    const actionCreator = actionCreators[key]    if (typeof actionCreator === 'function') {      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)    }  }  return boundActionCreators}复制代码

combineReducers

由于redux仅有一个store,所以当项目复杂的时候,数据需要分类,这时就会用到此函数。作用是将多个分开的reducers合并。

原型:(reducer1,reducer2, reducer3,...) => combinedReducer

export default function combineReducers(reducers) {  const reducerKeys = Object.keys(reducers)  const finalReducers = {}  // 遍历检查 reducer 不应该为 undefined  for (let i = 0; i < reducerKeys.length; i++) {    const key = reducerKeys[i]    if (process.env.NODE_ENV !== 'production') {      if (typeof reducers[key] === 'undefined') {        warning(`No reducer provided for key "${key}"`)      }    }    if (typeof reducers[key] === 'function') {      finalReducers[key] = reducers[key]    }  }  const finalReducerKeys = Object.keys(finalReducers)  let unexpectedKeyCache  if (process.env.NODE_ENV !== 'production') {    unexpectedKeyCache = {}  }  let shapeAssertionError  try {    // 检查 reducer 必须设置初始值,不可以返回 undefined    assertReducerShape(finalReducers)  } catch (e) {    shapeAssertionError = e  }  // combinedReducer  return function combination(state = {}, action) {    if (shapeAssertionError) {      throw shapeAssertionError    }    if (process.env.NODE_ENV !== 'production') {      // 检查 state 是否合法,例如state必须是对象、state应该有相对应的reducer等      // 使用了combineRudcer之后,state就是对象了,原来的state都放在了相对应的key下面      // 例如:combineReducers({ todos: reducer1, todoType: reducer2 });      // store 变成了 { todos: todoState, todoType: todoTypeState };      const warningMessage = getUnexpectedStateShapeWarningMessage(        state,        finalReducers,        action,        unexpectedKeyCache      )      if (warningMessage) {        warning(warningMessage)      }    }    let hasChanged = false    const nextState = {}    // 将action分发给每个reducer, 如果该改变就返回新的。    // 否则返回旧值,类似于你在每个reduer中的做法。    for (let i = 0; i < finalReducerKeys.length; i++) {      const key = finalReducerKeys[i]      const reducer = finalReducers[key]      const previousStateForKey = state[key]      const nextStateForKey = reducer(previousStateForKey, action)      if (typeof nextStateForKey === 'undefined') {        const errorMessage = getUndefinedStateErrorMessage(key, action)        throw new Error(errorMessage)      }      nextState[key] = nextStateForKey      hasChanged = hasChanged || nextStateForKey !== previousStateForKey    }    return hasChanged ? nextState : state  }}复制代码

总结

redux源码还是比较好理解的,记住reducer一定要保证是纯函数。这对于测试和与其他的库配合至关重要。例如 react-redux。 感兴趣的朋友可以看看我的react-redux源码分析

  • 下周会给大家带来redux-saga这个中间件源码解读,感兴趣的请关注~

参考阅读

转载于:https://juejin.im/post/5b94962f6fb9a05cf908127a

你可能感兴趣的文章
给C语言初学者的忠告——计算机达人成长之路(27)
查看>>
【思考】互联网产业发展趋势
查看>>
Vmware view 5.0 POC环境搭建参考v1.0
查看>>
编程小知识点范例-1
查看>>
同一Tomcat 多个端口部署不同的项目
查看>>
mysql启用审计功能
查看>>
《ASP.NET 开发从入门到精通》----第1章 ASP.NET基础 1.1 认识网页和网站
查看>>
从Docker Hub和docker-registry看优秀的后端服务设计实现
查看>>
暴增 Emacs 生产力的十大最佳插件
查看>>
C语言蛇形填数
查看>>
Java Reflection(四):变量
查看>>
图解css3:核心技术与案例实战. 2.2 基本选择器
查看>>
《通信技术导论(原书第5版)》——1.5 通过多路复用增加网络容量
查看>>
Disruptor入门
查看>>
ROS机器人程序设计(原书第2版)2.5 本章小结
查看>>
Kafka可靠性的思考
查看>>
《乐高EV3机器人搭建与编程》——2.9 小结
查看>>
《Cucumber:行为驱动开发指南》——导读
查看>>
最近的工作(mina vs. yanf4j)
查看>>
《Maven官方文档》创建Archetype
查看>>