ReactJS - useSyncExternalStore 钩子



React 版本 18 引入的新钩子是 'useSyncExternalStore' 钩子。这个钩子对于让我们订阅外部商店很有用。因此,在本教程中,我们将讨论这个钩子并查看示例以更好地理解它。

useSyncExternalStore 是一个 React 特性,它允许我们的组件订阅其他数据源,例如第三方库或浏览器 API,并随着数据的变化立即更新。

语法


const ss= useSyncExternalStore(subscribe, getSnapshot)

参数

  • subscribe - “subscribe”函数让我们的组件知道存储中的情况何时发生变化并触发重新渲染。
  • getSnapshot - “getSnapshot”函数提供了一种查看存储中内容的方法。并帮助 React 知道何时更新我们的组件。这两个函数协同工作,使我们的组件与外部数据保持同步。

返回值

useSyncExternalStore 返回当前存储快照,我们可以在渲染逻辑中使用该快照。

如何使用它?

若要从外部数据存储中读取值,请在组件的顶层调用 useSyncExternalStore。以下是我们如何使用 useSyncExternalStore 钩子的基本示例 -


import { useSyncExternalStore } from 'react';
import { notesStore } from './notesStore.js';

function NotesApp() {
	 	const notes = useSyncExternalStore(notesStore.subscribe, notesStore.getSnapshot);
	 	// ...
}

我们可以通过四种不同的方式使用这个钩子。首先是订阅第三方商店,其次是浏览器 API 订阅,第三是将逻辑转移到自定义 Hook,最后是添加服务器渲染支持。因此,使用这些示例,我们将一一检查这些 -

例子

订阅第三方商店

我们的大多数 React 组件都借助属性(props)、内部状态或上下文来获取数据。但是组件可能需要来自 React 之外的来源的数据,这些数据可能会随着时间的推移而变化。这些来源可以包括 -

  • 第三方状态管理库是由其他开发人员创建的库,用于帮助管理我们的组件所需的数据。他们将此信息保留在我们的组件之外,并可以根据需要对其进行修改。
  • 浏览器 API 是 Web 浏览器提供的方法,使我们的组件能够获取信息。其中一些数据可能会发生变化,如果发生这种情况,浏览器将通知我们的组件。

因此,我们将创建一个小型应用程序,其中我们将有两个文件:userStore.js用于用户存储,UserProfile.js用于使用 useSyncExternalStore 的 React 组件,App.js使用 UserProfile 组件。

UserProfile 组件使用 useSyncExternalStore 钩子来显示来自外部 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';

// Changes in temp
let temp = 20;
let tempListeners = [];

function getTemperatureSnapshot() {
	 	return temp;
}

function subscribeToTemperature(callback) {
	 	tempListeners.push(callback);
	 	
	 	// Every 5 seconds, try retrieving the temp
	 	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 的组件,在此组件中,自定义钩子将模拟检查天气服务的状态。之后,App 组件将使用 useSyncExternalStore 钩子来显示服务状态和天气预报,每 10 秒更新一次。这代表了一个独特的用例,同时保持一个框架。

WeatherStatus.js


import { useSyncExternalStore } from "react";
export function useWeatherStatus() {
	 	const isServiceOnline = useSyncExternalStore(subscribe, getSnapshot);
	 	return isServiceOnline;
}
function getSnapshot() {
	 	// Check the status of a weather service
	 	return checkWeatherStatus();
}
function subscribe(callback) {
	 	// Subscribe to updates about the weather service status
	 	const interval = setInterval(() => {
	 	 	 callback(checkWeatherStatus());
	 	}, 10000);
	 	
	 	return () => {
	 	 	 clearInterval(interval);
	 	};
}
function checkWeatherStatus() {
	 	// Check the weather status from an external source
	 	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 应用程序时,需要了解两件主要事情 -

  • 我们软件的某些方面仅在网络浏览器中可用,而在服务器上不可用。因此,在服务器上构建第一个页面时,我们不能使用它们。
  • 服务器上的数据和客户端浏览器中的数据必须相同。这样可以确保在第一个服务器呈现的页面上显示的信息与页面在用户浏览器中完全加载时显示的信息匹配。保持数据的一致性对于有效的用户体验至关重要。

在此示例中,我们将创建一个名为 OnlineStatus 的组件,并在 App 组件中调用它。useOnlineStatus 钩子将通过使用可用的 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 认为,如果我们在重新渲染期间使用不同的“订阅”函数,我们就是在听新的东西,所以它会再次订阅。为了避免这种不必要的订阅,请在我们的组件外部定义“subscribe”方法。
  • 如果商店在计划更新期间发生变化,React 可以被迫停止并重新启动。这样做是为了确保屏幕上的所有内容都代表商店的最新版本,即使已经进行了快速更新。
  • 根据“useSyncExternalStore”提供的值停止渲染是不可取的。