Jansiel Notes

React通过useSyncExternalStore实现一个简单的Redux

useSyncExternalStore

useSyncExternalStore 是一个让你订阅外部 store 的 React Hook。

1const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
2

我们在react中,实现数据共享,无非是三种方式

  • props传递或者useContext共享节点内的所有状态
  • 使用redux等状态管理库实现全局状态管理
  • 通过Event Bus去实现发布订阅

redux或者类似于zustand这种状态管理库他们是如何实现当状态更新而去重新渲染页面的呢?

在它们内部基本上都会维护两个状态,一个是currentState数据源,一个是listeners监听器数组。当我们订阅的时候,会去给监听器数组添加一个监听,当我们去修改数据的时候,再去循环执行监听器的函数,而这个函数就是我们需要想办法去重新render这个组件。 而使用了useSyncExternalStore这个hooks,就相当于为我们写好了这个监听器的函数。

代码实现

 1export function CreateStore(reducer: any) {
 2  let currentState = null;
 3  const listeners = [];
 4
 5  function getSnapshot() {
 6    return currentState;
 7  }
 8
 9  function dispatch(action) {
10    currentState = reducer(currentState, action);
11    listeners.forEach((listener) => listener());
12  }
13
14  function subscribe(listener) {
15    listeners.push(listener);
16  }
17
18  return {
19    getSnapshot,
20    dispatch,
21    subscribe,
22  };
23}
24
25export function useStore() {
26  const storeRef = useRef();
27
28  if (!storeRef.current) {
29    storeRef.current = CreateStore(countReducer);
30  }
31
32  return storeRef.current;
33}
34
35function countReducer(state = 0, action) {
36  console.log(state, action);
37  switch (action.type) {
38    case "ADD":
39      return state + 1;
40    case "MINUS":
41      return state - 1;
42    default:
43      return state;
44  }
45}
46

外部使用

 1import { Button } from "@nextui-org/react";
 2import { useStore } from "../../store/createStore";
 3import { useSyncExternalStore } from "react";
 4
 5function Index() {
 6  const store = useStore();
 7  const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
 8
 9  console.log(state);
10  return (
11    <div className="flex flex-col gap-y-10 items-center justify-center">
12      <Button onClick={() => store.dispatch({ type: "ADD" })}>Add</Button>
13      <Button onClick={() => store.dispatch({ type: "MINUS" })}>MINUS</Button>
14      {state}
15    </div>
16  );
17}
18
19export default Index;
20