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 的是否具有默认的返回值,讲解一下步骤:

  • 便利 reducerskey。小伙伴如果有看 combineReducers.ts 我写的例子就能知道,这里的 key 就是 reducer1reducer2
  • 每个 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)

不懂的小伙伴可以大致理解下就行。

不要看方法名很长,参数很多。我们慢慢来看。通过方法名,我们大致可以知道这个方法的作用:获取异常 Statekey,发出警告。我们来看下步骤:

  • 首先保证 reducers 不为 {}inputState 为简单函数。
  • 取出 inputStatereducers 不存在的 key
  • 如果是替换 reduceraction 就直接跳过。
  • 如果存在未定义的 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) 方法
  • 方法中,遍历 statekey 将每个子 reducerstate 都取出来放到一起去。
  • 判断是否有改变,如果改变了就返回新的 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))); 
}

reduxindex.ts 是有把 compose 暴露出来,只是可能小伙伴用的比较少。官网也有将 compose 作为 api 单独拎出来说。

点这里打开官网 compose 介绍

我们先来看看官网给出的示例:

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()
  )
)
  • composeredux 的作用是什么呢:将多个 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 是在这里传递进来的。并且一并将 reducerpreloadState 也传递过来了, createStore 在是 applyMiddleware 被执行的

因为中间件是外部的东西(将来小伙伴们厉害了,也可以自己搞一个),所以在遍历执行每个中间件的时候,将数据 statedispatch 暴露出去给中间件执行和使用。

整合好后通过 compose 重写 dispatch 最后将 storedispach 返回回去。

5、bindActionCreators.ts

redux 提供的这个方法,其实可以通过 react-redux 提供 dispatch 去执行。

本人对此没有太深的研究(不想看了)。还请感兴趣的小伙伴自行学习哈。

记的双击么么哒~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值