简化 React 状态管理:使用 useSyncExternalStore 的实用指南

194 阅读4分钟

题外话

不知道有多少人和我一样,在炎炎夏日走在路上口渴难耐,在迫切的想要喝上一口冰凉饮料的时候,总会站在冰柜门前犹豫,今天究竟该宠幸哪一瓶呢?是曾经最喜欢如今涨价到三块五的屌丝冰红茶,还是肥宅最爱的快乐水,亦或是品味象征的各种茶.....

前言

在进行一个react项目的编写时,我们可以通过许多方法来进行组件的状态管理。 而各种各样的状态管理方案看的人眼花缭乱,Redux、Mobx、Zustand、Content Api....

众多方案让人难以选择,各有各的好处。但如果你一开始的目的就是为了简单的解解渴,只想要一个简单方便的状态管理方法,那么我相信useSyncExternalStore一定能够完美的解决你的烦恼。

是什么

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

useSyncExternalStore是一个官方提供的hook用于状态管理。

useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

  • subscribe:一个函数,接收一个单独的 callback 参数并把它订阅到 store 上。当 store 发生改变,它应当调用被提供的 callback。这会导致组件重新渲染。subscribe 函数会返回清除订阅的函数。
  • getSnapshot:一个函数,返回组件需要的 store 中的数据快照。在 store 不变的情况下,重复调用 getSnapshot 必须返回同一个值。如果 store 改变,并且返回值也不同了(用 Object.is 比较),React 就会重新渲染组件。
  • 可选 getServerSnapshot:一个函数,返回 store 中数据的初始快照。它只会在服务端渲染时,以及在客户端进行服务端渲染内容的 hydration 时被用到。快照在服务端与客户端之间必须相同,它通常是从服务端序列化并传到客户端的。如果你忽略此参数,在服务端渲染这个组件会抛出一个错误。

以上是官方文档中对于useSyncExternalStore的介绍,是不是感觉莫名奇妙的,作者在最开始也是感觉完全看不懂,但没关系,跟着用一用就知道了。

App.js

import { todosStore } from './todoStore.js';

export default function TodosApp() {
  const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
  return (
    <>
      <button onClick={() => todosStore.addTodo()}>Add todo</button>
      <hr />
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </>
  );
}

todoStore.js

let nextId = 0;
let todos = [{ id: nextId++, text: 'Todo #1' }];
let listeners = [];

export const todosStore = {
  addTodo() {
    todos = [...todos, { id: nextId++, text: 'Todo #' + nextId }]
    emitChange();
  },
  subscribe(listener) {
    listeners = [...listeners, listener];
    return () => {
      listeners = listeners.filter(l => l !== listener);
    };
  },
  getSnapshot() {
    return todos;
  }
};

function emitChange() {
  for (let listener of listeners) {
    listener();
  }
}

官方的例子实现了一个简单的状态管理。我们可以看到在这个例子中最重要的两个函数是emitChange()和subscribe(),它们分别用于管理和订阅状态变化。

  • emitChange() :这个函数的主要作用是通知所有的订阅者状态发生了变化。它通常会被调用在状态发生变化的时候,比如说在更新数据之后。emitChange() 会触发一系列的回调函数,这些回调函数是由 subscribe() 函数注册的,这样订阅者就可以得到最新的状态并进行相应的更新。

  • subscribe() :这个函数用于注册一个回调函数,回调函数会在状态变化时被调用。通过 subscribe(),你可以将一个组件或其他逻辑与状态管理系统进行绑定,从而确保每当状态变化时,该组件或逻辑可以得到通知并作出响应。

简单来说,emitChange() 确保了状态更新能够被传播,而 subscribe() 确保了对这些更新的及时响应。

试试看

上面我们已经知道了基于useSyncExternalStore封装store的核心概念,那我们可以自己动手试试了。

myStore.js

let num = 0;
const listeners = new Set();

export const myStore = {
  addNum() {
    num++;
    emitChange();
  },
  subscribe(listener) {
    listeners.add(listener);
    return () => {
      listeners.delete(listener);
    };
  },
  getNum() {
    return num;
  },
};

function emitChange() {
  listeners.forEach((listener) => listener());
}

home.js

export default function Home() {
  const type = useSyncExternalStore(myStore.subscribe, myStore.getNum);
  return (
    <div>
      <h1>{type}</h1>
      <button onClick={() => myStore.changeType("可口可乐")}>可口可乐</button>
      <hr />
      <button onClick={() => myStore.changeType("洁厕灵")}>百事可乐</button>
    </div>
  );
}

我们将官方的listeners转为了set结构,同样实现了一个store的封装。

总结

使用 useSyncExternalStore,我们可以很简单地实现组件的状态管理,尤其是在需求相对简单的场景下。这种方法不仅减少了复杂性,也让我们可以专注于实际的业务逻辑,而不是过多关注状态管理的细节。

适用场景

useSyncExternalStore 最适合以下几种情况:

  1. 简单的应用状态管理:当你的应用状态比较简单且不需要复杂的逻辑时,useSyncExternalStore 是一个很好的选择。
  2. 小型应用或小模块:在小型应用或单个模块中,你可能不需要引入复杂的状态管理库,这时 useSyncExternalStore 提供了一个轻量级的解决方案。

最后

希望这篇文章能够帮助你更好地理解 useSyncExternalStore 的使用方法及其适用场景。如果你正在面临状态管理的选择,不妨试试这个简单而强大的 Hook,也许它正是你所需要的解决方案。