一文掌握如何使用React 18 中 useSyncExternalStore

原文链接:https://deadsimplechat.com/blog/usesyncexternalstore-in-react/

在这里插入图片描述
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 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 useReducerhooks,但在少数情况下不可能使用useStateanduseReducer我们可以使用 the useSyncExternalStore将我们的 React 组件与外部存储同步。!](https://img-blog.csdnimg.cn/eb76f66808604ab59cd8530340f57874.png)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值