React 19.3
2026年9月9日,由React 团队发布
🌐 September 9, 2026 by The React Team
React 19.3 现在可以在 npm 上获取了!
🌐 React 19.3 is now available on npm!
去年(链接),我们分享了 View Transitions 和 Fragment Refs 作为即将加入 React 的新实验性 API。我们很高兴地宣布,这两者现在在 React 19.3 中已经稳定了!
在这篇文章中,我们将讲解它们是如何工作的,同时还会介绍这个版本中的其他一些值得注意的变化。
🌐 In this post, we’ll go over how they work, and also cover some other notable changes in this release.
新的 React 功能
🌐 New React Features
视图过渡
🌐 View Transitions
新的 <ViewTransition> 组件让你可以在元素进入、退出、移动或调整大小时,使用浏览器的 视图过渡 API 进行动画。我们去年以实验性 API 的形式分享过它 链接,而在 19.3 版本中,它已经稳定并可以使用了。
🌐 The new <ViewTransition> component lets you animate elements as they enter, exit, move, or resize using the browser’s View Transition API. We shared it as an experimental API last year, and in 19.3 it’s stable and ready to use.
要给你界面的一部分添加动画,把它包在 <ViewTransition> 里:
🌐 To animate part of your UI, wrap it in <ViewTransition>:
import { ViewTransition } from 'react';
{isShowing && (
<ViewTransition>
<Component />
</ViewTransition>
)}现在,每当一个标记为 Transition 的更新改变子组件的样式,或者导致 ViewTransition 被挂载或卸载时,React 都会对该更新进行动画处理。
🌐 Now, whenever an update marked as a Transition changes the child component’s style, or causes the ViewTransition to be mounted or unmounted, React will animate that update.
React 会根据树的变化选择运行哪种动画:
🌐 React chooses which animation to run based on how the tree changed:
- 回车:已添加
<ViewTransition>。 - 退出:
<ViewTransition>已被移除。 - 更新:
<ViewTransition>的子元素会改变样式或内容。 - 分享:一个名为
<ViewTransition>的项目从一个地方移除,然后添加到另一个地方。
请注意,没有标记为“过渡”的更新不会触发动画,因为这些更新通常比较紧急,需要立即在 UI 中反映。位于 startTransition 内的状态更新、<Suspense> 的显示,或者来自 useDeferredValue 的更新,都会触发视图过渡动画。
🌐 Note that updates not marked as Transitions don’t trigger animations, as those are meant to be urgent and reflected immediately in the UI. State updates inside of startTransition, a <Suspense> reveal, or an update from useDeferredValue all cause a View Transition to animate.
这里有一个简单的进入/退出动画示例:
🌐 Here’s a simple example of an enter/exit animation:
import { ViewTransition, useState, startTransition } from 'react'; import { Video } from './Video'; import videos from './data'; export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }}> {showItem ? '➖' : '➕'} </button> {showItem && ( <ViewTransition> <Video video={videos[0]} /> </ViewTransition> )} </> ); }
默认情况下,<ViewTransition> 会以平滑的交叉淡入淡出动画呈现。你可以通过传入 View Transition Class 并在 CSS 中定义动画,来自定义每种动画,或者你也可以使用 Web Animations API 结合 event props (onEnter、onExit、onShare、onUpdate) 以命令式方式触发动画。
🌐 By default, <ViewTransition> animates with a smooth cross-fade. You can customize each kind of animation by passing a View Transition Class and defining the animation in CSS, or you can use the Web Animations API to trigger animations imperatively with the event props (onEnter, onExit, onShare, onUpdate).
目前,<ViewTransition> 只能在 DOM 中使用。我们正在努力支持 React Native 和其他平台。
🌐 Currently, <ViewTransition> only works in the DOM. We’re working on support for React Native and other platforms.
更多内容,请参见 <ViewTransition> 文档。
🌐 For more, see the <ViewTransition> docs.
addTransitionType
有时候,你会想自定义在同一个状态更新时使用哪种动画。例如,向前导航轮播到第三张幻灯片时,应该将幻灯片从右向左动画,而向后导航则应从左向右动画,尽管这两个动作都会将 currentSlide 设置为 3。
🌐 Sometimes, you’ll want to customize which animation is used for the same state update. For example, navigating a carousel forward to the third slide should animate the slides right-to-left, while navigating it backward should animate them left-to-right, even though both actions set the currentSlide to 3.
你可以通过在状态更新时调用 addTransitionType 来为特定的视图过渡自定义动画。这让你可以添加关于某个过渡原因的更多信息:
🌐 You can customize the animation for a given View Transition by calling addTransitionType alongside the state update. This lets you add more information about the cause of a particular transition:
function nextSlide() {
startTransition(() => {
addTransitionType('next');
setCurrentSlide(c => c + 1);
});
}
function previousSlide() {
startTransition(() => {
addTransitionType('previous');
setCurrentSlide(c => c - 1);
});
}然后,你可以根据那个过渡类型指定不同的动画:
🌐 Then, you can specify different animations based on that transition type:
<ViewTransition
enter={{
'next': 'from-right',
'previous': 'from-left',
}}
exit={{
'next': 'to-left',
'previous': 'to-right',
}}
>
<Page />
</ViewTransition>这里有一个例子:
🌐 Here’s an example:
import { ViewTransition, addTransitionType, useState, startTransition, Fragment } from 'react'; import { Video } from './Video'; import videos from './data'; import './animations.css'; export default function Component() { const [selected, setSelected] = useState(0) const video = videos[selected]; return ( <> <div className="button-container"> <button onClick={() => { startTransition(() => { addTransitionType('previous'); setSelected(c => c > 0 ? c - 1 : videos.length - 1 ) }); }}> ⬅️ </button> <button onClick={() => { startTransition(() => { addTransitionType('next'); setSelected(c => c + 1 < videos.length ? c + 1 : 0) }); }}> ➡️ </button> </div> <ViewTransition key={video.id} enter={{ 'next': 'from-right', 'previous': 'from-left' }} exit={{ 'next': 'to-left', 'previous': 'to-right' }} > <Video video={video} /> </ViewTransition> </> ); }
React 还会将每种过渡类型作为浏览器的 view transition type 添加到元素上,所以你可以在 CSS 中使用 :active-view-transition-type(...) 来限定动画范围。
🌐 React also adds every Transition Type to the element as a browser view transition type, so you can scope animations in CSS with :active-view-transition-type(...).
想了解更多,请查看 addTransitionType 文档。
🌐 To learn more, see the addTransitionType docs.
用 Suspense 为回退内容、图片和字体做动画
🌐 Animating fallbacks, images, and fonts with Suspense
关于 React 中的视图过渡,最令人兴奋的事情之一是它们如何与 Suspense 集成。
🌐 One of the most exciting things about View Transitions in React is how they integrate with Suspense.
你可以通过用 <ViewTransition> 封装它来在 Suspense 边界显示其子元素时为其添加动画:
🌐 You can animate a Suspense boundary as it reveals its children by wrapping it in <ViewTransition>:
<ViewTransition>
<Suspense fallback={<Loading />}>
<Component />
</Suspense>
</ViewTransition>当子组件加载完成时,React 会从备用内容触发到最终内容的更新动画。
🌐 When the children finish loading, React will trigger an update animation from the fallback to the final content.
这是一个例子。试着按 ➕ 来呈现一个首次渲染时会暂停的 LazyVideo:
🌐 Here’s an example. Try pressing ➕ to render a LazyVideo that suspends the first time it’s rendered:
import { Suspense, useState, startTransition, use, ViewTransition } from 'react'; import { Video, VideoPlaceholder } from './Video'; import { fetchVideo } from './data'; export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }} > {showItem ? '➖' : '➕'} </button> {showItem && ( <ViewTransition> <Suspense fallback={<VideoPlaceholder />}> <LazyVideo /> </Suspense> </ViewTransition> )} </> ); } function LazyVideo() { const video = use(fetchVideo()); return <Video video={video} />; }
虽然这样可以用,但你会注意到,视频在后续显示时也会做进出动画,即使它已经被加载过了。(你可能还会注意到,备用内容第一次显示时会淡入。)
🌐 While this works, you’ll notice that the video also animates in and out on subsequent reveals, even though it’s already been loaded. (You might also notice that the fallback fades in the first time it’s shown.)
一般来说,带有 Suspense 的动画在少量使用时效果最好,而且应避免用于本来会立即显示的缓存界面。
🌐 In general, animations with Suspense work best when they’re used sparingly, and avoided for cached UI that would otherwise appear instantly.
在使用 Suspense 做动画时,这里有一些实现良好用户体验的原则:
🌐 Here are some principles for achieving good UX when animating with Suspense:
- 回退内容应该立即出现 没有动画效果
- 备用内容应该用动画更新到最终内容
- 不暂停的子级应该立即出现 没有动画效果
这会让你的应用在内容已经加载好时感觉很流畅,而且只用动画来让从备用内容到最终内容的更新更加自然。
🌐 This keeps your app feeling snappy when things are already loaded, and only uses animation to make the update from fallback to final content more seamless.
要修复上面的例子,我们可以禁用除更新之外的所有动画:
🌐 To fix our example above, we can disable all animations other than updates:
<ViewTransition update="auto" default="none">
<Suspense fallback={<Fallback />}>
<Component />
</Suspense>
</ViewTransition>我们来看看它现在的表现吧:
🌐 Let’s see how it behaves now:
import { Suspense, useState, startTransition, use, ViewTransition } from 'react'; import { Video, VideoPlaceholder } from './Video'; import { fetchVideo } from './data'; export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }} > {showItem ? '➖' : '➕'} </button> {showItem && ( <ViewTransition update="auto" default="none"> <Suspense fallback={<VideoPlaceholder />}> <LazyVideo /> </Suspense> </ViewTransition> )} </> ); } function LazyVideo() { const video = use(fetchVideo()); return <Video video={video} />; }
注意,当点击按钮时,备用内容会立即出现,这让我们的界面在用户操作时感觉很灵敏。另外,一旦视频加载完成,切换它也能瞬间完成。
🌐 Notice how the fallback appears immediately when tapping the button, which keeps our UI feeling responsive to user actions. Additionally, once the video has been loaded, toggling it is instant.
根据你想要达到的效果,你可以使用其他模式。想了解更多,请查看关于 使用 Suspense 动画 的文档。
🌐 There are other patterns you can use depending on what effect you want to achieve. To learn more, check out the docs on animating with Suspense.
除了给回退动画效果,视图过渡还可以作为一种方式,让图片或字体在加载时触发 Suspense。
🌐 In addition to animating fallbacks, View Transitions act as a way to opt images or fonts into triggering Suspense while they load.
这让你避免浏览器的默认行为,即图片或字体在加载完成时可能会闪现,而是可以构建协调的加载顺序,考虑组件的所有资源。
🌐 This lets you avoid the browser’s default behavior where images or fonts may flicker in whenever they happen to finish loading, and instead build coordinated loading sequences that consider all of a component’s resources.
将图片或字体封装在 <ViewTransition> 里,以在它们加载时触发 Suspense:
🌐 Wrap images or fonts inside of <ViewTransition> to trigger Suspense while they load:
<ViewTransition>
<Suspense fallback={<Fallback />}>
<img src={imageSrc} />
<style href={fontSrc} precedence="default">
{`@font-face {
font-family: 'Fancy';
src: url(${fontSrc}) format('truetype');
font-display: swap;
}`}
</style>
</Suspense>
</ViewTransition>这是一个示例组件,它会暂停,直到它的数据、图片和字体都加载完成:
🌐 Here’s an example of a component that suspends until its data, image, and font have all loaded:
import { ViewTransition, Suspense, use, useState, startTransition } from 'react'; import { fetchQuote } from './data.js'; import { freshStylesheetUrl, freshImageUrl } from './resources.js'; import { ProfileCard, ProfileCardLoading } from './ProfileCard.js'; import { VanillaProfileCard } from './VanillaProfileCard.js'; export default function App() { const [resources, setResources] = useState(null); return ( <> <button onClick={() => { startTransition(() => { setResources({ quotePromise: fetchQuote(), stylesheet: freshStylesheetUrl(), image: freshImageUrl(), }); }); }}> Show profile </button> {resources && ( <ViewTransition update='auto' default='none'> <Suspense fallback={<ProfileCardLoading />}> <ProfileCard resources={resources} /> </Suspense> </ViewTransition> )} <hr /> <VanillaProfileCard /> </> ); }
要了解更多关于等待图片、字体或样式表加载的信息,请参阅 Suspense 文档。
🌐 To learn more about waiting for images, fonts, or stylesheets to load, see the Suspense docs.
片段引用
🌐 Fragment Refs
当你需要对组件的 DOM 节点进行更底层的控制时——例如添加事件监听器、观察可见性或者移动焦点——通常可以使用 ref。但有些情况下这样做会比较困难:
🌐 When you need lower-level control over a component’s DOM nodes—for example to attach an event listener, observe visibility, or move focus—you can usually use a ref. But there are some situations where this is difficult:
- 渲染一组没有单一父组件兄弟组件的组件
- 不会把它们的
ref属性传给其他元素的组件
function Component() {
// How can we work with the list of DOM nodes rendered by this component?
return (
{posts.map(post => (
<Heading key={post.id}>
{post.title}
</Heading>
))}
)
}仅仅为了持有一个 ref 而添加一个封装 <div> 有时可以奏效,但它也可能干扰你的组件样式或布局。而且,如果一个组件没有暴露 ref 属性,你就需要修改那个组件来实现这一点,如果它来自你无法控制的库,这可能是不可能的。
🌐 Adding a wrapper <div> just to hold a ref sometimes works, but it can also interfere with your component’s styling or layout. Moreover, if a component doesn’t expose a ref prop, you would need to modify that component to do so, which might be impossible if it comes from a library you don’t control.
Fragment Refs 通过提供一组有限的常用 DOM 方法来解决这些问题,这些方法可以与任何 React 组件一起使用,无论它渲染什么。
🌐 Fragment Refs solve these problems by providing a limited set of commonly used DOM methods that work with any React component, regardless of what it renders.
在 19.3 中,你可以通过直接将 ref 传递给 <Fragment> 来使用它们。这个 ref 会给你一个 FragmentInstance,你可以用它来操作 Fragment 的 DOM 子节点:
🌐 In 19.3, you can use them by passing a ref directly to a <Fragment>. This ref gives you a FragmentInstance, which you can use to work with the Fragment’s DOM children:
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>
)
}FragmentInstance 对子级的 DOM 进行操作是 作为一个整体,而不改变其结构:
🌐 The FragmentInstance operates on the children’s DOM as a group, without changing its structure:
addEventListener、removeEventListener和dispatchEvent负责管理一级子节点的事件。focus、focusLast和blur会在嵌套子元素间按深度优先移动焦点。observeUsing和unobserveUsing连接一个IntersectionObserver或ResizeObserver。getClientRects、getRootNode、compareDocumentPosition和scrollIntoView让你可以测量并滚动到片段的一级子元素。
因此,Fragment Refs 让你可以给其他组件附加行为,而不需要修改这些组件的内部结构,也不需要改变它们已经生成的 DOM 结构。
🌐 Thus, Fragment Refs let you attach behavior to other components without requiring you to modify those component’s internals, or without changing the DOM structure that they already produce.
这个例子展示了一个带有 onChange 属性的 InView 组件,每当它的子元素进入或离开视口时,该属性就会触发:
🌐 This example shows an InView component with an onChange prop that fires whenever its children enter or exit the viewport:
import { useState } from 'react'; import Card from './Card'; import InView from './InView'; export default function App() { const [isVisible, setIsVisible] = useState(true); return ( <div className={isVisible ? 'page visible' : 'page'}> <div className="filler">Scroll down</div> <InView onChange={setIsVisible}> <Card title="第一部分" /> <Card title="第二部分" /> </InView> <div className="filler">Scroll up</div> </div> ); }
注意一下,InView 如何能够给它的子元素添加行为,即使没有单一的父级 DOM 元素,而且 Card 并没有暴露 ref 属性。
🌐 Notice how InView is able to add behavior to its children, even though there’s no single parent DOM element, and in spite of Card not exposing a ref prop.
要了解更多关于使用 Fragment Refs 的信息,请参阅 <Fragment> 文档。
🌐 To learn more about working with Fragment Refs, see the <Fragment> docs.
新的 React DOM 功能
🌐 New React DOM Features
browser
如果你的应用使用服务器渲染,你的组件会在两种不同的环境中渲染:
🌐 If your app uses server rendering, your components will render in two different environments:
- 在服务器上,组件会渲染以生成初始的 HTML
- 在客户端,组件会渲染以用事件处理器丰富 HTML
大多数时候,你的组件应该能够生成与它们最初客户端渲染输出相匹配的 HTML,这样可以确保它们正确地水合,同时仍然让用户在初次加载时看到尽可能多的内容。
🌐 Most of time, your components should be able to produce HTML that matches their initial client-rendered output, ensuring they hydrate correctly while still letting users see as much content as possible on the initial load.
但在少数情况下,一个组件可能无法在服务器上生成有意义的 UI。例如,它可能依赖于只在浏览器中可用的 API,比如 localStorage,或者它可能读取浏览器的本地时区。在这些情况下,你可能会想让这个组件完全不参与服务器渲染。
🌐 But in rare cases, a component may not be able to produce meaningful UI on the server. For example, it might depend on a browser-only API like localStorage, or it might read from the browser’s local timezone. In these cases, you may want to opt that component out of server rendering altogether.
以前,你可能会通过在 effect 中更新某些状态,或者通过检查像 window 这样的浏览器 API 是否存在来做到这一点:
🌐 Previously, you might do this using some state that you’d update in an effect, or by checking for the presence of browser APIs like window:
function Component() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true)
}, [])
// ...
}
function Component() {
const isBrowser = typeof window !== 'undefined';
// ...
}在 19.3 中,React 现在为这种技术提供了一个一流的 API。
🌐 In 19.3, React now includes a first-class API for this technique.
一个组件可以调用 use(browser()) 来选择不进行服务器端渲染:
🌐 A component can call use(browser()) to opt out of server-side rendering:
import { use } from 'react';
import { browser } from 'react-dom';
function Component() {
use(browser());
// ...
}这会在服务器上触发 Suspense,但不会在客户端触发。在服务器端渲染期间,最近的 Suspense 边界的回退内容会显示在 HTML 中。一旦组件在客户端被水合,use(browser()) 不会挂起,让组件可以正常继续渲染。
🌐 This will trigger Suspense on the server, but not in the client. During server-side rendering, the nearest Suspense boundary’s fallback will show in the HTML. Once the component is hydrated on the client, use(browser()) does not suspend, allowing the component to continue rendering as normal.
这是一个组件示例,它会显示你设备的本地时区。点击 重新加载 来查看初始 HTML,然后是 React 在客户端的首次渲染:
🌐 Here’s an example of a component that renders the local time zone from your device. Press Reload to see the initial HTML followed by React’s first render on the client:
import { Suspense, use } from 'react'; import { browser } from 'react-dom'; function TimeZone() { use(browser()); const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone; return <p>{timeZone}</p> } export default function App() { return ( <> <p>Your current time zone is:</p> <Suspense fallback="Loading..."> <TimeZone /> </Suspense> </> ); }
因为 TimeZone 会在服务器上挂起,初始的 HTML 会包含 Suspense 的回退内容。经过一个小的人工延迟后,React 会对页面进行 hydrate,让组件可以在浏览器中正常渲染。
🌐 Because TimeZone suspends on the server, the initial HTML includes the Suspense fallback. After a small artificial delay, React hydrates the page, allowing the component to render as normal in the browser.
因此,对于那些在服务器渲染期间无法生成有意义 UI 的组件,browser 允许你在它们的加载状态中使用 Suspense,让它们可以和其他需要等待准备好才能渲染的组件一起参与。
🌐 Thus, for components that cannot produce meaningful UI during server rendering, browser lets you use Suspense for their loading states, allowing them to participate with other components that suspend until they’re ready to render.
像其他对 use 的调用一样,use(browser()) 也可以在条件语句中或提前返回后调用。这让你可以编写组件或自定义 Hooks,根据条件(比如某个 prop 的值)选择是否跳过服务端渲染。
🌐 Like other calls to use, use(browser()) can be called inside a conditional statement or after an early return. This lets you write components or custom Hooks that can opt out of server rendering based on a condition, such as the value of a prop.
这是上面同样的例子,只不过现在我们的 TimeZone 组件可以接受一个可选的默认值,它可以作为初始 HTML 的一部分进行渲染:
🌐 Here’s the same example from above, except now our TimeZone component accepts an optional default value it can render as part of the initial HTML:
import { Suspense, use } from 'react'; import { browser } from 'react-dom'; function TimeZone({ defaultValue }) { if (defaultValue) { return <p>{defaultValue}</p>; } use(browser()); const localTimeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone; return <p>{localTimeZone}</p> } export default function App() { return ( <> <div> <p>The event's time zone is:</p> <TimeZone defaultValue='America/New_York' /> </div> <hr /> <div> <p>Your current time zone is:</p> <Suspense fallback="Loading..."> <TimeZone /> </Suspense> </div> </> ); }
注意一下,只有在第二种情况没有提供默认值时,TimeZone 才会挂起。
🌐 Notice how TimeZone only suspends in the second case, when no default is provided.
这个模式的另一个有用例子是把像 useQuery 这样的数据获取 Hook 排除在服务端渲染之外,除非该查询的初始数据已经被传入(比如来自服务端组件或框架的 loader 函数):
🌐 Another useful example of this pattern is opting a data-fetching Hook like useQuery out of server rendering, unless that query’s initial data was passed in (for example from a Server Component or framework’s loader function):
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,
});
return <h1>{product.name}</h1>;
}现在,只要在服务器渲染时接收到 initialData,ProductDetails 组件就可以包含在 HTML 中。如果没有,它会暂停,直到在浏览器中渲染,这时 useQuery 就可以像平常一样获取数据或从缓存中读取。
🌐 Now, the ProductDetails component can be included in the HTML, provided it receives initialData during server rendering. If not, it suspends until it gets rendered in the browser, at which point useQuery can fetch the data or read from its cache as normal.
想了解更多关于 browser 的信息,查看文档。
🌐 To learn more about browser, check out the docs.
Trusted Types 支持
🌐 Trusted Types support
React 19.3 与浏览器的 Trusted Types API 集成,这是一个有助于防止基于 DOM 的 XSS 攻击的安全功能。当网站通过 Content-Security-Policy: require-trusted-types-for 'script' 强制使用 Trusted Types 时,浏览器要求传递给像 innerHTML 这样的注入点的值必须是通过你的清理策略创建的类型化对象(TrustedHTML、TrustedScript、TrustedScriptURL),而不是原始字符串。
🌐 React 19.3 integrates with the browser Trusted Types API, a security feature that helps prevent DOM-based XSS attacks. When a site enforces Trusted Types with Content-Security-Policy: require-trusted-types-for 'script', the browser requires that values passed to injection sinks like innerHTML are typed objects (TrustedHTML, TrustedScript, TrustedScriptURL) created through your sanitization policies, rather than raw strings.
以前,React 总是会在把值传给 DOM API 之前(通过 '' + value)把它们强制转换成字符串,这会把 Trusted Types 对象变回浏览器会拒绝的普通字符串。现在,React 会直接传递这些值而不做强制转换,这样浏览器就能验证它们,你的 Trusted Types 策略也能按预期工作。
🌐 Previously, React always coerced values to strings (via '' + value) before passing them to DOM APIs, which turned Trusted Types objects back into plain strings the browser would reject. React now passes these values through without coercion, so the browser can validate them and your Trusted Types policies work as intended.
新的 React 服务器组件功能
🌐 New React Server Components Features
<Context> 可以直接在服务器组件中渲染
🌐 <Context> can be rendered directly in Server Components
虽然服务器组件不能 创建 Context,但它们可以通过从 'use client' 模块导入来 渲染 Context。
🌐 While Server Components can’t create Context, they can render Context by importing it from a 'use client' module.
以前,这需要客户端模块导出一个单独的封装组件,通常叫做 Provider:
🌐 Previously, this required the client module to export a separate wrapper component, often called a Provider:
// user-context.js
'use client';
import { createContext } from 'react';
export const UserContext = createContext(null);
export function UserProvider({ currentUser, children }) {
return <UserContext value={currentUser}>{children}</UserContext>;
}// server-component.js
import { UserProvider } from './user-context';
export async function Layout({ children }) {
const currentUser = await getCurrentUser();
return (
<UserProvider currentUser={currentUser}>
{children}
</UserProvider>
)
}注意,在这个例子里,提供者除了把 prop 从服务器组件直接传给上下文,什么都没做。
🌐 Notice that in this example, the provider does nothing other than pass the prop from the Server Component directly to the Context.
在 React 19.3 中,服务器组件可以直接从 'use client' 模块导入并渲染 Context,而无需额外的封装组件:
🌐 In React 19.3, Server Components can import and render Context directly from a 'use client' module, without an additional wrapping component:
// user-context.js
'use client';
import { createContext } from 'react';
export const UserContext = createContext(null);// server-component.js
import { UserContext } from './user-context';
export async function Layout({ children }) {
const currentUser = await getCurrentUser();
return (
<UserContext value={currentUser}>
{children}
</UserContext>
)
}这对于那些仅存在于让服务器组件与客户端树其他部分共享一些数据的上下文特别有用。
🌐 This is especially useful for Contexts that solely exist to allow Server Components to share some data with the rest of the client tree.
更新日志
🌐 Changelog
其他显著变化
🌐 Other notable changes
react:单独渲染过渡效果,而不是把它们纠缠在一个渲染里,所以一个慢的过渡效果不会再拖慢不相关的过渡效果 #37290react-dom:在严格模式下进行 hydration 时重复调用 Effects,与客户端渲染的根匹配 #35961react:在条件语句中错误使用use时添加警告 #37104react:在useActionState错误信息中将“form state”重命名为“action state” #35790react-dom:添加对onFullscreenChange和onFullscreenError事件的支持 #34621react-dom:为maskTypeSVG 属性添加支持 #35921react-dom:支持模块资源的fetchPriority#36835react-dom:当 React 在服务器操作后自动重置表单时触发onReset#35176react-dom:在submit事件中包括submitter#35590react-dom:在 iframe 上将credentialless识别为布尔属性 #36148react-dom:从resize事件开始批量更新,直到下一帧 #35117react-server:将Error.cause#35810 和AggregateError.errors#36156 交付给客户react-server:在 Flight 中增加对<Activity>的支持 #34697
显著的错误修复
🌐 Notable bug fixes
react:修复useDeferredValue卡在旧值的问题 #36134react:修复上下文在 Suspense 回退中传播的问题 #36160 以及在挂起的 Suspense 边界中传播的问题 #35839react:修复在隐藏树中更新脱水的 Suspense 边界时出现的挂起问题 #37135react:修复useSyncExternalStore在<Activity>树隐藏期间发生的缺失 store 变更 #36947react:修复useEffectEvent以读取forwardRef和memo组件中的最新值 #34831react:修复组件状态更新时表单状态重置的问题 #34075react:修复了与lazy、memo以及更改组件类型的编辑相关的几个 Fast Refresh Bug #36965,#36964,#36963,#36950react:修复一个漏洞,即在包含<title>的<Activity>从visible模式改为hidden后,<title>仍然被提升到<head>#34983react:别让错误逃过隐藏的<Activity>#35074react:隐藏渲染在隐藏的<Activity>中的门户内容 #35091react:别在错误信息里引用内部的<Offscreen>类型 #35763react-dom:修复委托和已聚焦元素的焦点问题 #36010react-dom:通过根据 DOM 规范规范化捕获选项来修复FragmentInstance监听器泄漏 #36047react-dom:修复 Mobile Safari 中的<ViewTransition>崩溃 #35337react-dom:修复SuspenseList导致的<ViewTransition>崩溃 #35520react-dom:更新defaultValue以匹配其他输入类型的type="number"输入 #36980react-dom:当innerHTML没有变化时,避免设置它 #36949react-dom:修复nonce属性上的假阳性水合不匹配 #37030react-dom:修复 Deno 上react-dom/server卡住的问题 #35235react-server:修复decodeReplyFromBusboy中丢失的FormData条目 #36468react-server:修复深度异步链导致的堆栈溢出 #35612 以及由指数级调试信息增长引起的RangeError#37481
有关完整的更改列表,请参阅 更新日志。
🌐 For a full list of changes, please see the Changelog.
感谢 Sam Selikoff 撰写这篇文章,也感谢 Matt Carroll、Dan Abramov 和 Andrew Clark 对这篇文章进行审阅。
🌐 Thanks to Sam Selikoff for writing this post, and to Matt Carroll, Dan Abramov, and Andrew Clark for reviewing this post.