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 = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let 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 = false
ensureCanMutateNextListeners()
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 = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 依次通知 listener
const 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 = subscribe
return {
/**
* 最小的 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> & Ext
return 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: D
getState(): 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-middleware
const 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 usual
return 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 的 key
const 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: unknown
try {
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 = false
const 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.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
return 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 false
let proto = obj
// 当 proto === Object.prototype 时会跳出循环
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
// 判断原型链的 首是否等于尾
return Object.getPrototypeOf(obj) === proto
}
-
kindOf返回变量或对象的类型