知海

captureOwnerStack()

ReactAPI 参考:React 核心
markdown 复制代码
---
title: captureOwnerStack()
---

<Intro>

`captureOwnerStack` 在开发模式下读取当前的所有者堆栈(Owner Stack),并(如果可用)以字符串形式返回。

```js
const stack = captureOwnerStack();

参考 {/reference/}

captureOwnerStack() {/captureownerstack/}

调用 captureOwnerStack 可以获取当前的所有者堆栈。

js {5,5} 复制代码
import * as React from 'react';

function Component() {
  if (process.env.NODE_ENV !== 'production') {
    const ownerStack = React.captureOwnerStack();
    console.log(ownerStack);
  }
}

参数 {/parameters/}

captureOwnerStack 不接受任何参数。

返回值 {/returns/}

captureOwnerStack 返回 string | null

所有者堆栈在以下场景中可用:

  • 组件渲染过程中
  • 副作用(Effects)中(例如 useEffect
  • React 事件处理函数中(例如 <button onClick={...} />
  • React 错误处理函数中(React Root 选项中的 onCaughtErroronRecoverableErroronUncaughtError

如果没有可用的所有者堆栈,则返回 null(参见疑难解答:所有者堆栈为 null)。

注意事项 {/caveats/}

  • 所有者堆栈仅在开发模式下可用。在非开发模式下,captureOwnerStack 始终返回 null

所有者堆栈与组件堆栈 {/owner-stack-vs-component-stack/}

所有者堆栈与 React 错误处理函数中可用的组件堆栈(如 onUncaughtError 中的 errorInfo.componentStack)不同。

例如,考虑以下代码:

js src/App.js 复制代码
import {Suspense} from 'react';

function SubComponent({disabled}) {
  if (disabled) {
    throw new Error('disabled');
  }
}

export function Component({label}) {
  return (
    <fieldset>
      <legend>{label}</legend>
      <SubComponent key={label} disabled={label === 'disabled'} />
    </fieldset>
  );
}

function Navigation() {
  return null;
}

export default function App({children}) {
  return (
    <Suspense fallback="loading...">
      <main>
        <Navigation />
        {children}
      </main>
    </Suspense>
  );
}
js src/index.js 复制代码
import {captureOwnerStack} from 'react';
import {createRoot} from 'react-dom/client';
import App, {Component} from './App.js';
import './styles.css';

createRoot(document.createElement('div'), {
  onUncaughtError: (error, errorInfo) => {
    // 在控制台中记录堆栈信息,而不是直接在 UI 中展示,以突出
    // 浏览器会将 sourcemap 应用于日志中的堆栈。
    // 请注意,sourcemap 仅在真实的浏览器控制台中应用,而不是本页面的模拟控制台。
    // 点击 "fork" 可以在真实控制台中查看经过 sourcemap 映射的堆栈。
    console.log(errorInfo.componentStack);
    console.log(captureOwnerStack());
  },
}).render(
  <App>
    <Component label="disabled" />
  </App>
);
html public/index.html hidden 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <p>Check the console output.</p>
  </body>
</html>

SubComponent 会抛出一个错误。
该错误的组件堆栈为:

复制代码
at SubComponent
at fieldset
at Component
at main
at React.Suspense
at App

然而,所有者堆栈只会包含:

复制代码
at Component

App 和 DOM 组件(例如 fieldset)都不被认为是此堆栈中的所有者,因为它们没有参与“创建”包含 SubComponent 的节点。App 和 DOM 组件只是转发了该节点。App 只是渲染了 children 节点,而 Component 是通过 <SubComponent /> 创建了包含 SubComponent 的节点。

Navigationlegend 完全不在堆栈中,因为它们只是包含 <SubComponent /> 节点的兄弟节点。

SubComponent 被省略,因为它已经在调用栈中。

用法 {/usage/}

增强自定义错误覆盖层 {/enhance-a-custom-error-overlay/}

js [[1, 5, "console.error"], [4, 7, "captureOwnerStack"]] 复制代码
import { captureOwnerStack } from "react";
import { instrumentedConsoleError } from "./errorOverlay";

const originalConsoleError = console.error;
console.error = function patchedConsoleError(...args) {
  originalConsoleError.apply(console, args);
  const ownerStack = captureOwnerStack();
  onConsoleError({
    // 请注意,在真实应用中,console.error 可能会被传入多个参数,
    // 你需要考虑到这一点。
    consoleMessage: args[0],
    ownerStack,
  });
};

如果你拦截了 console.error 的调用,以便在错误覆盖层中高亮显示它们,你可以调用 captureOwnerStack 来包含所有者堆栈。

css src/styles.css 复制代码
* {
  box-sizing: border-box;
}

body {
  font-family: sans-serif;
  margin: 20px;
  padding: 0;
}

h1 {
  margin-top: 0;
  font-size: 22px;
}

h2 {
  margin-top: 0;
  font-size: 20px;
}

code {
  font-size: 1.2em;
}

ul {
  padding-inline-start: 20px;
}

label, button { display: block; margin-bottom: 20px; }
html, body { min-height: 300px; }

#error-dialog {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: white;
  padding: 15px;
  opacity: 0.9;
  text-wrap: wrap;
  overflow: scroll;
}

.text-red {
  color: red;
}

.-mb-20 {
  margin-bottom: -20px;
}

.mb-0 {
  margin-bottom: 0;
}

.mb-10 {
  margin-bottom: 10px;
}

pre {
  text-wrap: wrap;
}

pre.nowrap {
  text-wrap: nowrap;
}

.hidden {
 display: none;
}
html public/index.html hidden 复制代码
<!DOCTYPE html>
<html>
<head>
  <title>My app</title>
</head>
<body>
<!--
  使用原始 HTML 编写的错误对话框,
  因为 React 应用中的错误可能导致其崩溃。
-->
<div id="error-dialog" class="hidden">
  <h1 id="error-title" class="text-red">Error</h1>
  <p>
    <pre id="error-body"></pre>
  </p>
  <h2 class="-mb-20">所有者堆栈:</h4>
  <pre id="error-owner-stack" class="nowrap"></pre>
  <button
    id="error-close"
    class="mb-10"
    onclick="document.getElementById('error-dialog').classList.add('hidden')"
  >
    关闭
  </button>
</div>
<!-- 这是 DOM 节点 -->
<div id="root"></div>
</body>
</html>
js src/errorOverlay.js 复制代码
export function onConsoleError({ consoleMessage, ownerStack }) {
  const errorDialog = document.getElementById("error-dialog");
  const errorBody = document.getElementById("error-body");
  const errorOwnerStack = document.getElementById("error-owner-stack");

  // 显示 console.error() 消息
  errorBody.innerText = consoleMessage;

  // 显示所有者堆栈
  errorOwnerStack.innerText = ownerStack;

  // 显示对话框
  errorDialog.classList.remove("hidden");
}
js src/index.js active 复制代码
import { captureOwnerStack } from "react";
import { createRoot } from "react-dom/client";
import App from './App';
import { onConsoleError } from "./errorOverlay";
import './styles.css';

const originalConsoleError = console.error;
console.error = function patchedConsoleError(...args) {
  originalConsoleError.apply(console, args);
  const ownerStack = captureOwnerStack();
  onConsoleError({
    // 请注意,在真实应用中,console.error 可能会被传入多个参数,
    // 你需要考虑到这一点。
    consoleMessage: args[0],
    ownerStack,
  });
};

const container = document.getElementById("root");
createRoot(container).render(<App />);
js src/App.js 复制代码
function Component() {
  return <button onClick={() => console.error('Some console error')}>触发 console.error()</button>;
}

export default function App() {
  return <Component />;
}

疑难解答 {/troubleshooting/}

所有者堆栈为 null {/the-owner-stack-is-null/}

captureOwnerStack 的调用发生在 React 控制的函数之外,例如在 setTimeout 回调中、在 fetch 调用之后,或者在自定义 DOM 事件处理函数中。在渲染期间、副作用、React 事件处理函数以及 React 错误处理函数(例如 hydrateRoot#options.onCaughtError)中,所有者堆栈是可用的。

在下面的示例中,点击按钮会记录一个空的所有者堆栈,因为 captureOwnerStack 是在自定义 DOM 事件处理函数期间调用的。所有者堆栈必须更早捕获,例如将 captureOwnerStack 的调用移动到 Effect 的主体中。

js 复制代码
import {captureOwnerStack, useEffect} from 'react';

export default function App() {
  useEffect(() => {
    // 应该在这里调用 `captureOwnerStack`。
    function handleEvent() {
      // 在自定义 DOM 事件处理函数中调用就太晚了。
      // 此时所有者堆栈将为 `null`。
      console.log('所有者堆栈:', captureOwnerStack());
    }

    document.addEventListener('click', handleEvent);

    return () => {
      document.removeEventListener('click', handleEvent);
    }
  })

  return <button>点击我,了解所有者堆栈在自定义 DOM 事件处理函数中不可用</button>;
}

captureOwnerStack 不可用 {/captureownerstack-is-not-available/}

captureOwnerStack 仅在开发构建中导出。在生产构建中,它将是 undefined。如果在同时用于生产环境和开发环境打包的文件中使用了 captureOwnerStack,你应该通过命名空间导入来有条件地访问它。

js 复制代码
// 不要在与开发和生产环境一同打包的文件中使用 `captureOwnerStack` 的命名导入。
import {captureOwnerStack} from 'react';
// 应改用命名空间导入,并有条件地访问 `captureOwnerStack`。
import * as React from 'react';

if (process.env.NODE_ENV !== 'production') {
  const ownerStack = React.captureOwnerStack();
  console.log('所有者堆栈', ownerStack);
}
复制代码

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。