React 系列
React 系列 — 简单模拟语法(一)
React 系列 — Jsx, 合成事件与 Refs(二)
React 系列 — virtualdom diff 算法实现分析(三)
React 系列 — 从 Mixin 到 HOC 再到 HOOKS(四)
React 系列 — createElement, ReactElement 与 Component 部分源码解析(五)
React 系列 — 从使用 React 了解 Css 的各种使用方案(六)
React 系列 — 从零构建状态管理及 Redux 源码解析(七)
React 系列 — 扩展状态管理功能及 Redux 源码解析(八)
createStore.ts 源码解析
基本功能之后, 我们再回头看看 createStore.ts 里有什么关键代码实现功能的
import $$observable from 'symbol-observable'
import {
Store,
PreloadedState,
StoreEnhancer,
Dispatch,
Observer,
ExtendState
} from './types/store'
import {Action} from './types/actions'
import {Reducer} from './types/reducers'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
头部引入了 symbol-observable
做响应式数据, 其余都是一些类型声明和工具函数
/**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* @param reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
*
* @param preloadedState The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
*
* @param enhancer The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* @returns A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
函数注释来看有三个入参
参数 | 描述 |
---|---|
reducer | 给与当前 state 和 action 返回新的 state |
preloadedState | 初始化 state, 可以从服务器获取或者恢复以前用户序列化的缓存数据 |
enhancer | 可以指定例如中间件的第三方功能增强 |
返回的 store 可以让你读取 state, 触发 actions, 监听变化
------------- 省略部分代码 ----------------
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.'
)
}
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.')
}
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 reducer to be a function.')
}
都是一些基本的判断和报错机制, 也是我们手写代码省略掉的一步, 中间有关于 enhancer 部分的代码可以后面再讲
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice()
}
}
/**
* Reads the state tree managed by the store.
*
* @returns The current state tree of your application.
*/
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
}
开头是基本的声明变量, 相比较我们多了一个 nextListeners
和isDispatching
前者是作为 currentListeners
浅拷贝的临时变量给分发阶段使用的, 这样可以避免在这过程中会用 subscribe/unsubscribe 所导致的 bug
后者是用来锁定状态, 在 dispatching 的过程中做对应逻辑
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param listener A callback to be invoked on every dispatch.
* @returns A function to remove this change listener.
*/
function subscribe(listener: () => void) {if (typeof listener !== 'function') {throw new Error('Expected the listener to be a function.')
}
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-reference/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-reference/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
比起我们 redux 还做了几层机制
- 参数限制
-
isDispatching
状态控制 - 每次添加新的监听事件前都会更新最新队列去添加
-
isSubscribed
控制移除事件状态 - 通过索引值移除对应事件
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param action A plain object representing“what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action: A) {if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects.' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined"type"property.' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {throw new Error('Reducers may not dispatch actions.')
}
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
}
更加多的逻辑代码
- 参数判断
-
isDispatching
状态控制 - 纯函数更新数据代码加了捕获机制
- 每次都拿最新的监听队列遍历触发
- 返回原样 action
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* 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.
* @returns The same store instance with a new reducer in place.
*/
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.')
}
// TODO: do this more elegantly
;((currentReducer as unknown) as Reducer<
NewState,
NewActions
>) = nextReducer
// This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({type: ActionTypes.REPLACE} as A)
// change the type of the store by casting it to the new store
return (store as unknown) as Store<
ExtendState<NewState, StateExt>,
NewActions,
StateExt,
Ext
> &
Ext
}
reducers 的替代方法, 一般场景比较少用到, 基本代码不多
/**
* Interoperability point for observable/reactive libraries.
* @returns A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe(observer: unknown) {if (typeof observer !== 'object' || observer === null) {throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
const observerAsObserver = observer as Observer<S>
if (observerAsObserver.next) {observerAsObserver.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return {unsubscribe}
},
[$$observable]() {return this}
}
}
观察者模式的实现库做监听事件
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
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
方法的最后会触发一个 ActionTypes.INIT
的 action 做初始化数据, 返回一个包含暴露的方法对象出去.
createStore.ts 源码地址
我们再看看 actionTypes.ts
源码做了什么
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
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
不在于得到什么结果, 只需要它是复杂难以跟开发定义的 action 重复就行了, 为了执行一次 distapch 获取到初始的 state.
actionTypes.ts 源码地址
实例六(优化)
学习完 createStore.ts 源码之后我们可以将一些好的地方引入我们的库里
-
nextListeners
充当临时变量传递给其他函数使用 -
isDispatching
作为状态标记判断流程 -
dispatch
函数增加容错机制, 返回原样action
-
isSubscribed
控制监听事件解绑机制, 从重新过滤赋值改成根据索引值移除事件 - 增加一个不易重复的
action
执行预触发返回每个 reducer 的初始数据
createStore.js
function createStore (initStore = {}, reducer) {
// 唯一数据源
let state = initStore
// 监听队列
let listenList = []
// 监听队列浅拷贝
let nextListeners = listenList
// 是否 dispatch 中
let isDispatching = false
// 浅拷贝
function ensureCanMutateNextListeners () {if (nextListeners === listenList) {nextListeners = listenList.slice()
}
}
// 唯一获取数据函数
function getState () {
// 输出警告
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 state
}
// 纯函数来执行修改, 只返回最新数据
const dispatch = (action) => {
// 严格控制 dispatch, 不得中途再次发送
if (isDispatching) {throw new Error('Reducers may not dispatch actions.')
}
// 增加意外防止操作
try {
isDispatching = true
state = reducer(state, action)
} finally {isDispatching = false}
// 获取更改后的数据同时获取最新队列
const listeners = (listenList = nextListeners)
// 替换成原始遍历提高性能, 遍历触发事件
for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]
listener()}
// 为了方便将 action 原样返回
return action
}
// 添加监听器, 同时返回解绑该事件的函数
const subscribe = (fn) => {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.')
}
// 占位标记
let isSubscribed = true
// 每次添加监听事件时浅拷贝最新队列
ensureCanMutateNextListeners()
nextListeners.push(fn)
return function unsubscribe () {if (!isSubscribed) {return}
if (isDispatching) {
throw new Error('You may not unsubscribe from a store listener while the reducer is executing.')
}
isSubscribed = false
// 每次移除监听事件时浅拷贝最新队列
ensureCanMutateNextListeners()
// 根据索引值删除比 filter 过滤重新赋值效率高
const index = nextListeners.indexOf(fn)
nextListeners.splice(index, 1)
listenList = null
}
}
// 默认触发一次 dispatch 以获取各个 reduce 的初始数据
dispatch({type: `@@redux/INIT${Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')}`
})
return {
getState,
dispatch,
subscribe
}
}
文章的完整代码可以直接查看 demo6
applyMiddleware 源码解析
createStore
函数还有一个入参 enhancer
我们之前没实现,
React 提供使用中间件的唯一方式是 applyMiddleware
函数, 我们看一下怎么介绍它的
Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时,middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息
demo
import {createStore, applyMiddleware} from 'redux'
import todos from './reducers'
function logger({getState}) {return (next) => (action) => {console.log('will dispatch', action)
// 调用 middleware 链中下一个 middleware 的 dispatch。let returnValue = next(action)
console.log('state after dispatch', getState())
// 一般会是 action 本身,除非
// 后面的 middleware 修改了它。return returnValue
}
}
let store = createStore(
todos,
['Use Redux'],
applyMiddleware(logger)
)
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
// (将打印如下信息:)
// will dispatch: {type: 'ADD_TODO', text: 'Understand the middleware'}
// state after dispatch: ['Use Redux', 'Understand the middleware']
logger
是通用的中间件格式, 这是一个三层嵌套函数, 分别是 {getState, dispatch}, next(其实是下一个包装后的中间件) 和 action 入参, 其实相当于
function middleware ({getState, dispatch}) {return (next) => {return (action) => {
// dosomething
// 调用 middleware 链中下一个 middleware 的 dispatch。let returnValue = next(action)
// dosomething
// 一般会是 action 本身,除非
// 后面的 middleware 修改了它。return returnValue
}
}
}
知道这个基本规则之后我们就可以看看 applyMiddleware
里面做了什么
我们看一下先过一下源码里面做了些什么
import compose from './compose'
import {Middleware, MiddlewareAPI} from './types/middleware'
import {AnyAction} from './types/actions'
import {StoreEnhancer, StoreCreator, Dispatch} from './types/store'
import {Reducer} from './types/reducers'
/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
*
* @param middlewares The middleware chain to be applied.
* @returns A store enhancer applying the middleware.
*
* @template Ext Dispatch signature added by a middleware.
* @template S The type of the state supported by a middleware.
*/
总的来说就是创建一个应用程序的中间件去增强 redux store 的 dispatch 方法, 对多种类的任务来说非常便利, 例如以简洁的方式表达异步流程或者输出每个 action payload 的日志, 而每个中间件都会拿到 dispatch
和getState
入参
------------- 省略部分代码 ----------------
export default function applyMiddleware(...middlewares: Middleware[]
): StoreEnhancer<any> {return (createStore: StoreCreator) => <S, A extends AnyAction>(
reducer: Reducer<S, A>,
...args: any[]) => {const store = createStore(reducer, ...args)
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))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
大致分析一下代码里做了什么操作
- 接收多个中间件入参
- 接收
createStore
函数 - 接收
reducer
和其他入参 - 用上面的参数实例化新的
store
- 定义
dispatch
, 抛出异常 ’ 不允许在构建中间件的时候 dispatch, 因为其他中间件不会被应用到该次 dispatch’ - 构建
middlewareAPI
对象, 暴露出对应的方法, 目的是让每个执行中间件都是一样的入参条件 - 遍历中间件返回执行
middlewareAPI
之后的新函数数组 - 重新赋值
dispatch
函数为compose
之后的返回值 - 最终抛出
store
实例的属性方法和包装后的新dispatch
方法
applyMiddleware.ts 源码地址
上面有一个没解析的 compose
函数, 源码如下
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for the
* resulting composite function.
*
* @param funcs The functions to compose.
* @returns A function obtained by composing the argument functions from right
* to left. For example, `compose(f, g, h)` is identical to doing
* `(...args) => f(g(h(...args)))`.
*/
------------- 省略部分代码 ----------------
export default function compose(...funcs: Function[]) {if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {return funcs[0]
}
return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
总的来说, 除了类型判断, 实际代码只有一个 reduce
的应用 …, 这里可以知道每个中间件是有顺序关系的, 所以应用的时候需要注意一下.
compose.ts 源码地址
applyMiddleware
的相关源码已经过了一遍, 剩下我们回顾一下在 createStore
里是怎么处理相关逻辑的, 放心, 真的不多
------------- 省略部分代码 ----------------
if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState as PreloadedState<
S
>) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
------------- 省略部分代码 ----------------
检查到传入 enhancer
的时候直接中断流程, 返回执行结果,
我们再重新梳理一下流程:
调用方式
createStore(reducer, preloadedState, applyMiddleware(f1, f2, ...fn))
在 createStore 里如果检测到 enhancer 入参会
return enhancer(createStore)(reducer, preloadedState)
相当于
return applyMiddleware(f1, f2, ...fn)(createStore)(reducer, preloadedState)
在 applyMiddleware 源码可得知
// 初始化一个 store
let store = createStore(reducer, preloadedState);
// 每个中间件拿到的入参
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
// 遍历的中间件大概流程这样子
middlewares = [f1(middlewareAPI) => s1(next) => t1(...arg)
f2(middlewareAPI) => s2(next) => t2(...arg)
fn(middlewareAPI) => sn(next) => tn(...arg)
]
// chain 得到的数组就长这样子
const chain = [s1(next) => t1(...arg)
s2(next) => t2(...arg)
sn(next) => tn(...arg)
]
// compose 经过 reduce 方法包装返回
const composeFn = s1((s2(sn(next) => tn(...arg))()) => t2(...arg))()) => t1(...arg)
// 最终返回的 dispatch 方法
dispatch = (composeFn)(store.dispatch)
// 整体流程
const applyMiddleware = (中间件数组) => (createStore) => (reducer, preloadedState) => {...store, dispatch: compose(...chain)(store.dispatch)}
这时候再回到中间件的通用代码可以知道
function middleware ({getState, dispatch}) {return (next) => {return (action) => {
// dosomething
// 调用 middleware 链中下一个 middleware 的 dispatch。let returnValue = next(action)
// dosomething
// 一般会是 action 本身,除非
// 后面的 middleware 修改了它。return returnValue
}
}
}
阶段一: 接收相同的初始{getState, dispatch}
阶段二: 接收经过下一个中间件包装后的 dispatch 调用
阶段三: 接收 action 处理某些逻辑之后原样返回, 一般不该修改 action
效果: dispatch 一个 action, 会用倒序的方式逐一经过每个中间件的流程形成链式调用, 并且前后一般不需要关心做些什么操作.
applyMiddleware 简单实现
我们既然已经知道了它的实现思路, 接下来就可以简单封装一个了
compose.js
function compose (...funcs) {if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return arg
}
if (funcs.length === 1) {return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
applyMiddleware.js
// 接收中间件数组
function applyMiddleware (...middlewares) {
// 接收 createStore 函数和 reducer 和其他参数
return (createStore) => (reducer, ...args) => {
// 这就是原始的实例化 store, 所以 applyMiddleware 方法其实就是围绕在原始 store 的基础上添加功能
const store = createStore(reducer, ...args)
// 先初始化 dispatch 方法占位, 但是此时执行会抛出异常
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed.' +
'Other middleware would not be applied to this dispatch.'
)
}
/**
* 构建中间件第一层运行的入参对象, 保证每个中间件都是一样的参数条件, 所以上面的抛出异常也是如此
* applyMiddleware([f1(middlewareAPI) => s1(next) => t1(...arg)
fn(middlewareAPI) => sn(next) => tn(...arg)
* ])
*
*/
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
// 遍历运行每个中间件返回新的数组
// chain = [s1(next) => t1(...arg), ...sn(next) => tn(...arg)]
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
/* 返回增强功能后的 dispatch 方法
dispatch = (s1(sn(next) => tn(...arg))()) => t1(...arg))(store.dispatch) */
dispatch = compose(...chain)(store.dispatch)
// 替代原始的 store 对象
return {
...store,
dispatch
}
}
}
因为新增了增强功能, 所以我们也要把 createStore
修改一下, 按照源码对应一下
因为参数里只有 reducer
是必选, 其他两者都是可选, 所以我们还要把入参顺序也替换一下
createStore.js
function createStore (reducer, initStore = {}, enhancer) {
// 处理一下参数问题
if (typeof initStore === 'function' && typeof enhancer === 'undefined') {
enhancer = initStore
initStore = undefined
}
// 劫持 enhancer
if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')
}
// 返回包装后的 store
return enhancer(createStore)(reducer, initStore)
}
------------- 省略部分代码 ----------------
return {
getState,
dispatch,
subscribe
}
}
最后只剩执行函数
store.js
// 初始数据
const initStore = {
arNum: 0,
mdNum: 1
}
// 日志中间件
function logger ({getState}) {return (next) => (action) => {console.log('will dispatch', action)
// 调用 middleware 链中下一个 middleware 的 dispatch。let returnValue = next(action)
console.log('state after dispatch', getState())
// 一般会是 action 本身,除非
// 后面的 middleware 修改了它。return returnValue
}
}
// 实例化 store
let store = createStore(reducers, initStore, applyMiddleware(logger))
现在在 index.html 引入新的依赖执行可以发现也能正常输出日志了.
文章的完整代码可以直接查看 demo7
bindActionCreators 源码解析
其实上面就已经算是完成了一个简单的状态管理器了, 但是我们从 Redux 的 API 里其实能够看到还有一个方法是我们还没了解过的, 就大概说说
把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch 对每个 action creator 进行包装,以便可以直接调用它们。
惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。
至于什么情景会遇到需要使用
你或许要问:为什么不直接把 action creator 绑定到 store 实例上,就像传统的 Flux 那样?问题在于,这对于需要在服务端进行渲染的同构应用会有问题。多数情况下,你的每个请求都需要一个独立的 store 实例,这样你可以为它们提供不同的数据,但是在定义的时候绑定 action creator,你就只能使用一个唯一的 store 实例来对应所有请求了。
省略掉类型判断后的源码
import {Dispatch} from './types/store'
import {
AnyAction,
ActionCreator,
ActionCreatorsMapObject
} from './types/actions'
function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {return function(this: any, ...args: any[]) {return dispatch(actionCreator.apply(this, args))
}
}
/**
* Turns an object whose values are action creators, into an object with the
* same keys, but with every function wrapped into a `dispatch` call so they
* may be invoked directly. This is just a convenience method, as you can call
* `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
*
* For convenience, you can also pass an action creator as the first argument,
* and get a dispatch wrapped function in return.
*
* @param actionCreators An object whose values are action
* creator functions. One handy way to obtain it is to use ES6 `import * as`
* syntax. You may also pass a single function.
*
* @param dispatch The `dispatch` function available on your Redux
* store.
*
* @returns The object mimicking the original object, but with
* every action creator wrapped into the `dispatch` call. If you passed a
* function as `actionCreators`, the return value will also be a single
* function.
*/
------------- 省略部分代码 ----------------
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, instead received ${actionCreators === null ? 'null' : typeof 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
}
bindActionCreator
返回绑定 this 指向的新函数
bindActionCreators
做了三件事:
- 如果
actionCreators
是函数, 直接返回调用bindActionCreator
- 如果
actionCreators
非对象非 null 抛出异常 - 如果
actionCreators
是可迭代对象, 返回遍历调用bindActionCreator
包装后的对象
因为我们的 demo 不需要用到, 就没必要实现了, 大家知道原理即可
bindActionCreators.ts 源码地址