Redux 源码分析
Redux 目录结构
redux
├─ .babelrc.js
├─ .editorconfig
├─ .gitignore
├─ .prettierrc.json
├─ CHANGELOG.md
├─ CNAME
├─ CODE_OF_CONDUCT.md
├─ CONTRIBUTING.md
├─ LICENSE-logo.md
├─ LICENSE.md
├─ PATRONS.md
├─ README.md
├─ docs // 文档
├─ errors.json
├─ logo
├─ netlify.toml
├─ package-lock.json
├─ package.json
├─ rollup.config.js // rollup 打包配置
├─ scripts
├─ src // 源代码
│ ├─ applyMiddleware.ts
│ ├─ bindActionCreators.ts
│ ├─ combineReducers.ts
│ ├─ compose.ts
│ ├─ createStore.ts
│ ├─ index.ts
| └─ types
│ └─ utils // 一些工具方法
│ ├─ isPlainObject.ts
│ ├─ kindOf.ts
│ ├─ symbol-observable.ts
│ └─ warning.ts
├─ tsconfig.json
└─ website // redux 首页网站
主要对src目录下文件进行分析,对于utils中的方法,有意思的也可以看看
index.ts
这个主要导出一些对象或者做运行环境检测的,没有特殊功能
createStore.ts
对于一个redux 应用,整个应用的状态存储在一棵state 树中,由store 维护这个状态树,并通过dispatch 进行修改。
首先看看state 的数据结构
/types/store.ts
/*** 替换当前 store 用来计算最新 state 的 reducer 函数* * app 如果实现了 code splitting,那可能要动态加载 reducer。* 如果对 redux 要实现热重载,也可能要用到这个方法** @param nextReducer 返回新替换了 reducer 的 store*/replaceReducer<NewState, NewActions extends Action>(nextReducer: Reducer<NewState, NewActions>): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext/*** store 是一个维护 app 状态树的对象* 一个 redux app 只能有一个 store,拆分和组合只能发生在 reducer 层面中** @template S state 类型* @template action 类型* @template 来自 store enhancer 的 state 拓展* @template 来自 store enhancer 的 store 拓展*/
export interface Store<S = any,A extends Action = AnyAction,StateExt = never,Ext = {}
> {/*** dispatch action。触发 state 更新的唯一方法* * 创建 store 的时候要传入一个 reducer 函数,调 diapatch 函数的时候就会调那个 reducer 方法,* 并传入对应的 action* dispatch 方法会产生一个新的 state tree,且所有的监听者都会被通知* * 基础实现仅支持普通js对象的 action,如果想要 dispatch promise、observable、thunk* 或其他什么东西要使用 middleware。举例,可以去看 redux-thunk 包的文档。* 但是使用 middleware 之后最终也会使用这个方法 dispatch 一个普通js对象 action** @returns 图方便,返回传入的那个 actio*/dispatch: Dispatch<A>/*** 获取 store 维护的 state*/getState(): S/*** 添加一个变化监听者。* 任意 action 被 dispatch 的时候都会被调用,state 树的某个部分可能会被更新了。* 可以在传入的回调函数中调用 getState 来获取最新的 state 树。** @returns 返回一个取消订阅的方法*/subscribe(listener: () => void): Unsubscribe/*** 替换当前 store 用来计算最新 state 的 reducer 函数** You might need this if your app implements code splitting and you want to* load some of the reducers dynamically. You might also need this if you* implement a hot reloading mechanism for Redux.** @param nextReducer The reducer for the store to use instead.*/replaceReducer<NewState, NewActions extends Action>(nextReducer: Reducer<NewState, NewActions>): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext/*** 观察者/响应式库的互操作点。* * @returns {observable} 变化的最小 observable.* 详情可以查看 observable 提案:* https://github.com/tc39/proposal-observable*/[Symbol.observable](): Observable<S>
因此对于 createStore 方法就是要返回一个这样的数据结构:
/*** 创建一个 Redux store 来持有整个 state 树。* 唯一改变 store 中数据的方法是对它调用 `dispatch()`。** app 中应该只有单一的 store。为了弄清楚 state 树如何针对 state 树的不同部分进行响应,* 也可以使用 `combineReducers` 来将多个 reducer 组合到单一的 reducer 函数中去** @param reducer 一个返回下一个 state 树的函数,需要接收当前的 state 树和要处理的 action。** @param preloadedState 初始 state。* 你可以选择指定它以在通用 app 中从服务器还原状态,或还原以前序列化的用户会话。* 如果你使用 `combineReducers` 来生成根 reducer 函数,* 那么该函数必须是与 `combineReducers` 的键具有相同形状的对象。** @param enhancer store enhancer。 * 你可以选择指定它以使用第三方功能(如 middleware、时间旅行、持久性等)增强 store。* Redux附带的唯一 store enhancer 是 `applyMiddleware()`。** @returns 一个 redux store,让你可以读取 state,dispatch action,并订阅 state 变化*/
export default function createStore<S,A extends Action,Ext = {},StateExt = never
>(reducer: Reducer<S, A>,preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {// 做一些参数和运行环境的校验,省略// 如果 enhancer 是函数,则返回 enhancer 加强 store,省略let currentReducer = reducerlet currentState = preloadedState as Slet currentListeners: (() => void)[] | null = []let nextListeners = currentListenerslet isDispatching = false/*** 对 currentListeners 做一次浅拷贝,* 使得我们在 dispatch 过程中可以使用 nextListeners 作为临时的 list* * 这一步防止了任何 数据消费者 在 dispatch 过程中* 调用 subscribe/unsubscribe 出现的错误,*/function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice()}}/*** 读取 store 管理的 state 树。** @returns 当前 app 的 state 树*/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}/*** 添加一个 listener。在 action 被 dispatch 的时候,* 或 state tree 中的某些部分可能改变时被随时调用,* 你可以再回调函数中调用 `getState()` 来读取当前的 state * * 你可以从一个 listener 调用 `getState()`,但是伴随有以下警告:* * 1. 每个订阅都是在每个 `dispatch()` 调用之前的快照。* 如果你在 listener 正在被调用的时候 subscribe 或 unsubscribe,那么对于当前的 `dispatch()`* 流程来说根本没用。* 但是,下一次的 `dispatch()` 调用,无论是不是嵌套的调用,都会带上最新的 订阅 list 的快照。* * 2. listener 不应该盯着所有的 state 更改,因为在 listener 被调用之前 state 可能会* 在嵌套 `dispatch()` 过程中被多次更新。* 但是,可以保证在 `dispatch()` 启动之前注册的所有 listener 保证以最新状态调用。** @param listener 每次调用 dispatch 的时候都被触发的回调.* @returns 一个移除此 listener 的函数.*/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/*** 对于 nextListeners 也用的是不可变更新方式,* 以免在正在 dispatch 的时候添加或者移出 listener 发生错误* 也就是说,只有在对应 action 被 dispatch 之前添加或者移除 listener 才有效*/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 = falseensureCanMutateNextListeners()const index = nextListeners.indexOf(listener)nextListeners.splice(index, 1)currentListeners = null}}/*** dispatche 一个 action。这是改变 state 的唯一方法。** 用来创建 store 的 `reducer` 函数,将会根据当前的 state 树和给定 action被调用。* 它的返回解雇将会被视作 **下一个** state,并且会通知 listener。* * 基本实现仅仅支持普通的 action 对象。日过想要 dispatch 一个 Promise,Observable,* thunk 等,你得使用对应的 middleware 封装 store 创建函数。例如,可以去看 * `redux-thunk` 包的文档。虽然 middleware 最终也是通过这个方法 dispatch 一个普通对象。** @param action 一个用来表示“发生什么”的普通对象。 * 这样 action 能被序列化,你就可以记录和重现用户的会话,或者使用 `redux-devtools 完成时间旅行调试。* 一个 action 必须有 `type` 属性,且不能为 `undefined`。* 使用字符串常量来定义这个属性是个好主意** @returns 为了方便,返回你 dispatch 的那个原对象** 注意到,如果你使用一个通用 middleware,他可能会封装 `dispatch()` 从而返回一些其他东西* (比如,返回一个 Promise 你能 await)。*/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.`)}// action 必须拥有 type 字段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.')}try {isDispatching = truecurrentState = currentReducer(currentState, action)} finally {isDispatching = false}// 依次通知 listenerconst listeners = (currentListeners = nextListeners)for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}return action}/*** 替换当前 store 使用的 reducer 来计算 state。* * 可能你的 app 需要代码分割并动态加载一些 reducer,也可能要实现一些 redux 热重载机制** @param nextReducer 给 store 替换的那个 reducer* @returns 替换过 reducer 的同一个 store 实例*/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)}`)}// TODO:现在的实现不够优雅;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer// 这个 action 和 ActionTypes.INIT 效果一样// 新的和旧的 rootReducer 中存在的任何 Reducer 都将接收以前的状态。// 这将使用旧 state 树中的任何相关数据有效地填充新 state 树。dispatch({ type: ActionTypes.REPLACE } as A)// 通过强转类型为新 store 来改变 store 类型return store as unknown as Store<ExtendState<NewState, StateExt>,NewActions,StateExt,Ext> &Ext}/*** 观察式/响应式库的交互切点* @returns state 变化的最小可观察。* 有关更多信息,请参阅可观察提案:* https://github.com/tc39/proposal-observable*/function observable() {const outerSubscribe = subscribereturn {/*** 最小的 observable 订阅的方法* @param observer 可以被用作 observer 的任何对象* observer 对象都应该有 `next` 方法。* @returns 一个具有 `unsubscribe` 方法的对象,这个对象可以用来从 store 取消订阅 observable,* 并防止进一步从 observable 获得值*/subscribe(observer: unknown) {if (typeof observer !== 'object' || observer === null) {throw new TypeError(`Expected the observer to be an object. Instead, received: '${kindOf(observer)}'`)}function observeState() {const observerAsObserver = observer as Observer<S>if (observerAsObserver.next) {observerAsObserver.next(getState())}}observeState()const unsubscribe = outerSubscribe(observeState)return { unsubscribe }},[$$observable]() {return this}}}// 当 store 被初始化以后,一个 "INIT" action 就会被 dispatch,这样每个 reducer 返回他们的初始 state。// 这有效地填充了初始 state 树。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> & Extreturn store
}
bindActionCreators.ts
为了使用方便,redux 提供了一个根据 action 的 key 返回一个封装了 disptch 方法的函数bindActionCreators 。相当于不用手动dispatch 了。
这个方法不是必须的,手动调dispatch(createAction()) 也是可以的。
/*** 给单个 action 绑定 dispatch 函数* @param actionCreator 一个 action creator 函数* @param dispatch store.dispatch 方法* @returns 返回一个函数,这个函数直接调用就相当于 dispatch 一个对应的 action*/
function bindActionCreator<A extends AnyAction = AnyAction>(actionCreator: ActionCreator<A>,dispatch: Dispatch
) {return function (this: any, ...args: any[]) {return dispatch(actionCreator.apply(this, args))}
}
/*** 把一个值都是 action creator 的对象转化成另一个有着相同键的对象,* 但是每个函数都封装了一个 `dispatch` 调用进去,所以都能被直接调用。* 这是个简便方法,你可以自己调用 `store.dispatch(MyActionCreators.doSomething())`* * 为了方便,你也能传一个 action creator 进去作为第一个参数,* 返回值得到了一个 封装了 dispatch 的函数** @param actionCreators 一个值都是 action creator 函数的对象。* 一个简单的获得方法就是使用 ES6 语法 `import * as`,* 你也可以传单个函数。** @param dispatch Redux store 中可用的 `dispatch` 函数。** @returns 模仿原始对象的对象,但是每个 action creator 都封装进了 `dispatch` 调用。* 如果你传入一个函数比如 `actionCreators` ,返回值仍然是单个函数。*/
export default function bindActionCreators(actionCreators: ActionCreator<any> | ActionCreatorsMapObject,dispatch: Dispatch
) {if (typeof actionCreators === 'function') {return bindActionCreator(actionCreators, dispatch)}if (typeof actionCreators !== 'object' || actionCreators === null) {throw new Error(`bindActionCreators expected an object or a function, but instead received: '${kindOf(actionCreators)}'. ` +`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`)}const boundActionCreators: ActionCreatorsMapObject = {}for (const key in actionCreators) {const actionCreator = actionCreators[key]if (typeof actionCreator === 'function') {boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)}}return boundActionCreators
}
applyMiddleware.ts
顾名思义,就是将 middleware 应用到 store 上,来对 store 或者其他的一些东西进行增强。
/*** 创建一个 store enhancer 来将 middleware 应用到 redux store 的 dispatch 方法。* 这对于多种任务来说非常方便,例如,以简洁的方式表达异步操作,或记录每个 action payload。** 看 `redux-thunk` 包可以作为一个 redux middleware 的例子* 因为 middleware 可能是异步的,所以这应该是组合链中的第一个 store enhancer。** 注意每个 middleware 都会有 `dispatch` 和 `getState` 函数作为具名参数。** @param middlewares 需要应用的 middleware 链* @returns 一个应用 middleware 的 store enhancer** @template Ext middleware 添加的 dispatch 签名* @template S middleware 支持的 state 类型。*/
export default function applyMiddleware(...middlewares: Middleware[]
): StoreEnhancer<any> {return (createStore: StoreEnhancerStoreCreator) =><S, A extends AnyAction>(reducer: Reducer<S, A>,preloadedState?: PreloadedState<S>) => {const store = createStore(reducer, preloadedState)let dispatch: Dispatch = () => {throw new Error('Dispatching while constructing your middleware is not allowed. ' +'Other middleware would not be applied to this dispatch.')}const middlewareAPI: MiddlewareAPI = {getState: store.getState,dispatch: (action, ...args) => dispatch(action, ...args)}const chain = middlewares.map(middleware => middleware(middlewareAPI))/*** chain的结构: Array< (next: Dispatch<AnyAction>) => (action: AnyAction) => any >* compose 的作用:compose(A, B, C, arg) === A(B(C(arg))) 最后一个参数是 store.dispatch,* 使得 middleware 依次执行,最后执行 store.dispatch。 柯里化,串联* compose 传入的函数列表后,新生成的 dispatch 调用的时候相当于依次调用这些 middleware 后* 最后调用原生的 store.dispatch*/dispatch = compose<typeof dispatch>(...chain)(store.dispatch)return {...store,dispatch}}
}
applyMiddeware 方法调用后生成一个StoreEnhancer ,可以查看其类型定义:
/*** store enhancer 是一个高阶函数,将一个 store creator 组装成一个新的,增强过的* store creator。和 middleware 相似,以组合式方式更改 store。*/
export type StoreEnhancer<Ext = {}, StateExt = never> = (next: StoreEnhancerStoreCreator<Ext, StateExt>
) => StoreEnhancerStoreCreator<Ext, StateExt>/** 增强过的 storeCreator 类型 */
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <S = any,A extends Action = AnyAction
>(reducer: Reducer<S, A>,preloadedState?: PreloadedState<S>
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
因此,applyMiddleware 的使用方法大概是:
applyMiddleware(thunkMiddleware,middleware1,middleware2,...)({ reducer: ..., preloadedState: {} });
对于一个 middleware 来说,必须要实现 MiddlewareAPI,其类型定义如下:
export interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {dispatch: DgetState(): S
}export interface Middleware<_DispatchExt = {}, // TODO: remove unused component (breaking change)S = any,D extends Dispatch = Dispatch
> {(api: MiddlewareAPI<D, S>): (next: D) => (action: D extends Dispatch<infer A> ? A : never) => any
以redux-thunk 的实现为例(https://github.com/reduxjs/redux-thunk/blob/master/src/index.ts),实现一个middleware 需要传入的dispatch 方法和getState 方法作为参数:
function createThunkMiddleware<State = any,BasicAction extends Action = AnyAction,ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {// Standard Redux middleware definition pattern:// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middlewareconst middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =({ dispatch, getState }) =>next =>action => {// The thunk middleware looks for any functions that were passed to `store.dispatch`.// If this "action" is really a function, call it and return the result.if (typeof action === 'function') {// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"return action(dispatch, getState, extraArgument)}// Otherwise, pass the action down the middleware chain as usualreturn next(action)}return middleware
}
applyMiddleWare方法在生成强化后的dispatch方法的时候,用到了一个compose方法,该方法是将一个函数列表柯里化。
export default function compose(...funcs: Function[]) {if (funcs.length === 0) {// 推断参数类型,使其在推断中可用return <T>(arg: T) => arg}if (funcs.length === 1) {return funcs[0]}return funcs.reduce((a, b) =>(...args: any) =>a(b(...args)))
}
这里使用柯里化主要是:1、代码可读性;2、易于增删中间件
贴一个 ChatGPT 的回答:
combineReducers.ts
combineReducers方法是在 Redux 库中提供的一种工具。它的作用是合并多个 reducer 函数,并将它们合并成一个单一的 reducer 函数,以便在 Redux 中管理多个不同的数据状态。好处是能够让你的应用更加模块化和结构化,并且更加容易维护和管理。
/*** 将多个 Reducer 函数整合为一个 Reducer,以便在 Redux 中管理多个不同的数据状态* 它将调用所有的 子 reducer,并且把调用结果汇集到一个 单一的 state 对象中,* 这个 state 对象的键就是传入的 reducer 函数名。** @template S 组合 state 对象的类型。** @param reducers 一个值对应的 reducer 函数要被合并为一个的对象。* 一个简便方法是使用 ES6 `import * as reducers` 语法来获取。* reducers 对于任何 action 可能都不会返回 undefined。* 相反的,他们需要返回自己的 初始 state。* 相反,如果传递给它们的 state 是 undefined,它们应该返回初始 state,* 而对于任何无法识别的操作,则返回当前 state。** @returns 一个 reducer 函数,它调用传递对象内的每个 reducer,* 并构建具有相同形状的 state 对象。*/
export default function combineReducers(reducers: ReducersMapObject) {// 所有 reducer 的 keyconst reducerKeys = Object.keys(reducers)// 最终生成的 reducer 对象const finalReducers: ReducersMapObject = {}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}"`)}}// 仅保留 reducer 对象中 值为 function 的键值对if (typeof reducers[key] === 'function') {finalReducers[key] = reducers[key]}}const finalReducerKeys = Object.keys(finalReducers)// 这用于确保我们不担心使用到相同的键。let unexpectedKeyCache: { [key: string]: true }if (process.env.NODE_ENV !== 'production') {unexpectedKeyCache = {}}/*** 校验 reducer 是否都符合规定,见 assertReducerShape 方法(后面介绍)* 1. 能不能接受 init 的 action* 2. 能不能处理未知的 action*/let shapeAssertionError: unknowntry {assertReducerShape(finalReducers)} catch (e) {shapeAssertionError = e}return function combination(state: StateFromReducersMapObject<typeof reducers> = {},action: AnyAction) {// 存在不符合规范的 reducer,直接抛出错误if (shapeAssertionError) {throw shapeAssertionError}if (process.env.NODE_ENV !== 'production') {// getUnexpectedStateShapeWarningMessage 只是一个生成 warning 消息的方法const warningMessage = getUnexpectedStateShapeWarningMessage(state,finalReducers,action,unexpectedKeyCache)if (warningMessage) {warning(warningMessage)}}let hasChanged = falseconst nextState: StateFromReducersMapObject<typeof reducers> = {}/*** 遍历所有的 reducer 并分别执行,将计算出的 state 组合起来生成一个大的 state* 因此对于任何 action,redux 都会遍历所有的 reducer*/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.typethrow 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] = nextStateForKeyhasChanged = hasChanged || nextStateForKey !== previousStateForKey}hasChanged =hasChanged || finalReducerKeys.length !== Object.keys(state).lengthreturn hasChanged ? nextState : state}
}
assertReducerShape方法专门用来检查 reducer 是不是合法的,不合法则抛出错误:
-
试着使用 INIT Action 调用一下 Reducer,看是否能够得到一个初始状态
-
试着处理一个未知的 Action 类型
function assertReducerShape(reducers: ReducersMapObject) {Object.keys(reducers).forEach(key => {const reducer = reducers[key]const initialState = reducer(undefined, { type: ActionTypes.INIT })if (typeof initialState === 'undefined') {throw new Error(`The slice reducer for key "${key}" returned undefined during initialization. ` +`If the state passed to the reducer is undefined, you must ` +`explicitly return the initial state. The initial state may ` +`not be undefined. If you don't want to set a value for this reducer, ` +`you can use null instead of undefined.`)}if (typeof reducer(undefined, {type: ActionTypes.PROBE_UNKNOWN_ACTION()}) === 'undefined') {throw new Error(`The slice reducer for key "${key}" returned undefined when probed with a random type. ` +`Don't try to handle '${ActionTypes.INIT}' or other actions in "redux/*" ` +`namespace. They are considered private. Instead, you must return the ` +`current state for any unknown actions, unless it is undefined, ` +`in which case you must return the initial state, regardless of the ` +`action type. The initial state may not be undefined, but can be null.`)}})
}
其他——utils下一些方法
-
isPlainObject
/*** 通过 {} 或者 new Object() 方式创建的对象是纯粹对象* isPlainObject 函数的功能的判断依据与对象使用什么方式创建无关,而与的函数原型是否 === Object.prototype 有关*/
export default function isPlainObject(obj: any): boolean {if (typeof obj !== 'object' || obj === null) return falselet proto = obj// 当 proto === Object.prototype 时会跳出循环while (Object.getPrototypeOf(proto) !== null) {proto = Object.getPrototypeOf(proto)}// 判断原型链的 首是否等于尾return Object.getPrototypeOf(obj) === proto
}
-
kindOf返回变量或对象的类型