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
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, 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(
nextReducer: Reducer
): Store, 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,
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
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,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown) as Store, 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 {
return (createStore: StoreCreator) => (
reducer: Reducer,
...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(...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 (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, 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(
actionCreator: ActionCreator,
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 | 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源码地址