captureOwnerStack

captureOwnerStack 在开发环境中读取当前的 Owner Stack,并在可用时将其作为字符串返回。

const stack = captureOwnerStack();

参考

captureOwnerStack()

调用 captureOwnerStack 以获取当前的 Owner Stack。

import * as React from 'react';

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

参数

captureOwnerStack 不接受任何参数。

返回值

captureOwnerStack 返回 string | null

Owner Stacks 可在以下场景中使用:

  • 组件渲染
  • Effects(例如 useEffect
  • React 的事件处理器(例如 <button onClick={...} />
  • React 错误处理器(React Root options 中的 onCaughtErroronRecoverableErroronUncaughtError

如果没有可用的 Owner Stack,则返回 null(参见 故障排查:Owner Stack 为 null)。

注意事项

  • Owner Stacks 仅在开发环境中可用。captureOwnerStack 在开发环境之外始终返回 null
Deep Dive

Owner Stack 与 Component Stack 的区别

Owner Stack 与 React 错误处理器中可用的 Component Stack 不同,例如 onUncaughtError 中的 errorInfo.componentStack

例如,考虑以下代码:

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>
);

SubComponent 会抛出错误。
该错误的 Component Stack 将为:

at SubComponent
at fieldset
at Component
at main
at React.Suspense
at App

然而,Owner Stack 只会显示:

at Component

在这个堆栈中,App 和 DOM 组件(例如 fieldset)都不被视为 Owner,因为它们并未参与“创建”包含 SubComponent 的节点。App 和 DOM 组件只是传递了该节点。App 只是渲染了 children 节点,而 Component 则通过 <SubComponent /> 创建了一个包含 SubComponent 的节点。

Navigationlegend 根本不会出现在堆栈中,因为它们只是包含 <SubComponent /> 的节点的兄弟节点。

SubComponent 被省略了,因为它已经是调用栈的一部分。

用法

增强自定义错误覆盖层

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 来包含 Owner Stack。

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 />);

故障排查

Owner Stack 为 null

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

在下面的示例中,点击按钮会记录一个空的 Owner Stack,因为 captureOwnerStack 是在自定义 DOM 事件处理器中调用的。必须更早地捕获 Owner Stack,例如将 captureOwnerStack 的调用移动到 Effect 主体中。

import {captureOwnerStack, useEffect} from 'react';

export default function App() {
  useEffect(() => {
    // 应该在这里调用 `captureOwnerStack`。
    function handleEvent() {
      // 在自定义 DOM 事件处理器中调用它已经太晚了。
      // 此时 Owner Stack 将为 `null`。
      console.log('Owner Stack: ', captureOwnerStack());
    }

    document.addEventListener('click', handleEvent);

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

  return <button>点击我以查看在自定义 DOM 事件处理器中不可用的 Owner Stack</button>;
}

captureOwnerStack 不可用

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

// 不要在同时会被打包用于开发和生产的文件中使用 `captureOwnerStack` 的命名导入。
import {captureOwnerStack} from 'react';
// 而是改用命名空间导入,并有条件地访问 `captureOwnerStack`。
import * as React from 'react';

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