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์€ ๋‹ค์Œ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์‹œ
  • Effect (์˜ˆ: useEffect)
  • React ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ (์˜ˆ: <button onClick={...} />)
  • React ์˜ค๋ฅ˜ ํ•ธ๋“ค๋Ÿฌ (React ๋ฃจํŠธ ์˜ต์…˜ onCaughtError, onRecoverableError, onUncaughtError)

Owner Stack์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, null์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. (๋ฌธ์ œํ•ด๊ฒฐ: Owner Stack์ด null์ธ ๊ฒฝ์šฐ)

์ฃผ์˜ ์‚ฌํ•ญ

  • Owner Stack์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. captureOwnerStack์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๋ฐ–์—์„œ๋Š” ํ•ญ์ƒ null์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ธฐ

Owner Stack vs Component Stack

The Owner Stack is different from the Component Stack available in React error handlers like errorInfo.componentStack in onUncaughtError.

์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

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) => {
    // The stacks are logged instead of showing them in the UI directly to
    // highlight that browsers will apply sourcemaps to the logged stacks.
    // Note that sourcemapping is only applied in the real browser console not
    // in the fake one displayed on this page.
    // Press "fork" to be able to view the sourcemapped stack in a real console.
    console.log(errorInfo.componentStack);
    console.log(captureOwnerStack());
  },
}).render(
  <App>
    <Component label="disabled" />
  </App>
);

SubComponent์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ์˜ค๋ฅ˜์˜ ์ปดํฌ๋„ŒํŠธ Stack์€ ๋‹ค์Œ๊ณผ ๊ฐ™์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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

๊ทธ๋Ÿฌ๋‚˜, Owner Stack์—๋Š” ๋‹ค์Œ ๋‚ด์šฉ๋งŒ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

at Component

App๊ณผ DOM ์ปดํฌ๋„ŒํŠธ๋“ค(์˜ˆ: fieldset)์€ SubComponent๋ฅผ ํฌํ•จํ•˜๋Š” ๋…ธ๋“œ๋ฅผ โ€œ์ƒ์„ฑํ•˜๋Š”โ€ ๋ฐ์— ๊ธฐ์—ฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ ์ด ์Šคํƒ์— ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. App๊ณผ DOM ์ปดํฌ๋„ŒํŠธ๋“ค์€ ๋…ธ๋“œ๋ฅผ ์ „๋‹ฌํ•  ๋ฟ์ž…๋‹ˆ๋‹ค. App์€ <SubComponent />๋ฅผ ํ†ตํ•ด SubComponent๋ฅผ ํฌํ•จํ•œ ๋…ธ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” Component์™€ ๋‹ฌ๋ฆฌ children ๋…ธ๋“œ๋งŒ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

Navigation๊ณผ legend๋Š” <SubComponent />๋ฅผ ํฌํ•จํ•˜๋Š” ๋…ธ๋“œ์˜ ํ˜•์ œ ์š”์†Œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์Šคํƒ์— ์ „ํ˜€ ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

SubComponent๋Š” ์ด๋ฏธ ํ˜ธ์ถœ ์Šคํƒ์— ํฌํ•จ๋˜์–ด ์žˆ๊ธฐ ๋–„๋ฌธ์— Owner Stack์— ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•

์ปค์Šคํ…€ ์˜ค๋ฅ˜ ์˜ค๋ฒ„๋ ˆ์ด ๊ฐœ์„ ํ•˜๊ธฐ

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({
// Keep in mind that in a real application, console.error can be
// called with multiple arguments which you should account for.
consoleMessage: args[0],
ownerStack,
});
};

console.error ํ˜ธ์ถœ์„ ๊ฐ€๋กœ์ฑ„์„œ ์˜ค๋ฅ˜ ์˜ค๋ฒ„๋ ˆ์ด์— ํ‘œ์‹œํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, captureOwnerStack์„ ํ˜ธ์ถœํ•˜์—ฌ OwnerStack์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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({
    // Keep in mind that in a real application, console.error can be
    // called with multiple arguments which you should account for.
    consoleMessage: args[0],
    ownerStack,
  });
};

const container = document.getElementById("root");
createRoot(container).render(<App />);

๋ฌธ์ œ ํ•ด๊ฒฐ

Owner Stack์ด null์ธ ๊ฒฝ์šฐ

captureOwnerStack์ด setTimeout ์ฝœ๋ฐฑ๊ณผ ๊ฐ™์ด React๊ฐ€ ์ œ์–ดํ•˜์ง€ ์•Š๋Š” ํ•จ์ˆ˜ ๋ฐ”๊นฅ์—์„œ ํ˜ธ์ถœ๋์„ ๊ฒฝ์šฐ, fetch ํ˜ธ์ถœ ํ›„, ์ปค์Šคํ…€ DOM ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ์—์„œ๋Š” Owner Stack์ด null์ด ๋ฉ๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ์ค‘์ด๋‚˜ Effect, React ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ, React ์˜ค๋ฅ˜ ํ•ธ๋“ค๋Ÿฌ (์˜ˆ: hydrateRoot#options.onCaughtError) ๋‚ด์—์„œ๋งŒ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ์—์„œ, ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋นˆ Owner Stack์ด ๋กœ๊ทธ๋กœ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” captureOwnerStack์ด ์ปค์Šคํ…€ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด์—์„œ ํ˜ธ์ถœ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Owner Stack์€ ๋” ์ด๋ฅธ ์‹œ์ , ์˜ˆ๋ฅผ ๋“ค์–ด ์ดํŽ™ํŠธ ๋‚ด๋ถ€์—์„œ captureOwnerStack๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ์ด๋™์‹œ์ผœ์•ผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์บก์ฒ˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import {captureOwnerStack, useEffect} from 'react';

export default function App() {
  useEffect(() => {
    // Should call `captureOwnerStack` here.
    function handleEvent() {
      // Calling it in a custom DOM event handler is too late.
      // The Owner Stack will be `null` at this point.
      console.log('Owner Stack: ', captureOwnerStack());
    }

    document.addEventListener('click', handleEvent);

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

  return <button>Click me to see that Owner Stacks are not available in custom DOM event handlers</button>;
}

captureOwnerStack์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ

captureOwnerStack์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๋นŒ๋“œ์—์„œ๋งŒ Export๋ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ๋นŒ๋“œ์—์„œ๋Š” undefined์ž…๋‹ˆ๋‹ค. captureOwnerStack์ด ๊ฐœ๋ฐœ๊ณผ ํ”„๋กœ๋•์…˜์ด ๋ชจ๋‘ ๋ฒˆ๋“ค๋ง๋˜๋Š” ํŒŒ์ผ์—์„œ ์‚ฌ์šฉ๋  ๋•Œ๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค import๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์กฐ๊ฑด๋ถ€๋กœ ์ ‘๊ทผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

// Don't use named imports of `captureOwnerStack` in files that are bundled for development and production.
import {captureOwnerStack} from 'react';
// Use a namespace import instead and access `captureOwnerStack` conditionally.
import * as React from 'react';

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