React 19.3 新特性总结
React 19.3 已发布到 npm,核心是把去年以实验 API 形式公布的 View Transitions 和 Fragment Refs 正式转正,同时新增了几个 DOM 与 RSC 相关的 API。下面按特性整理。
一、View Transitions(视图过渡动画,正式发布)
是什么
<ViewTransition> 是基于浏览器原生 View Transition API 封装的组件。把你想要加动画的元素包起来,当元素进入、退出、移动或尺寸变化时,React 自动播放过渡动画。
基本用法
import { ViewTransition } from 'react';
{isShowing && (
<ViewTransition>
<Component />
</ViewTransition>
)}
只要组件树发生变化(被挂载、卸载或样式改变),并且这次更新被标记为 Transition(即通过 startTransition、<Suspense> 揭示或 useDeferredValue 触发),就会播放动画。紧急更新不会触发动画,这是设计上的选择 —— 紧急更新要立刻反映到 UI 上。
四种动画类型
React 根据组件树变化方式自动决定播哪种:
- enter:
<ViewTransition>被添加 → 入场动画 - exit:
<ViewTransition>被移除 → 离场动画 - update: 子元素样式或内容变了 → 更新动画
- share: 同名的
<ViewTransition>在 A 处被移除、在 B 处被添加 → 跨位置共享动画
默认是平滑的交叉淡入淡出。要自定义动画,可以传 View Transition Class(CSS 里定义 ::view-transition-group(.xxx) 之类的选择器),或者用 Web Animations API 的事件钩子 onEnter / onExit / onShare / onUpdate。
目前
<ViewTransition>仅在 Web 端可用,原生移动端(React Native)还在跟进。
用 addTransitionType 区分"同一种更新的不同方向"
同一个状态变更可能有多种动画需求。典型场景:轮播图,前进到第 3 页要右进左出、后退到第 3 页要左进右出 —— 两次操作都是 setCurrentSlide(3),但动画方向相反。
解法:在 startTransition 里调用 addTransitionType 标记本次更新的"原因类型",然后在 JSX 上按类型配动画:
function nextSlide() {
startTransition(() => {
addTransitionType('next');
setCurrentSlide(c => c + 1);
});
}
function previousSlide() {
startTransition(() => {
addTransitionType('previous');
setCurrentSlide(c => c - 1);
});
}
<ViewTransition
enter={{
'next': 'from-right',
'previous': 'from-left',
}}
exit={{
'next': 'to-left',
'previous': 'to-right',
}}
>
<Page />
</ViewTransition>
Transition Type 也会作为浏览器 view-transition-type 加到元素上,方便在 CSS 里用 :active-view-transition-type(...) 限定作用域。
和 Suspense 结合:给 fallback、image、字体加动画
把 <Suspense> 边界也包进 <ViewTransition>,fallback 揭示时会有从占位过渡到真实内容的更新动画:
<ViewTransition>
<Suspense fallback={<Loading />}>
<Component />
</Suspense>
</ViewTransition>
进阶用法 —— 用 update 和 default 区分初次揭示与后续切换:
<ViewTransition update="auto" default="none">
<Suspense fallback={<Fallback />}>
<img src={imageSrc} />
</Suspense>
</ViewTransition>
default 控制首次加载行为(默认会动画),update 控制后续切换行为。cached 的图片没必要每次都动画 —— 用 default="none" 跳过首次。
Suspense 动画的几个 UX 原则:
- fallback 出现时不动画(要立即可见,否则用户以为卡住)
- fallback 切到真实内容时动画
- 没有挂起的子元素直接显示,不要动画
二、Fragment Refs(正式发布)
解决的痛点
需要操作组件渲染的 DOM 节点时(绑事件、监听可见性、移动焦点),通常用 ref。但有两种情况用 ref 很难受:
- 组件渲染的是一组没有单一父元素的兄弟节点(Fragment 形式)
- 组件不把 ref 转发到内部元素,比如第三方库的组件
传统解法是套一层 <div> 持有 ref —— 但会污染布局结构,而且对不暴露 ref 转发机制的库无效。
解法:把 ref 直接传给 <Fragment>
import { Fragment, useRef, useEffect } from 'react';
function Component() {
const fragmentRef = useRef(null);
useEffect(() => {
const fragmentInstance = fragmentRef.current;
fragmentInstance.focus();
}, []);
return (
<Fragment ref={fragmentRef}>
{posts.map(post => (
<Heading key={post.id}>{post.title}</Heading>
))}
</Fragment>
);
}
ref.current 现在是 FragmentInstance 对象,暴露一组常用的 DOM 方法:
addEventListener/removeEventListener/dispatchEvent—— 给第一层子元素批量绑事件focus/focusLast—— 焦点控制measure—— 测量布局- 等等
FragmentInstance 操作的是"作为一组的子元素",不改变 DOM 结构,所以不会破坏现有样式和布局。
想自动包一层 ref 又不想污染结构,Fragment Refs 是首选。
三、react-dom 新增 browser
react-dom 导出新的 browser API,让 SSR 框架能拿到渲染层的全局 loader 数据(请求级缓存、cookie、请求头等)。这是 loaderData 类型的钩子。
import { use } from 'react';
import { browser } from 'react-dom';
function useBrowserQuery(query, options) {
if (options.initialData === undefined) {
use(browser());
}
return useQuery(query, options);
}
function ProductDetails({ productId, initialData }) {
const product = useBrowserQuery(
`/api/products/${productId}`,
{ initialData }
);
// ...
}
use(browser()) 的作用是告诉 React:“这里依赖渲染层的全局数据,请按请求级缓存处理”,避免跨请求的数据泄漏。具体协议由框架实现(Remix、Next.js 等)。
四、Trusted Types 支持
React DOM 现在支持 Trusted Types —— 浏览器层面的 XSS 防护机制。在启用 Trusted Types 的页面里,React 不再用字符串构造 DOM,而是通过 Trusted Types 策略创建,避免被 CSP 拦下。
应用开发者无需改动代码,升级到 19.3 自动生效。如果你的应用被 TrustedHTML 之类报错,升级即可。
五、<Context> 可在 Server Component 直接渲染
之前 Server Component 不能渲染 <Context.Provider> —— Provider 是客户端组件标记,服务端没法用。新版允许:
// user-context.js —— 客户端模块
'use client';
import { createContext } from 'react';
export const UserContext = createContext(null);
// server-component.js
import { UserContext } from './user-context';
export function UserProvider({ user, children }) {
return (
<UserContext value={user}>
{children}
</UserContext>
);
}
关键点:
- Provider 仍要
'use client'(因为 Context 本身需要客户端能力) - 但 Provider 组件本身可以在 Server Component 里被调用,把 user 数据传给一整棵客户端子树
- Server Component 端不再需要写一个包装层把 Provider 挪到 Client Component 里
特别适合"只在 server 端构造一次数据、整个客户端树共享"的场景(比如用户信息、主题、地区设置等只读 Context)。
六、Changelog
完整更新列表与重要变更见官方仓库 CHANGELOG.md。其他比较重要的修复:
react: 把多个 Transition 渲染解耦,慢的 Transition 不再阻塞其他不相关的 Transition [#37290]react-dom: 严格模式在注水阶段双调用副作用,与客户端渲染根节点行为对齐 [#35961]react:use在条件分支里被错误调用时会给出警告 [#37104]
如果您觉得这篇文章有帮助,请点个赞吧~
评论
请登录后发表评论
去登录