ReactJS - useSyncExternalStore Hook
React 版本 18 引入的新Hooks(钩子)是"useSyncExternalStore"Hooks(钩子)。此Hooks(钩子)可用于让我们订阅外部存储。因此,在本教程中,我们将讨论此Hooks(钩子)并查看示例以更好地理解它。
useSyncExternalStore 是 React 的一项功能,它允许我们的组件订阅其他数据源,例如第三方库或浏览器 API,并在数据发生变化时立即更新。
语法
const ss= useSyncExternalStore(subscribe, getSnapshot)
参数
subscribe − "subscribe"函数让我们的组件知道存储中的内容何时发生变化并触发重新渲染。
getSnapshot − "getSnapshot"函数提供了一种查看存储中内容的方法。并帮助 React 知道何时更新我们的组件。这两个函数协同工作,使我们的组件与外部数据保持同步。
返回值
useSyncExternalStore 返回当前存储快照,我们可以在渲染逻辑中使用它。
如何使用它?
要从外部数据存储中读取值,请在组件的顶层调用 useSyncExternalStore。以下是我们如何使用 useSyncExternalStore Hooks(钩子)的基本示例 −
import { useSyncExternalStore } from 'react'; import { notesStore } from './notesStore.js'; function NotesApp() { const notes = useSyncExternalStore(notesStore.subscribe, notesStore.getSnapshot); // ... }
我们可以通过四种不同的方式使用此Hooks(钩子)。第一种是通过订阅第三方商店,第二种是通过浏览器 API 订阅,第三种是将逻辑转移到自定义Hooks(钩子),最后是添加服务器渲染支持。因此,我们将使用示例逐一检查这些 −
示例
订阅第三方商店
我们的大多数 React 组件都是在属性 (props)、内部状态或上下文的帮助下获取数据的。但是组件可能需要来自 React 外部来源的数据,这些数据会随时间而变化。这些来源可以包括 −
第三方状态管理库是由其他开发人员创建的库,用于协助管理我们的组件所需的数据。他们将这些信息保留在我们的组件之外,并可以根据需要对其进行修改。
浏览器 API 是由 Web 浏览器提供的方法,使我们的组件能够获取信息。其中一些数据可能会发生变化,如果发生这种情况,浏览器将通知我们的组件。
示例
因此,我们将创建一个小型应用程序,其中将有两个文件:用于用户存储的 userStore.js、用于使用 useSyncExternalStore 的 React 组件的 UserProfile.js 和用于使用 UserProfile 组件的 App.js。
UserProfile 组件使用 useSyncExternalStore Hooks(钩子)显示来自外部 userStore 的用户数据。当我们单击"更改用户"按钮时,存储中的用户数据会更新,并且组件会自动显示这些更改。
userStore.js
let user = { id: 1, name: "Alia" }; let userListeners = []; export const userStore = { updateUser(newUser) { user = newUser; emitUserChange(); }, subscribeUser(listener) { userListeners.push(listener); return () => { userListeners = userListeners.filter((l) => l !== listener); }; }, getUserSnapshot() { return user; } }; function emitUserChange() { for (let listener of userListeners) { listener(); } }
UserProfile.js
import React from "react"; import { useSyncExternalStore } from "react"; import { userStore } from "./userStore"; function UserProfile() { const user = useSyncExternalStore( userStore.subscribeUser, userStore.getUserSnapshot ); return ( <div> <h2>User Profile</h2> <p>Name: {user.name}</p> <button onClick={() => userStore.updateUser({ id: 1, name: "Shantanu" })}> Change User </button> </div> ); } export default UserProfile;
App.js
import React from "react"; import UserProfile from "./UserProfile"; function App() { return ( <div> <h1>React User Profile Example</h1> <UserProfile /> </div> ); } export default App;
输出

浏览器 API 订阅
有时我们需要 React 组件显示 Web 浏览器提供的随时间变化的信息。例如,我们想要显示我们的设备现在已连接到互联网。此信息由 Web 浏览器在名为 navigator.onLine 的属性的帮助下提供。
由于此值可能会在 React 不知情的情况下发生变化,因此我们需要使用 useSyncExternalStore 使我们的组件与此变化的数据保持同步。因此,我们的组件将始终显示互联网连接的准确状态。
示例
在此示例中,我们将在 App.js 文件中管理组件本身内的温度数据和变化。我们使用 useState 和 useEffect 来订阅温度变化,并在温度变化时每 5 秒更新一次组件。
import React, { useEffect, useState } from 'react'; // 温度变化 let temp = 20; let tempListeners = []; function getTemperatureSnapshot() { return temp; } function subscribeToTemperature(callback) { tempListeners.push(callback); // 每 5 秒尝试检索温度 const tempInterval = setInterval(() => { temp = getRandomTemperature(); emitTemperatureChange(); }, 5000); return () => { tempListeners = tempListeners.filter(l => l !== callback); clearInterval(tempInterval); }; } function emitTemperatureChange() { for (let listener of tempListeners) { listener(); } } function getRandomTemperature() { return Math.floor(Math.random() * 30) + 10; // Simulated temp range: 10°C to 40°C } export default function App() { const [currentTemperature, setCurrentTemperature] = useState(getTemperatureSnapshot); useEffect(() => { const unsubscribe = subscribeToTemperature(() => { setCurrentTemperature(getTemperatureSnapshot); }); return () => { unsubscribe(); }; }, []); return ( <div> <h1>React Temperature Indicator</h1> <h2>Current Temperature: {currentTemperature}°C</h2> </div> ); }
输出

将逻辑转移到自定义 Hook
在大多数情况下,我们不会在组件中明确写入 useSyncExternalStore。我们通常会从我们自己的自定义 Hook 中调用它。这允许我们从许多组件中使用相同的外部存储。
示例
在此示例中,我们将创建一个名为 WeatherStatus 的组件,在此组件中,自定义Hooks(钩子)将模拟检查天气服务的状态。之后,App 组件将使用 useSyncExternalStore Hooks(钩子)来显示服务状态和天气预报,每 10 秒更新一次。这代表了一个独特的用例,同时保持了框架的完整性。
WeatherStatus.js
import { useSyncExternalStore } from "react"; export function useWeatherStatus() { const isServiceOnline = useSyncExternalStore(subscribe, getSnapshot); return isServiceOnline; } function getSnapshot() { // 检查天气服务的状态 return checkWeatherStatus(); } function subscribe(callback) { // 订阅有关天气服务状态的更新 const interval = setInterval(() => { callback(checkWeatherStatus()); }, 10000); return () => { clearInterval(interval); }; } function checkWeatherStatus() { // 从外部来源检查天气状态 return Math.random() < 0.8; }
App.js
import React from "react"; import { useWeatherStatus } from "./WeatherStatus"; function ServiceStatus() { const isServiceOnline = useWeatherStatus(); return ( <div> <h1>Weather Service Status</h1> <p>{isServiceOnline ? "Service Online" : "Service Offline"}</p> </div> ); } function WeatherForecast() { const isServiceOnline = useWeatherStatus(); return ( <div> <h1>Weather Forecast</h1> <p>{isServiceOnline ? "Today: Sunny" : "Service Offline"}</p> </div> ); } export default function App() { return ( <div> <ServiceStatus /> <WeatherForecast /> </div> ); }
输出

添加服务器渲染支持
开发可在服务器上显示页面的 React 应用时,主要有两点需要了解 −
我们软件的某些方面仅在 Web 浏览器中可用,而不在服务器上可用。因此,我们在服务器上构建第一个页面时无法使用它们。
服务器上的数据和客户端浏览器中的数据必须相同。这可确保第一个服务器渲染页面上显示的信息与页面在用户浏览器中完全加载时显示的信息相匹配。保持数据的一致性对于有效的用户体验至关重要。
示例
在此示例中,我们将创建一个名为 OnlineStatus 的组件并在 App 组件中调用它。 useOnlineStatus Hooks(钩子)将利用可用的 getSnapshot 和 getServerSnapshot 方法来管理在线状态,以保持服务器呈现的 HTML 和客户端呈现的数据之间的完整性。 App 组件显示服务器的在线状态,并保持服务器和客户端之间的数据完整性。
OnlineStatus.js
import { useSyncExternalStore } from 'react'; export function useOnlineStatus() { const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); return isOnline; } function getSnapshot() { return window.navigator.onLine; } function getServerSnapshot() { return true; // Assume the server is always "Online" when generating HTML } function subscribe(callback) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; }
App.js
import React from "react"; import { useOnlineStatus } from "./OnlineStatus"; function OnlineStatusDisplay() { const isOnline = useOnlineStatus(); return ( <div> <h1>Online Status</h1> <p>{isOnline ? "Online" : "Offline"}</p> </div> ); } export default function App() { return ( <div> <OnlineStatusDisplay /> </div> ); }
输出

限制
"getSnapshot"方法应为我们提供存储中不变的可用内容的快照。如果存储包含可更改的数据,请确保每当数据发生变化时,我们都会向 React 提供该数据的最新、不变的快照。
如果我们在重新渲染期间使用不同的"订阅"函数,React 会认为我们正在监听新内容,因此它会再次订阅。为了避免这种不必要的订阅,请在组件之外定义"订阅"方法。
如果在计划的更新期间存储发生变化,React 可能会被迫停止并重新启动。这样做是为了确保屏幕上的所有内容都代表商店的最新版本,即使进行了快速更新也是如此。
不建议根据"useSyncExternalStore"提供的值停止渲染。