写点什么

如何在 React 18 中使用 useSyncExternalStore

  • 2023-08-03
    北京
  • 本文字数:4083 字

    阅读完需:约 13 分钟

如何在 React 18 中使用 useSyncExternalStore

原文


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在组件中使用钩子TodoListTodoList该组件也会自动更新以显示新添加的待办事项。


现在,让我们最终构建我们的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 useTodoexport 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 useReducerhooks,但在少数情况下不可能使用useStateanduseReducer我们可以使用 the useSyncExternalStore将我们的 React 组件与外部存储同步。

用户头像

前端技术创新 体验优化 分享经验 共同进步 2018-11-25 加入

通过技术的创新和优化,为用户创造更好的使用体验,并与更多的前端开发者分享我们的经验和成果。我们欢迎对前端开发感兴趣的朋友加入我们的团队,一同探讨技术,共同进步。

评论

发布
暂无评论
如何在 React 18 中使用 useSyncExternalStore_js_汽车之家客户端前端团队_InfoQ写作社区