useInsertionEffect

Chaerin Kim·2023년 12월 8일

Pitfall

useInsertionEffect는 CSS-in-JS 라이브러리 작성자를 위한 것. CSS-in-JS 라이브러리에서 작업하고 스타일을 삽입할 위치가 필요한 경우가 아니라면 useEffect 또는 useLayoutEffect를 대신 사용할 수 있음.

useInsertionEffect를 사용하면 레이아웃 효과가 실행되기 전에 요소를 DOM에 삽입할 수 있음.

useInsertionEffect(setup, dependencies?)

Reference

useInsertionEffect(setup, dependencies?)

레이아웃을 읽어야하는 effect가 실행되기 전에 스타일을 삽입하려면 useInsertionEffect를 호출:

import { useInsertionEffect } from 'react';

// Inside your CSS-in-JS library
function useCSS(rule) {
  useInsertionEffect(() => {
    // ... inject <style> tags here ...
  });
  return rule;
}

Parameters

  • setup: Effect의 로직이 포함된 함수. Setup 함수는 선택적으로 cleanup 함수를 반환할 수도 있음. 컴포넌트가 DOM에 추가되었지만 레이아웃 effect가 실행되기 전에 React는 setup 함수를 실행함. 변경된 dependencies로 다시 렌더링할 때마다 React는 먼저 (사용자가 제공한 경우) 이전 값으로 cleanup 함수를 실행한 다음 새 값으로 setup 함수를 실행함. 컴포넌트가 DOM에서 제거되면 React는 cleanup 함수를 실행함.

  • dependencies (optional): setup 코드 내에서 참조된 모든 반응형 값의 목록. 반응형 값에는 props, state, 컴포넌트 본문 내부에서 직접 선언된 모든 변수와 함수가 포함됨. Linter가 React에 대해 구성된 경우, 모든 반응형 값이 dependency로 올바르게 지정되었는지 확인함. Dependencies 목록에는 일정한 수의 항목이 있어야 하며 [dep1, dep2, dep3]와 같이 인라인으로 작성해야 함. React는 Object.is 비교 알고리즘을 사용하여 각 dependency를 이전 값과 비교함. Dependencies를 전혀 지정하지 않으면 컴포넌트를 다시 렌더링할 때마다 Effect가 다시 실행됨.

Returns

undefined를 반환함.

Caveats

  • Effect는 클라이언트에서만 실행됨. 서버 렌더링 중에는 실행되지 않음.
  • useInsertionEffect 내부에서는 state를 업데이트할 수 없음.
  • useInsertionEffect가 실행될 때까지 참조는 아직 첨부되지 않음.
  • useInsertionEffect는 DOM이 업데이트되기 전이나 후에 실행될 수 있음. 특정 시점에 DOM이 업데이트되는 것에 의존해서는 안됨.
  • 모든 Effect에 대해 cleanup을 실행한 다음 모든 Effect에 대해 setup을 실행하는 다른 유형의 Effect와 달리, useInsertionEffect는 한 번에 하나의 컴포넌트에 대해 cleanup과 setup을 모두 실행함. 그 결과 cleanup과 setup 함수가 "interleaving"됨.

Usage

Injecting dynamic styles from CSS-in-JS libraries

기존에는 일반 CSS를 사용하여 React 컴포넌트의 스타일을 지정했음.

// In your JS file:
<button className="success" />

// In your CSS file:
.success { color: green; }

일부 팀은 CSS 파일을 작성하는 대신 JavaScript 코드에서 직접 스타일을 작성하는 것을 선호함. 이를 위해서는 일반적으로 CSS-in-JS 라이브러리 또는 도구를 사용해야 함. CSS-in-JS에는 세 가지 일반적인 접근 방식이 있음:

  1. 컴파일러를 사용하여 CSS 파일로 정적 추출하기
  2. 인라인 스타일(예: <div style={{ opacity: 1 }}>)
  3. <style> 태그의 런타임 삽입

CSS-in-JS를 사용하는 경우 처음 두 가지 접근 방식(정적 스타일의 경우 CSS 파일, 동적 스타일의 경우 인라인 스타일)을 조합하여 사용하는 것이 좋음. 런타임 <style> 태그 삽입은 두 가지 이유로 권장하지 않음:

  1. 런타임 삽입으로 인해 브라우저가 스타일을 훨씬 더 자주 다시 계산해야 함.
  2. 런타임 삽입이 React 라이프사이클에서 잘못된 시점에 발생하면 속도가 매우 느려질 수 있음.

첫 번째 문제는 해결할 수 없지만, useInsertionEffect는 두 번째 문제를 해결하는 데 도움이 됨.

레이아웃 effect가 실행되기 전에 스타일을 삽입하려면 useInsertionEffect를 호출:

// Inside your CSS-in-JS library
let isInserted = new Set();
function useCSS(rule) {
  useInsertionEffect(() => {
    // As explained earlier, we don't recommend runtime injection of <style> tags.
    // But if you have to do it, then it's important to do in useInsertionEffect.
    if (!isInserted.has(rule)) {
      isInserted.add(rule);
      document.head.appendChild(getStyleForRule(rule));
    }
  });
  return rule;
}

function Button() {
  const className = useCSS('...');
  return <div className={className} />;
}

useEffect와 마찬가지로 useInsertionEffect는 서버에서 실행되지 않음. 서버에서 어떤 CSS 규칙이 사용되었는지 수집해야 하는 경우 렌더링 중에 수집할 수 있음:

let collectedRulesSet = new Set();

function useCSS(rule) {
  if (typeof window === 'undefined') {
    collectedRulesSet.add(rule);
  }
  useInsertionEffect(() => {
    // ...
  });
  return rule;
}

DEEP DIVE: How is this better than injecting styles during rendering or useLayoutEffect?

렌더링 중에 스타일을 삽입하고 React가 non-blocking 업데이트를 처리하는 경우 브라우저는 컴포넌트 트리를 렌더링하는 동안 매 프레임마다 스타일을 다시 계산하므로 속도가 매우 느려질 수 있음.

useInsertionEffect는 컴포넌트에서 다른 Effect가 실행될 때쯤 이미 <style> 태그가 삽입되어 있을 것을 보장하기 때문에, useLayoutEffectuseEffect중에 스타일을 삽입하는 것보다 나음. 그렇지 않으면 오래된 스타일로 인해 일반 Effect의 레이아웃 계산이 잘못될 수 있음.

0개의 댓글