一、combineReducers
我一开始就看的是这个。这个文件只有这一个方法combineReducers
,即功能明显,很容易理解(哈哈,然而我也看了好久),感觉就是合多为一
的作用吧,主要想后面改变更新state树的话,都通过这一个方法来更新,不错,源码看起来太香了,比看别人总结的清晰。
/**
* @param {*} reducers 传入的是一个对象,即所有的reducer集合
* 数据结构:
{
reducer1: function reducer1() {},
reducer2: function reducer2() {}
}
*/
export default function combineReducers(reducers) {
// 获取 reducers 的 key值,结果为数组
const reducerKeys = Object.keys(reducers)
// 后面用的是这个,复制了一套reducers
const finalReducers = {}
// 循环,把 reducers 里面的每一项都复制到 finalReducers 里
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') { // 每一个 reducers[key] 都应该是function
finalReducers[key] = reducers[key]
}
}
// 获取 finalReducers 的 key值,结果为数组
const finalReducerKeys = Object.keys(finalReducers)
/**
* combineReducers API 最终结果返回一个新的reducer函数
* state:createStore会先dispatch一个初始action,来获取所有reducer的初始值
* action:为每一次dispatch的action,用来更新数据
*/
return function combination(state = {}, action) { // 可以试试dispatch更新reducer2的action,就知道这个函数的作用了。每次返回还都是所有的state树。
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i] // 获取key:reducer1、reducer2
const reducer = finalReducers[key] // 获取key的值:function reducer1() {...}、function reducer2() {...}
const previousStateForKey = state[key] // 刚开始,所有的reducer值都为{},dispatch之后才会改变
const nextStateForKey = reducer(previousStateForKey, action) // 每一个reducer函数都会去调用,只有匹配到actopn.type的值才有可能变;
if (typeof nextStateForKey === 'undefined') { // 不允许返回undefined,报错,需返回原来的state,或者null
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey // {}
hasChanged = hasChanged || nextStateForKey !== previousStateForKey // 只要有一个值更新了就为true,后面就一直为true
}
return hasChanged ? nextState : state // 状态有更新就用新的state,没更新就用原来的state
}
}
/**
* 顺带去看了createStore的实现,先总结如下:
* createStore的第一个入参其实就是combination函数(新的reducer,里面的变量有存放所有的reducer)
* 每一次dispatch(action)前,createStore会先走 observable -> subscribe - (调用getState,获取到最新的state树) 的流程
* 然后调用combination函数,将新获取的state以及需要dispatch的action作为参数传入,最后又返回新的state。依次循环。
*/
好了,那接下来就对createStore的代码好好说明一下。
二、createStore
弄清了两个API:dispatch
和 getState
,经常用到的也就这俩,还有几个没弄清楚,就先记录下吧。
总结:dispatch方法返回的是自身入参的action,中间去调用reducer总函数获取最新state树。
// redux自己私有的action,用来获取所有reducer的初始状态,不允许其他地方定义或者使用这个action
export const ActionTypes = {
INIT: '@@redux/INIT'
}
/**
* 创建一个redux store来保留状态树
* 改变store的唯一方式就是调用 dispatch() 来改变
*
* @param {Function} reducer 通过 combineReducers 返回的函数,接收当前state树 和 一个action
*
* @param {any} [preloadedState] 【可选参数】
* 如果为函数:且enhancer正常传,则走(2.2)但也许会报错,因为reducer的state参数是个对象;
* 如果为对象:且enhancer正常传,则走(2.2)作为初始的state,给哪个reducer设置初始值,则必须key和reducer的key要一样才有效.
*
* @param {Function} [enhancer] 一般不会传这个参数,但是只要preloadedState为函数,就也会走(2.2),
* 例如:经常会这样使用redux的`applyMiddleware()`来增强store,传在第二个参数里,也会正常走(2.2)。
*
* @returns {Store} 返回getState方法获取store树;返回dispatch来触发action;返回subscribe监听变化
*/
export default function createStore(reducer, preloadedState, enhancer) {
// (1)如果没有传enhancer,则会将preloadedState赋给enhancer,并将preloadedState置为undefined;
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// (2)如果三个参数都存在的话, 或者上面将preloadedState的值赋给enhancer也算有效
if (typeof enhancer !== 'undefined') {
// (2.1)如果enhancer不是函数的话
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// (2.2)enhancer是函数,则就会直接返回这个函数结果了
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer
// 如果preloadedState是对象,就赋值;不是对象,最后就变为undefined;或者在上面直接return,不会走到这里了
let currentState = preloadedState
// 表示dispatch是否结束
let isDispatching = false
/**
* 获取整个state树的值
*/
function getState() {
return currentState
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* reducer改变了state之后,监听器就会知道改变了,就会触发监听器,and the change listeners
* will be notified.
*
* dispatch方法只支持普通的对象,如果想dispatch一个Promise、Observable、thunk 或者其他,可以看下redux-thunk
* 实际上使用redux-thunk后,middleware最终也是diapatch普通对象(仅仅翻译,后面待了解redux-thunk后再进一步确定一下)
*
* @param {Object} action:是一个普通的对象,type字段为一个`字符串常量`
* @returns {Object} 返回入参action
*/
function dispatch(action) { // action 必须是个对象,且type不能为undefined
// 表示一个reducer更新完后才能dispatch下一个,否则报错
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 将之前最新的state集合 和 要dispatch的 action 传给 reducer,获取最新的state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
return action // 返回和入参一样的action对象(至于有没有用到,后面再说,现在我还不知道)
}
/**
* 替换当前reducer函数
* 如果应用程序使用了代码分割并且想动态加载一些reducer、如果为redux实现了热加载机制,这两种都需要使用这个replaceReducer
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
// 当调用createStore的时候,createStore会自己先dispatch一个 `@@redux/INIT` action,拿到每一个reducer的初始值来填充store树
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe, // 没弄清
getState,
replaceReducer, // 没弄清
[$$observable]: observable // 没弄清
}
}
接下来先看compose
,因为源码比较少,比较简单。
三、compose
这个函数主要作用就是将多个函数连接起来,将一个函数的返回值作为另一个函数的传参进行计算,得出最终的返回值。
/**
* 特点:从右到左执行,若需要从左往右执行,则调换顺序即可 b(a(...args))
*
*
* @param {...Function} 多个要组合的函数.
* @returns {Function} 通过从右到左组合实参函数而得到的一个总函数
* 例如:调用 compose(f, g, h),最后返回的是 (...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)))
}
经过查看这个源码,理解了里面arguments
对象在函数中的用法及作用:
function consoleNew() {
console.log(arguments);
}
consoleNew(function() {}, function(){}, {a: 1, b: 2}, '字符串')
四、applyMiddleware
applyMiddleware函数是用来绑定中间件函数的函数(这句话是我copy过来的官话)。即:通过改变dispatch的指向,一步一步完成每个中间件的执行,最终走向真正的dispatch(action),走向reducer,更新state。
一般的中间件函数middleware
的形式为:
//middleware.js文件
const middleware = ({dispatch, getStore})=>(next)=>(action)=>{
//dosomething
}
// 或
const middleware = (store)=>(next)=>(action)=>{
//dosomething
}
这个需要和createStore结合起来一起看。
// applyMiddleWare.js
export default function applyMiddleware(...middlewares) {
// 返回函数A,函数A返回函数B
return (createStore) => (reducer, preloadedState, enhancer) => {
// 函数B内部,函数B返回store
const store = createStore(reducer, preloadedState, enhancer)
// ...
return {
...store,
dispatch
}
}
}
// createStore.js
export default function createStore(reducer, preloadedState, enhancer) {
// ...
return enhancer(createStore)(reducer, preloadedState)
// ...
}
// 我们一般这样调用createStore,不传enhancer参数
import { createStore } from 'redux' ;
// applyMiddleware(m1, m2, m3)的结果为函数A
const store = createStore(reducer, applyMiddleware(m1, m2, m3))
我们知道createStore函数的处理,将preloadedState = applyMiddleware(m1, m2, m3)
赋值给enhancer
,并将preloadedState = undefined
,即会执行enhancer的函数。因此,applyMiddleware(m1, m2, m3)
的结果为函数A,刚好和createStore的返回函数吻合。因此,接下来直接看函数B的内部实现。
-
enhancer(createStore)
:相当于调用了函数A,返回函数B; -
A(reducer, preloadedState)
:相当于调用了函数B,生成store,并且绑定中间件。
return (createStore) => (reducer, preloadedState, enhancer) => {
// 函数B内部
const store = createStore(reducer, preloadedState, enhancer)
// 这已经是第二次调用了,preloadedState和enhancer都为undefined
// 因此,直接返回store。
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI)) // 难点一
dispatch = compose(...chain)(store.dispatch) // 难点二
return {
...store,
dispatch
}
}
难点一:chain = middlewares.map(middleware => middleware(middlewareAPI))
-
middlewares.map
:遍历每一个中间件; -
middleware(middlewareAPI)
:为每一个中间件的第一层函数传入{ getState, dispatch }
对象,运行并返回得到(next)=>(action)=>{}
的函数; - 返回
(next)=>(action)=>{}
的中间件函数数组。
即以一个为例,中间件middleware
函数的形式为:
//middleware.js文件
const middleware = ({dispatch, getStore})=>(next)=>(action)=>{
//dosomething
}
// 或
const middleware = (store)=>(next)=>(action)=>{
//dosomething
}
难点二:dispatch = compose(...chain)(store.dispatch)
- ------从右到左执行 & 分析-------
- 前面查看过compose的源码,已经知道,是从右至左运行的,这里将
store.dispatch
作为最后一个中间件的参数来运行,也就是说最后一个中间件的next
参数就是store.dispatch
; - 后面每一个中间件的next参数都是前一个函数的返回值
(action)=>{ //... }
; - 即:最后的返回值是第一个中间件函数的返回值
(action)=>{ //...中间有好多层next替换后的嵌套 }
函数,将其赋给dispatch
;
注意
:因此,在这里,需要注意 千万不能在某个中间件中直接调用dispatch,不然会无限循环。因为,直接调用dispatch会从第一个中间件重新开始。(如果是dispatch另一个action,不至于无限循环;如果是dispatch(action)
)和一开始的action一样,那就会进入无限循环。
- ------从左到右调用时-------
- 之后每次调用dispatch,就是调用第一个中间件的
(action)=>{ //... }
函数; - 每个中间件函数在
(action)=>{ //... }
中调用next就是调用下一个中间件的(action)=>{ //... }
函数; - 一步一步会走向调用最后一个中间件的next,会调用原生的
store.dispatch
,更新state。
举例拆分说明如下:
// m1、m2、m3值如下:
m3 = (next) => (action) => {
next(action) // store.dispatch(action)
}
// 分析:
dispatch = m1(m2(m3(store.dispatch)))
dispatch(action)
m1(m2(m3(store.dispatch)))(action)
// (1)进入m1,返回next(action) next指向下面
m2(m3(store.dispatch))
// (2)进入m2,返回next(action) next指向下面
m3(store.dispatch)
// (3)进入m3,返回next(action) next为store.dispatch
store.dispatch(action)
总结一句话,只要使用了applyMiddleWare(m1, m2, ...)
这个函数,都会得到一个增强型的store
,主要表现为改变dispatch
的指向,第一次dispatch就会进入嵌套的最外层的middleware里,即指向是从左向右顺序的第一个中间件的(action)=>{ //... }
,然后开始依次向下走,直到最后调用store.dispatch(action)才跳出中间件,进入reducer里,改变state(没有匹配到就不更新state)。
之前有个问题:
问:会不会无限循环呢,一直在中间件里面走,能否next跳出中间件而去进入reducer改变state呢?
答:终于弄通了,最后无论如何肯定是会进入reducer里的呀!其实就是上面总结的一段话,2020.5.7日凌晨24分终于想通了,当时给忘了createStore的dispatch的作用了,只要一dispatch,就会去总的reducer里判断state是否改变来更新state树,从而,就跳出了中间件,更新了状态,不管怎样,最终都会走向store.dispatch(action)
,然后进入reducer来更新状态的。
若使用了middleware
,执行顺序:dispatch(action) -> middleware -> reducer
整理了一下createStore和applyMiddleware在项目中的使用,代码执行分析如图:
再赠送一个用比画出来的:
此时,我也看了下redux-thunk
的源码,整理了下,点击查看。
接下来看下bindActionCreators
是怎么回事吧!
五、bindActionCreators
这里跟connect有关系,我先说一下connect,有两种写法:
- 不使用装饰器
@
写法:这里主要表现为容器型组件
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { action1, action2 } from './actions'; // 都用export导出
// export {action1, action2}
class MyComponent extends Component {
// ...
this.props.action1(); // 调用自己定义的方法名
this.props.actionNew(); // 调用自己定义的方法名
}
const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch) => {
action1: (params) => { // 定义时可以和action名一样
dispatch(action1(params))
},
actionNew: (params) => { // 定义时也可以和action不一样
dispatch(action2(params))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
- 使用装饰器
@
写法:就需要用到bindActionCreators
其实bindActionCreators
这个方法相当于简化了上面的写法,函数内部帮我们实现了mapDispatchToProps
的作用,不过唯一的区别就是this.props.xxx
中的xxx
是我们暴露出来的action函数名,而不能像上面代码在mapDispatchToProps
中自定义函数名。如下:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from './actions'; // 都用export导出
// export {action1, action2}
@connect(
state => state,
dispatch => bindActionCreators(actions, dispatch)
)
class MyComponent extends Component {
// ...
this.props.action1(); // 只能是action1
this.props.action2(); // 只能是action2
}
export default MyComponent;
是不是代码量少了好多,关键是两种方式实现的功能是一样的,那我们何尝不尝试一下少写点代码的方式呢!我这里只是写了两个action,那如果一个页面有很多action,如果用第一种方式,那mapDispatchToProps
这个函数就会很长很长,那个时候就能发现使用bindActionCreators
这个方法的好处了。
- 其实,那我觉得也可以将第一种和第二种搭配起来使用,即:不用装饰器
@
,和使用bindActionCreators
,如下:站在原理的层面上,我觉得应该也没什么问题 - 虽然一般没人这样用,哈哈哈!!!
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from './actions'; // 都用export导出
// export {action1, action2}
class MyComponent extends Component {
// ...
this.props.action1(); // 调用自己定义的方法名
this.props.actionNew(); // 调用自己定义的方法名
}
const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch) => bindActionCreators(actions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
至于源码,总体就下面这一点点,很简单的实现,跟我们第一种不用装饰器@
写法的实现是一样的,只不过bindActionCreators
帮我们统一实现处理了,让我们不用一个一个去dispatch(action方法)
了。
// 处理单个action,把action放进dispatch里,作为props.xxx 的 值(类似定义action1)
// 即:调用这个方法,返回的是`dispatch(action(params))`
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(undefined, arguments));
};
}
// actionCreators:所有的action对象 or 一个函数
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') { (1)
return bindActionCreator(actionCreators, dispatch);
}
// actionCreators必须是函数或者对象
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"?');
}
// 以下是`actionCreators`为对象的一系列操作
var keys = Object.keys(actionCreators); // 获取所有的action函数的 key
var boundActionCreators = {}; // 创建一个空的对象
for (var i = 0; i < keys.length; i++) { // 循环action函数的 key 数组
var key = keys[i]; // actions对象的健
var actionCreator = actionCreators[key]; // 健对应的每一个action函数赋值给`actionCreator`
if (typeof actionCreator === 'function') { // 判断`actionCreator`是否为函数,是就走下面,和(1)处一样
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators; // `actionCreators`为对象最终返回这个对象
}
好了,redux这几个API的源码已经理解了,接下来就是想对react-redux
经常用到的两个API(Provider
、connect
)进行了解。