资讯专栏INFORMATION COLUMN

redux 核心源码解析

cgh1999520 / 1540人阅读

摘要:核心源码解析本文默认,你已经有了一定的基础。定义如何更新初始状态是让中间件按照规定模型洋葱模型回形针模型执行的函数,就是下文将会介绍的函数的返回值返回中的。洋葱模型回形针模型调用后的结果对上面的代码换一种展现方式中间件要执行,先得执行。

redux 核心源码解析
本文默认,你已经有了一定的es6基础。源码有删减,对其中的核心逻辑做解释

redux 是 一个用 javascript 管理数据的容器,这个容器的状态是可以预测的。

redux 可以跟任何是个 视图(view)层的框架结合使用

开发react应用,经常结合使用 redux

redux 比较抽象,核心旨在定义一个数据读写的规范,和数据变化后的回调接口

三大原则

单一数据源 :整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

State 是只读的:惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

使用纯函数来执行修改 :为了描述 action 如何改变 state tree ,你需要编写 reducers。纯函数:http://web.jobbole.com/86136/

createStore.js 核心逻辑
function createStore(reducer, preloadedState, enhancer) {
    /*
      参数格式/类型 匹配。
      reducer:定义 state 如何更新
      preloadedState: 初始状态
      enhancer 是让中间件按照规定模型(洋葱模型/回形针模型)执行的函数,就是下文将会介绍的 applyMiddleware函数的返回值
    */
    if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
      enhancer = preloadedState
      preloadedState = undefined
    }
    if (typeof enhancer !== "undefined") {
      if (typeof enhancer !== "function") {
        throw new Error("Expected the enhancer to be a function.")
      }
      return enhancer(createStore)(reducer, preloadedState)
    }
  
    let currentReducer = reducer
    let currentState = preloadedState
    let currentListeners = []
    let nextListeners = currentListeners
  
    /**
     *  返回 store 中的 state。
     *  State 是只读的:这个 state 是 createStore 函数内部变量,只能通过 createStore 提供的 getState 方法读取 state。
     */
    function getState() {
      return currentState
    }
    
    /**
     * listener 是函数,维护在内部变量 nextListeners(数组) 中
     * 调用 subscribe(订阅) 方法,添加 listener 在数组中
     * 并返回一个方法,可以把当前添加在数组中的 listener 函数取出。
     * 
     * listener 的作用:当 state 发生变化的时候,listener具体去做些事情,比如更新 UI。
     * 只定义做事情的时机,具体做什么由调用者自己实现
     */
    function subscribe(listener) {
      nextListeners.push(listener)
      return function unsubscribe() {
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
      }
    }
    
    /**
     * dispatch的作用:更改 state
     * 更改的方式:根据传入的 reducer 和 action,产生一个新的 state。
     * 
     * action的要求:是一个纯粹的对象字面量,并且具有 type 字段
     * 经过 reducer 根据 action 把 当前 state 更新。一旦更新结束,立即把数组中的每一个 listener 调用一遍
    */
    function dispatch(action) {
      if (!isPlainObject(action)) {
        throw new Error(
          "Actions must be plain objects. " +
            "Use custom middleware for async actions."
        )
      }
  
      if (typeof action.type === "undefined") {
        throw new Error(
          "Actions may not have an undefined "type" property. " +
            "Have you misspelled a constant?"
        )
      }
  
      currentState = currentReducer(currentState, action)
  
      const listeners = (currentListeners = nextListeners)
      for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
      }
  
      return action
    }
  
    /**
     * 可以更改 reducer 
     * reducer 一旦更改,立即 dispatch 一次,把 state 更新,把注册的 listener 执行一遍
     */
    function replaceReducer(nextReducer) {
      if (typeof nextReducer !== "function") {
        throw new Error("Expected the nextReducer to be a function.")
      }
  
      currentReducer = nextReducer
      dispatch({ type: ActionTypes.REPLACE })
    }
  
    /**
     * 一旦 调用 createStore 函数,立即 dispatch 一遍,把 state 更新,把注册的 listener 执行一遍
     */
    dispatch({ type: ActionTypes.INIT })
  
    return {
      dispatch,
      subscribe,
      getState,
      replaceReducer,
    }
  }
redux 中的中间件
redux 中的中间件一个典型的应用场景是处理异步接口返回数据

常见的 web 系统中的中间件,不侵入业务逻辑,功能优盘化,即用即插。

洋葱模型/回形针模型
    middleWareA(middleWareB(middleWareC("hello world")));
    /**
     * 调用后的结果: 
     * hello world form middleWareC;form middleWareB;form middleWareA;
     * */ 

    function middleWareA(string){
        return `${string} form middleWareA;`;
    };
    function middleWareB(string){
        return `${string} form middleWareB;`;
    };
    function middleWareC(string){
        return `${string} form middleWareC;`;
    };

对上面的代码换一种展现方式

    middleWareA(
        middleWareB(
            middleWareC("hello world")
        )
    );

中间件 middleWareA 要执行,先得执行 middleWareB。中间件 middleWareB 要执行,先得执行 middleWareC,数据层层传递
调用顺序为: A-->B-->C
执行结束的顺序为:C-->B-->A
这就是 洋葱模型 或者 回形针模型
下面看一个 redux-thunk 中间件

redux-thunk 核心逻辑
function createThunkMiddleware() {
  /**
   * dispatch 是基于 store.dispatch,且层层经过中间件包装后的方法
   * getState 是 createStore 函数执行后导出的方法;
   * next 为下一个中间件
   * action 的预期格式为 createStore 中设计的 action的格式,是一个字面量对象。在这里做action类型的判断
  */
  return ({ dispatch, getState }) => next => action => {
    // 如果 action 是函数
    if (typeof action === "function") {
        return action(dispatch, getState);
    }
    // 如果不是函数,流转到下一个中间件,期待最后的一个中间件的 action 是一个预期模式的 action
    return next(action);
  };
}

export default createThunkMiddleware();
applyMiddleware 核心逻辑
/**
 * middlewares 是包含中间件的数组
 * 需要的中间件,依次传入。如: applyMiddleware(middlewaresA, middlewaresB, middlewaresC)
 * applyMiddleware 执行后的返回的函数就是一个 enhancer
*/
export default function applyMiddleware(...middlewares) {
    return createStore => (...args) => {
      // args解构后,就是 reducer, preloadedState
      // 这里的 store 就是 createStore 函数生产的全局的状态树,参见上文对 createStore 的解释
      const store = createStore(...args)
  
      let dispatch = () => { };
      let chain = []
  
      /**
       * 每个中间件中需要传递的数据
       */
      const middlewareAPI = {
        getState: store.getState,// 获取 state 的函数,用于读
        dispatch: (...args) => dispatch(...args)// 更新 state 的函数,用于写。
      }
      // 遍历中间件
      chain = middlewares.map(middleware => middleware(middlewareAPI))
      
      // 下面的代码的效果,就是要运用洋葱模型/回形针模型:middlewareA(middlewareB(middlewareC(store.dispatch)))
      // 关于 compose 比较简单:https://github.com/reactjs/redux/blob/master/src/compose.js
      // 中间件的开始是 原生的store.dispatch,结束时是 一个携带了各种中间件信息的 dispatch
      dispatch = compose(...chain)(store.dispatch)
  
      /**
       * 尤其注意这里的 dispatch
       * 开始时定义了一个空的 dispatch 函数,是为了给 middlewareAPI 的 dispatch 一个初始值
       * 实际上真正的 dispatch 是中间件根据原生的 store.dispatch 按照洋葱模型/回形针模型,执行了一边,调用所有中间件后的 dispatch
       * 这个 dispatch 携带了传入的每一个中间件的操作。一次如果此时传入 action 更新 state,所有的中间件都会被执行一遍。
      */
  
      return {
        ...store,
        dispatch
      }
    }
  }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92645.html

相关文章

  • redux源码解析

    摘要:此篇文章可作为源码导读使用,只说明了其中部分核心代码,并进行了一些简化处理用法回顾用来创建创建在中编写纯函数来处理用通过和来操作里的数据用来监听中的数据是否发生了变化用来获取中的,并更新视图核心代码初始化数据存放,是传入的默认值存放传入的创 此篇文章可作为redux源码导读使用,只说明了其中部分核心代码,并进行了一些简化处理 用法回顾 用createStore来创建store 创建A...

    crossea 评论0 收藏0
  • redux源码解读--compose源码解析

    摘要:源码解析模块的代码十分简练,但是实现的作用却是十分强大。只传递一个参数的时候,就直接把这个函数返回返回组合函数这就是对源码的一个整体解读,水平有限,欢迎拍砖。后续的源码解读和测试例子可以关注源码解读仓库 compose源码解析 compose模块的代码十分简练,但是实现的作用却是十分强大。redux为何称为redux?有人说就是reduce和flux的结合体,而reduce正是comp...

    lk20150415 评论0 收藏0
  • redux源码解读--createStore源码解析

    摘要:源码解析是最核心的模块。比如,当我们需要使用中间件的时候,就会像第三个参数传递一个返回值是一个。后续的源码解读和测试例子可以关注源码解读仓库 createStore源码解析 createStore是redux最核心的模块。这个模块就是用于创建一个store对象,同时,对外暴露出dispatch,getState,subscribe和replaceReducer方法。(源码中关于obse...

    tianren124 评论0 收藏0
  • 简单梳理Redux源码与运行机制

    摘要:然后循环调用中的更新函数,更新函数一般是我们的渲染函数,函数内部会调用来获取数据,所以页面会更新。 欢迎访问个人网站:https://www.neroht.com/ 前言 前几天写了一篇react另一个状态管理工具Unstated的源码解析。开启了我的看源码之路。想一想用了好长时间的redux,但从没有深究过原理,遇到报错更是懵逼,所以就啃了一遍它的源码,写了这篇文章,分享我对于它的理...

    betacat 评论0 收藏0
  • 简单梳理Redux源码与运行机制

    摘要:然后循环调用中的更新函数,更新函数一般是我们的渲染函数,函数内部会调用来获取数据,所以页面会更新。前言 前几天写了一篇react另一个状态管理工具Unstated的源码解析。 开启了我的看源码之路。想一想用了好长时间的redux,但从没有深究过原理,遇到报错更是懵逼,所以就啃了一遍它的源码,写了这篇文章, 分享我对于它的理解。 API概览 看一下redux源码的index.js,看到了我们最...

    刘东 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<