browser 让你在服务器渲染时将一个组件标记为仅在浏览器使用。

use(browser(reason?))

参考

🌐 Reference

browser(reason?)

在服务器渲染期间,调用 use 中的 browser 来标记组件仅在浏览器中使用:

🌐 Call browser inside use to mark a component as browser-only during server rendering:

import { use } from 'react';
import { browser } from 'react-dom';

function BrowserOnly() {
use(browser('This component requires browser APIs.'));
return <BrowserContent />;
}

在服务器渲染期间,use(browser()) 会停止渲染该组件,并在其位置留下最近的 <Suspense> 边界的回退。在浏览器中,use(browser()) 会返回 undefined,所以组件会正常渲染。

🌐 During server rendering, use(browser()) stops rendering the component and leaves the closest <Suspense> boundary’s fallback in its place. In the browser, use(browser()) returns undefined, so the component renders normally.

查看更多示例。

参数

🌐 Parameters

  • 可选 reason:一个字符串或函数,用于解释为什么内容需要在浏览器中渲染。该字符串或函数的返回值会成为传递给 onBrowserBailoutErrorcause。每当服务器渲染器遇到 browser 返回的值时,React 会调用理由函数,但在浏览器中不会调用。如果创建理由很耗资源,可以传递像 () => new Error(...) 这样的函数。

返回

🌐 Returns

browser 返回一个不透明值,你可以在组件中传给 use,或者在中止服务器渲染时用作原因。在浏览器中,把这个值传给 use 会返回 undefined

注意事项

🌐 Caveats

  • use(browser()) 在服务器渲染时必须位于 <Suspense> 边界内。没有它,服务器渲染会失败。
  • 在 React 服务器组件应用中,use(browser()) 必须从 客户端组件 调用,而不是 服务器组件
  • 单独调用 browser() 没有任何效果。要将组件标记为仅在浏览器中使用,把 browser 返回的值传给 use。不要抛出它。

用法

🌐 Usage

仅在浏览器中渲染内容

🌐 Rendering content only in the browser

在一个只应该在浏览器中渲染的组件里,在 use 里面调用 browser:

🌐 Call browser inside use in a component that should only render in the browser:

你可以用这个来代替检查 typeof window、等待 Effect 设置已挂载状态,或者使用框架选项来禁用服务端渲染。

🌐 You can use this instead of checking typeof window, waiting for an Effect to set mounted state, or using a framework option to disable server rendering.

点击 重新加载 查看初始 HTML 中的加载回退。水化后,React 会显示从 localStorage 加载的草稿。

🌐 Click Reload to see the loading fallback in the initial HTML. After hydration, React displays the draft loaded from localStorage.

import { Suspense, use, useState } from 'react';
import { browser } from 'react-dom';

function SavedDraft() {
  use(browser('The draft is stored in localStorage.'));
  const [draft, setDraft] = useState(
    () => localStorage.getItem('draft') ?? ''
  );

  function handleChange(event) {
    const nextDraft = event.target.value;
    setDraft(nextDraft);
    localStorage.setItem('draft', nextDraft);
  }

  return (
    <label>
      Draft:
      <textarea
        value={draft}
        onChange={handleChange}
        rows={4}
        cols={30}
      />
    </label>
  );
}

export default function App() {
  return (
    <>
      <h1>Saved draft</h1>
      <Suspense fallback={<p>Loading draft...</p>}>
        <SavedDraft />
      </Suspense>
    </>
  );
}

注意

在 React 服务器组件应用中,use(browser()) 必须从客户端组件中调用。如果你的框架默认使用服务器组件,可以在该文件中添加 'use client' 指令,或者把调用移动到子客户端组件中:

🌐 In a React Server Components app, use(browser()) must be called from a Client Component. If your framework uses Server Components by default, add the 'use client' directive to that file or move the call to a child Client Component:

'use client';

import { use, useState } from 'react';
import { browser } from 'react-dom';

export default function SavedDraft() {
use(browser('The saved draft is stored in localStorage.'));
const [draft] = useState(() => localStorage.getItem('draft') ?? '');
return <DraftEditor initialDraft={draft} />;
}

在服务器上有条件地渲染

🌐 Conditionally rendering on the server

像其他对 use 的调用一样,use(browser()) 可以在条件语句内或提前返回后调用。这让组件或自定义 Hook 可以根据某个条件(比如 prop 的值)选择不进行服务端渲染。

🌐 Like other calls to use, use(browser()) can be called inside a conditional statement or after an early return. This lets a Component or custom Hook opt out of server rendering based on a condition, such as the value of a prop.

例如,这个 useTimeZone Hook 接受一个可选的默认值。如果提供了默认值,React 会在初始 HTML 和浏览器中渲染该默认值。如果没有默认值,组件在服务器渲染时会挂起,并在浏览器中显示设备的本地时区。

🌐 For example, this useTimeZone Hook accepts an optional default value. When provided, React renders the default value in the initial HTML and in the browser. Without a default value, the Component suspends during server rendering and shows the device’s local time zone in the browser.

点击 重新加载 以在用户的时区显示之前查看加载回退。

🌐 Click Reload to see the loading fallback before the user’s time zone appears.

import { use } from 'react';
import { browser } from 'react-dom';

export function useTimeZone(defaultTimeZone) {
  if (defaultTimeZone !== undefined) {
    return defaultTimeZone;
  }

  use(browser('No default time zone was provided.'));
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
}

你可以应用类似的模式,在使用支持 Suspense 的数据获取库时,有条件地避免服务器渲染:

🌐 You can apply a similar pattern to conditionally avoid server rendering when using a Suspense-enabled data-fetching library:

function useBrowserQuery(query, options) {
if (options.initialData === undefined) {
use(browser('useBrowserQuery: No initial data was provided.'));
}

return useQuery(query, options);
}

function ProductDetails({ productId, initialData }) {
const product = useBrowserQuery(`/api/products/${productId}`, {
initialData,
});

return <h1>{product.name}</h1>;
}

有了 initialData,React 会在服务器上把组件渲染成 HTML。没有它的话,React 会在 HTML 中保留最近 <Suspense> 边界的备用内容。在浏览器里,useQuery 可以像平常一样获取数据或从客户端缓存中读取数据。

🌐 With initialData, React renders the Component to HTML on the server. Without it, React leaves the closest <Suspense> boundary’s fallback in the HTML. In the browser, useQuery can fetch the data or read it from its client cache as usual.


在服务器上报告仅浏览器渲染

🌐 Reporting browser-only rendering on the server

将一个 onBrowserBailout 回调传给服务端渲染器,以报告仅浏览器渲染。当 React 为浏览器离开 Suspense 回退内容时,它不会调用服务端渲染器的 onError 回调或 hydrateRootonRecoverableError 回调。这个例子还传递了一个原因,可以在报告的错误的 cause 中查看:

🌐 Pass an onBrowserBailout callback to the server renderer to report browser-only rendering. When React leaves a Suspense fallback for the browser, it does not call the server renderer’s onError callback or hydrateRoot’s onRecoverableError callback. This example also passes a reason, which is available as the reported error’s cause:

import { Suspense, use, useState } from 'react';
import { browser } from 'react-dom';
import { renderToPipeableStream } from 'react-dom/server';

function SavedDraft() {
use(browser(() => new Error('The saved draft is stored in localStorage.')));
const [draft] = useState(() => localStorage.getItem('draft') ?? '');
return <DraftEditor initialDraft={draft} />;
}

function App() {
return (
<Suspense fallback={<p>Loading saved draft...</p>}>
<SavedDraft />
</Suspense>
);
}

const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
pipe(response);
},
onBrowserBailout(error, errorInfo) {
logBrowserBailout(error, errorInfo);
}
});

onBrowserBailout 接收两个参数:

  1. 一个描述仅浏览器渲染的 Error。如果你给 browser 传了一个原因,它会作为错误的 cause 可用。
  2. 一个带有 componentStackerrorInfo 对象,显示了仅浏览器渲染发生的位置。

原因函数可以返回任何值。返回一个新的 Error 来给原因自己的堆栈,而不在浏览器中创建 Error。React 不会把原因序列化到 HTML 中。

🌐 The reason function can return any value. Return a new Error to give the cause its own stack without creating the Error in the browser. React does not serialize the reason into the HTML.

如果没有 Suspense 边界来提供备用内容,服务器渲染就会失败。React 会通过渲染器的常规错误回调而不是 onBrowserBailout 来报告错误。

🌐 If there is no Suspense boundary to provide a fallback, the server render fails. React reports the failure through the renderer’s usual error callbacks instead of onBrowserBailout.


正在取消浏览器的待处理服务器渲染

🌐 Aborting pending server rendering for the browser

如果你直接调用服务器渲染 API,你可以停止等待未完成的内容,让浏览器完成渲染。在中止服务器渲染时,把 browser 返回的值作为原因传入。React 会把等待中的 Suspense 边界保持在它们的备用状态,并在浏览器中渲染它们的内容:

🌐 If you call a server rendering API directly, you can stop waiting for pending content and let the browser finish rendering it. Pass the value returned by browser as the reason when aborting the server render. React then leaves pending Suspense boundaries in their fallback state and renders their content in the browser:

import { browser } from 'react-dom';
import { renderToPipeableStream } from 'react-dom/server';

const { pipe, abort } = renderToPipeableStream(<App />, {
onShellReady() {
pipe(response);
setTimeout(() => {
abort(browser('The server render timed out.'));
}, 10000);
}
});

browser 中止原因不会触发服务器渲染器的 onError 回调或 hydrateRootonRecoverableError 回调。相反,服务器渲染器会将每个恢复的 Suspense 边界报告给 onBrowserBailout

🌐 A browser abort reason does not trigger the server renderer’s onError callback or hydrateRoot’s onRecoverableError callback. Instead, the server renderer reports each recovered Suspense boundary to onBrowserBailout.

对于接受 AbortSignal 的服务器渲染 API,将 browser() 作为原因传给 AbortController.abort

🌐 For server rendering APIs that accept an AbortSignal, pass browser() as the reason to AbortController.abort.