资讯专栏INFORMATION COLUMN

不一样的redux源码解读

Salamander / 1638人阅读

摘要:这里还有一个疑问点就是的嵌套,最开始也我不明白,看了源码才知道,这里返回的也是接受也就是一个所以可以正常嵌套。以作为参数,调用上一步返回的函数以为参数进行调用。

1、本文不涉及redux的使用方法,因此可能更适合使用过 redux 的同学阅读
2、当前redux版本为4.0.1
3、更多系列文章请看

Redux作为大型React应用状态管理最常用的工具。虽然在平时的工作中很多次的用到了它,但是一直没有对其原理进行研究。最近看了一下源码,下面是我自己的一些简单认识,如有疑问欢迎交流。

1、createStore

结合使用场景我们首先来看一下createStore方法。

// 这是我们平常使用时创建store
const store = createStore(reducers, state, enhance);

以下源码为去除异常校验后的源码,

export default function createStore(reducer, preloadedState, enhancer) {

// 如果有传入合法的enhance,则通过enhancer再调用一次createStore
 if (typeof enhancer !== "undefined") {
   if (typeof enhancer !== "function") {
     throw new Error("Expected the enhancer to be a function.")
   }
   return enhancer(createStore)(reducer, preloadedState) // 这里涉及到中间件,后面介绍applyMiddleware时在具体介绍
 }

 let currentReducer = reducer     //把 reducer 赋值给 currentReducer
 let currentState = preloadedState   //把 preloadedState 赋值给 currentState
 let currentListeners = []     //初始化监听函数列表
 let nextListeners = currentListeners   //监听列表的一个引用
 let isDispatching = false  //是否正在dispatch

 function ensureCanMutateNextListeners() {}

 function getState() {}

 function subscribe(listener) {}
 
 function dispatch(action) {}

 function replaceReducer(nextReducer) {}
 
// 在 creatorStore 内部没有看到此方法的调用,就不讲了
 function observable() {}
 //初始化 store 里的 state tree
 dispatch({ type: ActionTypes.INIT })

 return {
   dispatch,
   subscribe,
   getState,
   replaceReducer,
   [$$observable]: observable
 }
}

我们可以看到creatorStore方法除了返回我们常用的方法外,还做了一次初始化过程dispatch({ type: ActionTypes.INIT });那么dispatch干了什么事情呢?

/**
  * dispath action。这是触发 state 变化的惟一途径。
  * @param {Object} 一个普通(plain)的对象,对象当中必须有 type 属性
  * @returns {Object} 返回 dispatch 的 action
  */
 function dispatch(action) {
 // 判断 dispahch 正在运行,Reducer在处理的时候又要执行 dispatch
   if (isDispatching) {
     throw new Error("Reducers may not dispatch actions.")
   }

   try {
     //标记 dispatch 正在运行
     isDispatching = true
     //执行当前 Reducer 函数返回新的 state
     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
 }

这里dispatch主要做了二件事情

通过reducer更新state

执行所有的监听函数,通知状态的变更

那么reducer是怎么改变state的呢?这就涉及到下面的combineReducers

2、combineReducers

回到上面创建store的参数reducers,

// 两个reducer
const todos = (state = INIT.todos, action) => {
  // ....
};
const filterStatus = (state = INIT.filterStatus, action) => {
  // ...
};

const reducers = combineReducers({
  todos,
  filterStatus
});
// 这是我们平常使用时创建store
const store = createStore(reducers, state, enhance);

下面我们来看combineReducers做了什么

export default function combineReducers(reducers) {
   // 第一次筛选,参数reducers为Object
  // 筛选掉reducers中不是function的键值对
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    if (typeof reducers[key] === "function") {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
  
  // 二次筛选,判断reducer中传入的值是否合法(!== undefined)
  // 获取筛选完之后的所有key
  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    // 遍历所有的key和reducer,分别将reducer对应的key所代表的state,代入到reducer中进行函数调用
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 这里就是reducer function的名称和要和state同名的原因,传说中的黑魔法
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === "undefined") {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 将reducer返回的值填入nextState
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 发生改变了返回新的nextState,否则返回原先的state
    return hasChanged ? nextState : state
  }
}

这里 reducer(previousStateForKey, action)执行的就是我们上面定义的todosfilterStatus方法。通过这二个reducer改变state值。

以前我一直很奇怪我们的reducer里的state是怎么做到取当前reducer对应的数据。看到const previousStateForKey = state[key]这里我就明白了。

这里还有一个疑问点就是combineReducers的嵌套,最开始也我不明白,看了源码才知道combineReducers()=> combination(state = {}, action),这里combineReducers返回的combination也是接受(state = {}, action)也就是一个reducer所以可以正常嵌套。

看到这初始化流程已经走完了。这个过程我们认识了dispatchcombineReducers;接下来我们来看一下我们自己要怎么更新数据。

用户更新数据时,是通过createStore后暴露出来的dispatch方法来触发的。dispatch 方法,是 store 对象提供的更改 currentState 这个闭包变量的唯一建议途径(注意这里是唯一建议途径,不是唯一途径,因为通过getState获取到的是state的引用,所以是可以直接修改的。但是这样就不能更新视图了)。
正常情况下我们只需要像下面这样

//action creator
var addTodo = function(text){
    return {
        type: "add_todo",
        text: text
    };
};
function TodoReducer(state = [], action){
    switch (action.type) {
        case "add_todo":
            return state.concat(action.text);
        default:
            return state;
    }
};

// 通过 store.dispatch(action) 来达到修改 state 的目的
// 注意: 在redux里,唯一能够修改state的方法,就是通过 store.dispatch(action)
store.dispatch({type: "add_todo", text: "读书"});// 或者下面这样
// store.dispatch(addTodo("读书"));

也就是说dispatch接受一个包含type的对象。框架为我们提供了一个创建Action的方法bindActionCreators

3、bindActionCreators

下面来看下源码

// 核心代码,并通过apply将this绑定起来
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
// 如果actionCreators是一个函数,则说明只有一个actionCreator,就直接调用bindActionCreator
  if (typeof actionCreators === "function") {
    return bindActionCreator(actionCreators, dispatch)
  }
  // 遍历对象,然后对每个遍历项的 actionCreator 生成函数,将函数按照原来的 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
}

bindActionCreators的作用就是使用dispatch action creator包裹起来,这样我们就可以直接调用他们了。这个在平常开发中不常用。

4、applyMiddleware

最后我们回头来看一下之前调到的中间件,

import thunkMiddleware from "redux-thunk";
// 两个reducer
const todos = (state = INIT.todos, action) => {
  // ....
};
const filterStatus = (state = INIT.filterStatus, action) => {
  // ...
};

const reducers = combineReducers({
  todos,
  filterStatus
});
// 这是我们平常使用时创建store
const store = createStore(reducers, state, applyMiddleware(thunkMiddleware));

为了下文好理解这个放一下redux-thunk的源码

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;

可以看出thunk返回了一个接受({ dispatch, getState })为参数的函数
下面我们来看一下applyMiddleware源码分析

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)
    }
    // 每个 middleware 都以 middlewareAPI 作为参数进行注入,返回一个新的链。此时的返回值相当于调用 thunkMiddleware 返回的函数: (next) => (action) => {} ,接收一个next作为其参数
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 并将链代入进 compose 组成一个函数的调用链
    // compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函数对象。
    // 在目前只有 thunkMiddleware 作为 middlewares 参数的情况下,将返回 (next) => (action) => {}
    // 之后以 store.dispatch 作为参数进行注入注意这里这里的store.dispatch是没有被修改的dispatch他被传给了next;
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

// 定义一个代码组合的方法
// 传入一些function作为参数,返回其链式调用的形态。例如,
// compose(f, g, h) 最终返回 (...args) => f(g(h(...args)))
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  } else {
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  }
}

我第一眼看去一脸闷逼,咋这么复杂。但是静下心来看也就是一个三级柯里化的函数,我们从头来分析一下这个过程

// createStore.js
if (typeof enhancer !== "undefined") {
  if (typeof enhancer !== "function") {
    throw new Error("Expected the enhancer to be a function.")
  }
  return enhancer(createStore)(reducer, preloadedState)
}

也就是说,会变成这样

applyMiddleware(thunkMiddleware)(createStore)(reducer, preloadedState)

applyMiddleware(thunkMiddleware)

applyMiddleware接收thunkMiddleware作为参数,返回形如(createStore) => (...args) => {}的函数。

applyMiddleware(thunkMiddleware)(createStore)

createStore 作为参数,调用上一步返回的函数(...args) => {}

applyMiddleware(thunkMiddleware)(createStore)(reducer, preloadedState)

(reducer, preloadedState)为参数进行调用。 在这个函数内部,thunkMiddleware被调用,其作用是监测typefunctionaction

因此,如果dispatch的action返回的是一个function,则证明是中间件,则将(dispatch, getState)作为参数代入其中,进行action 内部下一步的操作。否则的话,认为只是一个普通的action,将通过next(也就是dispatch)进一步分发

也就是说,applyMiddleware(thunkMiddleware)作为enhance,最终起了这样的作用:

dispatch调用的action进行检查,如果action在第一次调用之后返回的是function,则将(dispatch, getState)作为参数注入到action返回的方法中,否则就正常对action进行分发,这样一来我们的中间件就完成喽~

因此,当action内部需要获取state,或者需要进行异步操作,在操作完成之后进行事件调用分发的话,我们就可以让action 返回一个以(dispatch, getState)为参数的function而不是通常的Object,enhance就会对其进行检测以便正确的处理

到此redux源码的主要部分讲完了,如有感兴趣的同学可以去看一下我没讲到的一些东西转送门redux;

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

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

相关文章

  • redux源码解读--applyMiddleware源码解析

    摘要:的中间件主要是通过模块实现的。返回的也是一个对象这个其实就是,各个中间件的最底层第三层的哪个函数组成的圆环函数构成的这就是对源码的一个整体解读,水平有限,欢迎拍砖。后续的源码解读和测试例子可以关注源码解读仓库 applyMiddleware源码解析 中间件机制在redux中是强大且便捷的,利用redux的中间件我们能够实现日志记录,异步调用等多种十分实用的功能。redux的中间件主要是...

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

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

    tianren124 评论0 收藏0
  • [源码解读] react-redux

    摘要:本文并不逐行地对源码进行细致分析,不如说是基于以下几个问题,对源码进行大致的扫览。我们已经知道,中,允许用户注册监听器,这些监听器会在每次执行结束后遍历触发。省略一些无关代码其中,是为了在嵌套的中嵌套执行。 react-redux 源码解读 [TOC] 前置知识 阅读本篇文章前,请先确认你是否了解以下知识: react redux 高阶组件 react diff 机制 其中高阶组件...

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

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

    lk20150415 评论0 收藏0
  • dva系列源码解读

    摘要:介绍概述本次对源码的解读除了传统的从入手外还将引入带入问题读源码的理念,因为只有这样当读完源码之后才会有切身的收获。 介绍 概述 本次对 dva 源码的解读除了传统的从 api 入手外还将引入带入问题读源码的理念,因为只有这样当读完源码之后才会有切身的收获。另外除了 dva 的源码外还会解读一些常用的 dva 插件的源码。 电子书 https://dva-source-docs.net...

    focusj 评论0 收藏0

发表评论

0条评论

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