题外话
不知道有多少人和我一样,在炎炎夏日走在路上口渴难耐,在迫切的想要喝上一口冰凉饮料的时候,总会站在冰柜门前犹豫,今天究竟该宠幸哪一瓶呢?是曾经最喜欢如今涨价到三块五的屌丝冰红茶,还是肥宅最爱的快乐水,亦或是品味象征的各种茶.....
前言
在进行一个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 最适合以下几种情况:
- 简单的应用状态管理:当你的应用状态比较简单且不需要复杂的逻辑时,
useSyncExternalStore是一个很好的选择。 - 小型应用或小模块:在小型应用或单个模块中,你可能不需要引入复杂的状态管理库,这时
useSyncExternalStore提供了一个轻量级的解决方案。
最后
希望这篇文章能够帮助你更好地理解 useSyncExternalStore 的使用方法及其适用场景。如果你正在面临状态管理的选择,不妨试试这个简单而强大的 Hook,也许它正是你所需要的解决方案。