useSyncExternalStore
是 React 18 中提供的自定义挂钩,可让您订阅外部存储并在外部存储更新时更新您的 React 组件。
它对于订阅不是建立在 React 状态管理之上的外部存储特别有用。
useSyncExternalStore
API
您应该在组件的顶层调用useSyncExternalStore
方法
import { useSyncExternalStore } from 'react';
import { myStore } from "./mystore.js";
function MyComponent() {
const data = useSyncExternalStore(myStore.subscribe, myStore.getSnapshot);
// Rest of the component ...
}
该useSyncExternalStore
方法接受两个参数:
subscribe
- subscribe 方法应该订阅商店更新,并且应该返回一个函数来取消订阅商店更新。我们将通过下面的示例了解如何创建与useSyncExternalStore
.getSnapshot
- 方法将从存储返回数据的快照。
useSyncExternalStore
基本示例使用
让我们构建一个非常基本的商店来了解useSyncExternalStore
API。
我们的商店将仅存储一个计数,并提供一种递增和递减计数的方法。
let count = 0; // Variable to store count
let subscribers = new Set(); // Set to store callback functions
const countStore = {
read() {
// Method to get the count, this is basically getSnapshot method.
return count;
},
// Subscribe method adds the "callback" to the "subscribers" set, and
// return a method to unsubscribe from the store.
subscribe(callback) {
subscribers.add(callback);
return () => subscribers.delete(callback);
},
// Method to increment the count
increment() {
count++;
subscribers.forEach((callback) => callback());
},
decrement() {
count--;
subscribers.forEach((callback) => callback());
},
};
export default countStore;
count
用于存储我们的计数器,数组用于subscribers
存储订阅者方法的列表。
该read()
方法是getSnapshot()
获取存储快照的方法。
该subscribe(callback)
方法用于订阅商店,并且采用useSyncExternalStore
.
我们将该callback
方法存储在一个 Set 中,每次更新计数时,我们都会迭代所有方法callback
并调用它们。
一旦被callback
调用,它将useSyncExternalStore
调用该read()
方法从存储中获取值。
现在,让我们构建组件来使用商店,我们将更新App.js
组件以使用新创建的商店。
import "./App.css";
import countStore from "./store";
import { useSyncExternalStore } from "react";
function App() {
const count = useSyncExternalStore(countStore.subscribe, countStore.read);
return (
<div className="App">
count: {count}
<div>
<button onClick={countStore.increment}>Increment</button>
<button onClick={countStore.decrement}>Decrement</button>
</div>
</div>
);
}
export default App;
使用useSyncExternalStore
构建一个Todo应用
让我们创建一个 Todo 应用程序,首先,我们将创建一个store.js
用于存储待办事项的文件:
let todos = [];
let subscribers = new Set();
const store = {
getTodos() {
// Method to get the todos array.
return todos;
},
// Subscribe method adds the "callback" to the "subscribers" set, and
// return a method to unsubscribe from the store.
subscribe(callback) {
subscribers.add(callback);
return () => subscribers.delete(callback);
},
addTodo(text) {
todos = [
...todos,
{
id: new Date().getTime(),
text: text,
completed: false,
},
];
subscribers.forEach((callback) => {
callback();
});
},
toggleTodo(id) {
todos = todos.map((todo) => {
return todo.id === id ? { ...todo, completed: !todo.completed } : todo;
});
subscribers.forEach((callback) => callback());
},
};
export default store;
在我们的store.js
文件中,我们创建了一个名为 as 的数组todos来存储待办事项列表。
接下来与之前的示例类似,我们创建了一个名为 as 的变量subscribers
,其中包含已订阅商店的回调函数数组。
每当存储的值更新时,我们就必须调用这些函数。
在该addTodo
方法中,您可能已经注意到,我们没有使用push方法将待办事项添加到数组中todos
,因为通过这样做,React 将不会检测到更改并重新加载组件,因为 React 中的不变性。
因此,您应该记住不要todo
就地更新数组,而是在添加待办事项时创建一个新数组。
我们在方法中做同样的事情toggleTodo
,而不是在方法中按索引更新待办事项,而是使用包含更新值的方法toggleTodo
创建一个新todos数组。map
接下来,我们将创建一个TodoList.js
文件来保存我们的TodoList
组件,该TodoList组件将使用useSyncExternalStore
订阅存储并在添加新的 Todo 时更新其 UI。
import { useSyncExternalStore } from "react";
import store from "./store";
function TodoList() {
const todos = useSyncExternalStore(store.subscribe, store.getTodos);
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<label>
<input
type="checkbox"
value={todo.completed}
onClick={() => store.toggleTodo(todo.id)}
/>
{todo.completed ? <s>{todo.text}</s> : todo.text}
</label>
</li>
))}
</ul>
);
}
export default TodoList;
在我们的组件中,我们使用钩子从商店中TodoList
获取列表。todosuseSyncExternalStore
toggleTodo
当检查待办事项时,我们也会调用该方法,当我们运行此代码时,您将看到 UI 将被更新。
接下来,我们将创建一个组件,以添加新的 Todo,这是我们组件AddTodoForm
的代码:AddTodoForm
import store from "./store";
import { useState } from "react";
function AddTodoForm() {
const [text, setText] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
store.addTodo(text);
setText("");
};
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">Add Todo</button>
</form>
);
}
export default AddTodoForm;
该AddTodoForm
组件也非常基本,这里我们导入我们的商店,并使用useStatereact
中的方法。
我们创建了一个名为 as 的状态变量,text它将存储待办事项的文本。
接下来,我们创建了两个事件监听器,我们在标签上添加了一个onChange事件监听器<input />
,当输入标签的值发生变化时,我们将使用text在输入标签上键入的值更新名为 as 的状态变量。
onSubmit
我们添加到表单的第二个事件侦听器是在按下“添加待办事项”按钮提交表单时添加的事件侦听器。
在onSubmit
事件监听器中我们正在调用handleSubmit
方法,这个方法,这个方法会调用addTodo
我们Store
的方法。
当从此组件添加待办事项时,由于我们useSyncExternalStore
在组件中使用钩子TodoList
,TodoList
该组件也会自动更新以显示新添加的待办事项。
现在,让我们最终构建我们的App.js
组件,并导入我们的AddTodoForm
和TodoList组件:
import React, { useState } from "react";
import AddTodoForm from "./AddTodoForm";
import TodoList from "./TodoList";
function App() {
return (
<div>
<h1>Todo App</h1>
<AddTodoForm />
<TodoList />
</div>
);
}
export default App;
使用自定义钩子改进代码
我们可以进一步改进我们的代码,但在自定义挂钩中提取调用脚趾useSyncExternalStore
,然后在我们的组件中简单地使用自定义挂钩。
我们将更新我们的store.js
文件以包含我们的自定义挂钩的代码,称为useTodo
import { useSyncExternalStore } from "react";
// Custom Hook useTodo
export function useTodo() {
const todos = useSyncExternalStore(store.subscribe, store.getTodos);
return todos;
}
然后我们将更新我们的TodoList.js
文件以包含我们的自定义useTodo
挂钩:
import store, { useTodo } from "./store";
function TodoList() {
const todos = useTodo();
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<label>
<input
type="checkbox"
value={todo.completed}
onClick={() => store.toggleTodo(todo.id)}
/>
{todo.completed ? <s>{todo.text}</s> : todo.text}
</label>
</li>
))}
</ul>
);
}
export default TodoList;
什么时候使用useSyncExternalStore
?
建议您使用 React 内置的状态管理钩子(如useState
和 )useReducer
来管理状态。
但有一些场景是useSyncExternalStore
有意义的:
将 React 与现有的非 React 代码库集成
如果您有一个使用某种类型的外部存储的非 React 代码库,并且您希望将 React 应用程序与现有存储集成,那么在这种情况下,您可以围绕存储构建一个与 API 一致的包装器以无缝useSyncExternalStore
集成带有 React 应用程序的商店。
订阅浏览器 API
您可以使用它来订阅浏览器 API,例如 Web 推送通知或属性navigator.onLine
。
React 官方文档有一个很好的例子,解释了如何使用hooknavigator.onLine
属性useSyncExternalStore
。
结论
在这篇博文中,我们学习了如何useSyncExternalStore
在代码中使用 React 与外部存储同步。
我们应该尽可能在我们的代码中使用useStateand
useReducer
hooks,但在少数情况下不可能使用useState
anduseReducer
我们可以使用 the useSyncExternalStore
将我们的 React 组件与外部存储同步。