New features in React 18

2022.04.05
16 minutes read
396 views
thumbnail

๋“ค์–ด๊ฐ€๋ฉด์„œ

React 18์ด ์ •์‹์œผ๋กœ Release ๋˜์—ˆ๊ณ  ๋งŽ์€ ๊ธฐ๋Šฅ๋“ค์ด ์†Œ๊ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ ๊ธฐ๋Šฅ๋“ค์„ ์กฐ๊ธˆ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ณ  ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ์‹ค์Šต๊นŒ์ง€ ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € ์–ด๋–ค ๊ธฐ๋Šฅ๋“ค์ด ์ถ”๊ฐ€ ๋˜์—ˆ๋Š”์ง€ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

React Concurrency ๊ฐœ๋…์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ๋งํฌ๋ฅผ ์ด์šฉํ•˜์‹œ๊ธธ ๊ฐ•๋ ฅํ•˜๊ฒŒ ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
Inside React - ๋™์‹œ์„ฑ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ธฐ์ˆ 
๋ฐœํ‘œ์ž๋ฃŒ

new features

React 18 ๋ณ€ํ™”๋ฅผ ํ•œ ๋‹จ์–ด๋กœ ์ •๋ฆฌํ•˜์ž๋ฉด Concurrency ์ž…๋‹ˆ๋‹ค.
๋Œ€๋ถ€๋ถ„์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์€ React์—์„œ ๋™์‹œ์„ฑ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

automatic batching

Batching์€ React๊ฐ€ ๋‹ค์ˆ˜์˜ state๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๋•Œ ๋” ๋‚˜์€ ์„ฑ๋Šฅ์„ ์œ„ํ•ด single re-renderํ•˜๋„๋ก ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

promises, setTimeout, native event handlers, React์—์„œ batch๋˜์ง€ ์•Š๋˜ event๋“ค์˜ ๊ฒฝ์šฐ์— ๊ธฐ์กด์—๋Š” ๊ฐ state๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค re-render๊ฐ€ ์ผ์–ด๋‚ฌ๋‹ค๋ฉด, react 18์—์„œ๋Š” automatic batching์œผ๋กœ ์ธํ•ด Batchingํ•˜์—ฌ ์ž๋™์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ re-renderํ•˜๋„๋ก ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

// Before: only React events were batched.
setTimeout(() => {
  setCount((c) => c + 1);
  setFlag((f) => !f);
  // React will render twice, once for each state update (no batching)
}, 1000);

// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched.`
setTimeout(() => {
  setCount((c) => c + 1);
  setFlag((f) => !f);
  // React will only re-render once at the end (that's batching!)
}, 1000);
jsx

transitions

legacy-rendering

concurrent-rendering

  • Urgent updates(๊ธด๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ)์€ ํƒ€์ดํ•‘, ํด๋ฆญ, ํ‚ค์ž…๋ ฅ๊ณผ ๊ฐ™์€ ๊ฒƒ๋“ค ๋ฐ”๋กœ ๋ฐ˜์‘ํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ๋“ค์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
  • Transition updates(ํŠธ๋žœ์ง€์…˜ ์—…๋ฐ์ดํŠธ)์€ ํ•˜๋‚˜์˜ view์—์„œ ๋˜ ๋‹ค๋ฅธ view๋กœ UI๋ฅผ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

ํƒ€์ดํ•‘, ํด๋ฆญ, ํ‚ค์ž…๋ ฅ๊ณผ ๊ฐ™์€ Urgent Update๋Š” ๋ฌผ๋ฆฌ์  ๊ฐ์ฒด๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์šฐ๋ฆฌ์˜ ์ง๊ด€๊ณผ ๊ด€๋ จ๋˜์–ด ์žˆ์–ด์„œ ์ฆ‰๊ฐ์ ์ธ ์‘๋‹ต์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ์›€์ง์ด์ง€ ์•Š๋Š”๋‹ค๋ฉด "์ž˜๋ชป๋œ" ๋А๋‚Œ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ํŠธ๋žœ์ง€์…˜์€ ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋ฆฐ์— ํŠธ๋žœ์ง€์…˜ ์ค‘๊ฐ„์˜ ๋ชจ๋“  ๊ฐ’์„ ๋ณผ ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— urgent update์™€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์ตœ์ƒ์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•ด์„œ๋Š” ๋‹จ์ผ user input์€ urgent update์™€ non-urgent update ๋ชจ๋‘ ๋ฐœ์ƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. input event ๋‚ด์— startTransition API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–ค update๊ฐ€ urgentํ•œ์ง€, ์–ด๋–ค ๊ฒƒ์ด "transition"์ธ์ง€ React์—๊ฒŒ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

with.png
with transition : concurrent
without.png
without transition : blocked

transition์„ ์‚ฌ์šฉํ–ˆ๋˜ ์œ„ ์‚ฌ์ง„์—์„œ๋Š” task๊ฐ€ ์ž˜๊ฒŒ ์ชผ๊ฐœ์ ธ์„œ concurrent ํ•˜๊ฒŒ ๋™์ž‘๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ transition์ด ์—†๋Š” ์•„๋ž˜ ์‚ฌ์ง„์€ long task๊ฐ€ block๋œ ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. input์— ํƒ€์ดํ•‘์„ ํ•˜๋”๋ผ๋„ block์œผ๋กœ ์ธํ•ด ํ™”๋ฉด์— ๋ Œ๋”๋ง๋˜์ง€ ์•Š์€ ์ด์œ ์ž…๋‹ˆ๋‹ค.

import { startTransition } from 'react';

const handleInput = () => {
  // Urgent: Show what was typed
  setInputValue(input);

  // Mark any state updates inside as transitions
  startTransition(() => {
    // Transition: Show the results
    setSearchQuery(input);
  });
};
jsx

startTransition์œผ๋กœ ๊ฐ์‹ธ์ง„ update๋Š” non-urgent(๊ธ‰ํ•˜์ง€ ์•Š์€ ๊ฒƒ)์œผ๋กœ ๋‹ค๋ค„์ง‘๋‹ˆ๋‹ค.
๋งŒ์•ฝ click์ด๋‚˜ ํ‚ค์ž…๋ ฅ๊ณผ ๊ฐ™์€ ๋” urgent(๊ธด๊ธ‰ํ•œ) ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด interrupt ๋ฉ๋‹ˆ๋‹ค.

๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ transition์„ ์ผ์‹œ์ค‘๋‹จํ•˜๋ฉด(์˜ˆ๋ฅผ ๋“ค์–ด, ์—ฌ๋Ÿฌ ๋ฌธ์ž๋ฅผ ์—ฐ์†์œผ๋กœ ์ž…๋ ฅ), React๋Š” ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์˜ค๋ž˜๋œ rendering ์ž‘์—…์„ throw out(๋ฒ„๋ฆผ)ํ•˜๊ณ  ์ตœ์‹  ์—…๋ฐ์ดํŠธ๋งŒ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
๋‹ค์‹œ ๋งํ•ด, ๋‹ค์ˆ˜์˜ ๊ธ€์ž๋ฅผ ์ž…๋ ฅํ•  ๋•Œ ๋ Œ๋”๋ง์„ ๊ธฐ๋‹ค๋ ธ๋˜ ์ค‘๊ฐ„ ์ƒํƒœ๋“ค์€ ๋ฒ„๋ ค์ง€๊ณ  ๋งˆ์ง€๋ง‰ ์ƒํƒœ๋งŒ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

  • useTransition: transition์„ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•œ pending ์ƒํƒœ๋ฅผ ํฌํ•จํ•œ hook
  • startTransition: hook์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š์„ ๋•Œ transition์„ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•œ method

transition์€ concurrent rendering์—์„œ ํ•ด๋‹น update๋ฅผ ์ผ์‹œ์ค‘๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. content๊ฐ€ ๋‹ค์‹œ ์ผ์‹œ์ค‘๋‹จ(re-suspend) ์ƒํƒœ๊ฐ€ ๋œ๋‹ค๋ฉด, transition์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ trantition content๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ ํ˜„์žฌ content๋ฅผ ๊ณ„์† ๋ณด์—ฌ์ฃผ๋„๋ก React์—๊ฒŒ ๋งํ•ฉ๋‹ˆ๋‹ค.

Note:
transition์—์„œ update๋Š” ํด๋ฆญ๊ณผ ๊ฐ™์€ ๋ณด๋‹ค ๊ธด๊ธ‰ํ•œ(urgent) update๋ฅผ ์œ„ํ•ด ์ผ์‹œ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค.
transition์—์„œ update๋Š” ๋‹ค์‹œ ์ผ์‹œ์ค‘๋‹จ(re-suspend)๋œ content๋ฅผ ์œ„ํ•ด fallback๋ฅผ ๋ณด์—ฌ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
update๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ๋™์•ˆ์— ์‚ฌ์šฉ์ž์—๊ฒŒ ํ˜„์žฌ content์™€ ๊ณ„์†ํ•ด์„œ ์ƒํ˜ธ์ž‘์šฉํ•˜๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

๊ธฐ์กด์— ๋ Œ๋”๋ง block์ด ํ•œ ๋ฌถ์Œ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ๋ฌด๊ฑฐ์šด ๋ Œ๋”๋ง์ด ์žˆ์„ ๊ฒฝ์šฐ ๋™์‹œ์„ฑ ๋ณด์žฅ์ด ํž˜๋“ค์—ˆ๋‹ค๋ฉด, useTransition์„ ํ†ตํ•ด ์ž‘์—…์„ ๋‚˜๋ˆ„๊ณ  ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋‘์–ด ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

์ •๋ฆฌํ•˜์ž๋ฉด input์— ๊ฐ’์„ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ urgent update๋Š” ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ฐ˜์‘์„ ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ heavyํ•œ render๋กœ ์ธํ•ด urgent update๊ฐ€ prevent ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ์ด๊ฒƒ์€ UX์— ์•ˆ ์ข‹์€ ์˜ํ–ฅ์„ ์ค๋‹ˆ๋‹ค. useTransition์€ ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. non-urgent update๋ฅผ startTransition์œผ๋กœ ๊ฐ์‹ผ๋‹ค๋ฉด react๋Š” '์ด๊ฒƒ์€ non-urgent update๊ตฌ๋‚˜'๋ผ๊ณ  ์ดํ•ดํ•˜๊ณ  render๋ฅผ urgnet update๋ณด๋‹ค ํ›„์ˆœ์œ„๋กœ ๋ฏธ๋ฃน๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์‚ฌ๋žŒ์˜ ์ธ์ง€์™€ ๊ด€๋ จ์žˆ๋Š” urgent update๋ฅผ ๋” ๋น ๋ฅด๊ฒŒ ๋ Œ๋”๋งํ•˜์—ฌ ๋” ๋‚˜์€ UX๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

useTransition vs useDeferredValue

react 18์—์„œ ๊ณต๊ฐœ๋œ useDeferredValue ๋ผ๋Š” hook์ด ์žˆ์Šต๋‹ˆ๋‹ค.
๋™์‹œ์„ฑ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ๋˜๊ธฐ์— useTransition๊ณผ ์–ผํ• ๋น„์Šทํ•ด๋ณด์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.
์ € ๋˜ํ•œ ์ฐจ์ด๊ฐ€ ์–ด๋–ค ์ ์—์„œ ๋ฐœ์ƒํ•˜๋Š”์ง€ ๋А๋‚Œ์œผ๋กœ๋งŒ ์•Œ๊ณ  ์žˆ๋Š”๋ฐ ์ž์„ธํžˆ ๋ณด๊ธฐ ์œ„ํ•ด ์กฐ๊ธˆ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

useTransition์€ ์œ„์—๋„ ์‚ดํŽด๋ณด์•˜๋“ฏ์ด React์—๊ฒŒ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ update๋ฅผ ์•Œ๋ ค์ฃผ์–ด ํ•ด๋‹น non-urgent update๋ฅผ urgent update ๋’ค๋กœ rebaseํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ useTransition์€ ์–ด๋–ค ์ฝ”๋“œ๋ฅผ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ๋‹ค๋ฃฐ์ง€, ์–ด๋–ค ์ฝ”๋“œ๋ฅผ wrappingํ• ์ง€ ๊ฒฐ์ •ํ•˜๋„๋ก ๋ชจ๋“  ๊ถŒํ•œ์„ ์ค๋‹ˆ๋‹ค.

function App() {
  const [isPending, startTransition] = useTransition();
  const [filterTerm, setFilterTerm] = useState('');

  const filteredProducts = filterProducts(filterTerm);

  function updateFilterHandler(event) {
    startTransition(() => {
      setFilterTerm(event.target.value);
    });
  }

  return (
    <div id="app">
      <input type="text" onChange={updateFilterHandler} />
      {isPending && <p>Updating List...</p>}
      <ProductList products={filteredProducts} />
    </div>
  );
}
jsx

๊ทธ๋Ÿฌ๋‚˜ ์ฝ”๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ์‹ค์ œ ์ƒํƒœ์— ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด third-party library์—์„œ ์ˆ˜ํ–‰๋˜๋Š” ๊ฒฝ์šฐ ๋“ฑ). ๋˜๋Š” ๋ช‡๊ฐ€์ง€ ์ด์œ ๋กœ ์ธํ•ด useTransition์„ ์‚ฌ์šฉ ๋ชปํ•˜๊ฒŒ ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์— useDeferredValue๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useDeferredValue๋Š” ์ƒํƒœ๋ฅผ wrapping ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹ ์— ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋•Œ๋ฌธ์— ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์žฌ์ƒ์„ฑ๋œ value๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค(๊ทธ ์ƒํƒœ ๊ทธ๋Œ€๋กœ ์“ฐ๊ฑฐ๋‚˜ ๋˜๋Š” ๊ธฐ๋ณธ ์ƒํƒœ์— computed ๊ฐ’).

function ProductList({ products }) {
  const deferredProducts = useDeferredValue(products);
  return (
    <ul>
      {deferredProducts.map((product) => (
        <li>{product}</li>
      ))}
    </ul>
  );
}
jsx

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ๋ฌด์—‡์„ ์จ์•ผํ•ฉ๋‹ˆ๊นŒ?
์œ„์—์„œ ์—…๊ธ‰ํ–ˆ๋“ฏ์ด useTransition์€ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์ฝ”๋“œ๋ฅผ wrappingํ•˜๋Š” ๋ฐ˜๋ฉด useDeferredValue๋Š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ์˜ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๊ฐ’(์ƒํƒœ ๊ทธ๋Œ€๋กœ์˜ ๊ฐ’ ๋˜๋Š” ์ƒํƒœ์—์„œ computed๋œ ๊ฐ’)์„ ์“ด๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

๊ฒฐ๊ตญ ๋‘˜ ๋‹ค ๊ฐ™์€ ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.

๋” ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ๊ณ  ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์ฝ”๋“œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ useTransition์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค.
ํ•ด๋‹น ์ฝ”๋“œ์— ์ ‘๊ทผ ๊ถŒํ•œ์ด ์—†๋‹ค๋ฉด useDeferredValue๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

๋ฐ˜๋“œ์‹œ ์จ์•ผํ• ๊นŒ์š”?
๋ชจ๋“  ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ useTransition์ด๋‚˜ useDeferredValue๋กœ wrappingํ•˜์ง€ ๋งˆ์„ธ์š”.
๋‹ค๋ฅธ ์ˆ˜๋‹จ์œผ๋กœ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์—†๋Š” ๋ณต์žกํ•œ UI๋‚˜ component๊ฐ€ ์žˆ์„ ๋•Œ ์ด๋Ÿฌํ•œ hook์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
lazy loading ์‚ฌ์šฉ, pagination ์‚ฌ์šฉ, worker thread์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ, backend ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ ๋“ฑ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ํ•ญ์ƒ ์—ผ๋‘ํ•ด ๋‘์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Suspense

Suspense๋Š” ์•„์ง ํ™”๋ฉด์— ๋ณด์—ฌ์ค„ ์ค€๋น„๊ฐ€ ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์— component ํŠธ๋ฆฌ์˜ ์ผ๋ถ€์— ๋Œ€ํ•œ loading ์ƒํƒœ๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>
jsx

Suspense๋Š” React ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ชจ๋ธ์—์„œ "UI loading ์ƒํƒœ"๋ฅผ first-class ์„ ์–ธ์  ๊ฐœ๋…์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
(์ฆ‰, <Component>...</Component> ํ˜•ํƒœ๋กœ ์“ธ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ง์ž…๋‹ˆ๋‹ค.)

fisrt-class(์ผ๊ธ‰๊ฐ์ฒด)
๋‹ค๋ฅธ ๊ฐ์ฒด๋“ค์— ์ผ๋ฐ˜์ ์œผ๋กœ ์ ์šฉ ๊ฐ€๋Šฅํ•œ ์—ฐ์‚ฐ์„ ๋ชจ๋‘ ์ง€์›ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต ํ•จ์ˆ˜์— ์ธ์ž๋กœ ๋„˜๊ธฐ๊ธฐ, ์ˆ˜์ •ํ•˜๊ธฐ, ๋ณ€์ˆ˜์— ๋Œ€์ž…ํ•˜๊ธฐ์™€ ๊ฐ™์€ ์—ฐ์‚ฐ์„ ์ง€์›ํ•  ๋•Œ ์ผ๊ธ‰ ๊ฐ์ฒด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ๋Š” ํ•จ์ˆ˜๊ฐ€ ์ผ๊ธ‰๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ ์„ ์–ธ์  ๊ฐœ๋…์œผ๋กœ ๋‹ค๋ฃจ๋ฉด <Component> ํ˜•ํƒœ๋กœ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.(React.createElement(...)์™€ ๋™์ผํ•˜๋ฉฐ ์ด ํ‘œํ˜„์‹์€ ๋ช…๋ นํ˜• ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.)

์ด๋ฅผ ํ†ตํ•ด ๋” ๋†’์€ ์ˆ˜์ค€์˜ ๊ธฐ๋Šฅ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ช‡ ๋…„ ์ „์— ์ œํ•œ๋œ ๋ฒ„์ „์˜ Suspense๋ฅผ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์œ ์ผํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” React.lazy ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…์— ์‚ฌ์šฉ๋œ ๊ฒฝ์šฐ์˜€์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•  ๋•Œ๋Š” ์ „ํ˜€ ์ง€์›๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

React 18์—์„œ๋Š” ์„œ๋ฒ„์—์„œ Suspense์— ๋Œ€ํ•œ ์ง€์›์„ ์ถ”๊ฐ€ํ•˜๊ณ  Concurrent rendering ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.

Suspense๋Š” transition API(useTransition, startTransition)์™€ ํ•จ๊ป˜ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
transitionํ•˜๋Š” ๋™์•ˆ์— suspend(์ผ์‹œ์ค‘์ง€) ๋œ๋‹ค๋ฉด React๋Š” ์ด๋ฏธ ๋ณด์ด๋Š”(already-visible) ์ปจํ…์ธ ๊ฐ€ fallback์œผ๋กœ ๋Œ€์ฒด๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  React๋Š” ์ž˜๋ชป๋œ loading ์ƒํƒœ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด data๊ฐ€ ์ถฉ๋ถ„ํžˆ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋ Œ๋”๋ง์„ ์ง€์—ฐํ•ฉ๋‹ˆ๋‹ค.

Streaming HTML and Selective Hydration

https://github.com/reactwg/react-18/discussions/37
https://codesandbox.io/s/kind-sammet-j56ro?file=/src/App.js

React์—์„œ SSR์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํ•ญ์ƒ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

  1. ์„œ๋ฒ„์—์„œ: App์— ํ•„์š”ํ•œ data๋ฅผ fetch ํ•ฉ๋‹ˆ๋‹ค.
  2. ์„œ๋ฒ„์—์„œ: App์„ HTML๋กœ renderํ•˜๊ณ  response๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  3. ํด๋ผ์ด์–ธํŠธ์—์„œ: App์„ ์œ„ํ•œ JavaScript ์ฝ”๋“œ๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.
  4. ํด๋ผ์ด์–ธํŠธ์—์„œ: ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ HTML์— JavaScript ๋กœ์ง์„ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.(์ด๊ฒƒ์„ "hydration"์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.)

์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋Š” ๋‹ค์Œ ๋‹จ๊ณ„ ์‹œ์ž‘ ์ „์— ๊ฐ ๋‹จ๊ณ„๊ฐ€ ์ „์ฒด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ ์ž‘์—…์„ ํ•œ ๋ฒˆ์— ์™„๋ฃŒ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•ฑ์˜ ๊ฑฐ์˜ ๋ชจ๋“  ๋ถ€๋ถ„์ด non-trival์ธ ๊ฒฝ์šฐ, ์•ฑ์˜ ์ผ๋ถ€๊ฐ€ ๋‹ค๋ฅธ ๋ถ€๋ถ„๋ณด๋‹ค ๋А๋ฆฐ ๊ฒฝ์šฐ, ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” ํšจ์œจ์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

csr-only.png
1. CSR: ๋นˆ HTML

SSR์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ JavaScript๊ฐ€ ๋กœ๋“œ๋˜๋Š” ๋™์•ˆ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์€ ๋นˆ ํŽ˜์ด์ง€๋ฟ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๊ถŒ์žฅ๋˜์ง€ ์•Š์œผ๋ฉฐ ์ธํ„ฐ๋„ท ์†๋„๊ฐ€ ๋А๋ฆฐ ๊ฒฝ์šฐ(์ผ๋ฐ˜์ ์œผ๋กœ JavaScript ํฌ๊ธฐ๊ฐ€ ํฌ๊ธฐ ๋•Œ๋ฌธ์—) ํŠนํžˆ ๋” ์‹ฌํ•ด์ง‘๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ SSR์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

ssr-yet-hydration.png
2. SSR + hydration X: ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ HTML, but ์ด๋ฒคํŠธ๊ฐ€ X

SSR์„ ์‚ฌ์šฉํ•˜์—ฌ React components๋ฅผ HTML๋กœ ๋ Œ๋”๋งํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ HTML์€ ๋งค์šฐ ์ƒํ˜ธ์ž‘์šฉ์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.(link, form, ๋“ฑ ๊ฐ„๋‹จํ•œ built-in Web interactive ๋“ฑ ์ œ์™ธ, ๋งŒ์•ฝ build-in Web interactive๋กœ๋งŒ ์ž‘์„ฑ๋˜์—ˆ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฌธ์ œ๋Š” ์—†์Šต๋‹ˆ๋‹ค.) ํ•˜์ง€๋งŒ ์žฅ์ ์œผ๋กœ๋Š” JavaScript๊ฐ€ ๋กœ๋“œ๋˜๋Š” ๋™์•ˆ(hydration) ์‚ฌ์šฉ์ž๋Š” ํ™”๋ฉด์— ๋ฌด์–ธ๊ฐ€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์œ„ ์‚ฌ์ง„์—์„œ ํšŒ์ƒ‰ ํ‘œ์‹œ๋Š” ์™„์ „ํžˆ ์ƒํ˜ธ์ž‘์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ทธ ๋ถ€๋ถ„์— ์ƒํ˜ธ์ž‘์šฉ์ด ํ•„์š”ํ•œ JavaScript ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ด๋ฒคํŠธ๋ฅผ ํ˜ธ์ถœ(ํด๋ฆญ๊ณผ ๊ฐ™์€)ํ•˜๋”๋ผ๋„ ์•„๋ฌด ์ž‘์—…๋„ ์ˆ˜ํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋ฉด CSR๊ณผ ์ฐจ์ด๊ฐ€ ์—†๋Š”๊ฑฐ ์•„๋‹๊นŒ์š”? JavaScript๋ฅผ ๋กœ๋“œํ•˜๋Š” ์‹œ๊ฐ„์€ CSR๊ณผ ๋™์ผํ•œ๋ฐ SSR ์‹œ๊ฐ„์ด ์ถ”๊ฐ€๊ฐ€ ๋œ ๊ฑฐ ์•„๋‹Œ๊ฐ€์š”?

๊ทธ๋Ÿฌ๋‚˜ ํŠนํžˆ ์ฝ˜ํ…์ธ ๊ฐ€ ๋งŽ์€ ์›น ์‚ฌ์ดํŠธ์˜ ๊ฒฝ์šฐ SSR์€ JavaScript๊ฐ€ ๋กœ๋“œ๋˜๋Š” ๋™์•ˆ ์—ฐ๊ฒฐ ์ƒํƒœ๊ฐ€ ์ข‹์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
๋˜ํ•œ CSR๊ณผ ๋‹ฌ๋ฆฌ ๋ฉ”๋ชจ๋ฆฌ์— ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ๋ Œ๋”๋ง์€ ํ•˜์ง€๋งŒ ์ด์— ๋Œ€ํ•œ DOM ๋…ธ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋Œ€์‹  ๊ธฐ์กด HTML DOM ๋…ธ๋“œ์— ์ด๋ฒคํŠธ๋ฅผ ๋ถ™์ด๋Š” ํ˜•ํƒœ๋กœ ์ž‘์—…๋ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์„ hydration์ด๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. ๊ฑด์กฐํ•œ HTML(SSR๋กœ ์ƒ์„ฑ๋œ HTML)์— ๋ฌผ์„ ์ฃผ๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค๊ณ  ํ•ด์„œ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค.

ssr+hydration.png
3. SSR + hydration O: ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ HTML + ์ด๋ฒคํŠธ

SSR์€ ์ผ์ข…์˜ "๋งˆ์ˆ  ํŠธ๋ฆญ"์ž…๋‹ˆ๋‹ค. ์•ฑ์ด ์™„์ „ํžˆ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์†๋„๊ฐ€ ๋นจ๋ผ์ง€์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ๋น„๋Œ€ํ™”ํ˜•(non-interactive) ๋ฒ„์ „์˜ ์•ฑ์„ ๋” ๋นจ๋ฆฌ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์‚ฌ์šฉ์ž๋Š” JavaScript๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ ์ •์  ์ฝ˜ํ…์ธ ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์ƒํƒœ๊ฐ€ ์ข‹์ง€ ์•Š์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ํฐ ์ฐจ์ด๋ฅผ ๋งŒ๋“ค๊ณ  ์ „๋ฐ˜์ ์œผ๋กœ ์ธ์ง€์ ์ธ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋” ์‰ฌ์šด ์ธ๋ฑ์‹ฑ๊ณผ ๋” ๋‚˜์€ ์†๋„ ๋•๋ถ„์— SEO์—๋„ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

Note:
SSR์„ Server Component์™€ ํ—ท๊ฐˆ๋ฆฌ์ง€ ๋งˆ์„ธ์š”. Server Component๋Š” ํ˜„์žฌ ์‹คํ—˜์ ์ธ ๊ธฐ๋Šฅ์ด๋ฉฐ ์—ฌ์ „ํžˆ ์—ฐ๊ตฌ์ค‘์— ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ดˆ๊ธฐ React 18 ๋ฆด๋ฆฌ์ฆˆ์—๋Š” ํฌํ•จ๋˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ Server Component๋ฅผ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Server Component๋Š” SSR์„ ๋ณด์™„ํ•˜๊ณ , data fetching ์ ‘๊ทผ๋ฒ•์ค‘ ์ถ”์ฒœ๋˜๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์—ฌ๊ธฐ์„  ๋‹ค๋ฃจ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์œ„์— ์ ‘๊ทผ ๋ฐฉ์‹์€ ํšจ๊ณผ๊ฐ€ ์žˆ์ง€๋งŒ ์—ฌ๋Ÿฌ ๋ฉด์—์„œ ์ตœ์ ์ด ์•„๋‹™๋‹ˆ๋‹ค.

  • ์–ด๋–ค ๊ฒƒ์„ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ชจ๋“  ๊ฒƒ์„ fetch ํ•ด์•ผํ•˜๋Š” ๊ฒƒ
  • ์–ด๋–ค ๊ฒƒ์„ hydrateํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ load ํ•ด์•ผํ•˜๋Š” ๊ฒƒ
  • ์–ด๋–ค ๊ฒƒ์„ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ชจ๋“  ๊ฒƒ์— hydrate ํ•ด์•ผํ•˜๋Š” ๊ฒƒ

React 18์—์„  ์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด <Suspense>๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ์กด Suspense๋Š” Reacy.lazy๋ฅผ ์ด์šฉํ•œ Code Splitting์—์„œ๋งŒ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค๋งŒ React 18์—์„œ๋Š” Server Side์—์„œ๋„ ์ž˜ ๋™์ž‘ํ•˜๋ฉฐ, ์œ„ ๋ฌธ์ œ๋ฅผ ๋” ๋‚˜์€ ๋ฐฉํ–ฅ์œผ๋กœ ํ•ด๊ฒฐํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

React 18์—์„œ <Suspense>๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ฑ์„ ์ž‘๊ณ  ๋…๋ฆฝ์ ์ธ ๋‹จ์œ„๋กœ ๋‚˜๋ˆ„์–ด์ฃผ์–ด ์œ„์˜ ๋‹จ๊ณ„๋“ค์—์„œ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋˜๋„๋ก ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์•ฑ์˜ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์ด block๋˜์ง€ ์•Š๋„๋ก ํ•ด์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์•ฑ ์‚ฌ์šฉ์ž๋Š” content๋ฅผ ๋” ๋นจ๋ฆฌ ๋ณด๊ณ  ํ›จ์”ฌ ๋” ๋นจ๋ฆฌ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ React.lazy๊ฐ€ SSR ํ™˜๊ฒฝ์—์„œ "์ž‘๋™"ํ•˜๊ฒŒ ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
(ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์„œ๋ฒ„์—์„œ HTML generateํ•˜๋Š” ๋ถ€๋ถ„์„ ๋ฐ”๊ฟ”์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค.์˜ˆ์‹œ)

Suspense๊ธฐ๋Šฅ์œผ๋กœ ์ธํ•ด unlock๋œ React 18์—์„œ 2๊ฐ€์ง€ ์ค‘์š”ํ•œ feature๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Streaming HTML on the server: switch renderToString to new renderToPipeableStream
  • Selective hydration on the client: switch to new hydrateRoot. and wrapping parts of your app with <Suspense>

์—ฌ๋‹ด: server-side rendering ์ง์ ‘ ๋งŒ๋“ค์–ด์„œ ํ…Œ์ŠคํŠธํ•ด๋ณด๋ ค๋‹ค๊ฐ€ ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ์ปค์ง„ ์ผ€์ด์Šค๋ž„๊นŒ... ์ฒ˜์Œ React SSR ์„œ๋ฒ„๋ฅผ ์ง์ ‘ ์ž‘์„ฑ(์ด๋ผ๊ณค ํ•˜์ง€๋งŒ ๊ตฌ๊ธ€์—์„œ ๋Œ€๋ถ€๋ถ„ ๊ฐ€์ ธ์˜ด)ํ•ด๋ณด์•˜๋Š”๋ฐ ์ƒ๋‹นํžˆ ๋ฒ„๊ฑฐ์šด ๋А๋‚Œ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜๋„ ๋™์ž‘?์€ ๋˜์—ˆ๊ณ  Suspense๊ฐ€ server-side์—์„œ๋„ ์ž˜ ๋™์ž‘ํ•˜๋„๋ก ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์žˆ๋Š”๊ฑธ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค. loadable-component์™€ ๊ฐ™์€ ๋ณ„๋„์˜ tool์—†์ด Suspense SSR์ด ์ ์šฉ์ด ๊ฐ€๋Šฅํ•œ ๊ฒƒ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ๊ฒƒ์„ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ชจ๋“  ๊ฒƒ์„ fetch ํ•ด์•ผํ•˜๋Š” ๊ฒƒ

streaming.png
๋ชจ๋‘ fetchํ•˜๊ธฐ ์ „์— HTML Streaming

Streaming HTML์ด ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>
jsx

Suspense๋ฅผ ํ†ตํ•ด Comments๊ฐ€ ์™„๋ฃŒ๋˜๋Š”์ง€ ์•ˆ๋˜๋Š”์ง€ ์ƒ๊ด€์—†์ด HTML์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํด๋ผ์ด์–ธํŠธ๋Š” ์ฒ˜์Œ HTML์„ ๋ฐ›๊ณ  ๋‚œ ํ›„๋ถ€ํ„ฐ Comments๊ฐ€ ๋งŒ๋“ค์–ด์ง€๊ธฐ ๊นŒ์ง€ Suspense๊ฐ€ fallback์— ์„ ์–ธํ•ด๋‘” component๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋˜๊ณ , Comments๊ฐ€ ์™„๋ฃŒ๋œ ์‹œ์ ์— ์ถ”๊ฐ€ HTML์„ ๋™์ผ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณด๋‚ด๊ณ  ํ•ด๋‹น HTML์„ ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ๋„ฃ์„ ์ˆ˜ ์žˆ๊ฒŒ ์ž‘์€ ์ธ๋ผ์ธ <script> ํƒœ๊ทธ๋ฅผ ๋ณด๋‚ด์ฃผ์–ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ๋„ฃ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

๋˜ํ•œ ๊ธฐ์กด HTML Streaming ๋ฐฉ์‹๊ณผ ๋‹ค๋ฅด๊ฒŒ ํƒ‘๋‹ค์šด ์ˆœ์„œ๋กœ ์ง„ํ–‰๋  ํ•„์š”๋„ ์—†์Šต๋‹ˆ๋‹ค.
๋ฐ์ดํ„ฐ๊ฐ€ ํŠน์ • ์ˆœ์„œ์— ๋งž์ถฐ ๋กœ๋“œ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ์š”๊ตฌ์‚ฌํ•ญ๋„ ์—†์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ๊ฒƒ์„ hydrateํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ load ํ•ด์•ผํ•˜๋Š” ๊ฒƒ

์œ„ ํ•ด๊ฒฐ๋กœ ์ธํ•ด ์ดˆ๊ธฐ HTML์„ ๋” ์ผ์ฐ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. JavaScript ์ฝ”๋“œ๊ฐ€ ๋ชจ๋‘ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ํด๋ผ์ด์–ธํŠธ์—์„œ hydration์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

ํฐ ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ๋กœ "์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…"์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ํŠน์ • ์ฝ”๋“œ ๋ถ€๋ถ„์ด ๋™๊ธฐ์ ์œผ๋กœ ๋กœ๋“œ๋  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ผ๊ณ  ๋ช…์‹œํ•ด์ฃผ๋ฉด ๋ฒˆ๋“ค๋Ÿฌ๊ฐ€ ์ด๋ฅผ ๋ณ„๋„์˜ <script>ํƒœํฌ๋กœ ๋ถ„ํ• ํ•ฉ๋‹ˆ๋‹ค.

React์—์„œ๋Š” React.lazy๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ช…์‹œํ•ด์ค„ ์ˆ˜ ์žˆ๊ณ  ๋ฉ”์ธ ๋ฒˆ๋“ค์—์„œ <Comments>๋ฅผ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ… ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { lazy } from 'react';

const Comments = lazy(() => import('./Comments.js'));

// ...

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>;
jsx

์ด์ „์—๋Š” server rendering์—์„œ ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. (์šฐ๋ฆฌ๊ฐ€ ์•„๋Š”ํ•œ, ์œ ๋ช…ํ•œ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์กฐ์ฐจ๋„ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ… component์— ๋Œ€ํ•ด SSR์—์„œ ์ œ์™ธํ•˜๊ฑฐ๋‚˜ ๊ทธ ์ฝ”๋“œ๊ฐ€ ๋ชจ๋‘ ๋กœ๋“œ๋œ ์ดํ›„์— hydrateํ•˜๋Š” ๊ฒƒ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜๋„๋ก ๊ฐ•์š”ํ•˜์—ฌ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ… ๋ชฉ์ ์„ ๋‹ค์†Œ ๋ฌด๋ ฅํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.)

๊ทธ๋Ÿฌ๋‚˜ React 18์—์„œ๋Š” <Comments>๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „์— hydrate ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

selective-hydrate.png
๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ load๋˜๊ธฐ ์ „์— hydrate

์ด๊ฒƒ์€ Selective hydration์˜ ํ•œ ์˜ˆ์ž…๋‹ˆ๋‹ค. <Comments>๊ฐ€ ์•„์ง ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์–ด๋„ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์ด hydrateํ•˜๋Š” ๊ฒƒ์„ ๋ง‰์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ ํ›„ <Comments>๊ฐ€ ๋กœ๋“œ๋˜๋ฉด ํ•ด๋‹น ๋ถ€๋ถ„์„ hydrateํ•˜๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

selective hydration ๋•๋ถ„์— JavaScript์˜ ํŽ˜์ด์ง€์˜ ๋ฌด๊ฑฐ์šด ๋ถ€๋ถ„์ด ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์„ interactive๋˜๋Š” ๊ฒƒ์„ ๋ง‰์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ๊ฒƒ์„ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ชจ๋“  ๊ฒƒ์— hydrate ํ•ด์•ผํ•˜๋Š” ๊ฒƒ

streaming HTML๊ณผ selective hydrateion์œผ๋กœ ์ธํ•ด hydrate ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์„ ๋”์ด์ƒ ์ฐจ๋‹จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

interactive-hydrate.png
๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ hydrate๋˜๊ธฐ ์ „์— interactive

React 18์—์„œ๋Š” Suspense ๋‚ด๋ถ€์˜ hydrate๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž‘์€ ๊ฐ„๊ฒฉ์œผ๋กœ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋•๋ถ„์— ํด๋ฆญ์ด ์ฆ‰์‹œ ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ๊ณ  low-end ์žฅ์น˜์—์„œ๋„ ์˜ค๋žœ hydrate ์‹œ๊ฐ„ ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฉˆ์ถ˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๋” ์ด์ƒ ๊ด€์‹ฌ์ด ์—†๋Š” ํŽ˜์ด์ง€์—์„œ block๋•Œ๋ฌธ์— ๋” ์ด์ƒ ๋จธ๋ฌด๋ฅด์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„์˜ ์˜ˆ์‹œ๋Š” <Comments> ๋ถ€๋ถ„๋งŒ Suspense๋กœ wrapping๋˜์–ด ์žˆ์–ด์„œ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ ํ•œ ๋ฒˆ์— hydrate๋ฉ๋‹ˆ๋‹ค. ๋” ๋งŽ์€ ๊ณณ์—์„œ Suspense๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋ถ€๋ถ„์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<Layout>
  <NavBar />
  <Suspense fallback={<Spinner />}>
    <Sidebar />
  </Suspense>
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>
jsx

Suspense๋ฅผ ์ œ์™ธํ•œ ๋ถ€๋ถ„์ด ์ดˆ๊ธฐ HTML์— ํฌํ•จ๋˜๊ณ  ๊ทธ ํ›„ <Sidebar>, <Comments>๊ฐ€ hydrate๋ฉ๋‹ˆ๋‹ค. React๋Š” ๋‘ ๊ฐœ ๋ชจ๋‘ hydrate๋ฅผ ์ง„ํ–‰ํ•˜๋Š”๋ฐ ํŠธ๋ฆฌ์—์„œ ์•ž๋ถ€๋ถ„์— ์žˆ๋Š” Suspense๋ถ€ํ„ฐ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” <Sidebar>๋ถ€ํ„ฐ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

click-on-hydrating.png
click on hydrate <Sidebar>

๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ ์œ„ ์ด๋ฏธ์ง€์ฒ˜๋Ÿผ <Sidebar>๊ฐ€ hydrate๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š” ์ƒํƒœ์—์„œ <Comments> ์˜์—ญ์— ์ƒํ˜ธ์ž‘์šฉ(์—ฌ๊ธฐ์„œ๋Š” ํด๋ฆญ)์„ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค.

React๋Š” ํ•ด๋‹น ํด๋ฆญ ์ด๋ฒคํŠธ์˜ capture phase ๋™์•ˆ์— <Comments>๋ฅผ ๋™๊ธฐ์ ์œผ๋กœ hydrate ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ phase

๊ทธ ๊ฒฐ๊ณผ <Comments>๊ฐ€ ์ ์‹œ์— hydrateํ•˜์—ฌ ์ƒํ˜ธ์ž‘์šฉ์— ์‘๋‹ตํ•  ์ˆ˜ ์žˆ๊ณ  ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ๋‹ค๋ฃจ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ ํ›„์— React๋Š” ๊ธด๊ธ‰ํ•œ(urgent) ์ž‘์—…์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— <Sidebar>๋ฅผ hydrate ํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ์šฐ๋ฆฌ 3๋ฒˆ์งธ ๋ฌธ์ œ์ธ "์–ด๋–ค ๊ฒƒ์„ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ชจ๋“  ๊ฒƒ์— hydrate ํ•ด์•ผํ•˜๋Š” ๊ฒƒ"์„ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

React๋Š” ๊ฐ€๋Šฅํ•œ ๋นจ๋ฆฌ ๋ชจ๋“  ๊ฒƒ์„ hydrateํ•˜๊ธฐ ์‹œ์ž‘ํ•˜๊ณ  ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์— ๊ธฐ๋ฐ˜ํ•œ ํ™”๋ฉด์˜ ๊ฐ€์žฅ ๊ธด๊ธ‰ํ•œ(urgent) ๋ถ€๋ถ„์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ •ํ•ฉ๋‹ˆ๋‹ค. ์•ฑ ์ „์ฒด์— Suspense๋ฅผ ์ฑ„ํƒํ•œ๋‹ค๋ฉด ๊ทธ ๊ฒฝ๊ณ„๊ฐ€ ๋” ์„ธ๋ถ„ํ™”๋˜๊ธฐ ๋•Œ๋ฌธ์— Selective Hydration์˜ ์žฅ์ ์ด ๋” ๋ช…ํ™•ํ•ด์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

advance-hydrate.png
more granualr

Note:
์™„์ „ํ•˜๊ฒŒ hydrate๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ์•ฑ์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๊ฒƒ์ด ๋™์ž‘ํ•˜๋„๋ก ํ•˜๋Š” ๋””์ž์ธ์—๋Š” ๋ช‡ ๊ฐ€์ง€ ์„ธ๋ฐ€ํ•œ ๋””ํ…Œ์ผ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๊ฐ๊ฐ์˜ component๊ฐ€ ๊ฐœ๋ณ„์ ์œผ๋กœ hydrationํ•˜์ง€ ์•Š๊ณ  <Suspense> boundary ์ „์ฒด์— ๋Œ€ํ•ด hydration์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • <Suspense>๋Š” ๋ฐ”๋กœ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š” ์ฝ˜ํ…์ธ ์— ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ฝ”๋“œ๋Š” ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” children์— ํƒ„๋ ฅ์ น์œผ๋กœ ์ž‘์„ฑ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • React๋Š” ํ•ญ์ƒ ๋ถ€๋ชจ๋ถ€ํ„ฐ hydrate๋˜๋ฏ€๋กœ component์—๋Š” ํ•ญ์ƒ props๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
  • React๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ์ง€์ ์œผ๋กœ๋ถ€ํ„ฐ ๋ถ€๋ชจtree ์ „์ฒด๊ฐ€ hydrate๋  ๋•Œ๊นŒ์ง€ ์ด๋ฒคํŠธ ์ „๋‹ฌ์„ ๋ณด๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค.
  • React๋Š” ๋งŒ์•ฝ parent๊ฐ€ not-yet-hydrated HTML์„ staleํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ์‹์œผ๋กœ ์—…๋ฐ์ดํŠธ๋œ๋‹ค๋ฉด ํ•ด๋‹น ๋ถ€๋ถ„์ด load๋  ๋•Œ๊นŒ์ง€ ์ด๋ฅผ ์ˆจ๊ธฐ๊ณ  ์ง€์ •๋œ fallback์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํŠธ๋ฆฌ๊ฐ€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ผ๊ด€๋˜๊ฒŒ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.


๊ฒฐ๋ก ์ ์œผ๋กœ

React 18์€ SSR์— 2๊ฐ€์ง€ ์ฃผ์š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • Streaming HTML: ์›ํ•˜๋Š” ์ฆ‰์‹œ HTML์„ ๋‚ด๋ณด๋‚ด๊ณ  ์ ์ ˆํ•œ ์œ„์น˜์— ๋ฐฐ์น˜๋˜์–ด ์ถ”๊ฐ€ ์ฝ˜ํ…์ธ ์— ๋Œ€ํ•œ HTML์„ Streamingํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Selective Hydration: ์ดˆ๊ธฐ ์ดํ›„์˜ HTML, JavaScript ์ฝ”๋“œ๊ฐ€ ์™„์ „ํžˆ ๋‹ค์šด๋กœ๋“œ ๋˜๊ธฐ ์ „์— ์ตœ๋Œ€ํ•œ ๋นจ๋ฆฌ ์•ฑ์— hydratation์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ถ€๋ถ„์— ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋‘์–ด ์ฆ‰๊ฐ์ ์ธ hydration์˜ ํ™˜์ƒ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

<Suspense> ์ปดํฌ๋„ŒํŠธ๋Š” ์ด ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
React ๋‚ด๋ถ€์—์„œ ์ž๋™์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฉฐ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ์กด React ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•  ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ loading state๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ํž˜์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

if (isloading)์—์„œ <Suspense>๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด ํฐ ์ฐจ์ด๋กœ ๋ณด์ด์ง„ ์•Š์ง€๋งŒ, ์ด๋Ÿฌํ•œ ๋ชจ๋“  ๊ฐœ์„ ์‚ฌํ•ญ์„ ์ž ๊ธˆํ•ด์ œํ•˜๋Š” ์ค‘์š”ํ•œ ๋ณ€ํ™”์ž…๋‹ˆ๋‹ค.

new client and server rendering APIs

React 18์—์„œ client์™€ server์—์„œ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ APIs๊ฐ€ ์ƒˆ๋กญ๊ฒŒ ๋””์ž์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

React DOM Client

import ... from 'react-dom/client';
jsx

react-dom/client์—์„œ ์ œ๊ณต๋˜๋Š” ์ƒˆ๋กœ์šด APIs๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • createRoot: render ๋˜๋Š” unmount๋ฅผ ์œ„ํ•œ root๋ฅผ ๋งŒ๋“œ๋Š” ์ƒˆ๋กœ์šด method์ž…๋‹ˆ๋‹ค. ReactDOM.render ๋Œ€์‹ ์— createRoot๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. React 18์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋“ค์€ ์ด๊ฒƒ ์—†์ด๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • hydrateRoot: ์„œ๋ฒ„ ๋ Œ๋”๋ง ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ hydrateํ•˜๋Š” ์ƒˆ๋กœ์šด ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ReactDOM.hydrate ๋Œ€์‹ ์— ์ƒˆ๋กœ์šด React DOM Server APIs์™€ ํ•จ๊ป˜ ๋™์ž‘ํ•  hydrateRoot๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ React 18์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋“ค์€ ์ด๊ฒƒ ์—†์ด ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

createRoot

import { createRoot } from 'react-dom/client';

const container = document.getElementById('...');
const root = createRoot(container);
root.render(element);
jsx
createRoot(container[, options]);
jsx
root.umount();
jsx

createRoot()๋Š” ์ „๋‹ฌ๋œ container node์˜ content๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€์˜ ๊ธฐ์กด DOM element๋Š” render๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ๊ต์ฒด๋ฉ๋‹ˆ๋‹ค. ์ดํ›„ ํ˜ธ์ถœ์€ ํšจ์œจ์ ์ธ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด React์˜ DOM diffing ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
container node๋Š” ์ˆ˜์ •๋  ์ˆ˜ ์—†์œผ๋ฉฐ ์˜ค์ง container์˜ chilren๋งŒ ์ˆ˜์ •๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด children์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š๊ณ  ๊ธฐ์กด DOM node์— component์— ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
createRoot()๋Š” ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ container๋ฅผ hydrateํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  hydrateRoot()๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

createRoot๋Š” ๋‘ ๊ฐ€์ง€ ์˜ต์…˜์„ ์ œ๊ณตํ•˜๋Š”๋ฐ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • onRecoverableError: ๋ Œ๋”๋ง ์‹œ์— ์˜ค๋ฅ˜๋กœ ์ธํ•ด React๊ฐ€ ์ž๋™์œผ๋กœ recoverํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” callback ์ž…๋‹ˆ๋‹ค.
  • identifierPrefix: React.useId hook์— ์˜ํ•ด ์ƒ์„ฑ๋œ ids๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” prefix์ž…๋‹ˆ๋‹ค. ๊ฐ™์€ ํŽ˜์ด์ง€์— ๋‹ค์ˆ˜์˜ root๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ conflict๋ฅผ ํ”ผํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ๋œ ๊ฒƒ๊ณผ ๋™์ผํ•œ prefix์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

root์—์„œ๋Š” unmount๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋งˆ์šดํŠธ ํ•ด์ œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

hydrateRoot

import { hydrateRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('...');
hydrateRoot(container, <App />);
jsx
hydrateRoot(element, container[, options])
jsx

hydrateRoot()๋Š” createRoot()์™€ ๊ฑฐ์˜ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ReactDOMServer์—์„œ ๋ Œ๋”๋ง๋œ container์˜ HTML contents๋ฅผ hydrateํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. React๋Š” ๊ธฐ์กด markup์— ์ด๋ฒคํŠธ ๋ฆฌ์Šคํ„ฐ๋ฅผ ๋ถ™์ด๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.

createRoot, hydrateRoot ๋‘˜ ๋‹ค onRecoverableError ๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์˜ต์…˜์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์ด ์˜ต์…˜์€ React๊ฐ€ rendering์ด๋‚˜ hydration ํ•˜๋Š” ๋™์•ˆ์— ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜๋กœ๋ถ€ํ„ฐ recoverํ•  ๋•Œ ๋กœ๊น…์„ ์œ„ํ•ด ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ React๋Š” WEB APIs์—์„œ ์ œ๊ณตํ•˜๋Š” reportError๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋ž˜๋œ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” console.error๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

reportError

reportError.png

createRoot์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ hydrateRoot๋„ ๋‘ ๊ฐ€์ง€ ์˜ต์…˜์„ ๋™์ผํ•˜๊ฒŒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • onRecoverableError: ๋ Œ๋”๋ง ์‹œ์— ์˜ค๋ฅ˜๋กœ ์ธํ•ด React๊ฐ€ ์ž๋™์œผ๋กœ recoverํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” callback ์ž…๋‹ˆ๋‹ค.
  • identifierPrefix: React.useId hook์— ์˜ํ•ด ์ƒ์„ฑ๋œ ids๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” prefix์ž…๋‹ˆ๋‹ค. ๊ฐ™์€ ํŽ˜์ด์ง€์— ๋‹ค์ˆ˜์˜ root๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ conflict๋ฅผ ํ”ผํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ๋œ ๊ฒƒ๊ณผ ๋™์ผํ•œ prefix์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Note:
React๋Š” ๋ Œ๋”๋ง๋œ content๊ฐ€ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ๊ฐ„์— ๋™์ผํ•œ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•ฉ๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ๋‚ด์šฉ์˜ ์ฐจ์ด์ ์„ ํ•ด๊ฒฐํ•  ์ˆœ ์žˆ์ง€๋งŒ ๋ถˆ์ผ์น˜๋ฅผ ๋ฒ„๊ทธ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ React๋Š” hydrate ์ค‘ ๋ถˆ์ผ์น˜์— ๋Œ€ํ•ด ๊ฒฝ๊ณ ํ•ฉ๋‹ˆ๋‹ค. ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ attribute ์ฐจ์ด๊ฐ€ ํ•ด๊ฒฐ๋œ๋‹ค๋Š” ๋ณด์žฅ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์•ฑ์—์„œ ๋ถˆ์ผ์น˜๊ฐ€ ๋“œ๋ฌผ๊ณ  ๋ชจ๋“  markup์„ ๊ฒ€์ฆํ•˜๋Š”๋ฐ ์—„์ฒญ๋‚œ ๋น„์šฉ์ด ๋“ค๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ƒ์˜ ์ด์œ ๋กœ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

React DOM Server

react-dom/server์—์„œ ์ œ๊ณต๋˜๋Š” ์ƒˆ๋กœ์šด APIs๋Š” ์„œ๋ฒ„์—์„œ streaming Suspense๋ฅผ ์™„์ „ํžˆ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

  • renderToPipeableStream: Node์—์„œ streaming์„ ์œ„ํ•ด์„œ.
  • renderToReadableStream: Deno์™€ Cloudflare workes์ฒ˜๋Ÿผ modern edge runtim์„ ์œ„ํ•ด์„œ.

๊ธฐ์กด renderToString method๋Š” ์—ฌ์ „ํžˆ ๋™์ž‘ํ•˜์ง€๋งŒ ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ method๋Š” server์™€ browserํ™˜๊ฒฝ์—์„œ ๋‘˜ ๋‹ค ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • renderToString(): HTML string์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • renderToStaticMarkup(): renderToString๊ณผ ํก์‚ฌํ•ฉ๋‹ˆ๋‹ค๋งŒ React๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” data-reactroot๊ฐ™์€ ์ถ”๊ฐ€ DOM attribute๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ method๋Š” stream package์— ์˜์กดํ•˜๊ณ  ์žˆ์–ด์„œ ์˜ค์ง ์„œ๋ฒ„์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ  browser์—์„œ๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • renderToPipeableStream(): React 18์—์„œ ์ถ”๊ฐ€๋˜์—ˆ์œผ๋ฉฐ ์„œ๋ฒ„์—์„œ streaming์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • renderToReadableStream(): renderToReadableStream๊ณผ ํก์‚ฌํ•˜๋‚˜ Deno์™€ Cloudflare workes์ฒ˜๋Ÿผ modern edge runtim์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • renderToNodeStream()(Deprecated)
  • renderToStaticNodeStream()

renderToString

import ReactDOMServer from 'react-dom/server';

ReactDOMServer.renderToString(element);
jsx

React element๋ฅผ ์ดˆ๊ธฐ HTML๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. React๋Š” HTML string์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์„œ๋ฒ„์—์„œ HTML์„ ์ƒ์„ฑํ•˜๊ณ , ์ตœ์ดˆ ์š”์ฒญ์—์„œ ๋” ๋น ๋ฅธ ํŽ˜์ด์ง€ ๋กœ๋“œ๋ฅผ makrup์„ ๋‚ด๋ ค๋ณด๋‚ด๊ณ , SEO ๋ชฉ์ ์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ํฌ๋กค๋ง๋˜๋„๋ก ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ ์ด๋ฏธ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•œ markup์„ ๊ฐ€์ง„ node์—์„œ ReactDOM.hydrateRoot()๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด, React๋Š” ์ด๋ฅผ ๋ณด์กดํ•˜๊ณ  ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋งŒ ์—ฐ๊ฒฐํ•˜๋ฏ€๋กœ ๋งค์šฐ ์„ฑ๋Šฅ์ด ์ข‹์€ first-load ๊ฒฝํ—˜์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

renderToStaticMarkup

import ReactDOMServer from 'react-dom/server';

ReactDOMServer.renderToStaticMarkup(element);
jsx

renderToString๊ณผ ํก์‚ฌํ•˜๋‚˜ React๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” data-reactroot๊ฐ™์€ ์ถ”๊ฐ€ DOM attribute๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€ attribute๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด ์ผ๋ถ€ ๋ฐ”์ดํŠธ๋ฅผ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์–ด React๋ฅผ ๊ฐ„๋‹จํ•œ ์ •์  ํŽ˜์ด์ง€ ์ƒ์„ฑ๊ธฐ๋กœ ์‚ฌ์šฉํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ƒํ˜ธ์ž‘์šฉ๊ฐ€๋Šฅํ•œ markup์„ ๋งŒ๋“ค ๋•Œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. ๋Œ€์‹ ์— ์„œ๋ฒ„์—์„œ renderToString๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์—์„œ ReactDOM.hydrateRoot()๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”

renderToPipeableStream

import ReactDOMServer from 'react-dom/server';

ReactDOMServer.renderToPipeableStream(element, options);
jsx
type Controls = {
  // Cancel any pending I/O and put anything remaining into
  // client rendered mode.
  abort(): void,
  pipe<T: Writable>(destination: T): T,
};

type Options = {
  identifierPrefix?: string,
  namespaceURI?: string,
  nonce?: string,
  bootstrapScriptContent?: string,
  bootstrapScripts?: Array<string>,
  bootstrapModules?: Array<string>,
  progressiveChunkSize?: number,
  onShellReady?: () => void,
  onShellError?: () => void,
  onAllReady?: () => void,
  onError?: (error: mixed) => void,
};

function renderToPipeableStream(
  children: ReactNodeList,
  options?: Options,
): Controls {
  // ...
}
tsx
const { pipe, abort } = renderToPipeableStream(<App />, {
  onAllReady() {
    res.statusCode = 200;
    res.setHeader('Content-type', 'text/html');
    pipe(res);
  },
  onShellError(x) {
    res.statusCode = 500;
    res.send(
      '<!doctype html><p>Loading...</p><script src="clientrender.js"></script>'
    );
  },
});
jsx

React element๋ฅผ ์ดˆ๊ธฐ HTML๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. output์„ pipeํ•˜๊ฑฐ๋‚˜ abortํ•  ์ˆ˜ ์žˆ๋Š” Control object๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„์— JavaScript ์‹คํ–‰์„ ํ†ตํ•ด์„œ ๋“ค์–ด๊ฐˆ "์ง€์—ฐ๋œ" content block์ด ๋‚˜์ค‘์— JavaScript ์‹คํ–‰์„ ํ†ตํ•ด "Popping in"๋˜๋Š” Suspense์™€ HTML streaming์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Note:
Node.js์— ํŠนํ™”๋œ API์ž…๋‹ˆ๋‹ค. modern server(edge runtime)์—์„œ๋Š” renderToReadableStream์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

renderToReadableStream

ReactDOMServer.renderToReadableStream(element, options);
jsx
let controller = new AbortController();
try {
  let stream = await renderToReadableStream(
    <html>
      <body>Success</body>
    </html>,
    {
      signal: controller.signal,
    }
  );

  // This is to wait for all suspense boundaries to be ready. You can uncomment
  // this line if you don't want to stream to the client
  // await stream.allReady;

  return new Response(stream, {
    headers: { 'Content-Type': 'text/html' },
  });
} catch (error) {
  return new Response(
    '<!doctype html><p>Loading...</p><script src="clientrender.js"></script>',
    {
      status: 500,
      headers: { 'Content-Type': 'text/html' },
    }
  );
}
jsx

React element๋ฅผ ์ดˆ๊ธฐ HTML๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. Readable Stream์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. Suspense์™€ HTML streaming์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

renderToNodeStream(depreacated)

React 18์ด ๋˜๋ฉด์„œ depcreated ๋˜์—ˆ๊ณ , renderToPipeableStream๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

renderToStaticNodeStream

ReactDOMServer.renderToStaticNodeStream(element);
jsx

renderToPipeableStream์ด๋‚˜ renderToReadableStream๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ, renderToStaticMarkup์ฒ˜๋Ÿผ React๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” data-reactroot๊ฐ™์€ ์ถ”๊ฐ€ DOM attribute๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด stream์— ์˜ํ•œ HTML output์€ renderToStaticMarkup ๊ฒฐ๊ณผ์™€ ์™„์ „ํ•˜๊ฒŒ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

new hooks

  • useId: hydrate ๋ถˆ์ผ์น˜๋ฅผ ํ”ผํ•˜๋ฉด์„œ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ unique ID๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ hook ์ž…๋‹ˆ๋‹ค.
  • useTransition: ์ผ๋ถ€ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ non-urgentํ•œ ๊ฒƒ์œผ๋กœ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ hook ์ž…๋‹ˆ๋‹ค.
  • useDeferredValue: non-urgentํ•œ ๋ถ€๋ถ„์„ re-renderingํ•˜๋Š” ๊ฒƒ์„ ์ง€์—ฐํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” hook ์ž…๋‹ˆ๋‹ค.
  • useSyncExternalStroe: store ์ƒํƒœ๋ฅผ ๋™๊ธฐ์‹์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•˜๋„๋ก ๊ฐ•์ œํ•˜์—ฌ ํ•ด๋‹น store(redux store๊ฐ™์€)๊ฐ€ concurrent mode์—์„œ๋„ ์ž˜ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” hook์ž…๋‹ˆ๋‹ค.
  • useInsertionEffect: CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ Œ๋”๋งํ•  ๋•Œ ์Šคํƒ€์ผ์„ ์‚ฝ์ž…ํ•˜๋Š” ๊ฒƒ์—์„œ์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” hook์ž…๋‹ˆ๋‹ค.

useId

hydrate ๋ถˆ์ผ์น˜๋ฅผ ํ”ผํ•˜๋ฉด์„œ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ unique ID๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ hook ์ž…๋‹ˆ๋‹ค.

unique ID๊ฐ€ ํ•„์š”ํ•œ accessibility API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” component ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ฃผ๋กœ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

React 17์ดํ•˜์—์„œ ๊ธฐ์กด์— ์กด์žฌํ•˜๋˜ ์ด์Šˆ๋“ค์„ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ƒˆ๋กœ์šด streaming ์„œ๋ฒ„ renderer๊ฐ€ HTML์„ ์ˆœ์„œ ์—†์ด ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹ ๋•Œ๋ฌธ์— React 18์—์„œ ํ›จ์”ฌ ๋” ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

function Checkbox() {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Do you like React?</label>
      <input id={id} type="checkbox" name="react" />
    </>
  );
}

function NameFields() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id + '-firstName'}>First Name</label>
      <div>
        <input id={id + '-firstName'} type="text" />
      </div>
      <label htmlFor={id + '-lastName'}>Last Name</label>
      <div>
        <input id={id + '-lastName'} type="text" />
      </div>
    </div>
  );
}
jsx

Note:
useId๋Š” : ํ† ํฐ์„ ํฌํ•จํ•˜๋Š” ๋ฌธ์ž์—ด์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด token์ด ๊ณ ์œ ํ•œ์ง€ ํ™•์ธํ•˜๋Š”๋ฐ ๋„์›€์ด ๋˜์ง€๋งŒ CSS selector๋‚˜ querySelectorAll๊ณผ ๊ฐ™์€ API์—์„œ๋Š” ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

useId๋Š” multi-root ์•ฑ์—์„œ ์ถฉ๋Œ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด identifierPrefix๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. hydrateRoot ์˜ต์…˜์„ ํ™•์ธํ•˜์„ธ์š”.

useTransition

useTransition๊ณผ startTransition์€ non-urgentํ•œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ mark ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•ž์„  ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

useDeferredValue

useDeferredValue๋Š” ๋” ๊ธด๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด ์ง€์—ฐ๋  ๊ฐ’์˜ ๋ณต์‚ฌ๋ณธ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
ํ˜„์žฌ ๋ Œ๋”๋ง์ด ์‚ฌ์šฉ์ž input๊ณผ ๊ฐ™์€ ๊ธด๊ธ‰ํ•œ(urgent) ์—…๋ฐ์ดํŠธ์ธ ๊ฒฝ์šฐ React๋Š” ์ด์ „ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ ๋‹ค์Œ ๊ธด๊ธ‰ํ•œ ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋œ ํ›„ ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

์ง€์—ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด debouncing์ด๋‚˜ throttling๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž hook๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค.
React์—์„œ useDeferredValue๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์ ์€ ๋‹ค๋ฅธ ์ž‘์—…์ด ์™„๋ฃŒ๋˜์ž๋งˆ์ž(์ผ์ •ํ•œ ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ ค์•ผํ•˜๋Š” ๊ฒƒ๊ณผ ๋‹ฌ๋ฆฌ) ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐ”๋กœ ๋™์ž‘ํ•  ๊ฒƒ์ด๋ผ๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  startTransition๊ณผ ๊ฐ™์ด, ๊ธฐ์กด content๋ฅผ ์œ„ํ•ด ์˜ˆ๊ธฐ์น˜ ์•Š์€ fallback์„ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š๊ณ  deferred ๊ฐ’์„ suspenseํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Memoizing deferred children

useDeferredValue์—์„œ ์ „๋‹ฌํ•œ ๊ฐ’๋งŒ ์ง€์—ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— urgent update ์ค‘์— children์ด re-rendering๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด ํ•ด๋‹น ๋ถ€๋ถ„์„ React.memo ๋˜๋Š” React.useMemo๋กœ memoizing ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

function Typeahead() {
  const query = useSearchQuery('');
  const deferredQuery = useDeferredValue(query);

  // memoizing์€ React์—๊ฒŒ query๊ฐ€ ๋ฐ”๋€” ๋•Œ๊ฐ€ ์•„๋‹Œ
  // dependency๋กœ ๋“ฑ๋ก๋œ deferredQuery๊ฐ€ ๋ฐ”๋€” ๋•Œ ๋ Œ๋”๋งํ•˜๋„๋ก ์•Œ๋ ค์ค๋‹ˆ๋‹ค.
  const suggestions = useMemo(
    () => <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );

  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading results...">{suggestions}</Suspense>
    </>
  );
}
jsx

useSyncExternalStore

store ์ƒํƒœ๋ฅผ ๋™๊ธฐ์‹์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•˜๋„๋ก ๊ฐ•์ œํ•˜์—ฌ ํ•ด๋‹น store(redux store๊ฐ™์€)๊ฐ€ concurrent mode์—์„œ๋„ ์ž˜ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” hook์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์™ธ๋ถ€ data source์— ๊ตฌ๋…์„ ๊ตฌํ˜„ํ•  ๋•Œ useEffect๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๊ณ  React ์™ธ๋ถ€์˜ ์ƒํƒœ์™€ ํ†ตํ•ฉ๋˜๋Š” ๋ชจ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

Note: useSyncExternalStore๋Š” ์•ฑ ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);

// ์ „์ฒด ์Šคํ† ์–ด ๊ตฌ๋…
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);

// ํŠน์ • ํ•„๋“œ ๊ตฌ๋…
const selectedField = useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().selectedField,
);

// ์„œ๋ฒ„ ๋ Œ๋”๋ง์‹œ์— ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ๋˜๋Š” store ๊ฐ’์„ serialize ๊ฐ€ ํ•„์š”ํ•˜๊ณ  ๊ทธ๊ฒƒ์„ ์ œ๊ณต ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
// hydration ๋™์•ˆ server ๋ถˆ์ผ์น˜๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
const selectedField = useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().selectedField,
  () => INITIAL_SERVER_SNAPSHOT.selectedField,
);
jsx
  • subscribe: ์Šคํ† ์–ด๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑ์„ ๋“ฑ๋กํ•˜๋Š” ํ•จ์ˆ˜
  • getSnapshot: ์Šคํ† ์–ด์˜ ํ˜„์žฌ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
  • getServerSnapshot: ์„œ๋ฒ„ ๋ Œ๋”๋ง ์ค‘์— ์‚ฌ์šฉ๋œ snapshot์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜

useInsertionEffect

CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ Œ๋”๋งํ•  ๋•Œ ์Šคํƒ€์ผ์„ ์‚ฝ์ž…ํ•˜๋Š” ๊ฒƒ์—์„œ์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” hook์ž…๋‹ˆ๋‹ค. CSS-in-JS๋ฅผ ์ด๋ฏธ ๋งŒ๋“ค์ง€ ์•Š์•˜๋‹ค๋ฉด ์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด hook์€ DOM์ด ๋ณ€๊ฒฝ๋œ ์ดํ›„์— ์‹คํ–‰๋˜์ง€๋งŒ layout effect๊ฐ€ ์ƒˆ๋กœ์šด layout์„ ์ฝ๊ธฐ ์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
์ด๊ฒƒ์€ React 17์ดํ•˜์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ React๊ฐ€ concurrent ๋ Œ๋”๋ง ๋™์•ˆ์— ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ layout์„ ์žฌ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ๋•Œ๋ฌธ์— React 18์—์„œ ๋” ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

Note: useSyncExternalStore๋Š” ์•ฑ ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

useInsertionEffect(didUpdate);
jsx

signature๋Š” useEffect์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ชจ๋“  DOM ๋ณ€๊ฒฝ ์ „์— ๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. useLayoutEffect์—์„œ layout์„ ์ฝ๊ธฐ ์ „์— DOM์•ˆ์— ์Šคํƒ€์ผ์„ ์‚ฝ์ž…ํ•˜๊ธฐ ์œ„ํ•ด ์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด hook์€ ๋ฒ”์œ„๊ฐ€ ์ œํ•œ๋˜์–ด ์žˆ์–ด์„œ ref์— ์—‘์„ธ์Šคํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ์—…๋ฐ์ดํŠธ๋ฅผ ์˜ˆ์•ฝ ํ•  ์ˆ˜๋„ ์—†์Šต๋‹ˆ๋‹ค.

Note: useInsertionEffect๋Š” css-in-js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž‘์„ฑ์ž๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. useEffect๋‚˜ useLayoutEffect ๋Œ€์‹ ์— ๋” ์„ ํ˜ธ๋ฉ๋‹ˆ๋‹ค.

์ฒดํ—˜์šฉ ํ”„๋กœ์ ํŠธ

https://github.com/JHSeo-git/lets-go-react-18

React 18 ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋“ค ์ค‘ ๋ช‡ ๊ฐ€์ง€๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณด๊ณ  ์‹ถ์–ด์„œ ๋ฒˆ๋“ค๋ง ์„ค์ •๋ถ€ํ„ฐ ํ•˜๋‚˜ํ•˜๋‚˜ ์ฐพ์•„๊ฐ€๋ฉด์„œ ํ•ด๋ณธ ํ”„๋กœ์ ํŠธ ์ž…๋‹ˆ๋‹ค.

๊ธ€๋กœ ์ž‘์„ฑ๋œ ๋‚ด์šฉ ์ค‘ suspense๊นŒ์ง„ ์‚ฌ์šฉ์„ ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.
CSR, SSR ๋กœ ๋‘˜๋‹ค ์ž‘์„ฑํ•ด๋ณด์•˜๊ณ  ์†Œ์Šค๋Š” ๋ชจ๋‘ ๊ณต๊ฐœ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

React 18 ์ฒซ ๋“ฑ์žฅ์ด ์ž‘๋…„ ์ดˆ์˜€๋˜ ๊ฒƒ์œผ๋กœ ๊ธฐ์–ตํ•˜๋Š”๋ฐ ์ €๋ฒˆ์ฃผ์— ๋“œ๋””์–ด ๊ณต์‹ ๋ฆด๋ฆฌ์ฆˆ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ ๋™์•ˆ ๋ฏธ๋ฃจ๊ณ  ๋ฏธ๋ฃจ์—ˆ๋˜ React 18 ๋‚ด์šฉ์„ ์ด๋ฒˆ ๊ธฐํšŒ์— ์ œ๋Œ€๋กœ ๋ณด๋Š” ๊ณ„๊ธฐ๊ฐ€ ๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋“ค์„ ์ตํžˆ๊ณ  ๋ฐฐ์šฐ๋ฉด์„œ CSR, SSR ๊ฐœ๋…์„ ์ข€ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ์•Œ์•„๊ฐ„ ๊ฒƒ ๊ฐ™์•„์„œ ๋” ์ข‹์•˜์Šต๋‹ˆ๋‹ค.

์—ฐ์Šต์šฉ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ CSR ์ž‘์„ฑ์€ ๋ณ„๋กœ ์–ด๋ ต์ง€ ์•Š์•˜๋Š”๋ฐ SSR์„ ์ง์ ‘ ๋งŒ๋“ค๋ ค๊ณ  ํ•˜๋‹ˆ ์ œ ๋ถ€์กฑํ•จ์ด ๋ญ”์ง€ ํ™•์‹คํžˆ ๋А๊ปด์ง„ ๊ฒƒ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค.(์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•œ ์†Œ์Šค์ธ๋ฐ๋„ ๋ถˆ๊ตฌํ•˜๊ณ ... ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์‹ค ์ œ๋Œ€๋กœ ์ž‘์„ฑ๋œ ๊ฑด์ง€๋„ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค...)

React Server Component๊ฐ€ 18.0.0์—๋Š” ํฌํ•จ๋˜์ง€ ์•Š๊ณ  experimental๋กœ ๊ณต๊ฐœ๋˜์—ˆ๋Š”๋ฐ ์ถ”ํ›„ ๊ณง ํฌํ•จ๋˜์ง€ ์•Š์„๊นŒ ์‹ถ์Šต๋‹ˆ๋‹ค.

์ด์ œ ๋ง‰ ์‹œ์ž‘ํ•œ ๊ฒƒ ๊ฐ™์€ ๋А๋‚Œ์ด๋ผ์„œ ๋” ๋งŽ์ด ์จ๋ณด๋ฉด์„œ ์ตํžˆ๋„๋ก ๋…ธ๋ ฅํ•ด๋ด์•ผ๊ฒ ์Šต๋‹ˆ๋‹ค.

reference