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返回变量或对象的类型
相关文章:

Redux 源码分析
Redux 目录结构 redux ├─ .babelrc.js ├─ .editorconfig ├─ .gitignore …...

第五十二章 BFS进阶(二)——双向广搜
第五十二章 BFS进阶(二)——双向广搜一、双向广搜1、优越之处2、实现逻辑3、复杂度分析二、例题1、问题2、分析3、代码一、双向广搜 1、优越之处 双向广搜是指我们从终点和起点同时开始搜索,当二者到达同一个中间状态的时候,即相…...
业务建模题
一. 单选题:1.在活动图中负责在一个活动节点执行完毕后切换到另一个节点的元素是( A)。A.控制流 B.对象流 C.判断节点 D.扩展区城2.以下说法错误的是(C)。A.活动图中的开始标记一般只有一一个,而终止标记可能有多个B.判断节点的出口条件必须保证不互相重复,并且不缺…...

电子秤专用模拟数字(AD)转换器芯片HX711介绍
HX711简介HX711是一款专为高精度电子秤而设计的24 位A/D 转换器芯片。与同类型其它芯片相比,该芯片集成了包括稳压电源、片内时钟振荡器等其它同类型芯片所需要的外围电路,具有集成度高、响应速度快、抗干扰性强等优点。降低了电子秤的整机成本ÿ…...

微服务 RocketMQ-延时消息 消息过滤 管控台搜索问题
~~微服务 RocketMQ-延时消息 消息过滤 管控台搜索问题~~ RocketMQ-延时消息实现延时消息RocketMQ-消息过滤Tag标签过滤SQL标签过滤管控台搜索问题RocketMQ-延时消息 给消息设置延时时间,到一定时间,消费者才能消费的到,中间件内部通过每秒钟扫…...
js发送邮件(node.js)
以前看别人博客留言或者评论文章时必须填写邮箱信息,感觉甚是麻烦。 后来才知道是为了在博主回复后让访客收到邮件,用心良苦。 于是我也在新增留言和文章评论的接口里,新增了给自己发送邮件提醒的功能。 我用的QQ邮箱,具体如下…...
English Learning - Day58 一周高频问题汇总 2023.2.12 周日
English Learning - Day58 一周高频问题汇总 2023.2.12 周日这周主要内容继续说说状语从句结果状语从句这周主要内容 DAY58【周日总结】 一周高频问题汇总 (打卡作业详见 Day59) 一近期主要讲了 一 01.主动脉修饰 以下是最常问到的知识点拓展ÿ…...

【微电网】基于风光储能和需求响应的微电网日前经济调度(Python代码实现)
目录 1 概述 2 知识点及数学模型 3 算例实现 3.1算例介绍 3.2风光参与的模型求解 3.3 风光和储能参与的模型求解 3.5 风光储能和需求响应都参与模型求解 3.6 结果分析对比 4 Python代码及算例数据 1 概述 近年来,微电网、清洁能源等已成为全球关注的热点…...

四种方式的MySQL安装
mysql安装常见的方法有四种序号 安装方式 说明1 yum\rpm简单、快速,不能定制参数2二进制 解压,简单配置就可使用 免安装 mysql-a.b.c-linux2.x-x86_64.tar.gz3源码编译 可以定制参数,安装时间长 mysql-a.b.c.tar.gz4源码制成rpm包 把源码制…...
软考高级信息系统项目管理师系列之九:项目范围管理
软考高级信息系统项目管理师系列之九:项目范围管理 一、范围管理输入、输出、工具和技术表二、范围管理概述三、规划范围管理四、收集需求1.收集需求:2.需求分类3.收集需求的工具与技术4.收集需求过程主要输出5.需求文件内容6.需求管理7.可跟踪性8.双向可跟踪性9.需求跟踪矩阵…...

【项目精选】javaEE健康管理系统(论文+开题报告+答辩PPT+源代码+数据库+讲解视频)
点击下载源码 javaEE健康管理系统主要功能包括:教师登录退出、教师饮食管理、教师健康日志、体检管理等等。本系统结构如下: (1)用户模块: 实现登录功能 实现用户登录的退出 实现用户注册 (2)教…...
ctfshow nodejs
web 334 大小写转换特殊字符绕过。 “ı”.toUpperCase() ‘I’,“ſ”.toUpperCase() ‘S’。 “K”.toLowerCase() ‘k’. payload: CTFſHOW 123456web 335 通过源码可知 eval(xxx),eval 中可以执行 js 代码,那么我们可以依此执行系…...
无线传感器原理及方法|重点理论知识|2021年19级|期末考试
Min-Max定位 【P63】 最小最大法的基本思想是依据未知节点到各锚节点的距离测量值及锚节点的坐标构造若干个边界框,即以参考节点为圆心,未知节点到该锚节点的距离测量值为半径所构成圆的外接矩形,计算外接矩形的质心为未知节点的估计坐标。 多边定位法的浮点运算量大,计算代…...
带你写出符合 Promise/A+ 规范 Promise 的源码
Promise是前端面试中的高频问题,如果你能根据PromiseA的规范,写出符合规范的源码,那么我想,对于面试中的Promise相关的问题,都能够给出比较完美的答案。 我的建议是,对照规范多写几次实现,也许…...
回流与重绘
触发回流与重绘条件👉回流当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为 回流。引起回流原因1.页面的首次渲染2.浏览器的窗口大小发生变化3.元素的内容发生变化4.元素的尺寸或者位置发生变化…...

openpyxl表格的简单实用
示例:创建简单的电子表格和条形图 在这个例子中,我们将从头开始创建一个工作表并添加一些数据,然后绘制它。我们还将探索一些有限的单元格样式和格式。 我们将在工作表上输入的数据如下: 首先,让我们加载 openpyxl 并创建一个新工作簿。并获取活动表。我们还将输入我们…...

【寒假day4】leetcode刷题
🌈一、选择题❤1.下列哪一个是析构函数的特征( )。A: 析构函数定义只能在类体内 B: 一个类中只能定义一个析构函数 C: 析构函数名与类名相同 D: 析构函数可以有一个或多个参数答案:B答案解析:析构函数是构造函…...
【竞赛题】6355. 统计公平数对的数目
题目: 给你一个下标从 0 开始、长度为 n 的整数数组 nums ,和两个整数 lower 和 upper ,返回 公平数对的数目 。 如果 (i, j) 数对满足以下情况,则认为它是一个 公平数对 : 0 < i < j < n,且 l…...

Redis集群搭建(主从、哨兵、分片)
1.单机安装Redis 首先需要安装Redis所需要的依赖: yum install -y gcc tcl然后将课前资料提供的Redis安装包上传到虚拟机的任意目录: 例如,我放到了/tmp目录: 解压缩: tar -xzf redis-6.2.4.tar.gz解压后࿱…...
Dart语法基础补充
Asynchrony support Dart 库中充满了返回 Future 或 Stream 对象的函数。 这些函数是异步的:它们在设置一个可能耗时的操作(例如 I/O)后返回,而不等待该操作完成。 async 和 await 关键字支持异步编程,让编写看起来类…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...