0%

全局状态管理

第一步,我需要知道怎么用,第二部才是阅读源码。

如何阅读源码

读源码前,必须先了解这个库的基本背景知识:

  • 它解决了什么问题
  • 基本的使用方法

redux

作为相对官方、基础的全局状态管理,多少还是要学习一下的。

还有 dva,umi-useModel

redux 有个基本原则,或者所有的数据都一个这个原则,各自管理各自的数据,只有在实际使用的时候才对数据做操作,且不能修改到源数据

  • 基本概念

    • action 动作

      1
      2
      3
      4
      {
      type: "INCREMENT";
      // 可以传入一些其他参数
      }
    • dispatch 触发

      1
      store.dispatch({ type: "INCREMENT" });
    • reducer 实际写入 store 的方法(纯函数)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      const counterReducer = (state = 0, action) => {
      switch (action.type) {
      case "INCREMENT":
      return state + 1;
      case "DECREMENT":
      return state - 1;
      case "ZERO":
      return 0;
      default:
      return state;
      }
      };
    • store 存放全局状态的地方

      1
      const store = createStore(counterReducer);
  • 安装 redux

    最开始,我们还是要先安装依赖。

    1
    yarn add redux
  • 使用 react-redux

    有多种方法可以与组件共享 redux-store。 我们将研究使用最新的,也是最简单的方法,即 react-redux 的 hooks-api 。

    1
    npm install react-redux

    这个库包含了许多 hooks 例如:useDispatchuseSelectoruseStore(哦吼,我没有看到其他的 hooks 了),以及 Provider

    • useSelector

      这个 hooks 接收一个函数,该函数的第一个参数是整个 store,我们可以通过这个返回我们需要的内容。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      import { useSelector } from "react-redux";
      const App = () => {
      const store = useSelector((state) => state); // 直接返回全部全局状态
      // 假设我们的全局状态中有 users 内容
      const users = useSelector((state) => state.users); // 返回 users
      const mans = useSelector((state) =>
      state.users.filter((user) => user.gender === "man"); // 返回 gender 为 man 的人
      );
      return <></>;
      };
    • Provider

      通过翻阅源码,找到 react-redux 关于 Provider 的一段内容

      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
      function Provider(_ref) {
      var store = _ref.store,
      context = _ref.context,
      children = _ref.children;
      var contextValue = useMemo(
      function () {
      var subscription = createSubscription(store);
      subscription.onStateChange = subscription.notifyNestedSubs;
      return {
      store: store,
      subscription: subscription,
      };
      },
      [store]
      );
      var previousState = useMemo(
      function () {
      return store.getState();
      },
      [store]
      );
      useIsomorphicLayoutEffect(
      function () {
      var subscription = contextValue.subscription;
      subscription.trySubscribe();

      if (previousState !== store.getState()) {
      subscription.notifyNestedSubs();
      }

      return function () {
      subscription.tryUnsubscribe();
      subscription.onStateChange = null;
      };
      },
      [contextValue, previousState]
      );
      var Context = context || ReactReduxContext; // 这里的 ReactReduxContext 实际上就是 const MyContext = React.createContext(defaultValue);
      return /_#**PURE**_/Racet.createElement(
      Context.Provider,
      {
      value: contextValue,
      },
      children
      );
      }

      if (process.env.NODE_ENV !== "production") {
      Provider.propTypes = {
      store: PropTypes.shape({
      subscribe: PropTypes.func.isRequired,
      dispatch: PropTypes.func.isRequired,
      getState: PropTypes.func.isRequired,
      }),
      context: PropTypes.object,
      children: PropTypes.any,
      };
      }

      export default Provider;

      也就是说,其实 react-redux 并不神秘,它只是一个上下文,包裹了全局的上下文。

      找到一段 react 官方关于 上下文 的解释:

      每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
      Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
      当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。

    • useDispatch

      Hook factory, which creates a useDispatch hook bound to a given context.

  • combineReducers

    combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

    合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
    // rootReducer 将返回如下的 state 对象
    {
    potato: {
    // ... potatoes, 和一些其他由 potatoReducer 管理的 state 对象 ...
    },
    tomato: {
    // ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 对象,比如说 sauce 属性 ...
    }
    }
  • applyMiddleware

    使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。

  • redux-thunk

    redux 官网关于异步逻辑的详细解释 http://cn.redux.js.org/tutorials/essentials/part-5-async-logic

其他的一些东西

关于手写 redux?不是的,相当于是原理之类的吧,转自知乎李元秋回答

大家都在安利自己的状态管理器,我们是否思考过一个问题,为什么需要状态管理器?我们需要先清理一下头脑:并不是因为 Redux 的存在,所以我们需要状态管理,因果关系不能搞混并不是因为写 React 应用,所以才需要状态管理,即使 VanillaJS,应用写大了,也是需要的我来做一个逐步演进的讲解,推演出为何我们需要状态管理:需求:A 模块内部状态发生了变化,需要 B 模块感知到并做出响应完成这个需求,最 plain 的写法是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A {
count = 0;
b = new B();
increase() {
this.count += 1;
b.updateCount(this.count);
}
}

class B {
count = 0;
updateCount(count) {
this.count = count;
}
}

这里存在一个问题,A 对 B 是强依赖的,A 需要关注 B 的 interface,这是一个致命问题,随着业务复杂度的提升,整个系统会成为一团乱麻。大多数刚入行的前端程序员,写应用写到后面无法维护了,多半是这个原因。因此我们可以用一个 event bus 来解藕:// event bus(功能不完整)

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
export const bus = (() => {
const listeners = {};
return {
dispatch(eventName, ...params) {
const listener = listeners[eventName];
if (listener !== undefined) {
listener(...params);
}
},
on(eventName, listener) {
listeners[eventName] = listener;
},
};
})();
import { bus } from "./event-bus";

class A {
count = 0;
increase(n) {
this.count += n;
bus.dispatch("INCREASE_COUNT", n);
}
}

class B {
count = 0;
constructor() {
bus.on("INCREASE_COUNT", (n) => (this.count += n));
}
}

这样一来,A 和 B 去除了直接的依赖。这里我们可以看到,虽然原始的 count 在 A 中,但实际它已经是一个脱离于 A 的状态了,我们可以让 A 自身也通过订阅方式来获得最新的 count:

1
2
3
4
5
6
7
8
9
class A {
count = 0;
constructor() {
bus.on("INCREASE_COUNT", (n) => (this.count += n));
}
increase(n) {
bus.dispatch("INCREASE_COUNT", n);
}
}

这时我们关注到一个问题,我们其实是想在每一个模块中,拿到最新的 count,然而我们事实上却在每一个模块中写入了递增 count 的逻辑代码 n => this.count += n。这是为什么?因为我们的 count 仍然是每个模块自己维护一个副本,所有对它进行的操作仍需在每个模块内部完成。而很明显的,INCREASE_COUNT 的意图是明确的,并不需要在各模块中都去实现,所以我们需要把这个逻辑提取出来,以减少重复。但一旦要把对 count 的操作逻辑提取出来,我们就需要在 event bus 中维护一份 count 的状态,所以需要对 event bus 进行一次改进,改进点如下:在 bus 中维护 count 的状态在 bus 中维护对状态进行操作的逻辑每次状态变更,都执行所有 listeners(因为不确定哪些 event 会修改状态)增加接口读取最新状态// event bus(功能不完整)

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
const initialState = {
count: 0,
};

export const bus = (() => {
const state = initialState;
const listeners = new Set();
function updateState(eventName, ...params) {
switch (eventName) {
case "INCREASE_COUNT": {
state.count += params[0];
break;
}
default:
}
}
return {
getState() {
return state;
},
dispatch(eventName, ...params) {
updateState(eventName, ...params);
for (const listener of listeners) {
listener();
}
},
subscribe(listener) {
listeners.add(listener);
},
};
})();

故事讲到这里时,我们惊讶地发现,这已经不是一个 event bus 了,分明就是一个低配版 redux。再看看基于这个低配版 redux,我们如何改造 A 和 B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { bus } from "./event-bus";

class A {
constructor() {
bus.subscribe(() => {
const { count } = bus.getState();
// do something with the latest count
});
}
increase(n) {
bus.dispatch("INCREASE_COUNT", n);
}
}

class B {
constructor() {
bus.subscribe(() => {
const { count } = bus.getState();
// do something with the latest count
});
}
}

— 分割线 —上面低配版的 redux,已经可以解决大部分状态管理的需求了。随着业务规模和业务特性的差异,我们对状态管理器还会有一些周边特性的需求,例如管理异步流、状态回溯、状态持久等,这些东西都是可以在这个低配版的 redux 上进行扩展改造的。总结这篇文章用一个极其简单的案例,逐步演进地推导出为何我们在写应用时,需要状态管理器,以及推导出了一个低配版 redux。只有搞清楚了状态管理器的来龙去脉,才能在市面上众多的方案中,寻找到最适合自己的一个,抑或是你根本不需要别人的方案,自己就可以来一套,代码量并不大,还可以完全针对你的业务特性进行定制。

dva

先放个屁股在这里

优先看这个一文彻底搞懂 DvaJS 原理,里面有一句话我很喜欢:

对于绝大多数不是特别复杂的场景来说,目前可以被 Hooks 取代

Dva.js 入门级教学文档

umi-useModel

我愿意把 useModel 成为全局状态管理的最终解决方案。