Redux源码解析(部分)

2023-05-16

相信用过React的小伙伴对于Redux一定不陌生,A Predictable State Container for JS Apps,这是官方文档对于Redux的定义,即一款适用于JS Apps的可预测的状态管理容器,简单来讲,Redux就是用来存放一些我们在项目中不同组件都需要使用的一些公共数据,并且通过Redux内置的方法在不同组件中去修改或者读取这些公共数据,并且实际上Redux和React并不是一种绑定的形式,也就是说Redux是一个通用的解决方案,不受制于框架类型的限制,只不过通常我们在使用Vue的时候,会习惯使用与Vue绑定的Vuex来管理我们的公共数据,所以慢慢地React和Redux,Vue和Vuex就被绑定到一起使用了。

其实对于Redux而言,是有专门适用于React框架的react-redux库来使用的,只不过今天的内容是对Redux的解析并且实际上react-redux也是对Redux的封装从而可以更加简单方便的在React框架中使用,所以弄清楚了Redux本身后,再去理解react-redux就会很轻松。

Redux使用

createStore(reducer, preloadedState, enhancer)

创建一个store对象,方法包含三个参数,reducer是我们传入的修改state数据的回调函数,且reducer的返回值必须是一个state,即我们定义的state数据(必须),preloadedState是我们传入的默认state数据(可选),enhancer是我们传入的增强器,Redux内部提供的唯一的内置增强器为applyMiddleware(可选),关于增强器的概念在后续我们会一步一步说明,这里暂时只需要现有一个概念即可,也就是我们可以在创建store对象时传入增强器回调函数用来改变一些操作行为

store.getState()

获取store对象的state数据

store.dispatch(action)

官方定义的改变store对象state数据的唯一行为,如果我们想对state数据进行操作,使用dispatch()方法是官方唯一推荐的方法

import { createStore } from 'redux'

// 单独定义一个state集合的目的在于如果后续我们需要添加更多的数据,则只需要在num里面添加对应的属性和数据即可
// 保证在当前的这个store中始终只存在一个数据源
const num = {
  obj: {
    id: 1,
    name: 'xiaohong',
    age: 20,
  }
}

// 自定义的reducer方法
const countReducer = (state = num.obj, action) => {
  switch (action.type) {
    case 'increment':
      state.age += 1
      return state
    case 'decrement':
      state.age -= 1
      return state
    default:
      return state
  }
}

// 这里说明一下,虽然方法提供了第二个参数用来初始化我们的state数据,但是实际上我们通常会在传入的reducer回调函数中的state参数传入我们的初始化state数据,最终也同样会作为我们store对象的默认state数据,至于原因,后续在源码解析里面我们会看到对应的处理方式
const store = createStore(countReducer)

// 在Redux中,官方定义的可以改变state数据的行为只有一种,那就是通过dispatch(action)方法,其中我们传入的action对象里面的type
// 会对应reducer中的switch()方法来判断不同的type执行不同的函数体
store.dispatch({type: 'increment'})

// 我们可以通过store对象内置的subscribe()方法来观测store的变化,每一次的dispatch()执行都会触发,同时执行我们传入的回调函数,并且观测方法的调用可以累加,也就是当我们写了多个subscribe()方法时,每一次的改变都会依次触发所有的subscribe()方法和执行传入的回调参数
store.subscribe(() => {
  // 通过getState()内置方法可以获取store对象的state数据
  console.log(store.getState())
})

console.log(store.getState()) // { id: 1, name: 'xiaohong', grade: 21 }

Redux源码解析

在了解了Redux的基本使用之后,正式的开始我们对于Redux源码的分析,同时在学习源码的过程中,也会对Redux内置的其他常用方法进行说明

Redux项目结构

image-20211117145445410

这里我们截取了redux源码src目录下的文件内容,可以看到主要分为三个部分

types文件夹:存放相关的类型定义,使用TypeScript实现

utils文件夹:存放一些公共方法

和上面两个文件夹处于同层的5个redux核心方法文件以及index.js入口文件

我们可以看到其实整个redux核心代码并不是很多,而且文件的结构和命名也使得我们可以很清楚的知道这个文件实现的是redux的哪一个部分,那么接下来我们就开始对这些文件进行进一部分解析。

types文件夹

存放redux实现过程中需要的各种类型定义

utils文件夹

存放一些需要使用的公共方法

actionTypes.ts

// 生成指定长度的随机字符串
const randomString = () =>
  Math.random().toString(36).substring(7).split('').join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${/* #__PURE__ */ randomString()}`,
  REPLACE: `@@redux/REPLACE${/* #__PURE__ */ randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

export default ActionTypes

文件结构很简单,最终暴露出来两个action类型,用于通过createStore()方法创建store对象时,对于state数据的初始化操作

randomString()方法的作用是生成一个随机的经过处理的字符串用于拼接在初始化类型字符串的后面,这里我想着重解析一下这个随机字符串处理的方法

Math.random():生成一个随机数这个是一个经常会使用到的方法

toString(36):注意这里的toString()方法并不是我们经常所使用的处于Object原型上的转化为字符串的toString()方法,而是Number.prototype.toString(redix),目的是返回执行Number类型的字符串表示形式,允许传入参数,用于数字到字符串的转换的基数(2 - 36),也就是通常我们想要将数字转换为其他的进制表示的形式,这里的处理是将随机数转换为36进制的字符串表示形式

Substring(7).split(’’).join(’.’):截取字符串从索引值为7开始到字符串末尾,且通过split()方法对字符串的每一个字母中间添加"."并返回新字符串,这里substring(7)设定为7的原因在于经过对随机数的36进制转化之后,从索引值为7开始截取一直到字符串末尾,可以保证生成的最终字符串为一个固定位数的字符串,如y.k.x.d.u.c这种形式

isPlainObject.ts

export default function isPlainObject(obj: any): boolean {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

判断对象是否是简单对象

什么是简单对象:只有通过{}或new Object()创建的对象才能称为简单对象

这里关于getPrototypeOf()方法还需要再了解清楚一点

这里再解释一下Object.getPrototypeOf()方法:方法返回指定对象的原型(内部prototype属性的值)

warning.ts

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

这里的代码很简单,目的就是为了打印错误信息,不过需要提的一点是在函数内部首先做了一个关于console和console.error的类型判断,目的是为了适配IE浏览器,因为在IE8及更低的版本当中是不支持console相关的打印语句的,只能说还是大神考虑的周到,说实话不是看到这个代码再去查了一下关于console方面的信息的话,我根本不知道IE8还有这一层限制。

为了适配低版本的浏览器,当不能执行console语句时,直接选择通过抛出异常的方式来显示错误信息

Redux核心方法实现部分

index.js

Redux的入口文件

import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

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/configuration/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}

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

首先可以看到入口文件最后导出了store的基本API,外部使用可以直接通过import来引入对应的方法即可

这里还有一个定义的空函数 isCrushed() 和 if条件判断语句:

​ 首先是对于node环境的判断,如果处于开发环境,则函数执行结束,不会打印错误信息

​ 其次是对于函数名类型的定义以及其名称的判断,这里需要解释一下为什么需要对函数名称进行处理,是因为我们在打包项目的时候需要进行压缩,经过压缩过后的代码其中的函数名称会被替换为一个字母,这样就很好理解为什么需要三个判断共同起作用了,当node环境不是生产环境且代码未经过压缩时,调用warn()方法打印警告信息。

// 未压缩
function isCrushed() {}
if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
)
  
// 压缩后
function d(){}"string"==typeof d.name&&"isCrushed"!==d.name

这里引用了https://segmentfault.com/a/1190000016460366文章中对于该函数的解释

在结尾的export导出内容当中,除了我们熟悉的方法以外,还多出了一个__DO_NOT_USE__ActionTypes,往上找可以看到这个就是我们在前面介绍的actionType.js文件的导出内容,也就是初始的action类型,目的应该是为了使得开发者可以检查一下自己命名的action中的type类型是不是和redux默认的初始化类型有所冲突,虽然这个概率非常低,但是可以看出作者思考的真的是很全面

createStore.ts

createStore()方法的实现文件,用来创建一个store对象

在官网上对于createStore(reducer, preloadedState, enhancer)方法的介绍包含三个参数

Reducer(Function):即用来操作state数据的回调函数

preloadedState(Object):初始传入的state数据,平常很少用到,我们习惯将初始数据通过reducer回调函数中的第一个参数state指定默认值的方式传入,前面提到,这两种方式最终都会成为store对象里面state的初始值

enhancer(Function):增强器,用来曾江Redux,并且通过增强器可以使用一些中间件来实现更多的修改行为的操作

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. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
    )
}

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
  preloadedState = undefined
}

  // 如果增强器参数不为空
 if (typeof enhancer !== 'undefined') {
    // 且增强器为函数的情况下
    if (typeof enhancer !== 'function') {
      throw new Error(
        `Expected the enhancer to be a function. Instead, received: '${kindOf(
          enhancer
        )}'`
      )
    }

    // 返回高阶函数,即createStore作为增强器的参数,增强器的返回值为函数,且reducer和preloadedState作为返回值函数的参数
    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  if (typeof reducer !== 'function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(
        reducer
      )}'`
    )
  }

在createStore()方法内部,首先是四个判断条件,我们一个一个来分析一下

第一个if条件判断:当传入的三个参数的第二个参数preloadedstate为函数类型且第三个参数enhancer也为函数类型时,或第三个参数enhancer为函数类型且存在第四个为函数类型的参数时,抛出异常(Redux只允许传入一个增强器,不允许传入多个,且第二个参数state的初始值不能是函数形式)

第二个if条件判断:当第二个参数preloadedstate为函数类型且不存在第三个参数时,将第二个函数当做增强器使用,且将原本函数定义的第二个参数preloadedState置为undefined

第三个if条件判断:当传入的enhancer不为空时,如果类型不是函数类型,抛出异常,反之调用增强器函数,将createStore作为参数,将reducer,preloadedState作为返回值函数的参数

第四个if条件判断:当传入的enhancer不是函数类型时,抛出异常

(上述四个条件判断的目的,是为了确保传入createStore()方法的参数都是正确的类型)

let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false

currentReducer:保存当前传入的reducer

currentState:保存当前传入的preloadedState(由于大多数state是、初始值不是通过传参,故当前值大部分情况下为undefined)

currentListeners:当前订阅者列表,初始为空

nextListeners:保存订阅回调函数(即当我们使用subscribe(callback)方法是传入的回调函数)

isDispatching:作为一个状态判断来使用,即确保数据的修改是串行而不是并行,也就是同一时间只允许一个action对state进行操作,true表示正在执行action中

// 对当前订阅者列表进行一次浅拷贝,在订阅/取消订阅时出现bug时,可以将nextListeners作为临时的订阅者列表进行操作
function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
function getState(): S {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState as S
  }

getState()方法,也就是我们在实际项目中使用的store.getState()来获取store对象中的state数据,当在执行action操作时,不允许同时执行getState()来读取state数据

function subscribe(listener: () => void) {
    if (typeof listener !== 'function') {
      throw new Error(
        `Expected the listener to be a function. Instead, received: '${kindOf(
          listener
        )}'`
      )
    }

    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api/store#subscribelistener for more details.'
      )
    }

    let isSubscribed = true
    
    ensureCanMutateNextListeners()
  
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api/store#subscribelistener for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

subscribe()方法,也就是我们在实际项目中使用的store.subscribe(callback)来实现对于state数据的订阅,当通过dispatch()改变state数据时,就会遍历订阅列表,执行其中的回调函数

当传入的listener不是函数类型或者正在执行action操作时,都会直接抛出异常

设置初始订阅判断:isSubscribed = true 表示已经处于订阅状态

ensureCanMutateNextListeners():执行浅拷贝操作,出现问题时可以将nextListeners作为临时的订阅列表进行操作

nextListeners.push(listener):将订阅回调函数推入新的订阅者列表数组保存

我们可以看到,subscribe()方法的返回值的函数命名是unscribe,也就是说当我们调用subscribe()时,处于订阅状态,如果使用subscribe()()实际上就取消了这一次的订阅,响应的将当前订阅者列表置空,从新的订阅者列表数组当中取出当前传入的listener订阅回调函数,即完成了本次的取消订阅操作

function dispatch(action: A) {
    if (!isPlainObject(action)) {
      throw new Error(
        `Actions must be plain objects. Instead, the actual type was: '${kindOf(
          action
        )}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    // 初始的preloadedState如果不传入的话为空,之所以通过getstate()依旧可以读取到我们在reducer中传入的初始state,是在此处,将currentReducer()方法
    // 的返回值传递给了currentState
    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
  }

dispatch()方法,即我们在实际项目中使用的官方推荐的唯一可以对state数据进行操作的行为store.dispatch(action)

当传入的action不是一个简单对象(简单对象的定义我们在解析isPlainObject()方法时提过,只有通过{}或new Object()出案件的对象才是简单对象),抛出异常

当传入的action不存在type属性时,抛出异常,因为我们在修改state数据时就是根据type来进入不同条件的代码块中执行的

当正在执行action行为时,抛出异常,Redux规定action的执行只能是串行而不能并行

修改isDispatching状态为true,表示正在执行action行为

currentState = currentReducer(currentState, action):覆盖保存通过action行为修改过后的state数据

执行结束后修改isDispatching状态为false,表示action行为执行结束

//遍历订阅者列表,执行其中存在的监听回调函数,即我们使用subscribe(callback)时传入的函数类型的参数,通过这个遍历我们可以看到为什么在实际使用时的时候,每调用一次dispatch()都会触发执行订阅列表的内容,是因为订阅列表的执行并不是在subscribe()方法内部而是在dispatch()方法内部
for (let i = 0; i < listeners.length; i++) {
   const listener = listeners[i]
   listener()
}
function replaceReducer<NewState, NewActions extends A>(
    nextReducer: Reducer<NewState, NewActions>
  ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
    if (typeof nextReducer !== 'function') {
      throw new Error(
        `Expected the nextReducer to be a function. Instead, received: '${kindOf(
          nextReducer
        )}`
      )
    }

    ;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer
  
    dispatch({ type: ActionTypes.REPLACE } as A)

    return store as unknown as Store<
      ExtendState<NewState, StateExt>,
      NewActions,
      StateExt,
      Ext
    > &
      Ext
  }

顺带提一下replaceReducer()方法,之所以是顺带是因为这个方法我们在实际项目中几乎不会去使用,它的作用是替换掉当前的reducer使用新的reducer,内部的代码也很简单,判断新的reducer是否是函数形式,将新的reducer赋值给currentReducer来替换掉原先的reducer,执行dispatch()且type类型为actionType.js文件中定义的默认ActionTypes.REPLACE类型

	dispatch({ type: ActionTypes.INIT } as A)

  const store = {
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  } as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext

  return store

其实在阅读到这里之前我都一直存在一个疑问,那就是state的默认值问题,通常情况下我们是不通过第二个参数preloadedState来传入state的初始数值的,那么首先 let currentState = preloadedState as S这段代码赋值后currentState的值肯定为undefined,我们在调用getState()方法时,可以看到其实方法本质就是return currentState所以我们才可以读取到state的内容。而currentState = currentReducer(currentState, action)这段代码是存在于dispatch()方法内部的。

所以就产生了一个问题,当我在不使用dispatch()方法时,就调用getState(),且通过reducer的方式传入state的默认值不通过preloadedState的参数传入,那么是如何获取到初始的state值的呢。

实际上,在createStore()方法的末尾,会执行一次dispatch()操作,并且传入的tye值为默认定义的ActionTypes.INIT,关于这个默认类型我们前面有专门介绍过,最终的结果是生成一个随机的字符串,那么这个type跟我们传入的reducer()方法内的type是没有对应匹配的,故直接返回state,也就是我们在reducer中传入的初始state的数值,故即使在使用时不调用任何dispatch()方法的前提下,也能通过getState()方法获取到state的数值,疑问自然而言也就解开了。

combineReducers.ts

combineReducers(reducers)方法的实现文件,用来合并多个reducer

方法接收一个reducers对象集合(Object)作为参数,如{one: reducerOne, two: reducerTwo}

const reducerKeys = Object.keys(reducers)
const finalReducers: ReducersMapObject = {}

for (let i = 0; i < reducerKeys.length; i++) {s
    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]
    }
}

reducerKeys:存储传入的reducers集合的属性名,如果是以{one: reducerOne, two: reducerTwo}方式传入,则属性名集合为[“one, two”],且会作为state数据中每一个reducer传入的默认state参数的集合名。如果以{reducerOne, reducerTwo}方式传入,则属性名集合为[“reducerOne”, “reducerTwo”],且会作为state数据中每一个reducer传入的默认state参数的集合名。

finalReducers:存储最终的reducer集合

for()循环存储的reducerKeys属性名集合,当前环境不是生产环境且当传入的reducers集合当中不存在reducerKeys中的属性名时,抛出警告异常

当reducers中对应属性名的类型是函数类型时,将该reducer存入finalReducers中

return function combination(
    // 这里的参数如果不使用dispatch()等方法操作时,state为{}且action为默认的初始值{type: "@@redux/INIT3.5.4.b.l"}
    // 这里还需要好好研究一下
    // 这里的传参实际上是dispatch()方法中 currentState = currentReducer(currentState, action)这段代码实现的,所以action的值对应的就是执行的type或初始的默认值
    // 而state也对应的是初始的值或已经经过dispatch
    state: StateFromReducersMapObject<typeof reducers> = {},
    action: AnyAction
  ) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    // console.log(state, action)

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState: StateFromReducersMapObject<typeof reducers> = {}
    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 actionType = action && action.type
        throw new Error(
          `When called with an action of type ${
            actionType ? `"${String(actionType)}"` : '(unknown type)'
          }, the slice reducer for key "${key}" returned undefined. ` +
            `To ignore an action, you must explicitly return the previous state. ` +
            `If you want this reducer to hold no value, you can return null instead of undefined.`
        )
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length

    console.log(nextState)
    return hasChanged ? nextState : state
  }

我们可以看到,其实combineReducers()方法的返回值是一个函数。

首先我们可以看到返回的函数接收两个参数,一个是state,一个是action,我们需要分析一下这两个参数是在什么地方传入的

const combine = combineReducer({reducerOne, reducerTwo})
const store = createStore(combine)

我们可以看到最终我们将合并后的reducer集合作为参数传入createStore()的参数,也就是createStore()方法中接收的reducer参数,在上面分析createStore.js文件的时候,我们分析过方法内部的state初始化实际上是在最后调用了dispatch({ type: ActionTypes.INIT } as A),在dispatch()方法内部,我们可以看到是通过currentState = currentReducer(currentState, action)这一行代码来进行赋值操作的,所以答案揭晓,我们在combineReducers()方法内部的返回函数中的参数就是这么传递过来的

    let hasChanged = false
    const nextState: StateFromReducersMapObject<typeof reducers> = {}
    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 actionType = action && action.type
        throw new Error(
          `When called with an action of type ${
            actionType ? `"${String(actionType)}"` : '(unknown type)'
          }, the slice reducer for key "${key}" returned undefined. ` +
            `To ignore an action, you must explicitly return the previous state. ` +
            `If you want this reducer to hold no value, you can return null instead of undefined.`
        )
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redux源码解析(部分) 的相关文章

随机推荐

  • 模式识别—判别函数分类法(几何分类法)

    目录 统计模式识别之判别分析判别函数定义解释样例 判断函数正负值的确定确定判别函数的两个因素 线性判别函数一般形式性质两类情况多类情况 小结 广义线性判别函数目的 线性判别函数的几何性质模式空间与超平面概念讨论小结 权空间与权向量解概念线性
  • 【脚本】echo 输出赋值给变量

    链接 xff1a http zhidao baidu com link url 61 FMhso6Hf4eeRQN7p2qqzLOBAYPwh6yMJCWOvgmBFTDYWAEZ9ceuREtWhggxtcYG1iBhaJgqrcU7ad
  • 联邦学习 - 基础知识+白皮书+杨强教授讲座总结+同态加密+ 差分隐私

    联邦学习 兴起原因概念分类横向联邦学习纵向联邦学习联邦迁移学习 优势系统架构联邦学习与现有研究的区别联邦学习与差分隐私理论的区别联邦学习与分布式机器学习的区别联邦学习与联邦数据库的关系 联邦学习的最新发展及应用 xff08 2019第四届全
  • boomlab 实验 炸弹实验 系统级程序设计 CMU

    MENU boomlab还有30s到达实验1Step1 反汇编vim大法检查boom原因gdb调试出结果examinequit 实验二分析汇编语言ENDING 实验三答案 实验四func4 实验五实验六gdb调试 答案汇总ENDING问题解
  • CSAPP Lab:attacklab

    大小尾端 首先关于这个 xff0c 我一直没记清楚 xff0c 所以做个总结 xff1a 在裘宗燕翻译的 程序设计实践 里 xff0c 这对术语并没有翻译为 大端 和小端 xff0c 而是 高尾端 和 低尾端 xff0c 这就好理解了 xf
  • Advances and Open Problems in Federated Learning 总结翻译

    摘要 联邦学习 FL 是一种机器学习设置 xff0c 在这种设置中 xff0c 许多客户 例如移动设备或整个组织 在中央服务 器 例如服务提供商 的协调下协作地训练模型 xff0c 同时保持训练数据分散 FL体现了集中数据收集和最 小化的原
  • Multi-Center Federated Learning

    Multi Center Federated Learning Motivation 现有的联合学习方法通常采用单个全局模型来通过汇总其梯度来捕获所有用户的共享知识 xff0c 而不管其数据分布之间的差异如何 但是 xff0c 由于用户行为
  • No Fear of Heterogeneity: Classifier Calibration for Federated Learning with Non-IID Data

    No Fear of Heterogeneity Classifier Calibration for Federated Learning with Non IID Data Existing Methods for non IID da
  • Three scenarios for continual learning

    Three scenarios for continual learning Standard artificial neural networks suffer from the well known issue of catastrop
  • MQ2烟雾传感器

    1 MQ 2气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡 SnO2 当传感器所处环境中存在可燃气体时 xff0c 传感器的电导率随空气中可燃气体浓度的增加而增大 使用简单的电路即可将电导率的变化转换为与该气体浓度相对应的输出
  • alembic

    alembic是sqlalchemy的作者开发的 用来做ORM模型与数据库的迁移与映射 alembic使用方式跟git有点了类似 xff0c 表现在两个方面 xff0c 第一个 xff0c alembic的所有命令都是以alembic开头
  • VScode远程免密登录

    安装配置python环境 xff1a 用VScode配置Python开发环境 xiaoj wang 博客园 cnblogs com VScode免密登录远程服务器 VS code ssh免密登陆 1 xff09 windows 下 xff0
  • linux虚拟机和主机能相互ping通,linux却不能访问外网

    linux虚拟机和主机能相互ping通 xff0c linux却不能访问外网 下面是试错过程 修改ifcfg eth0 xff08 名字可能不一样 xff09 vi etc sysconfig network scripts ifcfg e
  • 树莓派:树莓派的各个引脚

    由于第一次接触树莓派 xff0c xff0c xff0c emmmm xff0c 仔细写 xff0c 奥里给 3 3V 5V xff08 VCC xff09 xff1a 显然是电源正极啦 GND xff1a 接地用 xff0c 负极负极负极
  • 不分类工具:sd卡格式化工具安装教程

    下载地址 xff1a https www sdcard org downloads formatter 4 eula windows 进入上面这个链接 xff0c 你会看到满上面都是字 xff0c 有一个download xff0c 点完还
  • 不分类工具:Win32 DiskImager安装教程

    下载地址 xff1a http sourceforge net projects win32diskimager 这个也是很普普通通的下载安装 1 直接 download 2 双击安装文件 xff0c 弹出如下框 xff0c 选择我同意 x
  • Meta-Learning: Learning to Learn Fast

    Meta Learning Learning to Learn Fast 元学习 学习如何学习 译 原文 本文与原文基本没有区别 xff0c 仅供个人学习记录 电子笔记本 前言 xff1a 元学习解决 xff1a 遇到没有见过的任务 xff
  • 解决 Docker 容器时间与本地时间不一致的问题

    Linux 通过 Date 命令查看系统时间 xff0c 得到如下结果 xff1a root 64 iZ8vbg6m7f5ntzibw3t4huZ date Mon Aug 26 12 24 58 CST 2019 但是在 Docker 容
  • 记录ssh 和vnc命令

    ssh windows是客户端 linux是服务端 在windows powershell 输入 ssh rikirobot 64 192 168 x xxx xff08 ip地址 xff09 VNC Viewer 参考文章 xff1a 1
  • Redux源码解析(部分)

    相信用过React的小伙伴对于Redux一定不陌生 xff0c A Predictable State Container for JS Apps xff0c 这是官方文档对于Redux的定义 xff0c 即一款适用于JS Apps的可预测