CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 源码浅析

    • react的设计理念
    • redux 源码及简单实现
      • redux 的 createStore
      • redux 的 bindActionCreators
      • redux 的combineReducers
      • redux 的compose
      • redux 的 applyMiddleware
      • connect 嵌套方法
      • 总结
    • react-router 源码及简单实现
  • 工具方法

  • React笔记
  • 源码浅析
CD
2021-02-22
目录

redux 源码及简单实现

  • git 官网 (opens new window)
  • 中文文档 (opens new window)

# redux 的 createStore

// 路径:https://github.com/reduxjs/redux/blob/master/src/createStore.ts
// 现在已经是ts的版本,看起来会比较麻烦,我在这里转为js版本并挑重点写入
export function createStore(reducer, preloadedState, enhancer) {
  // 如果你除了reducer以外的参数都传递了函数,建议使用compose来整合他们进行使用,compose后面会写
  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."
    );
  }

  // enhancer可以作为第二、第三个参数来传入,默认传入的函数即为enhancer
  if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  // 倘若enhancer函数存在,则走入链式调用创建store的过程中,途中会调用到中间件,例如enhancer为:applyMiddleware(fn)
  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.");
  }

  let currentReducer = reducer;
  let currentState = preloadedState; // preloadedState: 初始化 State
  let currentListeners = [];
  let isDispatching = false;

  function getState() {
    return currentState;
  }
  function subscribe(fn) {
    if (typeof fn !== "function") {
      throw new Error("触发监听得是数组宝贝");
    }
    if (isDispatching) {
      throw new Error("正在触发派发的过程中,你在这时候搞监听不得行的");
    }
    // 下面去除掉一些判断、处理逻辑,保留本职代码

    currentListeners.push(fn);
    return function() {
      const index = currentListeners.indexOf(listener);
      index > 0 && currentListeners.splice(index, 1);
    };
  }
  function dispatch(action) {
    // if (action.type === undefined) ......
    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }
    currentListeners.forEach((item) => item && item());
  }
  function replaceReducer(nextRedux) {
    if (typeof nextRedux !== "function") {
      throw new Error();
    }
    // 替换reducer 用于热替换、按需加载等场景
    currentReducer = nextRedux;
    dispatch({
      type: "INIT", //用于派发整体更新
    });
  }
  function observable() {}

  dispatch({
    type: "INIT", //用于生成初始state树
  });
  return {
    getState,
    dispatch,
    subscribe,
    replaceReducer,
    observable,
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

store.subscribe()方法总结:

  • 入参函数放入监听队列

  • 返回消订阅函数

store.dispatch()方法总结:

  • 调用 Reducer,传参(currentState,action)。

  • 按顺序执行 listener。

  • 返回 action。

# redux 的 bindActionCreators (opens new window)

bindActionCreators 把 action creators 转成拥有同名 keys 的对象,使用 dispatch 把每个 action creator 包装起来,这样可以直接调用它们。

// https://github.com/reduxjs/redux/blob/bc53cd00a4/src/bindActionCreators.ts

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default bindActionCreators(actionCreators, dispatch) {
  // ... if ... if
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for(let i = 0; i< keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[i]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# redux 的combineReducers (opens new window)

简易版关键代码:

// https://github.com/reduxjs/redux/blob/bc53cd00a4/src/combineReducers.ts
export function combineReducers(obj) {
  return function(state = {}, action) {
    Object.keys(obj).forEach((key) => {
      state[key] = obj[key](state[key], action);
    });
    return state;
  };
}
1
2
3
4
5
6
7
8
9

这个方法的主要功能是用来合并 Reducer,因为当我们应用比较大的时候 Reducer 按照模块拆分看上去会比较清晰,但是传入 Store 的 Reducer 必须是一个函数,所以用这个方法来作合并。

# redux 的compose (opens new window)

export default function compose(...funcs) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return (args) => args;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  // funcs = [a, b, c]   => a(b(c(...args)))
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
1
2
3
4
5
6
7
8
9
10
11
12
13

compose 这个方法,主要用来组合传入的一系列函数,在中间件时会用到。可以看到,执行的最终结果是把各个函数串联起来。

compose(a, b, c)等价于 return (...args)=>a(b(c(...args)))

# redux 的 applyMiddleware

applyMiddleware 用于 Store 增强

可以看到执行方法有三层,那么对应我们源码看的话最终会执行最后一层。最后一层的执行结果是返回了一个正常的 Store 和一个被变更过的 dispatch 方法,实现了对 Store 的增强。

这里假设我们传入的数组 chain 是[f,g,h],那么我们的 dispatch 相当于把原有 dispatch 方法进行 f,g,h 层层过滤,变成了新的 dispatch。

由此的话我们可以推出中间件的写法:因为中间件是要多个首尾相连的,需要一层层的“加工”,所以要有个 next 方法来独立一层确保串联执行,另外 dispatch 增强后也是个 dispatch 方法,也要接收 action 参数,所以最后一层肯定是 action。

再者,中间件内部需要用到 Store 的方法,所以 Store 我们放到顶层,最后的结果就是:

# connect 嵌套方法

import React, { Component } from "react";
const MyContext = React.createContext();
export class Provider extends Component {
  render() {
    let { store, children } = this.props;
    return <MyContext.Provider value={store}>{children}</MyContext.Provider>;
  }
}

export function connect(mapStateToProps, mapDispatchToProps) {
  return function(Comp) {
    return class Temp extends Component {
      constructor(props, context) {
        super(props, context);
        this.state = mapStateToProps(context.getState());
        this.dispatch = mapDispatchToProps(context.dispatch);
      }
      static contextType = MyContext;
      componentDidMount() {
        this.context.subscribe(() => {
          this.setState(mapStateToProps(this.context.getState()));
        });
      }
      render() {
        return <Comp {...this.props} {...this.state} {...this.dispatch} />;
      }
    };
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 总结

用对象展开符增加代码可读性。

区分 smart component(know the State)和 dump component(完全不需要关心 State)。

component 里不要出现任何 async calls,交给 action creator 来做。

Reducer 尽量简单,复杂的交给 action creator。

Reducer 里 return state 的时候,不要改动之前 State,请返回新的。

immutable.js 配合效果很好(但同时也会带来强侵入性,可以结合实际项目考虑)。

action creator 里,用 promise/async/await 以及 Redux-thunk(redux-saga)来帮助你完成想要的功能。

action creators 和 Reducer 请用 pure 函数。

请慎重选择组件树的哪一层使用 connected component(连接到 Store),通常是比较高层的组件用来和 Store 沟通,最低层组件使用这防止太长的 prop chain。

请慎用自定义的 Redux-middleware,错误的配置可能会影响到其他 middleware.

有些时候有些项目你并不需要 Redux(毕竟引入 Redux 会增加一些额外的工作量)

编辑 (opens new window)
#JavaScript#React
上次更新: 2023/03/22, 15:28:00
react的设计理念
react-router 源码及简单实现

← react的设计理念 react-router 源码及简单实现→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式