首页 > 其他分享 >如何在 React 18 中使用 useSyncExternalStore

如何在 React 18 中使用 useSyncExternalStore

时间:2023-08-03 14:01:46浏览次数:40  
标签:useSyncExternalStore 18 React callback store todo todos

原文

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 组件与外部存储同步。

标签:useSyncExternalStore,18,React,callback,store,todo,todos
From: https://blog.51cto.com/u_16183891/6948735

相关文章

  • 算法-18-希尔排序
         ......
  • 【题解】Luogu[P5022] [NOIP2018 提高组] 旅行
    Link因为是道NOIP,那么我们不妨按照考场上的策略一点一点想。先看部分分,有一档有很明显的特征\(n=m-1\)这显然构成一棵树,对于一棵树,我们想把他按照题目的要求遍历完,一定是像dfs的遍历顺序一样,对于一个点,必然遍历完以它为根的子树,才能回到它的父亲节点,于是就有了一个很明显的贪......
  • 【HMS Core】【Push Kit】每天只能收到两条推送、状态码80100018
    【问题描述1】每天只能收到2条推送消息,其余的都无法收到【解决方案】1、请是否开通了消息自分类,因为现在是有咨询营销类消息限制的。没有使用自分类权益的话默认是资讯营销类消息。https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/message-restriction-d......
  • 18.vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间?
    18.vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间?1.vector越界访问下标std::vector是C++标准库中的一种动态数组,其大小可以根据需要进行调整。当你试图访问一个不存在的元素,即访问超出其当前大小范围的索引时,将会发生越界访问。在C++中,如果你使用operator[......
  • [刷题笔记] Luogu P1853 投资的最大效益
    ProblemSolution刚开始看这道题的时候不自主的想到了纪念品,但其实本题和纪念品还是有区别的。纪念品规定了每次只能买一个纪念品,而本题可以买无限个纪念品需要在原本的基础上买进卖出,钱有进有出,而本题时只有进,稳赚不赔。本题和纪念品不同的第一点决定了它时完全背包,纪念品......
  • LeetCode 热题 100 之 189. 轮转数组
    题目给定一个整数数组nums,将数组中的元素向右轮转k个位置,其中k是非负数。示例1:输入:nums=[1,2,3,4,5,6,7],k=3输出:[5,6,7,1,2,3,4]解释:向右轮转1步:[7,1,2,3,4,5,6]向右轮转2步:[6,7,1,2,3,4,5]向右轮转3步:[5,6,7,1,2,3,4]示例2:输入:nums=......
  • 【雕爷学编程】Arduino动手做(180)---Seeeduino Lotus开发板
    37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里准备逐一动手尝试系列实验,不管成功(程序走通)与否,都会记录下来—小小的进步或是搞......
  • React—01—基本学习
     React组件是返回标签的JavaScript函数: 哪个组件是通过改变state实现可响应的,或者哪个组件拥有这个state。然后我们需要确定吧这个state是放在这个组件,还是放在父组件(如何多个子组件都会受到相同state影响,那么需要放到父组件里统一管理)。  如何安装一个react项......
  • 国标GB28181国标平台LntonGBS(源码版)国标视频平台实现设备录像设置收留IP的具体操作方
    国标视频云服务LntonGBS支持设备/平台通过国标GB28181协议注册接入,并能实现视频的实时监控直播、录像、检索与回看、语音对讲、云存储、告警、平台级联等功能。平台部署简单、可拓展性强,支持将接入的视频流进行全终端、全平台分发,分发的视频流包括RTSP、RTMP、FLV、HLS、WebRTC等格......
  • 国标GB28181视频平台LntonGBS国标平台调用快照接口,未能正常返回快照图片的问题解决方
    LntonGBS国标视频云服务支持设备/平台通过国标GB28181协议注册接入,可实现视频的实时监控直播、录像、检索与回看、语音对讲、云存储、告警、平台级联等功能。LntonGBS平台便捷、丰富、灵活、可拓展的视频能力,已经使其成为当前安防市场的主流需求视频平台,并且已经在大量的项目中落地......