React19前瞻
React19前瞻
近日,React 团队成员在社交平台表示,团队正在开发 React v19 版本,并且没有计划发布 v18.3 版本。React 团队已经确定,下一个版本将是大版本号,即版本号会是 19.0.0。
现在,让我们根据 React 团队最新发布的消息,来抢先学习一下 v19 版本可能正式发布的新特性。
-
React Compiler 自动记忆化编译器
React 的核心思想是开发人员将其 UI 定义为当前状态的函数。可以使用普通的 JavaScript 值,如数字、字符串、数组、对象,并使用标准的 JavaScript 语法,如 if/else、for 等,来描述组件逻辑。其思维模型是,当应用程序状态发生更改时,React 将重新渲染。
问题在于,React 有时会过于 reactive:它可能会重新渲染太多次。例如,在 JavaScript 中,我们没有简单的方法来比较两个对象或数组是否相等(具有相同的键和值),因此在每次渲染时创建一个新对象或数组都可能会导致 React 执行更多的工作。
在使用新编译器以前,我们使用
useMemo
、useCallback
和memo
来手动缓存状态,以减少不必要的重新渲染,这种实现方式虽然可行,但 React 团队认为这并不是他们认为理想的方式,他们一直寻找让 React 在状态变化时自动且只重新渲染必要部分的方案。React团队表示,新的编译器,目前已经在 instagram 的生产环境中应用,React 团队计划在 Meta 的更多平台中应用,并在后续分享更多细节,相信这对开发者来说是又一次开发范式的改变。
-
新的Hooks
React 19 预计将推出 4 个全新 Hooks,这些 Hooks 主要关注 React 中的两个痛点:数据获取和表单。 这些 Hooks 目前在 React 预览版本中作为实验性 API 提供,预计会成为 React 19 的一部分,但是最终发布之前,API 可能会有所变化。
新的 Hooks 包括:
- use
- useOptimistic
- useFormState
- useFormStatus
-
use
use
是一个实验性React Hook
,它可以让读取类似于Promise
或context
的资源的值。官方文档:https://zh-hans.react.dev/reference/react/use
use
是一个 React Hook,它可以让你读取类似于 Promise 或 context 的资源的值。const value = use(resource);
use(Promise)
在组件中调用 use 以读取类似于 Promise 或 context 的资源的值。 可以将一个 promise 传递给它,React 将会在该 promise 解决之前进行 “挂起” 。它的基本语法如下:
import { use } from 'react'; function MessageComponent({ messagePromise }) { const message = use(messagePromise); const theme = use(ThemeContext); // ...
挂起:挂起:出自React中文文档,原英文文档中词为 ”suspends“ ”pending“,直译为“待定”,“暂停”。“挂起”概念在Next.js中被广泛使用,Next.js是一个服务器端渲染(SSR)框架,它需要在服务器端等待数据加载完成,然后再将页面渲染到客户端。挂起是指组件在等待某些操作完成时的状态。这些操作可能包括数据加载、网络请求、异步计算等。
-
Suspense (v16.6)
官方文档:https://zh-hans.react.dev/reference/react/Suspense
<Suspense>
中译名“悬念”,允许在子组件完成加载前展示后备方案。fallback
:备用UI,用于被挂起时渲染。children
:真正的 UI 渲染内容,当children
中的组件数据被挂起时,渲染fallback
。<Suspense fallback={<Loading />}> <SomeComponent /> </Suspense>
下面来看一个简单的例子:
import * as React from 'react'; import { useState, use, Suspense } from 'react'; import { faker } from '@faker-js/faker'; const usePormise = () => { const [newsPromise, setNewsPromise] = useState(() => fetchNews()); const handleUpdate = () => { fetchNews().then((news) => { setNewsPromise(Promise.resolve(news)); }); }; return ( <> <h3> 新闻列表 <button onClick={handleUpdate}>刷新</button> </h3> <NewsContainer newsPromise={newsPromise} /> </> ); }; let news = [...new Array(2)].map(() => faker.lorem.sentence()); const fetchNews = () => new Promise((resolve) => // 使用 setTimeout 模拟数据获取 setTimeout(() => { // 每次刷新时添加一个标题 news.unshift(faker.lorem.sentence()); resolve(news); }, 1000) ); const NewsContainer = ({ newsPromise }) => ( <Suspense fallback={<p>请求中...</p>}> <News newsPromise={newsPromise} /> </Suspense> ); const News = ({ newsPromise }) => { const news = use(newsPromise); return ( <ul> {news.map((title, index) => ( <li key={index}>{title}</li> ))} </ul> ); }; export default usePormise;
在上面的例子中,每次刷新时,都会先显示“请求中…”,请求到数据后进行展示。
官方文档中,关于 有一个警告:
目前尚不支持在不使用固定框架的情况下进行启用
Suspense
的数据获取。实现支持Suspense
数据源的要求是不稳定的,也没有文档。React
将在未来的版本中发布官方API
,用于与Suspense
集成数据源。在新版本中,use 可能就是用于与 Suspense 集成数据源的官方 API。
这个全新的
use hook
与其他Hooks
不同,它可以在循环和条件语句中像if
一样被调用。use(Context)
use(Context)
可以用于读取React Context
。它与useContext
完全相同,只是可以在循环和条件语句(如if
)中调用。import { use } from 'react'; function HorizontalRule({ show }) { if (show) { const theme = use(ThemeContext); return <hr className={theme} />; } return false; }
这将简化某些场景下的组件层级结构,因为在循环或条件语句中读取
context
,之前唯一的方法就是将组件一分为二。 -
Form Actions
官方文档:https://zh-hans.react.dev/reference/react-dom/components/form
React扩展了内置浏览器组件
<form>
的属性action
,这个新功能使您能够将函数传递给<form>
的 action 属性。当表单提交时,React 会调用这个函数。action
: URL 或函数。当 URL 传递到action
表单时,其行为将类似于 HTML 表单组件。当一个函数被传递给action
该函数时,该函数将处理表单提交。传递给的函数action
可以是异步的,并且将使用包含所提交表单的 formData 的单个参数来调用。<form action={search}> <input name="query" /> <button type="submit">Search</button> </form>
例如在 React 19 中,可以像这样编写一个表单:
import { useState } from "react"; const AddToCartForm = ({ id, title, addToCart }) => { const formAction = async (formData) => { try { await addToCart(formData, title); } catch (e) { } }; return ( <form action={formAction}> <h2>{title}</h2> <input type="hidden" name="itemID" value={id} /> <button type="submit">加入购物车</button> </form> ); }; export const Cart = ({ cart }) => { if (cart.length === 0) { return null; } return ( <> 购物车内容: <ul> {cart.map((item, index) => ( <li key={index}>{item.title}</li> ))} </ul> <hr /> </> ); }; export const FormActions = () => { const [cart, setCart] = useState([]); const addToCart = async (formData, title) => { const id = String(formData.get("itemID")); // 模拟一个 AJAX 请求 await new Promise((resolve) => setTimeout(resolve, 100)); setCart((cart) => [...cart, { id, title }]); return { id }; }; return ( <> <Cart cart={cart}></Cart> <AddToCartForm id="2" title="JavaScript权威指南" addToCart={addToCart} /> <AddToCartForm id="3" title="JavaScript高级程序设计" addToCart={addToCart} /> </> ); };
action 函数可以同步或异步操作。使用 action 时,React 将为开发者管理数据提交的生命周期,我们可以通过
useFormStatus
和useFormState
这两个 hook 来访问表单操作的当前状态和响应。下面就来看看新的Hook:useFormState
、useFormStatus
。useFormState
useFormState 是一个可以根据某个表单动作的结果更新 state 的 Hook。
官方文档:https://zh-hans.react.dev/reference/react-dom/hooks/useFormState
const [state, formAction] = useFormState(fn, initialState);
参数:
fn
:当按钮被按下或者表单被提交时触发的函数。当函数被调用时,该函数会接收到表单的上一个 state(初始值为传入的initialState
参数,否则为上一次执行完该函数的结果)作为函数的第一个参数,余下参数为普通表单动作接到的参数。
initialState
:state 的初始值。任何可序列化的值都可接收。当 action 被调用一次后该参数会被忽略。
返回值:
useFormState
返回一个包含两个值的数组:- 当前的 state。第一次渲染期间,该值为传入的
initialState
参数值。在 action 被调用后该值会变为 action 的返回值。
- 一个新的 action 函数用于在你的
form
组件的action
参数或表单中任意一个button
组件的formAction
参数中传递。
例如,这可以用来显示由表单操作返回的确认消息或错误消息:
import { useFormState } from "react-dom"; export async function addToCart(prevState, queryData) { console.log('prevState', prevState); const itemID = queryData.get('itemID'); if (itemID === "1") { return "已加入购物车"; } else { return "无法加入购物车:商品已售罄"; } } function AddToCartForm({itemID, itemTitle}) { const [message, formAction] = useFormState(addToCart, null); return ( <form action={formAction}> <h2>{itemTitle}</h2> <input type="hidden" name="itemID" value={itemID} /> <button type="submit">加入购物车</button> {message} </form> ); } export default function UseFormState() { return ( <> <AddToCartForm itemID="1" itemTitle="JavaScript:权威指南" /> <AddToCartForm itemID="2" itemTitle="JavaScript:优点荟萃" /> </> ) }
表单被提交后,传入的 action 函数会被执行。返回值将会作为该表单的新的 当前 state。
与直接通过表单动作调用的函数不同,传入
useFormState
的函数被调用时,会多传入一个代表 state 的上一个值或初始值的参数作为该函数的第一个参数。useFormStatus
useFormStatus
是一个提供上次表单提交状态信息的 Hook。官方文档:https://zh-hans.react.dev/reference/react-dom/hooks/useFormStatus
const { pending, data, method, action } = useFormStatus();
它不接收任何参数,会返回一个包含以下属性的
status
对象:pending
:布尔值。如果为true
,则表示父级<form>
正在等待提交;否则为 false。
data
:包含父级<form>
正在提交的数据;如果没有进行提交或没有父级<form>
,它将为null
。
method
:字符串,可以是 'get' 或 'post'。表示父级<form>
使用 GET 或 POST HTTP 方法 进行提交。默认情况下,<form>
将使用 GET 方法,并可以通过 method 属性指定。
action
:一个传递给父级<form>
的action
属性的函数引用。如果没有父级<form>
,则该属性为null
。如果在action
属性上提供了 URI 值,或者未指定action
属性,status.action
将为null
。
还是上面的购物车例子,利用
useFormStatus
将商品添加到购物车成功前,禁用添加按钮:import { useState } from "react"; import { Cart } from "./FormActions"; import { useFormStatus } from 'react-dom'; const AddToCartForm = ({ id, title, addToCart }) => { const formAction = async (formData) => { try { await addToCart(formData, title); } catch (e) {} }; return ( <form action={formAction}> <h2>{title}</h2> <input type="hidden" name="itemID" value={id} /> <SubmitButton /> </form> ); }; const SubmitButton = () => { const { pending } = useFormStatus(); return ( <button disabled={pending} type="submit"> 添加到购物车 </button> ); }; const UseFormStatus = () => { const [cart, setCart] = useState([]); const addToCart = async (formData, title) => { const id = String(formData.get("itemID")); // 模拟一个 AJAX 请求 await new Promise((resolve) => setTimeout(resolve, 2000)); setCart((cart) => [...cart, { id, title }]); return { id }; }; return ( <> <Cart cart={cart} /> <AddToCartForm id="2" title="JavaScript权威指南" addToCart={addToCart} /> <AddToCartForm id="3" title="JavaScript高级程序设计" addToCart={addToCart} /> </> ); }; export default UseFormStatus;
注意:
useFormStatus
Hook 必须从在<form>
内渲染的组件中调用,此外,它仅在父级表单使用action
属性时才有效。
useFormStatus
仅会返回父级<form>
的状态信息。它不会返回同一组件或子组件中渲染的任何<form>
的状态信息。
-
useOptimistic 乐观更新
乐观更新:是一种在前端开发中常用的处理异步操作反馈的策略。它基于一种“乐观”的假设:即假设无论我们向服务器发送什么请求,这些操作都将成功执行,因此在得到服务器响应之前,我们就提前在用户界面上渲染这些改变。
使用场景:点赞、收藏、评论、即时添加,编辑等。
useOptimistic(state, updateFn)
是一个新的 hook,允许你在异步操作(如网络请求)进行时,乐观地更新 UI。它通过接受当前状态和一个更新函数作为参数,返回一个在异步操作期间可能会有所不同的状态副本。你需要提供一个函数,这个函数接收当前状态和操作的输入,并返回在操作等待期间使用的乐观状态。官方文档:https://zh-hans.react.dev/reference/react/useOptimistic
该Hook 允许在进行提交动作的同时,能够乐观地更新用户界面,提升用户体验。其语法如下:
import { useOptimistic } from 'react'; function AppContainer() { const [optimisticState, addOptimistic] = useOptimistic( state, // 更新函数 (currentState, optimisticValue) => { // 合并并返回带有乐观值的新状态 }, ); }
参数
state
: 初始状态值,以及在没有操作进行时返回的值。
updateFn(currentState, optimisticValue)
: 一个函数,接收当前状态和传递给addOptimistic
的乐观值,返回结果乐观状态。updateFn
接收两个参数:currentState
和optimisticValue
。返回值将是currentState
和optimisticValue
的合并值。
返回值
optimisticState
: 产生的乐观状态。当有操作正在进行,它等于updateFn
返回的值,没有操作正在进行,它等于state
。
addOptimistic
: 这是在进行乐观更新时调用的调度函数。它接受一个参数optimisticValue
(任意类型),并调用带有state
和optimisticValue
的updateFn
。
更详细的用法如下:
import { useOptimistic, useState, useRef } from "react"; function List({ messages, sendMessage }) { const formRef = useRef(); async function formAction(formData) { addOptimisticMessage(formData.get("message")); formRef.current.reset(); await sendMessage(formData); } const [optimisticMessages, addOptimisticMessage] = useOptimistic( messages, (state, newMessage) => [ ...state, { text: newMessage, sending: true } ] ); return ( <> {optimisticMessages.map((message, index) => ( <div key={index}> {message.text} {!!message.sending && <small> (Sending...)</small>} </div> ))} <form action={formAction} ref={formRef}> <input type="text" name="message" placeholder="Hello!" /> <button type="submit">Send</button> </form> </> ); } function UseOptimisitc() { const [messages, setMessages] = useState([ { text: "Hello!", sending: false, key: 1 } ]); async function sendMessage(formData) { const sentMessage = await deliverMessage(formData.get("message")); setMessages((messages) => [...messages, { text: sentMessage }]); } async function deliverMessage(message) { await new Promise((res) => setTimeout(res, 1000)); return message; } return <List messages={messages} sendMessage={sendMessage} />; } export default UseOptimisitc;
在上面的例子中,当用户在表单中输入消息并点击“发送”按钮时,
useOptimistic
Hook 允许消息立即出现在列表中,并带有“发送中……”标签,即使消息实际上还没有发送到服务器。这种“乐观”方法给人一种快速和响应灵敏的印象。然后,表单在后台尝试真正发送消息。一旦服务器确认消息已收到,“发送中……”标签就会被移除。 -
更多更新
在此之上,React 团队的成员 Andrew Clark 也提出了2024年将会有如下的一些改变:
forwardRef
改为ref is a prop:这一改变将简化子组件内部元素或组件的引用方式,让
ref可以像一个普通的
prop` 一样传递React.lazy
变为RSC
,promise-as-child
:这一改变将提升代码分割和懒加载的能力<Context.Provider>
变为<Context>
:这一改变将使上下文提供者的使用变得更加简单
React 的目标十分宏大,他们期望能够打破前端和后端的界限,在保持自身客户端优势的同时,也能为社区的全栈框架提供基础设施。React 19 将会是继引入 hooks 之后的又一次里程碑式的版本,让我们一起期待。
转载自:https://juejin.cn/post/7344941254235914267