Redux 源码共读-2
Redux 源码共读-2
我们继续上一篇文章的学习,如果小伙伴们要回顾上一篇的内容,请点击这里 Redux 源码共读-1
3、combineReducers.ts
如果小伙伴直接看源码可能会比较懵一些,代码并不长,但是有些方法和判断未必看得懂。这里我先给小伙伴举个该文件的作用的最简单的例子。
我们知道 combineReducers
就是将所有的子 reducer
函数组成对象,转换成一个新的 reducer
函数。如:
const reducer1 = (
state = {
name1: 'name1',
age1: 1,
},action
) => { ... }
const reducer2 = (
state = {
name2: 'name2',
age2: 2
}, action
) => { ... }
export default combineReducers({
reducer1,
reducer2
})
最后要组合成的 state
样子长这个样子:
{
"reducer1": {
"name1": "name1",
"age1": 1
},
"reducer2": {
"name2": "name2",
"age2": 2
}
}
所以,其实该文件最核心的代码并不多,最大的作用就是将 state
进行一个转化。至于该文件中的其他方法和判断,就是增强 redux
的健壮性(本人理解,不一定正确)。
一共有3个方法和一个核心代码:我将分开讲解:
3个方法其一:getUndefinedStateErrorMessage()
通过方法名我们就可以理解,获取未定义 state
的错误信息。
function getUndefinedStateErrorMessage(key: string, action: Action) {
const actionType = action && action.type;
// 这里就是做个判断
// 如果 actionType 存在就展示,否则就展示 an action
const actionDescription = ( actionType && `action "${String(actionType)}"` ) || 'an action'
return (...) // 返回文字信息
}
3个方法其二:assertReducerShape(reducers)
该方法的作用是检查每个 reducer
的是否具有默认的返回值,讲解一下步骤:
- 便利
reducers
的key
。小伙伴如果有看combineReducers.ts
我写的例子就能知道,这里的key
就是reducer1
和reducer2
- 每个
reducer
都执行init
的操作。 - 如果执行了第二步,返回回来的是
undefined
, 则说明该reducer
未定义默认的返回值
function assertReducerShape(reducers: ReducersMapObject) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key];
const initialState = reducer(undefined, { type: ActionTypes.INIT });
if(typeof initalState === 'undefined') throw new Error('');
if(typeof reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION() }) === 'undefined') throw new Error('');
})
}
3个方法其三:getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache)
不懂的小伙伴可以大致理解下就行。
不要看方法名很长,参数很多。我们慢慢来看。通过方法名,我们大致可以知道这个方法的作用:获取异常 State
的 key
,发出警告。我们来看下步骤:
- 首先保证
reducers
不为{}
且inputState
为简单函数。 - 取出
inputState
中reducers
不存在的key
。 - 如果是替换
reducer
的action
就直接跳过。 - 如果存在未定义的
key
就将他们打印出来。
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
const reducerKeys = Object.keys(reducers);
if(reducerKeys.length === 0) return '';
if(!isPlainObject(inputState)) return '';
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 '';
}
核心代码
这里就是整合代码,该方法中有些判断的代码我就不做展示,如果小伙伴想看完整源码就去官网上拉取哈,我们来看下步骤:
- 对
reducers
做一层浅拷贝 - 返回一个
combination(state, action)
方法 - 方法中,遍历
state
的key
将每个子reducer
的state
都取出来放到一起去。 - 判断是否有改变,如果改变了就返回新的
state
否则就返回旧的state
const reducerKeys = Object.keys(reducers);
const finalReducers: ReducersMapObject = {} // 浅拷贝后的 reducers
for(let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
if(typeof reducers[key] === 'undefined') warining();
if(typeof reducers[key] === 'function') finalReducers[key] = reducers[key];
}
const finalReducersKeys = Object.keys(finalReducers);
return function combination(state, action) {
let hasChanged = false; // 这里做判断是否
const nextState = {};
for(let i = 0; i < finalReducersKeys.length; i++) {
const key = finalReducerKeys[i];
const reducer = finalReducers[key];
const previousStateForKey = state[key];
const nextStateForKey = reducer(previousStateForKey, action); // 这里就是取出每个 reducer 的数据源, 这里可以把 nextStateForKey 打印出来看看
if (typeof nextStateForKey === 'undefined') throw new Error;
nextState[key] = nextStateForKey; // 将每个数据源放到新的 state 中
hasChanged = hasChanged || nextStateForKey !== previousStateForKey // 判断新的 state 和之前的 state 有不一样。
}
// 这里还判断了一下前后 key 的长度,非常的严谨
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
如果小伙伴还觉得上面的代码麻烦的话,自己写的 demo,可以忽略掉 hasChanged
以及对 nextStateForKey
的类型判断。
3、compose.ts
小伙伴不一定很清楚这个类是干嘛的,如果是在 2019.8月后拉取的官网代码可能会觉得比较这个类量也不少,其实不是的。。最最核心的代码就下面的几行。小伙伴找找:
export default function compose(...funcs) {
if(funcs.length === 0) return funcs => funcs;
if(funcs.length === 1) return funcs[0];
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
redux
的 index.ts
是有把 compose
暴露出来,只是可能小伙伴用的比较少。官网也有将 compose
作为 api 单独拎出来说。
我们先来看看官网给出的示例:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import DevTools from './containers/DevTools'
import reducer from '../reducers/index'
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
DevTools.instrument()
)
)
-
compose
在redux
的作用是什么呢:将多个sotre 增强器
依次执行。 -
那
compose.ts
的作用又是什么呢?通过最后一行代码我们可以看出是从右到左来组合多个函数。
es6
里的 reduce
是个累加器, 写个demo 大家参考下。
const sum = [1,2,3,4].reduce((acc, current) => {
console.log(acc, current);
return acc + current;
})
console.log(sum);
打印结果为:
1 2
3 3
6 4
10
即 acc
为每次累加后 return
的结果,current
为当前的数组元素。
由此便可知道入参的方法至少要为两个,否则就会报错了。这也就解释了,为什么代码中 funcs
要对长度做 === 0
和 === 1
的判断了。
那么这个方法是如何操作多个 funcs
的呢?
官网中已经给出了示例:compose(funcA, funcB, funcC)
===> compose(funcA(funcB(funcC())))
从右到左把接收到的函数合成后的最终函数。
4、applyMiddleware.ts
代码量也不多。我们先来看下源码是怎么写的:
function applyMiddleware(...middlewares) {
return createStore = (...args) => {
const store = createStore(...args);
let dispatch = () => {}
const middlewareAPI = {
state: store.getState;
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
}
}
代码没几行,但是却实在看不懂。很迷是不是?比如 createStore
到底哪里来的是不是?
首先我们先打开刚刚讲解过的 createStore.ts
里的四个判断里的其中一个:判断 enhancer 增强器
一定要为方法。回顾一下代码:
if(enhancer !== 'undefined') {
if(enhancer !== 'function') throw new Error('');
return enhancer(createStore)(reducer, preloadedState);
}
小伙伴发现了没有?当在外部执行 createStore()
方法时,在做核心代码前会先判断下 enhancer
增强器是否存在
如果存在的话就直接返回 enhancer
增强器,并且将 createStore()
方法传递给了增强器。所以呀~当 createStore
存在增强器时,createStore
根本就没走。而是直接被返回出去了。。。
所以 applyMiddleware
方法中的 createStore
是在这里传递进来的。并且一并将 reducer
和 preloadState
也传递过来了, createStore
在是 applyMiddleware
被执行的。
因为中间件是外部的东西(将来小伙伴们厉害了,也可以自己搞一个),所以在遍历执行每个中间件的时候,将数据 state
和 dispatch
暴露出去给中间件执行和使用。
整合好后通过 compose
重写 dispatch
最后将 store
和 dispach
返回回去。
5、bindActionCreators.ts
redux
提供的这个方法,其实可以通过 react-redux
提供 dispatch
去执行。
本人对此没有太深的研究(不想看了)。还请感兴趣的小伙伴自行学习哈。