前言
整理一些 React18、19 的一些特性,方便查阅。
------------------>demo 地址 <---------------
在 React 19 中,use 是一个 API 有两种使用场景,分别是:获取 context 数据和异步数据获取。可以在任何 React 组件或 Hook 函数 中使用。它的设计目的是为了简化异步数据获取和条件读取 Context 的逻辑,同时支持与 Suspense 和 ErrorBoundary 错误边界集成。
注:可在循环和条件中使用。
1.use API 的使用场景
(1)异步数据获取
use 可以接收一个 Promise 作为参数,并在 Promise 未完成时挂起组件的渲染。当 Promise 完成后,use 会返回解析后的值。这种方式通常与 Suspense 配合使用,以展示加载状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { use, Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; const fetchMessage = (): Promise<string> => { return new Promise((resolve, reject) => { setTimeout( () => (Math.random() > 0.2 ? resolve("请求成功") : reject()), 1000 ); }); };
function MessageComponent({ messagePromise }: any) { const message = use<number>(messagePromise); return <p>{message}</p>; }
export default function Demo() { const messagePromise = fetchMessage(); return ( <ErrorBoundary fallback={<p>错误捕获</p>}> <Suspense fallback={<p>Loading...</p>}> <MessageComponent messagePromise={messagePromise} /> </Suspense> </ErrorBoundary> ); }
|
(2)条件读取 Context
1 2 3 4 5 6 7 8 9 10
| import { use } from "react"; import ThemeContext from "./ThemeContext";
function Button({ show }) { if (show) { const theme = use(ThemeContext); return <button style={{ backgroundColor: theme }}>Click me</button>; } return <></>; }
|
2.use API 的限制
(1) 必须在组件或 Hook 中调用
use 只能在 React 组件或 Hook 函数内部调用,不能在普通 JavaScript 函数或全局作用域中使用。如果尝试在非组件或 Hook 中调用 use,会抛出错误 17。
1 2 3
| function download() { const message = use(messagePromise); }
|
(2) 与错误边界集成
如果传递给 use 的 Promise 被拒绝(rejected),最近的错误边界会捕获该错误并显示后备 UI。因此,建议在使用 use 时包裹错误边界以处理可能的异常。
1 2 3 4 5 6 7 8
| import { ErrorBoundary } from "react-error-boundary"; function App() { return ( <ErrorBoundary fallback={<p>Something went wrong</p>}> <MessageComponent messagePromise={messagePromise} /> </ErrorBoundary> ); }
|
3.总结
它的主要作用是,能通过 Suspense 、ErrorBoundary 判断其 use 中 Promise 的状态,能在条件中使用它获取提前定义好的 Promise 和 Context 数据,并且能在异步数据获取中展示加载状态和错误捕获。
4.提示
- 不能在 try-catch 块中调用 use。可以选择将组件 包装在错误边界中,或者 使用 Promise.catch 方法提供替代值给 use。
- use 中的 Promise 须是上层组件传递的 promise 或者是外层实现定义好的 promise 并提前缓存下来的,原因是如果在当前组件中创建 promise 是无法缓存的。
- use 一般建议和 Suspense 、ErrorBoundary 结合使用,以展示加载状态和错误捕获,use 状态处于等待的时候会显示 Suspense 的 fallback,如果 Promise 被拒绝,会显示 ErrorBoundary 的 fallback。
useDeferredValue 是一个 React Hook,可以让你延迟更新 UI 的某些部分。useDeferredValue(value, initialValue?)
。
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { useDeferredValue, useEffect, useState } from "react";
export default function Demo() { const [value, setValue] = useState<string>(); const defvalue = useDeferredValue(value);
useEffect(() => { console.log("这是有state改变触发的Effect,首先触发"); console.log("value:", value); console.log("defvalue:", defvalue); }, [value]); useEffect(() => { console.log("这是有useDeferredValue改变触发的Effect,会延迟更新,空闲触发"); console.log("value:", value); console.log("defvalue:", defvalue); console.log( "他的触发一般是用于事件的延迟处理。比如用户输入过快导致的页面渲染卡顿,可以使用它来处理" ); }, [defvalue]); return ( <div> <input onChange={(e) => { setValue(e.target.value); }} /> <br /> value:{value} <br /> defvalue:{defvalue} </div> ); }
|
注意:
- 当更新发生在 startTransition 内部时,useDeferredValue 的值是最新值。
- 不要将渲染期间创建的对象给 useDeferredValue,没有意义,反而增加不必要的后台重新渲染。
- useDeferredValue 会触发该值对应的重新渲染(空闲时间触发),在此期间渲染的是旧值。在重新渲染期间如果有事件(输入事件等)进入,都会导致重新渲染中断,直到事件停止。
- useDeferredValue 值延迟渲染不会导致
<Suspense>
显示边界状态。
- useDeferredValue 引起的后台重新渲染在提交到屏幕之前不会触发 Effect。如果后台重新渲染被暂停,当前就不会触发 Effect,直到延迟更新完毕后才会触发。
使用
const [isPending, startTransition] = useTransition()
1 2 3 4 5 6 7 8 9 10 11
| function TabContainer() { const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState("about");
function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); } }
|
注意
useTransition
和useDeferredValue
功能类似都是实现延迟更新,他们在代码中是可以相互替代的。
react 中有一个静态函数 import {startTransition} from 'react'
可直接使用
Suspense 组件
使用
使用 lazy 函数触发 Suspense 边界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { lazy, Suspense } from "react";
export default function Demo() { const LazyComponent1 = lazy(() => import("./Component1.tsx")); return ( <> <a onClick={() => { window.location.reload(); }} > 请点击网页刷新 </a> <h5>使用lazy函数触发Suspense边界(加载状态)</h5> <Suspense fallback="加载中..."> <LazyComponent1 /> </Suspense> </> ); }
|
注意
只有启用了 Suspense 的数据源才会激活 Suspense 组件,它们包括:
支持 Suspense 的框架如 Relay 和 Next.js。(SSR 中的情况) next.js 数据更新案例
使用 lazy 懒加载组件代码。
使用 use 读取缓存的 Promise 值。
Suspense 无法 检测在 Effect 或事件处理程序中获取数据的情况。
在上面的 Albums 组件中,正确的数据加载方法取决于你使用的框架。如果你使用了支持 Suspense 的框架,你会在其数据获取文档中找到详细信息。
目前尚不支持在不使用固定框架的情况下进行启用 Suspense 的数据获取。实现支持 Suspense 数据源的要求是不稳定的,也没有文档。React 将在未来的版本中发布官方 API,用于与 Suspense 集成数据源。
useActionState
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import React, { useState } from "react"; import { Form, Button, message } from "antd"; import { addToCart } from "./actions.js";
function AddToCartForm({ itemID, itemTitle }) { const [isPending, setIsPending] = useState(false);
const onFinish = async (values) => { setIsPending(true); try { const response = await addToCart(values.itemID); message.success(response.message || "成功加入购物车"); } catch (error) { message.error(error.message || "加入购物车失败"); } finally { setIsPending(false); } };
return ( <Form name={`addToCartForm-${itemID}`} onFinish={onFinish} initialValues={{ itemID }} style={{ marginBottom: 16 }} > <h2>{itemTitle}</h2> <Form.Item name="itemID" hidden> <input type="hidden" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" loading={isPending}> 加入购物车 </Button> </Form.Item> </Form> ); }
export default function App() { return ( <> <AddToCartForm itemID="1" itemTitle="JavaScript:权威指南" /> <AddToCartForm itemID="2" itemTitle="JavaScript:优点荟萃" /> <AddToCartForm itemID="3" itemTitle="JavaScript:测试3" /> </> ); }
|
- action : 这是一个异步函数(通常是一个服务器请求),它会在表单提交时被调用。
- state : 这是 action 函数返回的结果,或者是初始状态(initialState)。
- formAction : 这是一个可以传递给
- isPending : 这是一个布尔值,表示当前是否有异步操作正在进行中。
useSyncExternalStore
用法
-
subscribe:一个函数,接收一个单独的 callback 参数并把它订阅到 store 上。当 store 发生改变时会调用提供的 callback,这将导致 React 重新调用 getSnapshot 并在需要的时候重新渲染组件。subscribe 函数会返回清除订阅的函数。
-
getSnapshot:一个函数,返回组件需要的 store 中的数据快照。在 store 不变的情况下,重复调用 getSnapshot 必须返回同一个值。如果 store 改变,并且返回值也不同了(用 Object.is 比较),React 就会重新渲染组件。
-
getServerSnapshot (可选): 一个函数,返回服务端渲染时的数据快照。它只会在服务端渲染(SSR)和客户端激活(hydration)时使用。如果未提供此参数,而组件在服务端渲染时被调用,React 会抛出错误。
getServerSnapshot 的作用
getServerSnapshot 的主要目的是确保服务端和客户端之间的状态一致性。在服务端渲染(SSR)时,React 需要获取初始状态以便生成 HTML。然后,在客户端激活时,React 会再次使用相同的初始状态来确保客户端和服务端的渲染结果一致。
如果没有提供 getServerSnapshot,React 在服务端渲染时无法获取初始状态,因此会抛出错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { useSyncExternalStore } from "react";
export default function ChatIndicator() { const isOnline = useSyncExternalStore(subscribe, getSnapshot); return <h1>{isOnline ? "✅ Online" : "❌ Disconnected"}</h1>; }
function getSnapshot() { return navigator.onLine; }
function subscribe(callback) { window.addEventListener("online", callback); window.addEventListener("offline", callback); return () => { window.removeEventListener("online", callback); window.removeEventListener("offline", callback); }; }
|
todoStore.js1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| let nextId = 0; let todos = [{ id: nextId++, text: "Todo #1" }]; let listeners = [];
export const todosStore = { addTodo() { debugger; todos = [...todos, { id: nextId++, text: "Todo #" + nextId }]; emitChange(); }, subscribe(listener) { debugger; listeners = [...listeners, listener]; return () => { listeners = listeners.filter((l) => l !== listener); }; }, getSnapshot() { debugger; return todos; }, };
function emitChange() { for (let listener of listeners) { listener(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { useSyncExternalStore } from "react"; import { todosStore } from "./todoStore.js";
export default function TodosApp() { const todos = useSyncExternalStore( todosStore.subscribe, todosStore.getSnapshot ); return ( <> <button onClick={() => todosStore.addTodo()}>Add todo</button> <hr /> <ul> {todos.map((todo) => ( <li key={todo.id}>{todo.text}</li> ))} </ul> </> ); }
|
不是特别重要的一些钩子
useId
useId 是一个 React18 的 Hook,可以生成传递给无障碍属性的唯一 ID。
1 2 3 4 5 6
| import { useId } from "react";
export default function Demo() { const id = useId(); return <>{id}</>; }
|
useDebugValue
useDebugValue 是一个 React18 的 Hook,可以用来在 React 开发者工具中显示自定义 Hook 的调试信息。
在组件中直接使用useDebugValue(value,?format)
,在使用 react component 调试的时候能看到给组件打的标签