前言
建议先下载redux,然后在node_modules中找到redux/src找到源代码。
npm install -s redux
redux简介
在react应用中,随着功能的不断丰富,组件越来越多,state也越来越复杂,直到有一天你发现,修改和共享某个state变得极其艰难:
共享的state需要放在最顶层维护,然后一层一层地往下传递修改state的方法和展现的数据。这时你会发现,很多数据中间层的组件根本不需要用到,但由于子组件需要用,不得不经过这些中间层组件的传递。更令人头疼的事,当state变化的时候,你根本分不清楚是由哪个组件触发的。
-
这时候如果使用Redux对应用进行重构,状态的变化就会变得非常清晰:
应用的state统一放在store里面维护,当需要修改state的时候,dispatch一个action给reducer,reducer算出新的state后,再将state发布给事先订阅的组件。
所有对状态的改变都需要dispatch一个action,通过追踪action,就能得出state的变化过程。整个数据流都是单向的,可检测的,可预测的。当然,另一个额外的好处是不再需要一层一层的传递props了,因为Redux内置了一个发布订阅模块。
redux使用场景
Redux虽好,但并不适用于所有项目。使用Redux需要创建很多模版代码,会让 state 的更新变得非常繁琐,谁用谁知道
正如 Redux 的作者 Dan Abramov 所言,Redux 提供了一个交换方案,它要求应用牺牲一定的灵活性以达到以下三个要求:
通过简单对象和数组描述应用状态 通过简单对象描述应用状态的改变 使用纯函数来描述状态改变的逻辑
相应的,你会得到以下好处:
可以很方便的将 state 存储到 Local Storage 中并在需要的时候取出并启动应用 可以在服务器端直接计算出 state 再存到 HTML 中,然后在客户端秒开页面 方便的序列化用户操作和对应的 state 快照,在出现 bug 的时候可以利用这些信息快速复现问题 通过在网络中传递 action 对象,可以在对代码进行很小改动的情况下实现分布式应用 可以在对代码进行很小改动的情况下实现撤销和恢复功能 在开发过程中可以任意跳转到应用的某个历史状态并进行操作 提供全面的审查和控制功能,让开发者可以定制自己的开发工具 将 UI 和业务逻辑分离,使业务逻辑可以在多个地方重用
另外,对于 React 来说,当遇到以下情况你或许需要 Redux 的帮助:
-
同一个 state 需要在多个 Component 中共享 -
需要操作一些全局性的常驻 Component,比如 Notifications,Tooltips 等 -
太多 props 需要在组件树中传递,其中大部分只是为了透传给子组件 -
业务太复杂导致 Component 文件太大,可以考虑将业务逻辑拆出来放到 Reducer 中
redux基本概念
Redux基本概念主要有以下几个:
Store
人如其名,Store就是一个仓库,它存储了所有的状态(State),还提供了一些操作他的API,我们后续的操作其实都是在操作这个仓库。假如我们的仓库是用来放牛奶的,初始情况下,我们的仓库里面一箱牛奶都没有,那Store的状态(State)就是:
{
milk: 0
}
Actions
一个Action就是一个动作,这个动作的目的是更改Store中的某个状态,Store还是上面的那个仓库,现在我想往仓库放一箱牛奶,那”我想往仓库放一箱牛奶”就是一个Action,代码就是这样:
{
type: "PUT_MILK",
count: 1
}
Reducers
前面”我想往仓库放一箱牛奶”只是想了,还没操作,具体操作要靠Reducer,Reducer就是根据接收的Action来改变Store中的状态,比如我接收了一个PUT_MILK
,同时数量count
是1,那放进去的结果就是milk
增加了1,从0变成了1,代码就是这样:
const initState = {
milk: 0
}
function reducer(state = initState, action) {
switch (action.type) {
case 'PUT_MILK':
return {...state, milk: state.milk + action.count}
default:
return state
}
}
可以看到Redux本身就是一个单纯的状态机,Store存放了所有的状态,Action是一个改变状态的通知,Reducer接收到通知就更改Store中对应的状态。
简单例子
下面我们来看一个简单的例子,包含了前面提到的Store,Action和Reducer这几个概念:
import { createStore } from 'redux';
const initState = {
milk: 0
};
function reducer(state = initState, action) {
switch (action.type) {
case 'PUT_MILK':
return {...state, milk: state.milk + action.count};
case 'TAKE_MILK':
return {...state, milk: state.milk - action.count};
default:
return state;
}
}
let store = createStore(reducer);
// subscribe其实就是订阅store的变化,一旦store发生了变化,传入的回调函数就会被调用
// 如果是结合页面更新,更新的操作就是在这里执行
store.subscribe(() => console.log(store.getState()));
// 将action发出去要用dispatch
store.dispatch({ type: 'PUT_MILK' }); // milk: 1
store.dispatch({ type: 'PUT_MILK' }); // milk: 2
store.dispatch({ type: 'TAKE_MILK' }); // milk: 1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2iIOmYE1-1608541169102)(http://qinniu.douziqianduan.icu/bVQtuq-500×300)]
源码目录
这里主要分为两大块,一块为自定义的工具库,另一块则是redux的逻辑代码。各个文件的大概功能如下。
-
utils/warnimng.js
控制台输出一个警告 -
utils/isPlainObject.js
判断一个对象是否是纯粹对象 -
utils/actionTypes.js
定义了一些基本的action的TYPE,并给action打上id -
applyMiddlewar.js
使用自定义的 middleware 来扩展 Redux -
bindActionCreators.js
把 action creators 转成拥有同名 keys 的对象,使用时可以直接调用 -
combineReducers.js
一个比较大的应用,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分 -
compose.js
从右到左来组合多个函数,函数编程中常用到 -
createStore.js
创建一个 Redux Store 来放所有的state
utils工具文件
actionTypes.js
/**
* 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类型,没什么难点。
主要值得关注的地方是,randomString这里,它返回了是何样的随机字符串,并且有什么作用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q8LUJ69l-1608541169108)(http://qinniu.douziqianduan.icu/image-20201211134829801.png-500×300)]
从上可以看出,最终结果大概是这个样子的,其主要作用就是为每个Action打上id。
ActionTypes = {
INIT: '@@redux/INITm.6.z.c.u',
REPLACE: '@@redux/REPLACEn.y.7.8.x.7',
PROBE_UNKNOWN_ACTION: () => '@@redux/PROBE_UNKNOWN_ACTIONk.y.a.h.a.q'
}
isPlainObject.js
源代码如下
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
isPlainObject.js向外暴露了一个用于判断是否简单对象的函数。
如何判断一个对象是否是纯粹对象?
其原理就是,简单对象的__proto__
等于Object.prototype。
用一句通俗易懂的话就是:
凡不是new Object()或者字面量的方式构建出来的对象都不是简单对象
warning.js
export default function warning(message) {
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
try {
throw new Error(message)
} catch (e) {}
}
打印错误信息,若浏览器不支持console对象,则改用异常抛出错误信息。
主要文件
index.js入口文件
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
主要注意的点只有两个,其它的都主要是将其它功能模块整理到index.js中,并export出来。
第一个,__DO_NOT_USE__ActionTypes。
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
可以理解为如下行为。
// 一个例子
ActionTypes = {
INIT: '@@redux/INITm.6.z.c.u',
REPLACE: '@@redux/REPLACEn.y.7.8.x.7',
PROBE_UNKNOWN_ACTION: () => '@@redux/PROBE_UNKNOWN_ACTIONk.y.a.h.a.q'
}
__DO_NOT_USE__ActionTypes = ActionTypes
里面定义了redux自带的action的类型,从这个变量的命名来看,这是帮助开发者检查不要使用redux自带的action的类型,以防出现错误。
第二个,函数isCrushed。 这里面定义了一个函数isCrushed,但是函数体里面并没有东西。
-
当process.env.NODE_ENV===’production’这句话直接不成立,所以warning也就不会执行;
-
当process.env.NODE_ENV!==’production’,即开发环境。不压缩代码的时候typeof isCrushed.name === ‘string’ && isCrushed.name !== ‘isCrushed’也不会成立;
-
当process.env.NODE_ENV!==’production’,同样是开发环境,进行了代码压缩,此时isCrushed.name === ‘string’ && isCrushed.name !== ‘isCrushed’就成立了。这里涉及到一些代码丑化和压缩的原理。经过压缩后,函数isCrushed的函数名将会被一个字母所替代。
举个例子,将redux项目的在development环境下进行了一次压缩打包。代码做了这么一层转换:
未压缩
function isCrushed() {}
压缩后
function d(){}"string"==typeof d.name&&"isCrushed"!==d.name
此时判断条件就成立了,错误信息就会打印出来。
由此可以判断,isCrushed这个主要作用就是防止开发者在开发环境下对代码进行压缩。
当然,英语好的也都容易知道,crushed的意思就有压缩的、压缩的意思。
createStore.js文件
该文件主要导入的模块如下所示
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
导出内容
导出的只有一个createStore函数,函数createStore接受三个参数(reducer、preloadedState、enhancer)
export default function createStore(reducer, preloadedState, enhancer) {。。。}
reducer和enhancer用得比较多,preloadedState用的比较少。
第一个reducer很常用,就是处理Action的,这里就不过多解释了。
第二个preloadedState,它代表着初始状态,平时在项目里很少用到它,格式为对象,例如{count:55555}
。
* @param {any} [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{any}[preloadstate]初始状态。您可以选择指定它要在通用应用程序中从服务器中补充状态,或恢复
以前序列化的用户会话。如果使用“combineReducers”生成根reducer函数,则必须与“combineReducers”有key形状相同的对象。
第三个,enhancer,中文名叫增强器,顾名思义就是来增强redux的,它的类型的是Function,可以理解为装饰器之类的东西,采用的是洋葱模型,可以实现state改变时自动打印等功能,通常和中间件一起出现。
判断和处理输入参数
在正式函数开始之前,先有一段代码是判断和处理输入参数的
function createStore(reducer, preloadedState, enhancer) {
// 以前 createStore 集成 redux-devtool 的时候提供 3 个参数
// 现在需要将后面的 用middleware 全部包裹起来:
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.'
)
}
// 只输入两个参数的情况 reducer 和 enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// enhancer不为空的情况
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
}
在上面的代码中,对enhancer尤其注意。
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
这行代码展示了enhancer的调用过程。根据这个调用过程,可以推导出enhancer的函数体的架子应该是这样子的,可能是一个闭包的样子:
function enhancer(createStore) {
// ...
return (reducer,preloadedState) => {
//逻辑代码
.......
}
}
常见的enhancer就是redux-thunk(实现异步性)
以及redux-saga(解决异步问题)
。一般都会配合applyMiddleware(应用中间件)一起使用,而applyMiddleware的作用就是将这些enhancer格式化成符合redux要求的enhancer。
redux-thunk的使用的例子如下:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// 两个参数
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
// 也可以自定义一些enhancer
// 自动打印current state
function autoLogger() {
return createStore => (reducer, initialState, enhancer) => {
const store = createStore(reducer, initialState, enhancer)
function dispatch(action) {
console.log(`dispatch an action: ${JSON.stringify(action)}`);
const res = store.dispatch(action);
const newState = store.getState();
console.log(`current state: ${JSON.stringify(newState)}`);
return res;
}
return {...store, dispatch}
}
}
const enhancer = compose(
applyMiddleware(...middlewares),
autLogger()
);
const store = createStore(
reducer,
enhancer
);
// 三个参数的情况例子如下
// 使用 composeEnhancer 包裹之前的 Middleware 然后传递给第二个参数。
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const store = createStore(
reducer,
composeEnhancers(),
applyMiddleware(thunk)
)
createStore返回的对象
下面说一下函数createStore执行完之后返回的对象。
在createStore.js最下面一行有这一行代码:
// 第287行开始
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
主要返回了有这么几个方法,其中前三个最为常用,后面两个在项目基本上不怎么用。
下面将一一介绍dispatch,subscribe和getState三个函数。
函数中主要使用的变量
// 第60行开始
let currentReducer = reducer //从函数createStore第一个参数reducer获得
let currentState = preloadedState //从函数createStore第二个参数preloadedState获得
let currentListeners = [] //当前订阅者列表
let nextListeners = currentListeners //新的订阅者列表
let isDispatching = false // 锁
redux是一个统一管理状态容器,它要保证数据的一致性,所以同一个时间里,只能做一次数据修改,如果两个action同时触发reducer对同一数据的修改,那么将会带来巨大的灾难。所以变量isDispatching就是为了防止这一点而存在的。
dispatch函数
function dispatch(action) {
// 判断action是否为纯粹对象
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 判断action.type是否存在
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 根据锁来判断当前是否有执行其他的reducer操作
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// 上锁
isDispatching = true
// 调用currentReducer
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
}
函数dispatch在函数体一开始就进行了三次条件判断,分别是以下三个:
-
判断action是否为纯粹对象 -
判断action.type是否存在 -
判断当前是否有执行其他的reducer操作
当前三个预置条件判断都成立时,才会执行后续操作,否则抛出异常。
在执行reducer的操作的时候用到了try-finally,与try-catch的主要区别如下:
-
try语句允许我们定义在执行时进行错误测试的代码块。
-
catch 语句允许我们定义当 try 代码块发生错误时,所执行的代码块。
-
finally 语句在 try 和 catch 之后无论有无异常都会执行。
-
执行前使用isDispatching上锁,阻止后续的action进来触发reducer操作,保证同步操作;
-
得到的state值赋值给currentState;完成之后再finally里将isDispatching再改为false,允许后续的action进来触发reducer操作;
-
一一通知订阅者做数据更新,不传入任何参数
-
最后返回当前的action。
getState函数
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
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 currentState
}
getState的功能很明确,就是返回currentState,但是前提得是在dispatch函数未上锁的情况下。
这个currentState在每次dispatch的时候都会得到响应的更新。
执行createStore函数生成的store,可不可以直接修改它的state,如果试着这样修改了,getState获取到的结果如何?
同样是为了保证数据的一致性,当在reducer操作的时候,是不可以读取当前的state值的。
但getState函数返回state的时候,并没有对currentState做一层拷贝再给我们,所以是可以直接修改的。并且getState也能获取到修改后的值。只是这么修改的话,就不会通知订阅者做数据更新。
总结来说就是:
store通过getState得出的state是可以直接被更改的,但是redux不希望这么做,因为这样不会通知订阅者更新数据。
subscribe函数
完整代码如下
/**
* 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 {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
// 判断监听者是否为函数
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 是否有reducer正在进行数据修改(保证数据的一致性)
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.'
)
}
// 表示该订阅者在订阅状态中,true-订阅中,false-取消订阅
let isSubscribed = true
// 确保可以更改下一个订阅者数组
ensureCanMutateNextListeners()
// 将listener加入到新的订阅者数组里
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
}
}
注释的机翻如下(仅供参考)
添加更改侦听器。它将在任何时候被调用,状态树的某些部分可能已经发生了变化。那你可以call`getState()`读取回调中的当前状态树。
您可以从变更侦听器调用'dispatch()',方法如下
注意事项:
1、订阅将在每次“dispatch()”调用之前快照。
如果在调用侦听器时订阅或取消订阅,则不会对当前正在进行的“dispatch()”产生任何影响。
但是,下一个“dispatch()”调用(无论是否嵌套)都将使用订阅列表的最近快照。
2、侦听器不应该期望看到所有状态更改,因为状态可能在之前的嵌套“dispatch()”期间更新了多次
倾听者被调用。但是,保证所有用户将使用最新的按时间退出。
@param{Function}listener在每次调度时调用的回调。
@返回{Function}一个删除此更改侦听器的函数。
在注册订阅者之前,做了两个条件判断:
-
判断监听者是否为函数 -
是否有reducer正在进行数据修改(保证数据的一致性)
接下来执行了函数ensureCanMutateNextListeners。
其具体实现逻辑如下:
// 判断nextListeners和currentListeners是否为同一个引用
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 如果是,则进行深度为1的浅拷贝
nextListeners = currentListeners.slice()
}
}
关于nextListeners
和currentListeners
,上文提到的主要有两个地方。
// 刚开始声明的使用变量
let currentListeners = [] //当前订阅者列表
let nextListeners = currentListeners //新的订阅者列表
// Function dispatch中的语句
const listeners = (currentListeners = nextListeners)
这两处将nextListeners和currentListeners引用了同一个数组。
而ensureCanMutateNextListeners(字面意思:确保可以更改下一个订阅者数组)就是用来判断这种情况的,当nextListeners和currentListeners为同一个引用时,则做一层浅拷贝,Array.prototype.slice方法会返回一个新的数组,这样就可以达到浅拷贝(深度为1)的效果。
函数ensureCanMutateNextListeners作为处理之后,将新的订阅者加入nextListeners中,并且返回取消订阅的函数unsubscribe,也是以闭包的形式。
函数unsubscribe执行时,也会执行两个条件判断:
-
是否已经取消订阅(已取消的不必执行)
if (!isSubscribed) {
return
} -
是否有reducer正在进行数据修改(保证数据的一致性)
if (isDispatching) {
throw new Error(......)
}
通过条件判断之后,讲该订阅者从nextListeners中删除。
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1) // 从index的位置开始往后,删除1个元素
currentListeners = null
可能会问的问题:函数dispatch里面将二者合并成一个引用,这里为什么又要分开始用呢?为什么使用nextlistners而不是currentListeners?
答:为了数据的一致性。
设想一下,有如下情况:由于js是单线程的,当redux在通知所有订阅者的时候,每通知一个订阅者就是一个发布任务。发布任务门以异步任务的形式,在等待队列中一个个排队,如果有订阅者接收到订阅消息后,又添加了一个订阅的功能,如果插入到了微队列中,可能会影响到原来发布的顺序。
此时又有一个新的订阅者加进来了。如果只用currentListeners的话,当新的订阅者插进来的时候,就会打乱原有的顺序,从而导致索引错乱的问题。当然这只是可能,同时也有本人的一些猜想,具体情况可能还会更加奇怪和复杂
replaceReducer函数
/**
* 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 {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
// 判断所传reducer是否为函数
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = 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 })
}
这个函数是用来替换reducer,replaceReducer函数执行前会做一个条件判断:
-
判断所传reducer是否为函数
通过条件判断之后,将nextReducer赋值给currentReducer,以达到替换reducer效果,并触发state更新操作。
observable函数
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} 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 {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} 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) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
这个observable函数,并没有调用,在该文件中的这部分代码也难以让我们看明白其具体作用,即便暴露出来我们也办法使用,不过可以根据其字面量判断,这是一个用于观察订阅模式的功能。
// createStore在开头导入的
import $$observable from 'symbol-observable'
下面是源码仓库中部分介绍翻译。
这个建议引入了一个可观察键入ECMAScript标准库。这个可观察类型可用于建模基于推送的数据源,如DOM事件、计时器间隔和套接字。此外,可观察到的有:
-
组分:可观测值可以由高阶组合子组成. -
懒惰可观测到的数据直到观察者已经订阅了。
示例:观察键盘事件
使用可观察构造函数,我们可以创建一个函数,它为任意DOM元素和事件类型返回可观察的事件流。
function listen(element, eventName) {
return new Observable(observer => {
// Create an event handler which sends data to the sink
let handler = event => observer.next(event);
// Attach the event handler
element.addEventListener(eventName, handler, true);
// Return a cleanup function which will cancel the event stream
return () => {
// Detach the event handler from the element
element.removeEventListener(eventName, handler, true);
};
});
}
然后,我们可以使用标准的组合器来过滤和映射流中的事件,就像我们使用数组一样。
// Return an observable of special key down commands
function commandKeys(element) {
let keyCommands = { "38": "up", "40": "down" };
return listen(element, "keydown")
.filter(event => event.keyCode in keyCommands)
.map(event => keyCommands[event.keyCode])
}
注:“过滤器”和“地图”方法不包括在本提案中。它们可以添加到本规范的未来版本中。
当我们想使用事件流时,我们使用观察者.
let subscription = commandKeys(inputElement).subscribe({
next(val) { console.log("Received key command: " + val) },
error(err) { console.log("Received an error: " + err) },
complete() { console.log("Stream complete") },
});
返回的对象。订阅将允许我们随时取消订阅。取消后,将执行可观察到的清理功能。
// After calling this function, no more events will be sent
subscription.unsubscribe();
如果想深入了解这个observable,可以到作者给的github的地址了解一下。
https://github.com/tc39/proposal-observable
其它注意点
createStore.js函数体里有这样一行代码。
dispatch({ type: ActionTypes.INIT })
其所处位置如下,在返回值的上面。
// 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 })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
可以根据INIT看出来,刚开始要初始化一下。
为什么createStore.js返回前,要调用dispatch()初始化一下呢?
原因很简单,假设没有这行代码,此时currentState就是undefined的,也就常说的没有默认值。
如果currentState是undefined,当dispatch一个action的时候,就无法在currentState基础上做更新。
所以需要拿到所有reducer默认的state,这样后续的dispatch一个action的时候,才可以更新state。
combineReducers.js文件
combineReducers
也是使用非常广泛的API,当我们应用越来越复杂,如果将所有逻辑都写在一个reducer
里面,最终这个文件可能会有成千上万行,所以Redux提供了combineReducers
,可以让我们为不同的模块写自己的reducer
,最终将他们组合起来。
combineReducers使用示例
下面定义了三个reducer
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
Redux 提供了 combineReducers()
工具类来做上面 todoApp
做的事情,这样就能消灭一些样板代码了。有了它,可以这样重构 todoApp
:
import { combineReducers } from 'redux'
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
注意上面的写法和下面完全等价:
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
也可以给它们设置不同的 key,或者调用不同的函数。下面两种合成 reducer 方法完全等价:
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
combineReducers()
所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。
导入的模块
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
导出内容
//reducers Object类型 每个属性对应的值都要是function
export default function combineReducers(reducers) {
......
}
这个js对应着redux里的combineReducers方法,主要作用就是合并多个reducer。
参数很好理解,就是reducers。
combineReducers()分析
第一步:浅拷贝reducers(拷贝深度为1)
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
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}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 。。。。。。省略后面代码
}
这里定义了一个finalReducers
和finalReducerKeys
,分别用来拷贝reducers
和其属性。
先用Object.keys方法拿到reducers所有的属性,然后进行for循环,每一项可根据其属性拿到对应的reducer,并浅拷贝到finalReducers中,但是前提条件是每个reducer的类型必须是Function,不然会直接跳过不拷贝。
-
第二步:检测finalReducers里的每个reducer是否都有默认返回值
function assertReducerShape(reducers) {
// 开始遍历
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
// 初始状态
const initialState = reducer(undefined, { type: ActionTypes.INIT })
// 不能占用<redux/*>的命名空间
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${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.`
)
}
// 如果遇到未知的action的类型,不需要要用默认返回值
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
throw new Error(
`Reducer "${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.`
)
}
})
}
export default function combineReducers(reducers) {
//省略第一步的代码
......
// This is used to make sure we don't warn about the same
// keys multiple times.
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
// 检测finalReducers里的每个reducer是否都有默认返回值
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
// 如果不是每个reducer都有默认返回值,就抛出异常
shapeAssertionError = e
}
//省略后面步骤的代码...
}
assertReducerShape方法主要检测两点:
-
不能占用<redux/*>的命名空间 -
如果遇到未知的action的类型,不需要要用默认返回值
如果传入type为 @@redux/INIT<随机值> 的action,返回undefined,说明没有对未知的action的类型做响应,需要加默认值。
如果对应type为 @@redux/INIT<随机值> 的action返回不为undefined,但是却对应type为 @@redux/PROBE_UNKNOWN_ACTION_<随机值> 返回为undefined,说明占用了 <redux/*> 命名空间。
-
第三步:返回一个函数,用于代理所有的reducer
后面的代码部分如下,先看对于生产环境的处理以及waringMessage的定义逻辑。
export default function combineReducers(reducers) {
//省略第一步和第二步的代码
......
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
// 返回一个combination函数
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
// ...省略后面的代码
}
}
首先对传入的state用getUnexpectedStateShapeWarningMessage做了一个异常检测。这个函数有什么作用呢?
getUnexpectedStateShapeWarningMessage接收四个参数 inputState(state)、reducers(finalReducers)、action(action)、unexpectedKeyCache(unexpectedKeyCache)。
很明显,它主要就是为了找出state里面没有对应reducer的key,并提示生产环境下的开发者做调整。
完整代码如下:
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
if (reducerKeys.length === 0) {
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (action && action.type === ActionTypes.REPLACE) return
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
unexpectedKeyCache是上一次检测inputState得到的其里面没有对应的reducer集合里的异常key的集合。整个逻辑如下:
-
前置条件判断,保证reducers集合不为{}以及inputState为简单对象 -
找出inputState里有的key但是 reducers集合里没有key -
如果是替换reducer的action,跳过第四步,不打印异常信息 -
将所有异常的key打印出来
接下来往下走,后面的全部代码都在这里了。
let hasChanged = false
const nextState = {}
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 errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
首先定义了一个hasChanged变量用来表示state是否发生变化,遍历reducers集合,将每个reducer对应的原state传入其中,得出其对应的新的state。
紧接着后面调用了getUndefinedStateErrorMessage
对新的state做了一层未定义的校验。
函数getUndefinedStateErrorMessage的代码如下:
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${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.`
)
}
逻辑比较简单,仅仅做了一下错误信息的拼接。未定义校验完了之后,会跟原state作对比,得出其是否发生变化。最后发生变化返回nextState,否则返回state。combinerReduces()那么到这里也就结束了。
compose.js文件
这个文件比较简单,只导出这么一个函数。
/**
* 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 {...Function} funcs The functions to compose.
* @returns {Function} 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) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
大致分为这么几步:
-
新建一个新数组funcs,将arguments里面的每一项一一拷贝到funcs中去 -
当funcs的长度为0时,返回一个传入什么就返回什么的函数 -
当funcs的长度为1时,返回funcs第0项对应的函数 -
当funcs的长度大于1时,调用Array.prototype.reduce方法进行整合
reduce这里就不赘述其用法了,可以看我的另一篇博文《一文带你彻底搞懂js的Array.prototype.reduce()方法!》。地址:https://blog.csdn.net/qq_41996454/article/details/108541886
这里的compose有个特点,它不是从左到右执行的,而是从右到左执行的,下面看个例子:
const value=compose(function(value){
return value+1;
},function(value){
return value*2;
},function(value){
return value-3;
})(2);
console.log(value); //(2-3)*2+1=-1
如果想要其从左向右执行也很简单,做一下顺序的颠倒即可。
转换前 return funcs.reduce((a, b) => (...args) => a(b(...args)))
转换后 return funcs.reduce((a, b) => (...args) => b(a(...args)))
为什么要设置成从右向左执行呢?可能是模仿栈的顺序吧,从后面加入,从后面弹出,后进后出(瞎猜的)。
这个compose
可能比较让人困惑,具体应用场景是怎么样的呢?比如有三个函数,这三个函数都是我们前面接收dispatch
返回新dispatch
的方法:
const fun1 = dispatch => newDispatch1;
const fun2 = dispatch => newDispatch2;
const fun3 = dispatch => newDispatch3;
当使用了compose(fun1, fun2, fun3)
后执行顺序是什么样的呢?
// 第一次其实执行的是
(func1, func2) => (...args) => func1(fun2(...args))
// 这次执行完的返回值是下面这个,用个变量存起来吧
const temp = (...args) => func1(fun2(...args))
// 我们下次再循环的时候其实执行的是
(temp, func3) => (...args) => temp(func3(...args));
// 这个返回值是下面这个,也就是最终的返回值,其实就是从func3开始从右往左执行完了所有函数
// 前面的返回值会作为后面参数
(...args) => temp(func3(...args));
// 再看看上面这个方法,如果把dispatch作为参数传进去会是什么效果
(dispatch) => temp(func3(dispatch));
// 然后func3(dispatch)返回的是newDispatch3,这个又传给了temp(newDispatch3),也就是下面这个会执行
(newDispatch3) => func1(fun2(newDispatch3))
// 上面这个里面用newDispatch3执行fun2(newDispatch3)会得到newDispatch2
// 然后func1(newDispatch2)会得到newDispatch1
// 注意这时候的newDispatch1其实已经包含了newDispatch3和newDispatch2的逻辑了,将它拿出来执行这三个方法就都执行了
applyMiddleware.js文件
这个文件的结构也比较简单,只有一个函数applyMiddleware,完整代码如下。
import compose from './compose'
/**
* 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 {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
正如本文前面所说的,enhancer经常和applyMiddleware搭配出现,其本质就是使用的洋葱模型,其实我感觉和装饰器模式是差不多的。下面是一个enhancer+applyMiddleware洋葱模型的简单示例。
function middleware1(store) {
return function(next) {
return function(action) {
console.log('A middleware1 开始');
next(action)
console.log('B middleware1 结束');
};
};
}
function middleware2(store) {
return function(next) {
return function(action) {
console.log('C middleware2 开始');
next(action)
console.log('D middleware2 结束');
};
};
}
function middleware3(store) {
return function(next) {
return function(action) {
console.log('E middleware3 开始');
next(action)
console.log('F middleware3 结束');
};
};
}
function reducer(state, action) {
if (action.type === 'MIDDLEWARE_TEST') {
console.log('======= G =======');
}
return {};
}
var store = Redux.createStore(
reducer,
Redux.applyMiddleware(
middleware1,
middleware2,
middleware3
)
);
store.dispatch({ type: 'MIDDLEWARE_TEST' });
// output:
A middleware1 开始
C middleware2 开始
E middleware3 开始
======= G =======
F middleware3 结束
D middleware2 结束
B middleware1 结束
enhancer
和applyMiddleware
二者的格式对比看一下。
// enhancer
function enhancer(createStore) {
return (reducer,preloadedState) => {
//逻辑代码
.......
}
}
//applyMiddleware
function //applyMiddleware(...middlewares) {
return createStore => (...args) => {
//逻辑代码
.......
}
}
下面看applyMiddleWare的具体实现逻辑:
-
通过createStore方法创建出一个store
const store = createStore(...args)
-
定一个dispatch,如果在中间件构造过程中调用,抛出错误提示
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
} -
定义middlewareAPI,有两个方法,一个是getState,另一个是dispatch,将其作为中间件调用的store的桥接
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
} -
middlewares调用Array.prototype.map进行改造,存放在chain
const chain = middlewares.map(middleware => middleware(middlewareAPI))
-
用compose整合chain数组,并赋值给dispatch
dispatch = compose(...chain)(store.dispatch)
-
将新的dispatch替换原先的store.dispatch,并返回结果
return {
...store,
dispatch
}
举个简单的例子,来看一下middleware的运作流程。
假设我们有以下middlewares:
const logger = middlewareAPI => next => action => {
console.log('start dispatch: ', action)
let result = next(action)
console.log('next state: ', store.getState())
return result
}
const errorHandler = middlewareAPI => next => action => {
try {
return next(action)
} catch (err) {
// handle error
}
}
// dummy reducer
function reducer() {}
const store = createStore(reducer, applyMiddleware(errorHandler, logger))
store.dispatch({ type: 'test' })
以上我们定义了两个middleware,分别是logger和errorHandler。代码执行流程如下:
-
执行 applyMiddleware
,首先会调用每个middleware函数,然后将middlewareAPI作为参数传入。middleware返回一个函数,这里才是真正的middleware的执行体,也就是:next => action => {}
的部分。然后代码会通过compose将所有middleware组成一条调用链,依次执行。
这里有个问题需要注意一下,就是原始的dispatch在什么时候执行?根据applyMiddleware.js
中的代码:
compose(...chain)(store.dispatch)
在我们上面的例子中,这句代码相当于:
errorHandler(logger(store.dispatch))
这句代码的执行顺序是:首先执行logger,然后遇到next(action)
的代码,接着执行errorHandler
,最后执行原始的store.dispatch
。
-
执行 createStore
,由于我们传递了enhancer
参数,会返回带有中间件的store(替换dispatch函数)。 -
我们在代码中调用store.dispatch,这时拿到的 dispatch
函数是被修改过的,串联了middleware的。所以会按照第一步中所描述的过程,依次执行所有middleware,最后执行原始的store.dispatch
函数。
接下来再来看看另一个例子。redux-thunk
是redux常用的一个enhancer。主要的功能就是可以让我们dispatch
一个函数,而不只是普通的 Object。接下来以redux-thunk为例,模拟一下整个过程。
由于redux-thunk的代码量非常少,直接把它的代码贴上来看一下。这里看的是v2.3.0的代码:
// 就这几行代码,很厉害
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
根据redux-thunk的源码,拿到的thunk应该是这样子的:
const thunk = ({ dispatch, getState })=>{
return next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
}
经过applyMiddleware
处理一下,到第四步的时候,chain数组应该是这样子的:
const newDispatch;
const middlewareAPI={
getState:store.getState,
dispatch: (...args) => newDispatch(...args)
}
const { dispatch, getState } = middlewareAPI;
const fun1 = (next)=>{
return action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
}
const chain = [fun1]
compose整合完chain数组之后得到的新的dispatch的应该是这样子:
const newDispatch;
const middlewareAPI={
getState:store.getState,
dispatch: (...args) => newDispatch(...args)
}
const { dispatch, getState } = middlewareAPI;
const next = store.dispatch;
newDispatch = action =>{
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
bindActionCreators.js文件
这个文件其实对于redux的理解没那么重要,bindActionCreators主要是需要结合react-redux一起使用的。只要知道bindActionCreators大概干了啥就行。下面是该文件的源代码。
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
/**
* 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 {Function|Object} 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 {Function} dispatch The `dispatch` function available on your Redux
* store.
*
* @returns {Function|Object} 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, 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 = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
bindActionCreators针对于三种情况有三种返回值,下面根据每种情况的返回值去分析。(为了方便理解,假定是在无集成中间件的情况下进行)
-
情况一:typeof actionCreators === ‘function’
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// ... 忽略后面的代码
}
// 所以第一种情况可以概括成以下语句
const fun1 = actionCreator;
const dispatch= store.dispatch;
const actionFun=function () {
return dispatch(fun1.apply(this, arguments))
}
根据上面的推导,当变量actionCreators的类型为Function时,actionCreators必须返回一个action。
-
情况二: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"?`
)
提示开发者actionCreators类型错误,应该是一个非空对象或者是函数。
-
情况三:默认
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
通过和第一种情况对比发现,当actionCreators的每一项都执行一次第一种情况的操作。换句话说,默认情况是第一种情况的集合。
小结
-
单纯的Redux只是一个状态机, store
里面存了所有的状态state
,要改变里面的状态state
,只能dispatch action
。 -
对于发出来的 action
需要用reducer
来处理,reducer
会计算新的state
来替代老的state
。 -
subscribe
方法可以注册回调方法,当dispatch action
的时候会执行里面的回调。 -
Redux其实就是一个发布订阅模式! -
Redux还支持 enhancer
,enhancer
其实就是一个装饰者模式,传入当前的createStore
,返回一个增强的createStore
。 -
Redux使用 applyMiddleware
支持中间件,applyMiddleware
的返回值其实就是一个enhancer
。 -
Redux的中间件也是一个装饰者模式,传入当前的 dispatch
,返回一个增强了的dispatch
。 -
单纯的Redux是没有View层的,所以他可以跟各种UI库结合使用,比如 react-redux
。
参考
《理解redux-thunk》https://zhuanlan.zhihu.com/p/85403048
《我的源码阅读之路:redux源码剖析》https://segmentfault.com/a/1190000016460366
《手写一个Redux,深入理解其原理》https://juejin.cn/post/6845166891682512909#heading-13
原文始发于微信公众号(豆子前端):[万字长文]redux@4.0.5源代码浅析
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56865.html