「Redux源码解析」亿(一)点点我的思考

434 阅读6分钟

以前一直在项目中会使用到react-redux来使用 redux ,但是从来没去仔细了解redux的原理!!!那么最近课少啦~可以仔细研究一下啦!!🤔🤔

在阅读过程,我会向自己提很多问题,然后到处去找答案,所以也算得上学有所获吧!

这篇文章适合使用过redux的朋友阅读

关于源码版本还是用的4.0,没有讲ts版本,我认为如果用ts的版本来分析的话,会涉及讲解很多类型判断,不太利于文章可读性

文章有点长,大致目录如下:
util文件夹
---actionTypes
---isPlainObject ( 详解 ,为什么redux采用这样的方式)
---warnings
src文件夹
---index (伪函数的作用)
---createStore
---柯里化、高阶函数(帮助理解applymiddleware)
---compose(函数组合、reduce、reduceRight方法)
---appMiddlewares (重点详解,用redux-thunk的栗子来讲解)
---bindActionCreators(什么是action creator)
---combineReducer (CombineReducers调用replaceReducers的判断)
我自己的逼逼赖赖~
---为什么reducer必须是纯函数
---为什么要通过中间件派发异步action
^^应该会持续更新吧^^
总结

utils文件夹

一般在阅读源码的时候,会从index文件开始阅读,但我认为先了解一下工具类的函数,能帮助我们更好的理解源码,所以我们先从utils文件开始:

actionTypes

//用于生成一个随机字符串
const randomString = () =>
  Math.random()
    .toString(36)
    .substring(7)
    .split('')
    .join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
export default ActionTypes   

这个文件主要是用来定义三种redux的保留types:

INIT(reducer的初始化类型)

REPLACE(reducer的替换类型)

PROBE_UNKNOWN_ACTION(随机类型)

同时,为了确保这三种types的唯一性,在每个type的末尾加了一个随机生成的字符串

*isPlainObject

export default function isPlainObject(obj) {
  // obj必须是一个非空的对象
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  //沿着原型链一直向上查找,直到proto的上一级(父亲)是null的时候停止,这个时候的proto是null的儿子
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }
  //将obj的上一级(父亲)与proto进行比较,相当于满足plainObject条件是 null => a object => plainObject
  return Object.getPrototypeOf(obj) === proto
}

这个函数表面上看来平平无奇,主要是用来判断对象是否是一个简单对象,但是它有一些很有趣的特点

首先 plain object是没有一个官方的定义的对象,一般俗称简单对象,

在上面的这个函数中,参数obj只能是通过字面量({}),或者new Object()创建的对象,

obj不能是通过Object.create()创建的对象,即使是Object.create(null)也不行,

当然,在看源码的时候,为什么这个判断不能直接这样写,明明可以实现一样的功能🧐🧐

let proto = Object.getPrototypeOf(obj)
return !!proto && Object.getPrototypeOf(proto) === null

后来我在redux的github上的commit历史上看见,他以前的写法是这样的

改变总是有一定道理的,在redux的github的issue中有这样的讨论,作者是这样说的 翻译过来是这样的:

它通过遍历原型链直到到达Object来处理跨领域对象,并检查“ Object”是否具有与我们需要判断的普通对象相同的原型。

对于来自iframe的对象,该对象不同于我们的本地对象,因为它们来自两个不同的JavaScript上下文。因此,我们使用这种技术来获得对象实例的Object定义。

它可以很好地提高性能,因为您可以在相当数量的情况下将它们短路,然后再进入较慢的toString()调用(这是使lodash / isPlainObject和is-plain-object变慢的部分)。

第三句话,我的理解是他能通过在调用toString()方法前,通过终止一些工作来提升性能(如果您有更好的理解,可以在留言区告诉我😝😝😝😝)

总的来说,使用循环判断的好处如下:

1.解决cross-realm对象的问题

2.可以拥有更高的性能

warning

export default function warning(message) {
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  try {
    throw new Error(message)
  } catch (e) {}
}

这个函数的功能没什么好说的啦,就是对错误信息的封装啦~

index

function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) { ...警告信息 }

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

index文件作为代码的入口,除了将方法暴露出去,还有以下妙用:

不知道你有没有注意到下面这个函数:

function isCrushed() {}

这是一个伪函数

函数在被压缩的情况下,函数名会被替换成单个字母甚至_

因此,我们可以利用这个伪函数,来判断在开发环境中,是否存在代码被压缩的情况

如果存在这样的情况,就抛出错误

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === "production". ' +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}

在学习接下来的源码之前,我们来想想,在实际redux项目中,是如何创建store的:

在这里enhancer就相当于通过applyMiddleware应用,并且被compose进行组合的一些中间件合集(可能听起来有点拗口😸)

createStore

createStore是redux的核心板块,他的作用是创建一个store对象,以及暴露ensureCanMutateNextListenersgetStatesubscribedispatchreplaceReducerobservable这几个方法

export default function createStore(reducer, preloadedState, enhancer){
  //一些判断
  
  function ensureCanMutateNextListeners() {}
  
  function getState() {}
  
  function subscribe() {}
  
  function dispatch() {}
  
  function replaceReducer() {}
  
  function observable() {}
  
  dispatch({ type: ActionTypes.INIT })
}

[$$observable]

createStore代码的第一行是:

import $$observable from 'symbol-observable'

他的作用是什么呢?

在这个npm包的readme中是这样描述的 意思是在搭配ReduxRxJS这样的库的时候,将我们的对象变得observable,同时需要避免在调用error、complete、unsubscribe之后再次调用error、complete等

其实在实际开发中,这个方法我们一般用不到👻👻👻👻

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

}

createStore接收三个参数,reducer(function)、preloadedState(初始的时候的state)、enhancer(function,是一个高阶函数)在官方文档中对这三个参数也有明确的解释:

一些判断

if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }

这个判断是为了保证单一enhancer的原则,如果有多个enhancer,是需要通过compose将他们组合在一起的

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

对于参数preloadedState是一个可选的,所以,当createStore的第二个参数是函数且第三个参数为undefined的时候,默认第二个参数传入的是enhancer。同时,结合上一个判断,preloadedState和enhancer不能同时为函数,这样就直接限制了preloadedState不能为函数,但这样会让我有一点迷惑,就是在redux官方文档中,preloadedState的类型是any,虽然这样有点抠字眼,后来我在github的issues中找到了答案

其实这个点也算不上很重要,但能有一个官方解释还是挺好的👻🤡😸🤠😬🤔😝😋

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

增强store的功能,让它拥有第三方的功能,比如middleware.applyMiddleware()就是一个enhancer(在讲applyMiddleware再具体讲🧐)

这样子也相当于在存在enhancer的情况下,将创建store的过程完全交给了中间件

构建store的初始化

//存储当前的reducer函数
let currentReducer = reducer
//存储当前的state
let currentState = preloadedState
//存储当前的事件监听器
let currentListeners = []
//当前的事件监听器的引用
let nextListeners = currentListeners
//当前是否在派发action(false)
let isDispatching = false

ensureCanMutateNextListeners

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

这是createStore的内部方法,从代码来讲,只是当nextListeners等于currentListeners时,将currentListeners拷贝并且赋值给nextListeners,感觉平平无奇。(后面再讲为啥👻)

getState

function getState() {
    //...正在派发action时调用会抛出错误
    return currentState
  }

直接返回当前的state

subscribe

 function subscribe(listener) {
 	//listener不是函数的时候抛出错误
    if (typeof listener !== 'function') { ... }
	//正在派发action的时候抛出错误
    if (isDispatching) { ... }
    //订阅状态设置为true
    let isSubscribed = true
    //对当前的currentListeners进行拷贝
    ensureCanMutateNextListeners()
    //对nextListeners添加lister
    nextListeners.push(listener)

    return function unsubscribe() {
    ...
      }
  }

unsubscribe

function subscribe(listener) {
    
    ...

    return function unsubscribe() {
    //没有订阅就取消
      if (!isSubscribed) { return }
      //正在派发action时抛出错误
      if (isDispatching) { ... }
      //取消订阅
      isSubscribed = false
      //再次拷贝,取消当前监听器
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

在这里,我们再来回顾一下 ensureCanMutateNextListeners ,为什么要进行拷贝不直接使用currentListers

其实就是避免在在dispatch的过程中,又去调用subscribe/unsubscribe

dispatch

function dispatch(action) {
    // action必须是纯对象
    if (!isPlainObject(action)) { ... }
    
    // action对象中必须有type属性
    if (typeof action.type === 'undefined') { ... }
    
    // 如果正在派发action则报错,避免在reducer中调用dispatch,dispatch又调用reducer,形成一个循环...
    if (isDispatching) { ... }

    try {
      // 设置当前状态为 派发
      isDispatching = true
      // 在reducer中传入当前状态和要改变的状态,获取到最新状态
      currentState = currentReducer(currentState, action)
    } finally {
      // action结束后,改变状态
      isDispatching = false
    }
    
    //将nextListeners赋值给currentListeners以及listeners,并挨个触发监听;
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

replaceReducer

function replaceReducer(nextReducer) {
    // nextReducer必须是函数
    if (typeof nextReducer !== 'function') { ... }
    
    // 改变当前的reducer
    currentReducer = nextReducer
    
    // 通知所有的reducer状态发生改变
    dispatch({ type: ActionTypes.REPLACE })
  }

对于 replaceReducer 的使用场景,我在官网中只看见了 代码分割、在其他地方看到的还有 热更新机制动态的加载不同的reducer

observable

RxJS-based middleware for Redux that allows developers to work with async actions

他是一个基于RxJS的中间件,用于异步action,在我们的日常使用中,其实不怎么会涉及,就先忽略吧🤔

完成store的初始化

dispatch({ type: ActionTypes.INIT })

暴露一些方法

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

柯里化、高阶函数

在学习compose和applymiddleware之前,我们需要简单了解一些前置知识,如果你已经了解了,就先跳过吧 !!!

从概念上来讲:

  • 高阶函数:是接收函数作为参数,或将函数作为返回输出函数
// 比如我们在react项目使用react-redux
connect()();
  • 柯里化:接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术(我认为柯里化就是高阶函数的一种应用)
// 非柯里化的形式
function add(x, y){
	return x + y; 
}
add(1, 2) // 3

// 柯里化形式
function curryAdd(x) {
	return function(y) {
		return x + y
	}
}
curryAdd(1)(2) // 3

上面提到的都没有细讲,他们涉及到很多函数式编程的模式,如果细讲,篇幅将会很长,从学习redux的角度来讲,我们先大致了解他们的意思就行

compose

compose指的是函数组合,是函数式编程中非常重要的思想,当然也不仅仅只局限于此

在redux中作用就是实现任意的、多种的、不同的功能模块的组合,从而达到增强redux的功能的作用

export default function compose(...funcs) {
//入参为空,返回任意函数
  if (funcs.length === 0) {
    return arg => arg
  }
//入参只有一个函数,就返回这个函数
  if (funcs.length === 1) {
    return funcs[0]
  }
//这才是compose的重点😝
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

除去一些判断,compose的核心代码也就是最后那一句话

  • 首先,用到了Array.prototype.reduce方法,reduce方法其实很强大,更多内容可以参考文档
他的语法 arr.reduce(callback,[initialValue])
他的功能 从左向右,对数组中的每个元素按照传入回调函数和初始值进行运算

callback:函数中包含四个参数
  - previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
  - currentValue (数组中当前被处理的元素)
  - index (当前元素在数组中的索引)
  - array (调用的数组)
  
initialValue (作为第一次调用 callback 的第一个参数)

const arr = [1, 2, 3]
const sum = arr.reduce((sum, val) => {
  return sum + val;
}, 0);
//sum: 6

compose的规律

compose(a, b)
	=> (...args) => a(b(...args))
    
compose(a, b, c)
	=> (...args) => a(b(c(...args)))    

也就是说他的处理方式是按照 入参顺序 相反 的顺序执行的,上一个函数的结果,作为下一个函数的入参

这和koa的洋葱圈模型很像 (aop)

再用一张图来说明: 其实我们还可以发现,这不就是reduceRight方法嘛!!=> 文档 也就是说,compose函数可以这样写:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  const last = funcs[funcs.length - 1];
  const rest = funcs.slice(0, -1);
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

而且从提交记录来看,以前真的用的是reduceRight方法

至于更改的原因,作者是这样回复的:

也就是说从性能上来讲,这样的方式会比之前快1到3倍

最后注意一下,compose返回的是一个函数,只有调用他的时候才会实现他的功能compose(a,b)()

applyMiddleware

默认情况下,createStore()所创建的Redux store没有使用middleware,所以只支持同步数据流

我们可以使用applyMiddleware()来增强createStore()。虽然这不是必须的,但是它可以帮助你用简便的方式来描述异步的action

没有使用middleware: 使用middleware后:

再来看源码:

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)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

我们一段一段的分析

return createStore => (...args) => {
    const store = createStore(...args)
    
    ...
}

分析之前,先来回顾一下:

redux官网中applymiddleware的用法

const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))

createStore中当enhancer存在且为函数时,enhancer(applymiddleware)的调用

return enhancer(createStore)(reducer, preloadedState)

结合来看,这个调用就相当于

applyMiddleware(logger)(createStore)(todos, ['Use Redux'])
or
applyMiddleware(middlewware)(createStore)(reducer, preloadedState)

这个格式让我们想到什么,柯里化,对不对!!再想想柯里化的运行顺序!

了解到这一点后,我们再来分析一下一下源码

// createStore就是createStore函数,...args指的是createStore的参数(reducer和preloadedState)
return createStore => (...args) => {
// 利用传进来的方法,创建一个store对象,相当于redux将创建store的权利完全交给了中间件
    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.'
      )
    }

这句代码的的意义就是,中间件构建过程中禁止调用dispatch,为什么呢?

中间件存在的原因就是加强redux的功能,也就是通过增强dispatch的功能,在这个dispatch组装好之前,他是没有覆盖redux原有的dispatch的,这可能会导致一些功能没办法实现,

比如redux本来只支持同步action,在使用middleware后才能支持异步action,如果这个中间件还没有构建好,调用dispatch就没有办法实现派发异步action的功能

同时,用let定义dispatch的目的也就是为了方便更改dispatch的引用

const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 将chain传入compose中进行组合后,改装dispatch
dispatch = compose(...chain)(store.dispatch)

middlewareAPI是一个applyMiddleware的内置对象

在遍历中间件(middlewares.map)的时候middlewareAPI作为参数传入每个中间件中,将返回结果传递给chain

结合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;

上面的代码是柯里化后的,我们转换成es5来看:

function createThunkMiddleware(extraArgument) {
	// 为中间件的第一层传入middlewareAPI对象
    return function({ dispatch, getState }) {
      // 这里返回的函数就是chain,对chain进行compose后将store.dispatch作为参数next传入
      return function(next) {
        // 改写过后的dispatch,覆盖原有的dispatch
        return function(action) {
          // 如果参数是函数,调用改写过后dispatch
          if (typeof action === 'function') {
              return action(dispatch, getState, extraArgument);
          }
          // 如果参数不是函数,调用原本的dispatch
          return next(action);
        };
      }
    }
}

说到这里可能有点绕,我们再来整理一下这个过程

  • 中间件的形式都为({getStore, dispatch}) => (next) => (action) => {...}

  • 通过遍历,给中间件的第一层函数传入middlewareAPI对象

  • 遍历后,返回了chain数组,chain数组的每一项都是形如(next)=>(action)={...}的函数

  • compose函数的每一个参数也就是(next)=>(action)={...}

  • compose的组合方式表明,next后得到的(action)={...}作为下一个函数的参数next

  • 最后一个组合函数的nextstore.dispatch

  • 最后compose后返回的函数(action)={...}赋值给dispatch

return {
      ...store,
      dispatch
    }

返回创建的store和包装后的dispatch

再用图来说明一下middleware的作用👻👻👻👻👻👻

------>

bindActionCreator

首先我们要先了解一个概念,什么是action creatoraction creator是一个创建action的函数,他相当于一个工厂,只负责action的创建(create),而不能分发(dispatch)

那么bindActionCreator做了什么呢?他相当于将这个工厂升级了,使工厂既能创建action,又能分发action

再来看看官网的定义

把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用dispatch对每个action creator进行包装,以便可以直接调用它们。

一般情况下你可以直接在 Store 实例上调用 dispatch。如果你在 React 中使用 Reduxreact-redux 会提供 dispatch 函数让你直接调用它。

来看 bindActionCreator 的源码

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

他返回了一个函数,这个函数实现了dispatch action的功能

我们再来看 bindActionCreators

export default function bindActionCreators(actionCreators, dispatch) {
  // 当actionCreators是单个函数时,直接调用bindActionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  // 当actionCreators不是对象的时候报错
  if (typeof actionCreators !== 'object' || actionCreators === null) {
   ...抛出错误
  }
  // 创建一个新对象
  const boundActionCreators = {}
  // 对actionCreators这个对象进行遍历,让他的每一项,调用bindActionCreator
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  // 最后返回一个新对象
  return boundActionCreators
}

对于这个actionCreators来讲

在其他文件引入后,会变成一个对象:

对于单个的actionCreator来讲,在调用 bindActionCreator 之后,就会变成

text => dispatch(addTodo('text');

对于多个的actionCreator来讲,在调用 bindActionCreators 之后,就会变成

{
   addTodo : text => dispatch(addTodo('text'));
   removeTodo : id => dispatch(removeTodo('id'));
}

从而实现了dispatch调用

combineReducers

在项目中,我们常常这样使用 combineReducers 来组合多个不同 reducer 函数

再来看源码

export default function combineReducers(reducers) {
  // 参数reducer是对象,获取key
  const reducerKeys = Object.keys(reducers)
  // 过滤后的reducer,比如过滤掉重复之类的
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    // 没有获取到对应key的value时,报错
    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
    // 当reducer是function时,相当于一将reducer拷贝给finalReducers
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // 非生产环境中,定义一个unexpectedKeyCache的‘池子’
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    // 检查finalReducer中的reducer接受一个{ActionTypes.INIT}或一个ActionTypes.PROBE_UNKNOWN_ACTION()时,是否依旧能够返回有效的值
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
  // 这个返回的函数就是一个组合后的reduce
  // 这个返回的函数 返回的是一个state
  return function combination(state = {}, action) {
    // 存在shapeAssertionError的错误就抛出错误
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      // 对入参再做一次校验,比如判断
      // state是不是纯对象等
      // reducers是否为空
      // 通过inputState的key不在reducers和unexpectedKeyCache中对inputState进行筛选
      // ...
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    // 定义一个变化标示符
    let hasChanged = false
    // 定义新的state
    const nextState = {}
    // 遍历过滤后的reducerKeys
    for (let i = 0; i < finalReducerKeys.length; i++) {
      // 获取key 和 reducer
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 获取当前key对应的state
      const previousStateForKey = state[key]
      // 更改对应reducer里面的state
      const nextStateForKey = reducer(previousStateForKey, action)
      // 不存在state就抛出错误
      if (typeof nextStateForKey === 'undefined') {
       // reducer不能返回undefined
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 生成新的state对象
      nextState[key] = nextStateForKey
      // 通过判断对象的引用来判断是否发生改变,所以每次reducer的改变总是要返回一个新的state对象
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 在循环中已经判断了hasChanged,在这里再判断一次是为了解决:
    // 当使用CombineReducers调用replaceReducers并删除传递给它的其中一个reducers时,状态不会更新的问题
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    // 返回state  
    return hasChanged ? nextState : state
  }
}

重复判断hasChanged原因: github issue中的讨论

简单说说我学到的

  • function isCrushed() {}

通过判断伪函数的name属性来判断是否存在开发环境中代码压缩的情况

  • 组合函数(compose)的编写

reduce方法、reduceRight方法

  • 纯函数(isPlainObject)的判别方式

lodash\jq\redux中都有

  • 高阶函数、柯里化

redux源码中,很多地方都用到了高阶函数,特别是中间件那一块,刚开始只会觉得看着云里雾里,看多了觉得这样的写法很简洁

  • 发布、订阅模式

发布订阅函数和观察者模式(很多时候会将他们混为一谈,虽然从功能实现上差不多,但还是有细微区别)

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

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

使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers

  • 纯函数
    相同输入永远得到相同的输出

函数与外界接触的渠道只有传入的参数和返回值

没有副作用

  • 为什么reduer必须是纯函数

我们再来看这段代码,我们是通过判断hasChanged是否为true来判断返回新的state,还是旧的state

hasChanged又是根据比较nextStateForKeypreviousStateForKey的引用来判断的

那么再来看这句代码:nextStateForKey是通过previousStateForKey获取的

如果reducer不采用纯函数的方式,我们就会这样编写reducer 这样子就会导致state无法更新,因为state的引用是没有改变的

为什么根据引用来判断而不是属性值呢,深层次的比较是很消耗性能的比如深拷贝,所以redux干脆对reducer做了一个纯函数的约束

  • redux数据流 先放俩经典图😝😝😝😝😝😝😝😝

用户通过交互事件触发了actionaction经过middleware的处理包装后被dispatchreducer

reducer根据action的指令改变state,页面view一直监听store中数据变化,变化后将新的state渲染在页面,从而实现用户响应

因为我们可能会在不同的组件中执行相同的action,you might want to debounce some actions(这句话不是很懂🤢😅😭😭,消除action??)或者某些本地状态(如自动递增ID)与action creator的关系更加紧密

所以说,从维护的角度,将action creator提取出来会更好

Redux Thunk或者Redux Promise只是一个语法糖,帮助我们dispatching thunk或者promise,也不是非要用到他

也就是说有了中间件,我们不用去思考什么异步同步,可以将我们的重心放在组件上. 讨论中还提到了redux-saga,很有意思,下次讲😝

image

思考中...还有很多呀,比如redux、mobx的区别、函数式编程啥啥啥的

总结

有一说一,学习源码的过程还是挺难的😵,需要静下心来猜为什么这样写,对于appyMiddleware这部分,要多去debugger哦!!

可能文章中存在需要不严谨的地方,欢迎在留言区与我讨论!😸

参考链接🔗基本上都放在了原文中最后附上 redux官网链接