原文
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
方法接受两个参数:
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 应用程序与现有存储集成,那么在这种情况下,您可以围绕存储构建一个与 API 一致的包装器以无缝useSyncExternalStore
集成带有 React 应用程序的商店。
您可以使用它来订阅浏览器 API,例如 Web 推送通知或属性navigator.onLine
。
React 官方文档有一个很好的例子,解释了如何使用 hooknavigator.onLine
属性useSyncExternalStore
。
结论
在这篇博文中,我们学习了如何useSyncExternalStore
在代码中使用 React 与外部存储同步。
我们应该尽可能在我们的代码中使用useStateand
useReducer
hooks,但在少数情况下不可能使用useState
anduseReducer
我们可以使用 the useSyncExternalStore
将我们的 React 组件与外部存储同步。
评论