useInsertionEffect

์ฃผ์˜ํ•˜์„ธ์š”!

useInsertionEffect๋Š” CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž‘์„ฑ์ž๋ฅผ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž‘์—… ์ค‘์— ์Šคํƒ€์ผ์„ ์ฃผ์ž…ํ•  ์œ„์น˜๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋ฉด, useEffect ๋˜๋Š” useLayoutEffect๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

useInsertionEffect๋Š” DOM ๋ณ€๊ฒฝ ์ „์— ์‹คํ–‰๋˜๋Š” useEffect์˜ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค.

useInsertionEffect(setup, dependencies?)

๋ ˆํผ๋Ÿฐ์Šค

useInsertionEffect(setup, dependencies?)

useInsertionEffect๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ DOM ๋ณ€๊ฒฝ ์ „์— ์Šคํƒ€์ผ์„ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค:

import { useInsertionEffect } from 'react';

// CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์•ˆ์—์„œ
function useCSS(rule) {
useInsertionEffect(() => {
// ... <style> ํƒœ๊ทธ๋ฅผ ์—ฌ๊ธฐ์—์„œ ์ฃผ์ž…ํ•˜์„ธ์š” ...
});
return rule;
}

๋” ๋งŽ์€ ์˜ˆ์‹œ ๋ณด๊ธฐ

๋งค๊ฐœ๋ณ€์ˆ˜

  • setup: ์ดํŽ™ํŠธ์˜ ๋กœ์ง์ด ํฌํ•จ๋œ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. setup ํ•จ์ˆ˜๋Š” ์„ ํƒ์ ์œผ๋กœ cleanup ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ DOM์— ์ถ”๊ฐ€๋˜๊ธฐ ์ „์— React๋Š” setup ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. dependencies๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค React๋Š” ๋จผ์ € ์ด์ „ ๊ฐ’์œผ๋กœ cleanup ํ•จ์ˆ˜(์ œ๊ณตํ•œ ๊ฒฝ์šฐ)๋ฅผ ์‹คํ–‰ํ•œ ๋‹ค์Œ ์ƒˆ ๊ฐ’์œผ๋กœ setup ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ DOM์—์„œ ์ œ๊ฑฐ๋˜๊ธฐ ์ „์— React๋Š” cleanup ํ•จ์ˆ˜๋ฅผ ํ•œ ๋ฒˆ ๋” ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

  • ์„ ํƒ์‚ฌํ•ญ dependencies: setup ์ฝ”๋“œ ๋‚ด์—์„œ ์ฐธ์กฐ๋œ ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’์˜ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. ๋ฐ˜์‘ํ˜• ๊ฐ’์—๋Š” props, state, ๊ทธ๋ฆฌ๊ณ  ์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ์— ์ง์ ‘ ์„ ์–ธ๋œ ๋ชจ๋“  ๋ณ€์ˆ˜์™€ ํ•จ์ˆ˜๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. linter๊ฐ€ React์šฉ์œผ๋กœ ์„ค์ •๋œ ๊ฒฝ์šฐ, ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’์ด ์˜์กด์„ฑ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ง€์ •๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ ๋ชฉ๋ก์—๋Š” ์ผ์ •ํ•œ ์ˆ˜์˜ ํ•ญ๋ชฉ์ด ์žˆ์–ด์•ผ ํ•˜๋ฉฐ [dep1, dep2, dep3]์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. React๋Š” Object.is ๋น„๊ต ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ์˜์กด์„ฑ์„ ์ด์ „ ๊ฐ’๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ์„ ์ „ํ˜€ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค Effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜ ๊ฐ’

useInsertionEffect๋Š” undefined๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

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

  • ์ดํŽ™ํŠธ๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ Œ๋”๋ง ์ค‘์—๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • useInsertionEffect ๋‚ด๋ถ€์—์„œ๋Š” ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • useInsertionEffect๊ฐ€ ์‹คํ–‰๋˜๋Š” ์‹œ์ ์— ref๋Š” ์•„์ง ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜๊ณ , DOM๋„ ์•„์ง ์—…๋ฐ์ดํŠธ ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•

CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ๋™์  ์Šคํƒ€์ผ ์ฃผ์ž…ํ•˜๊ธฐ

์ „ํ†ต์ ์œผ๋กœ plain CSS๋ฅผ ์‚ฌ์šฉํ•ด React ์ปดํฌ๋„ŒํŠธ์˜ ์Šคํƒ€์ผ์„ ์ง€์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

// JS ํŒŒ์ผ ์•ˆ์—์„œ
<button className="success" />

// CSS ํŒŒ์ผ ์•ˆ์—์„œ
.success { color: green; }

์ผ๋ถ€ ํŒ€์€ CSS ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹  ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ์—์„œ ์ง์ ‘ ์Šคํƒ€์ผ์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์ผ๋ฐ˜์ ์œผ๋กœ CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋˜๋Š” ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. CSS-in-JS์—๋Š” ์„ธ ๊ฐ€์ง€ ์ผ๋ฐ˜์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ CSS ํŒŒ์ผ๋กœ ์ •์  ์ถ”์ถœ
  2. ์ธ๋ผ์ธ ์Šคํƒ€์ผ, ์˜ˆ: <div style={{ opacity: 1 }}>
  3. ๋Ÿฐํƒ€์ž„์— <style> ํƒœ๊ทธ ์ฃผ์ž…

CSS-in-JS๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ฒ˜์Œ ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ ๋ฐฉ์‹(์ •์  ์Šคํƒ€์ผ์˜ ๊ฒฝ์šฐ CSS ํŒŒ์ผ, ๋™์  ์Šคํƒ€์ผ์˜ ๊ฒฝ์šฐ ์ธ๋ผ์ธ ์Šคํƒ€์ผ)์„ ์กฐํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋Ÿฐํƒ€์ž„ <style> ํƒœ๊ทธ ์ฃผ์ž…์€ ๋‹ค์Œ ๋‘ ๊ฐ€์ง€ ์ด์œ ๋กœ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  1. ๋Ÿฐํƒ€์ž„ ์ฃผ์ž…์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์Šคํƒ€์ผ์„ ํ›จ์”ฌ ๋” ์ž์ฃผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  2. ๋Ÿฐํƒ€์ž„ ์ฃผ์ž…์ด React ์ƒ๋ช…์ฃผ๊ธฐ ์ค‘์— ์ž˜๋ชป๋œ ์‹œ์ ์— ๋ฐœ์ƒํ•˜๋ฉด ์†๋„๊ฐ€ ๋งค์šฐ ๋Š๋ ค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†์ง€๋งŒ useInsertionEffect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‘ ๋ฒˆ์งธ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useInsertionEffect๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ DOM ๋ณ€๊ฒฝ ์ „์— ์Šคํƒ€์ผ์„ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค:

// CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์•ˆ์—์„œ
let isInserted = new Set();
function useCSS(rule) {
useInsertionEffect(() => {
// ์•ž์„œ ์„ค๋ช…ํ–ˆ๋“ฏ์ด <style> ํƒœ๊ทธ์˜ ๋Ÿฐํƒ€์ž„ ์ฃผ์ž…์€ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
// ํ•˜์ง€๋งŒ ๊ผญ ์ฃผ์ž…ํ•ด์•ผ ํ•œ๋‹ค๋ฉด 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;
}

๋Ÿฐํƒ€์ž„ ์ธ์ ์…˜์ด ์žˆ๋Š” CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ useInsertionEffect๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.

Deep Dive

์ด๊ฒƒ์ด ๋ Œ๋”๋ง ์ค‘์— ์Šคํƒ€์ผ์„ ์ฃผ์ž…ํ•˜๊ฑฐ๋‚˜ useLayoutEffect๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์–ด๋–ป๊ฒŒ ๋” ๋‚˜์€๊ฐ€์š”?

๋ Œ๋”๋ง ์ค‘์— ์Šคํƒ€์ผ์„ ์ฃผ์ž…ํ•˜๊ณ  React๊ฐ€ non-blocking update๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ ๋ธŒ๋ผ์šฐ์ €๋Š” ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์Šคํƒ€์ผ์„ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๋ฏ€๋กœ ๋งค์šฐ ๋Š๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useInsertionEffect๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ ๋‹ค๋ฅธ Effect๊ฐ€ ์‹คํ–‰๋  ๋•Œ <style> ํƒœ๊ทธ๊ฐ€ ์ฃผ์ž…๋˜์–ด ์žˆ์Œ์„ ๋ณด์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— useLayoutEffect ๋˜๋Š” useEffect๋กœ ์Šคํƒ€์ผ์„ ์ฃผ์ž…ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋‚ซ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์˜ค๋ž˜๋œ ์Šคํƒ€์ผ๋กœ ์ธํ•ด ์ผ๋ฐ˜ Effects์˜ ๋ ˆ์ด์•„์›ƒ ๊ณ„์‚ฐ์ด ์ž˜๋ชป๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.