<Activity>
允许你隐藏和显示部分 UI。
¥<Activity>
lets you hide and show part of the UI.
<Activity mode={mode}>
<Page />
</Activity>
参考
¥Reference
<Activity>
将 UI 的一部分封装在 <Activity>
中以管理其可见性状态:
¥Wrap a part of the UI in <Activity>
to manage its visibility state:
import {unstableActivity as Activity} from 'react';
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<Page />
</Activity>
当 “hidden” 启用时,<Activity />
的 children
在页面上不可见。如果新的 <Activity>
挂载为 “hidden”,则它会以较低优先级预渲染内容,而不会阻塞页面上的可见内容,但它不会通过创建副作用来挂载。当 “visible” Activity 切换到 “hidden” 时,它在概念上会通过销毁所有副作用来卸载,但会保存其状态。这允许在 “visible” 和 “hidden” 状态之间快速切换,而无需为 “hidden” Activity 重新创建状态。
¥When “hidden”, the children
of <Activity />
are not visible on the page. If a new <Activity>
mounts as “hidden” then it pre-renders the content at lower priority without blocking the visible content on the page, but it does not mount by creating Effects. When a “visible” Activity switches to “hidden” it conceptually unmounts by destroying all the Effects, but saves its state. This allows fast switching between “visible” and “hidden” states without recreating the state for a “hidden” Activity.
未来,“hidden” 活动可能会根据内存等资源自动销毁状态。
¥In the future, “hidden” Activities may automatically destroy state based on resources like memory.
属性
¥Props
-
children
:你打算渲染的实际 UI。¥
children
: The actual UI you intend to render. -
可选
mode
:“visible” 或 “hidden”。默认为 “visible”。当 “hidden” 启用时,对子级的更新将推迟到较低优先级。在 Activity 切换到 “visible” 之前,组件不会创建效果。如果 “visible” Activity 切换到 “hidden”,副作用将被销毁。¥optional
mode
: Either “visible” or “hidden”. Defaults to “visible”. When “hidden”, updates to the children are deferred to lower priority. The component will not create Effects until the Activity is switched to “visible”. If a “visible” Activity switches to “hidden”, the Effects will be destroyed.
注意事项
¥Caveats
-
隐藏时,
<Activity>
的children
在页面上是隐藏的。¥While hidden, the
children
of<Activity>
are hidden on the page. -
从 “visible” 切换到 “hidden” 时,
<Activity>
会卸载所有效果,而不会破坏 React 或 DOM 状态。这意味着原本预计在挂载时只运行一次的效果,在从 “hidden” 切换到 “visible” 时会再次运行。从概念上讲,“hidden” 活动已被卸载,但也不会被销毁。我们建议使用<StrictMode>
来捕获此行为可能带来的任何意外副作用。¥
<Activity>
will unmount all Effects when switching from “visible” to “hidden” without destroying React or DOM state. This means Effects that are expected to run only once on mount will run again when switching from “hidden” to “visible”. Conceptually, “hidden” Activities are unmounted, but they are not destroyed either. We recommend using<StrictMode>
to catch any unexpected side-effects from this behavior. -
与
<ViewTransition>
一起使用时,在过渡中显示的隐藏活动将激活 “enter” 动画。隐藏在过渡中的可见活动将激活 “exit” 动画。¥When used with
<ViewTransition>
, hidden activities that reveal in a transition will activate an “enter” animation. Visible Activities hidden in a transition will activate an “exit” animation. -
<Activity mode="hidden">
中封装的 UI 部分不包含在 SSR 响应中。¥Parts of the UI wrapped in
<Activity mode="hidden">
are not included in the SSR response. -
<Activity mode="visible">
中封装的 UI 部分的 hydrate 优先级将低于其他内容。¥Parts of the UI wrapped in
<Activity mode="visible">
will hydrate at a lower priority than other content.
用法
¥Usage
预渲染部分 UI
¥Pre-render part of the UI
你可以使用 <Activity mode="hidden">
预渲染部分 UI:
¥You can pre-render part of the UI using <Activity mode="hidden">
:
<Activity mode={tab === "posts" ? "visible" : "hidden"}>
<PostsTab />
</Activity>
当 Activity 使用 mode="hidden"
渲染时,children
在页面上不可见,但其渲染优先级低于页面上的可见内容。
¥When an Activity is rendered with mode="hidden"
, the children
are not visible on the page, but are rendered at lower priority than the visible content on the page.
当 mode
稍后切换到 “visible” 时,预渲染的子元素将挂载并可见。这可用于准备用户下次可能交互的 UI 部分,以减少加载时间。
¥When the mode
later switches to “visible”, the pre-rendered children will mount and become visible. This can be used to prepare parts of the UI the user is likely to interact with next to reduce loading times.
在下面的 useTransition
示例中,PostsTab
组件使用 use
获取了一些数据。当你点击“Posts”选项卡时,PostsTab
组件会暂停,导致按钮显示加载状态:
¥In the following example from useTransition
, the PostsTab
component fetches some data using use
. When you click the “Posts” tab, the PostsTab
component suspends, causing the button loading state to appear:
import { useTransition } from 'react'; export default function TabButton({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
在此示例中,用户点击 “帖子” 选项卡时需要等待帖子加载完成。
¥In this example, the user needs to wait for the posts to load when clicking on the “Posts” tab.
我们可以通过使用隐藏的 <Activity>
预渲染非活动选项卡来减少 “帖子” 选项卡的延迟:
¥We can reduce the delay for the “Posts” tab by pre-rendering the inactive Tabs with a hidden <Activity>
:
import { useTransition } from 'react'; export default function TabButton({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
保留部分 UI 的状态
¥Keeping state for part of the UI
你可以通过将 <Activity>
从 “visible” 切换到 “hidden” 来保持部分 UI 的状态:
¥You can keep state for parts of the UI by switching <Activity>
from “visible” to “hidden”:
<Activity mode={tab === "posts" ? "visible" : "hidden"}>
<PostsTab />
</Activity>
当 Activity 从 mode="visible"
切换到 “hidden” 时,children
将在页面上隐藏,并通过销毁所有副作用进行卸载,但会保留其 React 和 DOM 状态。
¥When an Activity switches from mode="visible"
to “hidden”, the children
will become hidden on the page, and unmount by destroying all Effects, but will keep their React and DOM state.
当 mode
稍后切换到 “visible” 时,通过创建所有效果,挂载子元素时将重新使用已保存的状态。这可用于在用户可能再次交互的 UI 部分中保存状态,以维护 DOM 或 React 状态。
¥When the mode
later switches to “visible”, the saved state will be re-used when mounting the children by creating all the Effects. This can be used to keep state in parts of the UI the user is likely to interact with again to maintain DOM or React state.
在下面的 useTransition
示例中,ContactTab
包含一个 <textarea>
,其中包含一条要发送的草稿消息。如果你输入一些文本并切换到其他选项卡,那么当你再次点击“联系人”选项卡时,草稿消息将丢失:
¥In the following example from useTransition
, the ContactTab
includes a <textarea>
with a draft message to send. If you enter some text and change to a different tab, then when you click the “Contact” tab again, the draft message is lost:
import { useTransition } from 'react'; export default function TabButton({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
这会导致丢失用户输入的 DOM 状态。我们可以通过使用 <Activity>
隐藏非活动选项卡来保持“联系人”选项卡的状态:
¥This results in losing DOM state the user has input. We can keep the state for the Contact tab by hiding the inactive Tabs with <Activity>
:
import { useTransition } from 'react'; export default function TabButton({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
故障排除
¥Troubleshooting
Activity 隐藏时效果不会挂载
¥Effects don’t mount when an Activity is hidden
当 <Activity>
为 “hidden” 时,所有副作用都将被卸载。从概念上讲,组件已被卸载,但 React 会保存状态以供以后使用。
¥When an <Activity>
is “hidden”, all Effects are unmounted. Conceptually, the component is unmounted, but React saves the state for later.
这是 Activity 的一项功能,因为它意味着隐藏的 UI 部分不会被订阅,从而减少了隐藏内容的工作量。它也意味着会触发清理操作,例如暂停视频(如果你在没有 Activity 的情况下卸载,这是可以预料到的)。当 Activity 切换到 “visible” 时,它将通过创建副作用进行挂载,这些副作用将订阅并播放视频。
¥This is a feature of Activity because it means subscriptions won’t be subscribed for hidden parts of the UI, reducing the amount of work for hidden content. It also means cleanup, such as pausing a video (which would be expected if you unmounted without Activity) will fire. When an Activity switches to “visible”, it will mount by creating the Effects, which will subscribe and play the video.
考虑以下示例,其中每个按钮播放不同的视频:
¥Consider the following example, where a different video is played for each button:
import { useState, useRef, useEffect } from 'react'; import './checker.js'; function VideoPlayer({ src, isPlaying }) { const ref = useRef(null); useEffect(() => { const videoRef = ref.current; videoRef.play(); return () => { videoRef.pause(); } }, []); return <video ref={ref} src={src} muted loop playsInline/>; } export default function App() { const [video, setVideo] = useState(1); return ( <> <div> <button onClick={() => setVideo(1)}>Big Buck Bunny</button> <button onClick={() => setVideo(2)}>Elephants Dream</button> </div> {video === 1 && <VideoPlayer key={1} // 'Big Buck Bunny' licensed under CC 3.0 by the Blender foundation. Hosted by archive.org src="https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4" /> } {video === 2 && <VideoPlayer key={2} // 'Elephants Dream' by Orange Open Movie Project Studio, licensed under CC-3.0, hosted by archive.org src="https://archive.org/download/ElephantsDream/ed_1024_512kb.mp4" /> } </> ); }
每当你切换视频并返回时,视频都会从头开始重新加载。要维护状态,你可以尝试渲染两个视频,并在 display: none
中隐藏非活动视频。然而,这会导致两个视频同时播放:
¥Whenever you change videos and come back, the video re-loads from the beginning. To maintain the state, you may try to render both videos, and hide the inactive video in display: none
. However, this will cause both videos to play at the same time:
import { useState, useRef, useEffect } from 'react'; import VideoChecker from './checker.js'; function VideoPlayer({ src, isPlaying }) { const ref = useRef(null); useEffect(() => { const videoRef = ref.current; videoRef.play(); return () => { videoRef.pause(); } }, []); return <video ref={ref} src={src} muted loop playsInline/>; } export default function App() { const [video, setVideo] = useState(1); return ( <> <div> <button onClick={() => setVideo(1)}>Big Buck Bunny</button> <button onClick={() => setVideo(2)}>Elephants Dream</button> </div> <div style={{display: video === 1 ? 'block' : 'none'}}> <VideoPlayer // 'Big Buck Bunny' licensed under CC 3.0 by the Blender foundation. Hosted by archive.org src="https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4" /> </div> <div style={{display: video === 2 ? 'block' : 'none'}}> <VideoPlayer // 'Elephants Dream' by Orange Open Movie Project Studio, licensed under CC-3.0, hosted by archive.org src="https://archive.org/download/ElephantsDream/ed_1024_512kb.mp4" /> </div> <VideoChecker /> </> ); }
这类似于 Activity 在隐藏状态下挂载副作用时发生的情况。同样,如果 Activity 在隐藏时未卸载副作用,视频将继续在后台播放。
¥This is similar to what would happen if Activity mounted Effects when hidden. Similarly, if Activity didn’t unmount Effects when hiding, the videos would continue to play in the background.
Activity 通过在首次渲染为 “hidden” 时不创建副作用,并在从 “visible” 切换到 “hidden” 时销毁所有副作用来解决这个问题:
¥Activity solves this by not creating Effects when first rendered as “hidden” and destroying all Effects when switching from “visible” to “hidden”:
import { useState, useRef, useEffect, unstable_Activity as Activity } from 'react'; import VideoChecker from './checker.js'; function VideoPlayer({ src, isPlaying }) { const ref = useRef(null); useEffect(() => { const videoRef = ref.current; videoRef.play(); return () => { videoRef.pause(); } }, []); return <video ref={ref} src={src} muted loop playsInline/>; } export default function App() { const [video, setVideo] = useState(1); return ( <> <div> <button onClick={() => setVideo(1)}>Big Buck Bunny</button> <button onClick={() => setVideo(2)}>Elephants Dream</button> </div> <Activity mode={video === 1 ? 'visible' : 'hidden'}> <VideoPlayer // 'Big Buck Bunny' licensed under CC 3.0 by the Blender foundation. Hosted by archive.org src="https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4" /> </Activity> <Activity mode={video === 2 ? 'visible' : 'hidden'}> <VideoPlayer // 'Elephants Dream' by Orange Open Movie Project Studio, licensed under CC-3.0, hosted by archive.org src="https://archive.org/download/ElephantsDream/ed_1024_512kb.mp4" /> </Activity> <VideoChecker /> </> ); }
因此,最好将 Activity 从概念上理解为 “unmounting” 和 “remounting” 组件,但会保存 React 或 DOM 状态以供后续使用。实际上,如果你遵循了 你可能不需要副作用 指南,这将按预期工作。为了快速查找有问题的效果,我们建议添加 <StrictMode>
,它将快速执行 Activity 的卸载和挂载,以捕获任何意外的副作用。
¥For this reason, it’s best to think of Activity conceptually as “unmounting” and “remounting” the component, but saving the React or DOM state for later. In practice, this works as expected if you have followed the You Might Not Need an Effect guide. To eagerly find problematic Effects, we recommend adding <StrictMode>
which will eagerly perform Activity unmounts and mounts to catch any unexpected side-effects.
我的隐藏 Activity 未在服务端渲染 (SSR) 中渲染
¥My hidden Activity is not rendered in SSR
在服务器端渲染期间使用 <Activity mode="hidden">
时,Activity 的内容将不会包含在服务器端渲染响应中。这是因为内容在页面上不可见,并且对于初始渲染来说不是必需的。如果你需要在 SSR 响应中包含内容,则可以使用其他方法(例如 useDeferredValue
)来延迟内容的渲染。
¥When you use <Activity mode="hidden">
during server-side rendering, the content of the Activity will not be included in the SSR response. This is because the content is not visible on the page and is not needed for the initial render. If you need to include the content in the SSR response, you can use a different approach like useDeferredValue
to defer rendering of the content.