§ 认识 Redux
What is store?
store是应用状态state的管理者。那什么是state?state是React组件的状态,一般是一个普通对象。
How to create store?
import { createStore } from 'redux'
...
// reducer:
// 负责接收action并更新state的函数
// initialState:
// 初始state
const store = createStore(reducer, initialState) // store 是靠传入 reducer 生成的哦!
How to use store?
store包括(不限于)以下API:
- getState()
- 获取整个 state, 即
state = store.getState()
- 获取整个 state, 即
- dispatch(action)
- 触发 state 改变的【唯一途径】,想要改变state,必须通过dispatch一个action
- action 必须是一个包含
type
属性的对象
- subscribe(function(){})
- 可以理解成是 DOM 中的 addEventListener,监听state的改变
So?
- store 由 Redux 的 createStore(reducer) 生成
- state 通过 store.getState() 获取,本质上一般是一个存储着整个应用状态的对象
- action 本质上是一个包含 type 属性的普通对象
- 改变 state 必须 dispatch 一个 action
- reducer 本质上是根据 action.type 来更新 state 并返回 nextState 的函数
- reducer 必须返回值,否则 nextState 即为 undefined
§ 学习Redux
辅助函数Compose(…functions)
// (以下本人翻译版,不考虑实际使用)
// 实际运用其实是这样子的:
// compose(f, g, h)(...args) 相当于 f(g(h(...args)))
function compose(...funcs: Function[]) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce(function (a, b) {
return function (...args: any) {
return a(b(...args));
}
})
}
创建函数createStore(reducer, initState, enhancer)
// (以下本人翻译版,不考虑实际使用)
function createStore (
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
) {
// 初次学习可跳过,此段目的是便于使用
// 使用时可不传入初始state,而是直接在第二个参数传入enhancer
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. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
)
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
// 初次学习可跳过,学习过下文enhancer后可回来继续看此段
// 如果需要enhance, 则直接使用加强过的createStore来返回store
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
// 正文
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() {
// dispatch正是改变state的时候,不能在此期间获取state
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
}
/**
* 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.
*/
// 可在了解下面的dispatch之后回头看此函数
function subscribe(listener: () => void) {
// dispatch正是改变state的时候,不能在此期间获取注册监听函数
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这个一个list,那么在dispatch期间,循环触发nextListeners中
// 的函数的时候,如果外界突然调用unsubscribe函数,nextListeners就会发生变化。
// 为了保证每个注册函数都被调用到,我们在修改nextListeners时候,都不
// 对dispatch中正在循环的list进行改变
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
}
}
/**
* 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(
// ...
)
}
// 必须包含type属性
if (typeof action.type === 'undefined') {
throw new Error(
// ...
)
}
// dispatch期间不允许dispatch其他action
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 根据创建store时候传入的recuder以及dispathc时传入的action,获取最新的state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 在state发生改变的时候触发监听函数
// 保证执行 nextListeners 中所有注册函数
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// 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)
return store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
};
}
⊙ Store Enhancer
Store 增强器就是对生成的 store API 进行改造
添加中间件applyMiddleware(…middlewares)
此函数用于添加中间件,返回enhancer
// (以下本人翻译版,不考虑实际使用)
function applyMiddleware(
...middlewares: Middleware[]
) {
return function (createStore) {
// 回到createStore函数中关于enhance的部分,该处通过两次将参数传递过来
// 为什么不一次传三个参数呢?
// 主要考虑两方面,一个是enhancer可能需要最初始的createStore来创建初始store
// 另一个方面是考虑到返回的函数与最初提供的createStore调用方式一致,让中间件的改动不被显式感知
// 可见,改造一个函数可以,但改造其调用方式是不建议的
// 联想到React里面的高阶组件,是一个意思
return function (reducer, initState) {
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)
}
// -------------------------------------------------------------------------------------------
// 传入两个可能需要的api
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 链式调用中间件
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
// 上面两个调用看下来,可能有个疑惑,传入的两次dispatch有什么差别与意义呢?
// 差别:
// 上面的middlewareAPI.dispatch所触发的dispatch会随着上下文发生改变,该值最终等于所有middleware都包裹过的dispatch的最外层
// 下面传入的dispatch是store.dispatch,通过compose函数的作用,每个中间件都拿着里面一层middleware包裹过的dispatch
// 这样以来,每个中间件都有两个dispatch,一个是最外层的所有中间件都包裹过的dispatch,另一个是里面一层中间件包裹过的dispatch
// 意义:
// 第一个dispatch稍微难理解,他可以让我们直接回到最外层,跳出中间件链。上面dispatch的代码中我们看到,是不允许在dispach期间继续dispatch任何action的。但是如果我们确实有这个需求呢?则可以调用此dispatch,在真正的dispach执行前跳脱出来,具体看这个https://github.com/fi3ework/blog/issues/14
// 第二个dispatch则很容易理解,相当于自己调用完了触发下一层中间件去dispatch
// ------------------------------------------------------------------------------------------
// 作为enhancer,我们是会改动原有的api的,所以初始的dispatch被我们覆盖调了
return {
...store,
dispatch
}
}
}
}
关于中间件模型:
--------------------------------------
| middleware1 |
| ---------------------------- |
| | middleware2 | |
| | ------------------- | |
| | | middleware3 | | |
| | | | | |
next next next ——————————— | | |
请求 ——————————————————> | handler | — 收尾工作->|
响应 <————————————————— | G | | | |
| A | C | E ——————————— F | D | B |
| | | | | |
| | ------------------- | |
| ---------------------------- |
--------------------------------------
顺序 A -> C -> E -> G -> F -> D -> B
\---------------/ \----------/
↓ ↓
请求响应完毕 收尾工作
上述代码中提到的第一个dispatch,当我们调用之后会直接从某个中间件跳脱出来到最外层。