๋ฐ˜์‘ํ˜•

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

 

๐Ÿ’ก Render-blocking: Concurrent ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๋•Œ React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋™๊ธฐ์ ์œผ๋กœ ๋ Œ๋”๋ง ํ•˜๋ฉฐ, ๋ Œ๋”๋ง์„ ์‹œ์ž‘ํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ์•„๋‹Œ ์ด์ƒ ๋ Œ๋”๋ง์„ ์ค‘๋‹จํ•  ์ˆ˜ ์—†๋‹ค. ์ฆ‰, ๋ Œ๋”๋ง์ด ๋๋‚˜์•ผ๋งŒ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

React 18 ๋ฒ„์ „์— ๊ณต๊ฐœํ•œ Concurrent ๊ธฐ๋Šฅ์€ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœ๋ฐœ๋๋‹ค. Concurrent๋Š” ์—ฌ๋Ÿฌ ์ž‘์—…์— ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ถ€์—ฌํ•˜์—ฌ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์„ ๋จผ์ € ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ, ๋น ๋ฅธ ์ž‘์—…๊ณผ ๋Š๋ฆฐ ์ž‘์—…์„ ์‹ค์งˆ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ฐ๊ฐ ์ž์‹ ์˜ ์†๋„๋กœ ์ž‘์—…์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. ์ฆ‰, ๋‹ค์Œ View๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ ํ˜„์žฌ View์˜ ๋ฐ˜์‘์„ฑ์„ ์œ ์ง€ํ•˜๋„๋ก ๋ Œ๋”๋ง ํ”„๋กœ์„ธ์Šค๋ฅผ ์žฌ์ž‘์—…ํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๋Š๊น€ ์—†์ด ์œ ์ง€ํ•˜๋ฉด์„œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐ˜์‘์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ํšจ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

 

๋™๊ธฐ์  ๋ Œ๋”๋ง์˜ ๋ฌธ์ œ์ 


์•„๋ž˜๋Š” โ‘ ์ œ์–ด(controlled) ์ปดํฌ๋„ŒํŠธ <input />, โ‘ก๋ Œ๋”๋งํ•˜๋Š”๋ฐ 2์ดˆ๊ฐ€ ์†Œ์š”๋˜๋Š” <Slow /> ์ปดํฌ๋„ŒํŠธ, โ‘ข๊ฐ•์ œ๋กœ <Slow /> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•˜๋Š” ๋ฒ„ํŠผ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค.

 

<Slow /> ์ปดํฌ๋„ŒํŠธ๋Š” props๋ฅผ ๋ฐ›์ง€ ์•Š๊ณ  ๋ฉ”๋ชจ์ด์ง•๋ผ์„œ input ๊ฐ’์ด ๋ณ€๊ฒฝ๋ผ๋„ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋Š”๋‹ค. ํ•˜์ง€๋งŒ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค key ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฏ€๋กœ <Slow /> ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ๋ Œ๋”๋ง ๋œ๋‹ค.

// ์ฝ”๋“œ ์ถœ์ฒ˜ via The Miners
export default function App() {
  const [value, setValue] = useState('');
  const [key, setKey] = useState(Math.random());

  return (
    <div className="container">
      <input
        value={value}
        onChange={(e) => {
          console.log(
            `%c Input changed! -> "${e.target.value}"`,
            'color: yellow;',
          );
          setValue(e.target.value);
        }}
      />
      <button onClick={() => setKey(Math.random())}>Render Slow</button>
      <Slow key={key} />
    </div>
  );
}

const Slow = memo(() => {
  sleep(2000);
  console.log('%c Slow rendered!', 'color: teal;');
  return <></>;
});

const sleep = (ms) => {
  const start = performance.now();
  while (performance.now() - start < ms);
};

 

๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์„œ <Slow /> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ input ํ•„๋“œ์— ๊ฐ’์„ ์ž…๋ ฅํ•ด๋„ ํ™”๋ฉด์— ์•„๋ฌด๋Ÿฐ ๋ฐ˜์‘์ด ์—†๋‹ค๊ฐ€, ๋ฆฌ๋ Œ๋”๋ง์ด ๋๋‚˜์•ผ๋งŒ ์ž…๋ ฅํ•œ ๊ฐ’์ด ๋ฐ˜์˜๋œ๋‹ค. ๋Š๋ฆฐ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์„ ๋งˆ์นœ ํ›„์—์•ผ ๋‹ค๋ฅธ UI์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋Š๋ฆฐ ์ž‘์—…์ด ๋น ๋ฅธ ์ž‘์—…์„ ์ฐจ๋‹จํ•˜๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•œ ๊ฒƒ์ด๋‹ค.

 

  1. a ์ž…๋ ฅ → ํ™”๋ฉด์— ๋ฐ”๋กœ ๋ฐ˜์˜
  2. ๋ฒ„ํŠผ ํด๋ฆญ → <Slow /> ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋”๋ง ์‹œ์ž‘
  3. b ์ž…๋ ฅ → ๋ฐ˜์‘ ์—†์Œ
  4. (๋ฆฌ๋ Œ๋”๋ง ์ข…๋ฃŒ) ์ž…๋ ฅํ•œ ๋‚ด์šฉ ๋ฐ˜์˜

 

 

๋ Œ๋”๋ง ์šฐ์„ ์ˆœ์œ„


Concurrent ๋ Œ๋”๋ง ๋งฅ๋ฝ์—์„œ ๋งํ•˜๋Š” ์—…๋ฐ์ดํŠธ๋Š” ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ์„ ๋งํ•œ๋‹ค. ์—…๋ฐ์ดํŠธ๋Š” ๋‘ ๊ฐ€์ง€ ์šฐ์„ ์ˆœ์œ„๋กœ ๋‚˜๋‰˜๋ฉฐ, ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋Š” ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•ด์•ผ๋งŒ ์‹คํ–‰ํ•œ๋‹ค. ๋˜ํ•œ, ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋Š” ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ์— ์˜ํ•ด ์ค‘๋‹จ๋  ์ˆ˜ ์žˆ๋‹ค. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋ฆฌ๋ Œ๋”๋ง์ด ์ค‘๋‹จ๋˜๋ฉด, ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฆฌ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•œ๋‹ค.

 

  1. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ
    • setState
    • dispatch (useReducer)
    • useSyncExternalStore (์™ธ๋ถ€ ์Šคํ† ์–ด ๊ตฌ๋…)
    • UI๊ฐ€ ์™ธ๋ถ€ ์Šคํ† ์–ด(Redux ๋“ฑ)์™€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” Tearing ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ํ›…
    • ReactDOM.createRoot (์•ฑ์„ ์ฒ˜์Œ ๋ Œ๋”๋งํ•  ๋•Œ ํ˜ธ์ถœ)
  2. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ
    • startTransition
    • useDeferredValue

 

๋™์ผํ•œ ํ˜ธ์ถœ ์Šคํƒ์—์„œ ๋ฐœ์ƒํ•œ ๋ชจ๋“  ์—…๋ฐ์ดํŠธ๋Š” ์ผ๊ด„ ์ฒ˜๋ฆฌ๋ผ์„œ ๋‹จ์ผ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•œ๋‹ค. ๋•Œ๋ฌธ์— ์•„๋ž˜ ์ฝ”๋“œ ๊ธฐ์ค€์œผ๋กœ ๋†’์€ ์šฐ์„ ์ˆœ์œ„์™€ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„์— ๋Œ€ํ•œ ๋‹จ์ผ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ด 2ํšŒ ๋ฐœ์ƒํ•œ๋‹ค.

const Component = () => {
  const [filter, setFilter] = useState('');
  const [otherFilter, setOtherFilter] = useState('');
  const [delayedFilter, setDelayedFilter] = useState('');
  const [delayedOtherFilter, setDelayedOtherFilter] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleInputChanged = (e) => {
    // ๋‘ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์ผ๊ด„ ์ฒ˜๋ฆฌ(batching) -> ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋‹จ์ผ ์—…๋ฐ์ดํŠธ ๋ฐœ์ƒ
    setFilter(e.target.value);
    setOtherFilter(e.target.value.toUpperCase());

    startTransition(() => {
      // ๋‘ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ์ผ๊ด„ ์ฒ˜๋ฆฌ(batching) -> ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋‹จ์ผ ์—…๋ฐ์ดํŠธ ๋ฐœ์ƒ
      setDelayedFilter(e.target.value);
      setDelayedOtherFilter(e.target.value.toUpperCase());
    });
  };

  return (
    <>
      <input value={filter} onChange={handleInputChanged} />
    </>
  );
};

 

์ด ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋Š๋ฆฐ ๋ถ€๋ถ„์„ ๋ Œ๋”๋งํ•˜๊ณ  ์žˆ๋”๋ผ๋„ ๋น ๋ฅธ ๋ถ€๋ถ„์˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋Š๋ฆฐ ๋ถ€๋ถ„์„ ์ค‘๋‹จ์‹œํ‚ค๊ณ  ๋น ๋ฅธ ๋ถ€๋ถ„์„ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ๋ฐ˜์‘์„ฑ์„ ์œ ์ง€ํ•œ๋‹ค. ๋น ๋ฅธ ๋ถ€๋ถ„์˜ ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•˜๋ฉด ๋‹ค์‹œ ๋Š๋ฆฐ ๋ถ€๋ถ„์˜ ๋ Œ๋”๋ง์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค.

 

์œ„ ๊ณผ์ •์„ ์„ค๋ช…ํ•˜๋Š” ๋‹ค์ด์–ด๊ทธ๋žจ via The Miners

 

Concurrent ์ง„ํ–‰ ํ๋ฆ„


Concurrent ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋น ๋ฅธ ์ž‘์—…(์ž…๋ ฅ)๊ณผ ๋Š๋ฆฐ ์ž‘์—…(๋ชฉ๋ก ํ•„ํ„ฐ๋ง)์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๋น ๋ฅธ ์ž‘์—…์˜ ์‘๋‹ต์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์•„๋ž˜ ์˜ˆ์ œ ์ฝ”๋“œ์—์„  ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ์— ์‚ฌ์šฉํ•  filter ์ƒํƒœ ์™ธ์—, ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ์— ์‚ฌ์šฉํ•  delayedFilter ์ƒํƒœ๋ฅผ ์ •์˜ํ•˜์—ฌ <List /> ์ปดํฌ๋„ŒํŠธ์— prop์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๋‹ค. <List /> ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฉ”๋ชจ์ด์ง• ๋ผ์„œ ์ด์ „ ๋ Œ๋”๋ง ๋•Œ ์ „๋‹ฌํ•œ props(delayedFilter)๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”๋ง ๋œ๋‹ค. ์ž…๋ ฅ์„ ์‹œ์ž‘ํ•˜๋ฉด onChange ํ•ธ๋“ค๋Ÿฌ์— ์˜ํ•ด ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ์™€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ชจ๋‘ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค.

export default function App() {
  // ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ
  const [filter, setFilter] = useState('');
  // ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ
  const [delayedFilter, setDelayedFilter] = useState('');
  const [isPending, startTransition] = useTransition();

  useDebug({ filter, delayedFilter });

  return (
    <div className="container">
      <input
        value={filter}
        onChange={(e) => {
          // ๋†’์€&๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ชจ๋‘ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ
          setFilter(e.target.value); // ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ
          startTransition(() => {
            setDelayedFilter(e.target.value); // ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ
          });
        }}
      />
      <List filter={delayedFilter} /> {/* delayedFilter ์ƒํƒœ ๋ณ€๊ฒฝ์‹œ ๋ฆฌ๋ Œ๋”๋ง */}
    </div>
  );
}

const List = memo(({ filter }) => {
  const filteredList = list.filter((entry) =>
    entry.name.toLowerCase().includes(filter.toLowerCase()),
  );

  sleep(100);

  return (
    <ul>
      {filteredList.map((item) => (
        <li key={item.id}>
          {item.name} - ${item.price}
        </li>
      ))}
    </ul>
  );
});

 

<App />์„ ์ฒ˜์Œ ๋ Œ๋”๋งํ•  ๋•Œ ReactDOM.createRoot์— ์˜ํ•ด ์ฒซ๋ฒˆ์งธ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ Œ๋”๋ง์„ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค. ์ด๋•Œ filter, delayedFilter ๋ชจ๋‘ ๋นˆ ๋ฌธ์ž์—ด์„ ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ๊ฐ–๋Š”๋‹ค. ๊ทธ ํ›„ a, b, c๋ฅผ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ˜์†”์„ ์ถœ๋ ฅํ•œ๋‹ค. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ ๋„์ค‘์—” ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง„ํ–‰ํ•˜์ง€ ์•Š๊ณ , ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋ฆฌ๋ Œ๋”๋ง์€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ์— ์˜ํ•ด ์ฆ‰์‹œ ์ค‘๋‹จ๋˜๊ณ  ๋ฒ„๋ ค์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์ž.

๋”๋ณด๊ธฐ

Interrupted ์—†์ด ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ Œ๋”๋ง ์‹œ์ž‘/์ข…๋ฃŒ → ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋ Œ๋”๋ง ์‹œ์ž‘/์ข…๋ฃŒ ์ˆœ์œผ๋กœ ๋ฐ˜๋ณตํ•ด์„œ ์ง„ํ–‰

 

    1. [High Priority Start] a ์ž…๋ ฅ → ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฆฌ๋ Œ๋”๋ง ์‹œ์ž‘
      • filter : """a"
      • delayedFilter : "" — ๋ณ€๊ฒฝ ์—†์Œ
        • ์ด ๋‹จ๊ณ„์—์„  ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง„ํ–‰ํ•˜์ง€ ์•Š์Œ
        • <List />๋Š” ์ด์ „ ๋ Œ๋”๋ง๊ณผ ๋™์ผํ•œ delayedFilter ๊ฐ’์„ ์ „๋‹ฌ๋ฐ›์œผ๋ฏ€๋กœ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ
    2. [High Priority End] ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฆฌ๋ Œ๋”๋ง ์ข…๋ฃŒ → VDOM์— ์ปค๋ฐ‹ → ์ƒ๋ช…์ฃผ๊ธฐ ์ง„ํ–‰
      ์ƒ๋ช…์ฃผ๊ธฐ(Lifecycle) : ์ดํŽ™ํŠธ ์ฃผ์ž… → DOM ๋ณ€๊ฒฝ → ๋ ˆ์ด์•„์›ƒ → ๋ ˆ์ด์•„์›ƒ ์ดํŽ™ํŠธ → ํŽ˜์ธํŠธ → ์ดํŽ™ํŠธ
    3. [Low Priority Start] a ์ž…๋ ฅ์— ๋Œ€ํ•œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋ฆฌ๋ Œ๋”๋ง ์‹œ์ž‘
      • filter : "a" — ๋ณ€๊ฒฝ ์—†์Œ
      • delayedFilter : """a"
    4. [Low Priority Interrupted] b ์ž…๋ ฅ → ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋ฆฌ๋ Œ๋”๋ง ์ค‘์ง€
      • ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ์— ์˜ํ•ด ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋ Œ๋”๋ง์ด ์ค‘๋‹จ๋จ (์ปค๋ฐ‹ ์•ˆ๋จ)
      • ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ปค๋ฐ‹ํ•˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ delayedFilter ์ƒํƒœ ๊ฐ’์€ ์ด์ „๊ณผ ๋™์ผ
      • ์ด์ „๊ณผ ๋™์ผํ•œ ์ƒํƒœ๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ <List /> ์—ญ์‹œ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ
    5. [High Priority Start] b ์ž…๋ ฅ์— ๋Œ€ํ•œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฆฌ๋ Œ๋”๋ง ์‹œ์ž‘
      • filter : "a""ab"
      • delayedFilter : "" — ๋ณ€๊ฒฝ ์—†์Œ
    6. ์ž…๋ ฅ์„ ์ค‘๋‹จํ•  ๋•Œ๊นŒ์ง€ ์œ„ ์‚ฌ์ดํด ๋ฐ˜๋ณต…

 

์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ(์ž…๋ ฅ)์—์„œ delayedFilter ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— <List /> ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š๊ณ , ๊ทธ๋กœ ์ธํ•ด UI์˜ ๋น ๋ฅธ ๋ถ€๋ถ„์˜ ์‘๋‹ต์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ๋œ ๊ฒƒ์ด๋‹ค.

 

Branch ์ „๋žต๊ณผ Concurrent ๋‹ฎ์€๊ผด


Concurrent ๊ธฐ๋Šฅ์—์„œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ๋Š” ๊ธด๊ธ‰ํ•œ ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” hotfix ๋ธŒ๋žœ์น˜ ์ž‘์—…, ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋Š” feature ๋ธŒ๋žœ์น˜ ์ž‘์—…๊ณผ ์œ ์‚ฌํ•˜๋‹ค.

 

 

๊ธฐ๋Šฅ ๊ฐœ๋ฐœ์ค‘ ํ•ซํ”ฝ์Šค๋ฅผ ๋ฐฐํฌํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ํ•ซํ”ฝ์Šค ๋ฐฐํฌ๊ฐ€ ๊ธฐ๋Šฅ ๋ฐฐํฌ๋ณด๋‹ค ๋” ์‹œ๊ธ‰ํ•˜๋ฏ€๋กœ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ์„ ์ค‘๋‹จํ•˜๊ณ  ํ•ซํ”ฝ์Šค ๋ธŒ๋žœ์น˜์—์„œ ์ž‘์—…์„ ์‹œ์ž‘ํ•œ๋‹ค. ํ•ซํ”ฝ์Šค ์ž‘์—…์„ ์™„๋ฃŒํ•˜๋ฉด main ๋ธŒ๋žœ์น˜์— ์ž‘์—… ๋‚ด์šฉ์„ ๋ณ‘ํ•ฉํ•œ๋‹ค.

 

 

๋‹ค์‹œ ๊ธฐ๋Šฅ ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด์„  main ๋ธŒ๋žœ์น˜์— ๋ฐ˜์˜๋œ ํ•ซํ”ฝ์Šค ๋‚ด์šฉ์„ feature ๋ธŒ๋žœ์น˜๋กœ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด feature ๋ธŒ๋žœ์น˜๋กœ ์ด๋™ํ•ด์„œ main ๋ธŒ๋žœ์น˜๋ฅผ ๋ณ‘ํ•ฉํ•˜๊ฑฐ๋‚˜ ๋ฆฌ๋ฒ ์ด์Šค๋ฅผ ํ†ตํ•ด main ๋ธŒ๋žœ์น˜๋ฅผ ๋”ฐ๋ผ ์žก๋Š”๋‹ค.

 

 

๋งŒ์•ฝ ๋‹ค๋ฅธ ๊ธด๊ธ‰ํ•œ ์ˆ˜์ •์„ ์œ„ํ•ด ์ถ”๊ฐ€์ ์ธ ํ•ซํ”ฝ์Šค ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค๋ฉด ๊ธฐ๋Šฅ ์ž‘์—…์„ ๋‹ค์‹œ ์ค‘๋‹จํ•˜๊ณ  ์œ„ ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•œ๋‹ค. ์ด์ฒ˜๋Ÿผ ๊ธฐ๋Šฅ ๋ธŒ๋žœ์น˜ ์ž‘์—…์€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ํ•ซํ”ฝ์Šค ์ž‘์—…์œผ๋กœ ์ธํ•ด ์–ธ์ œ๋“ ์ง€ ์ค‘๋‹จ๋  ์ˆ˜ ์žˆ๋Š” ์ ์—์„œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ์™€ ์œ ์‚ฌํ•˜๋‹ค.

 

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

 

Git ๋ธŒ๋žœ์น˜ ์ „๋žต์„ Concurrent ๋ Œ๋”๋ง์— ๋Œ€์ž…ํ•ด๋ณธ ํ™”๋ฉด via The Miners

 

Concurrent ๊ธฐ๋Šฅ


Concurrent ๋ Œ๋”๋ง์€ Transition ํ˜น์€ DeferredValue ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‘ ๊ฐ€์ง€ ๋ชจ๋‘ ํŠน์ • ์—…๋ฐ์ดํŠธ๋ฅผ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ํ‘œ์‹œํ•˜๋ฉด์„œ Concurrent ๋ Œ๋”๋Ÿฌ๊ฐ€ ๋‚˜๋จธ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

 

ํŠธ๋žœ์ง€์…˜ | useTransition

๋…๋ฆฝ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š” startTransition์„ importํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค

 

์ƒํƒœ ๋ณ€ํ™”์˜ ์šฐ์„  ์ˆœ์œ„๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด useTransition ํ›…์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ด ํ›…์€ isPending, startTransition ๋‘ ๊ฐ€์ง€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. isPending์€ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ์ž‘์—…์ด ์ง€์—ฐ ์ค‘์ž„์„ ์•Œ๋ฆฌ๋Š” boolean ๊ฐ’์ด๋ฉฐ(์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฆฌ๋ Œ๋”๋ง ์ง„ํ–‰ ์ค‘), startTransition์€ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ์‹คํ–‰ํ•  ํ•จ์ˆ˜๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค(๋ช…๋ นํ˜• ๋ฐฉ๋ฒ•).

const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);

function handleClick() {
  startTransition(() => {
    setCount((c) => c + 1);
  });
}

 

startTransition์€ ๋†’์€/๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ์—…๋ฐ์ดํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค

startTransition์€ ํ•ญ์ƒ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ์™€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ชจ๋‘ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค. ์ฆ‰, startTransition ์ฝœ๋ฐฑ ๋‚ด๋ถ€์— ๋”ฐ๋กœ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š์•„๋„ ํ•ญ์ƒ ๋†’์€/๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค.

export default function App() {
  const [isPending, startTransition] = useTransition();

  useDebug();

  return (
    <button
      onClick={() => {
        startTransition(() => {});
      }}
    >
      Start Transition
    </button>
  );
}

 

๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ์ฝ˜์†” ์ถœ๋ ฅ ํ™”๋ฉด

 

startTransition์˜ ์ฝœ๋ฐฑ์€ ์ฆ‰์‹œ ์‹คํ–‰๋œ๋‹ค

startTransition ์ธ์ž๋กœ ์ „๋‹ฌํ•œ ํ•จ์ˆ˜๋Š” ๋™๊ธฐ์ ์œผ๋กœ ์ฆ‰์‹œ ์‹คํ–‰๋œ๋‹ค. ๋•Œ๋ฌธ์— ์ฝœ๋ฐฑ ๋‚ด๋ถ€์— ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค. ๋ Œ๋”๋ง์„ ์ฐจ๋‹จํ•  ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

๋ฐ˜๋ฉด, startTransition ์ฝœ๋ฐฑ์— ์˜ํ•ด ํŠธ๋ฆฌ๊ฑฐ๋œ ์—…๋ฐ์ดํŠธ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ํ•˜๋ฏ€๋กœ ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•ด๋„ ๋ฌด๋ฐฉํ•˜๋‹ค.

export default function App() {
  const [isPending, startTransition] = useTransition();

  useDebug();

  return (
    <button
      onClick={() => {
        console.log('Clicked!');
        startTransition(() => {
          console.log('Callback ran!');
        });
      }}
    >
      Start Transition
    </button>
  );
}

 

๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ์ฝ˜์†” ์ถœ๋ ฅ ํ™”๋ฉด

 

startTransition ์ฝœ๋ฐฑ๊ณผ ๊ฐ™์€ ํ˜ธ์ถœ ์Šคํƒ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค

์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ์ง€์ •๋˜๋ ค๋ฉด startTransition ์ฝœ๋ฐฑ ์ž์ฒด์™€ ๋™์ผํ•œ ํ˜ธ์ถœ ์Šคํƒ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์•„๋ž˜ 3๊ฐ€์ง€ ์˜ˆ์‹œ ๋ชจ๋‘ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ์ง€์ •๋˜๋Š” ๊ฒƒ์„ ๊ธฐ๋Œ€ํ–ˆ์ง€๋งŒ, startTransition ์ฝœ๋ฐฑ๊ณผ ๋‹ค๋ฅธ ํ˜ธ์ถœ ์Šคํƒ์— ์žˆ์–ด์„œ ๋†’์€ ์šฐ์„ ์ˆœ์œ„ ์—…๋ฐ์ดํŠธ๋กœ ์ง€์ •๋œ๋‹ค.

startTransition(() => {
  setTimeout(() => {
    // ๋‹ค๋ฅธ ํ˜ธ์ถœ ์Šคํƒ
    setCount((count) => count + 1);
  }, 1000);
});
startTransition(async () => {
  await asyncWork();
  // ๋‹ค๋ฅธ ํ˜ธ์ถœ ์Šคํƒ
  setCount((count) => count + 1);
});
startTransition(() => {
  asyncWork().then(() => {
    // ๋‹ค๋ฅธ ํ˜ธ์ถœ ์Šคํƒ
    setCount((count) => count + 1);
  });
});

 

์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ startTransition ์ฝœ๋ฐฑ ๋ฐ–์œผ๋กœ ๋นผ๊ฑฐ๋‚˜ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ startTransition์„ ํ˜ธ์ถœํ•˜๋„๋ก ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค.

setTimeout(() => {
  startTransition(() => {
    setCount((count) => count + 1);
  });
}, 1000);
await asyncWork();

startTransition(() => {
  setCount((count) => count + 1);
});
asyncWork().then(() => {
  startTransition(() => {
    setCount((count) => count + 1);
  });
});

 

๋ชจ๋“  ํŠธ๋žœ์ง€์…˜์€ ๋‹จ์ผ ๋ฆฌ๋ Œ๋”๋ง์œผ๋กœ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•œ๋‹ค

์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—ฌ๋Ÿฌ ์—…๋ฐ์ดํŠธ๋Š” ๋‹จ์ผ ๋ฆฌ๋ Œ๋”๋ง์„ ํ†ตํ•ด ์ผ๊ด„ ์ฒ˜๋ฆฌ๋œ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ A ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ณด๋ฅ˜์ค‘์ผ ๋•Œ ๋‹ค๋ฅธ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ B ์—…๋ฐ์ดํŠธ๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๋ฉด, A ์—…๋ฐ์ดํŠธ๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋™์ผํ•œ ๋ฆฌ๋ Œ๋”๋ง์—์„œ ์ผ๊ด„ ์ฒ˜๋ฆฌ๋œ๋‹ค.

 

๋˜ํ•œ ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•˜์ง€ ์•Š๊ณ  ์ค‘๋‹จ๋˜๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ์ •์ฑ…์ด ์ ์šฉ๋œ๋‹ค.

export default function App() {
  return (
    <div style={{ display: 'flex' }}>
      <Component name="First" />
      <Component name="Second" />
    </div>
  );
}

export const Component = ({ name }) => {
  const [filter, setFilter] = useState('');
  const [delayedFilter, setDelayedFilter] = useState('');
  const [isPending, startTransition] = useTransition();

  useDebug({ name });

  return (
    <div className="container">
      <input
        value={filter}
        onChange={(e) => {
          setFilter(e.target.value);
          startTransition(() => {
            setDelayedFilter(e.target.value);
          });
        }}
      />
      {isPending && 'Recalculating...'}

      <List filter={delayedFilter} />
    </div>
  );
};

// List ์ปดํฌ๋„ŒํŠธ ์ƒ๋žต

 

์ฒซ๋ฒˆ์งธ / ๋‘๋ฒˆ์งธ ํ•„๋“œ์— ๊ฐ๊ฐ a๋ฅผ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ–ˆ์„ ๋•Œ ์ฝ˜์†” ์ถœ๋ ฅ ํ™”๋ฉด

  1. ์ฒซ๋ฒˆ์žฌ ํ•„๋“œ a ์ž…๋ ฅ
    • ๋†’์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์‹œ์ž‘ ~ ์ข…๋ฃŒ
    • ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์‹œ์ž‘
  2. ๋‘๋ฒˆ์งธ ํ•„๋“œ a ์ž…๋ ฅ
    • ์ฒซ๋ฒˆ์งธ ํ•„๋“œ์˜ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์ค‘์ง€
    • ๋‘๋ฒˆ์งธ ํ•„๋“œ์˜ ๋†’์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์‹œ์ž‘ ~ ์ข…๋ฃŒ
    • ์ฒซ๋ฒˆ์งธ / ๋‘๋ฒˆ์งธ ํ•„๋“œ์˜ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์‹œ์ž‘
    • ์ฒซ๋ฒˆ์งธ / ๋‘๋ฒˆ์งธ ํ•„๋“œ์˜ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์ข…๋ฃŒ

 

ํŠธ๋žœ์ง€์…˜์€ ์ƒํƒœ์—๋งŒ ์ ์šฉ๋œ๋‹ค

startTransition ์ฝœ๋ฐฑ ์•ˆ์—์„œ ref๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ref๋Š” ํŠธ๋žœ์ง€์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. ๋•Œ๋ฌธ์— ์•„๋ž˜ ์˜ˆ์ œ์—์„œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฆฌ๋ Œ๋”๋ง์ผ ๋•Œ delayedFilter ๊ฐ’๋„ ๋ณ€๊ฒฝ๋ผ์„œ filter ์ƒํƒœ์™€ ํ•ญ์ƒ ๋™์ผํ•˜๋‹ค.

export default function App() {
  const [filter, setFilter] = useState('');
  const delayedFilterRef = useRef(filter);
  const [isPending, startTransition] = useTransition();

  const delayedFilter = delayedFilterRef.current;

  useDebug({ filter, delayedFilter });

  return (
    <div className="container">
      <input
        value={filter}
        onChange={(e) => {
          setFilter(e.target.value);
          startTransition(() => {
            delayedFilterRef.current = e.target.value;
          });
        }}
      />
      {isPending && 'Recalculating...'}

      <List filter={delayedFilter} />
    </div>
  );
}

// List ์ปดํฌ๋„ŒํŠธ ์ƒ๋žต

 

ํ•„๋“œ์— a, b๋ฅผ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ–ˆ์„ ๋•Œ์˜ ์ฝ˜์†” ์ถœ๋ ฅ ํ™”๋ฉด

 

์ง€์—ฐ๋œ ๊ฐ’ | useDeferredValue

๋ช…๋ นํ˜•์œผ๋กœ ์ž‘๋™ํ•˜๋Š” ํŠธ๋žœ์ง€์…˜(Transition) API์™€ ๋‹ฌ๋ฆฌ, useDeferredValue ํ›…์€ ์„ ์–ธ์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค. useDeferredValue ํ›…์˜ ์ธ์ž๋กœ ์ „๋‹ฌํ•œ ๊ฐ’์€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ์˜ ๊ฒฐ๊ณผ(์ƒํƒœ)๊ฐ€ ๋œ๋‹ค. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ Œ๋”๋ง ์ข…๋ฃŒ ์‹œ์ ์— useDeferredValue ํ›…์— ์ „๋‹ฌํ•œ ๊ฐ’๊ณผ ์ด์ „ ๊ฐ’์ด ๋‹ค๋ฅด๋ฉด ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค.

export default function App() {
  // ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ
  const [filter, setFilter] = useState('');
  // ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ
  const deferredFilter = useDeferredValue(filter);

  useDebug({ filter, deferredFilter });

  return (
    <div className="container">
      <input
        value={filter}
        onChange={(e) => {
          setFilter(e.target.value);
        }}
      />

      <List filter={deferredFilter} />
    </div>
  );
}

// List ์ปดํฌ๋„ŒํŠธ ์ƒ๋žต

 

์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ฒ˜์Œ ๋ Œ๋”๋งํ•˜๋ฉด ํ•ญ์ƒ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ Œ๋”๋ง์„ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค. useDeferredValue๋ฅผ ์ฒ˜์Œ ํ˜ธ์ถœํ•  ๋• ์šฐ์„  ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š๊ณ  ์ดˆ๊ธฐํ™”๋œ ๊ฐ’๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

๋”ฐ๋ผ์„œ filter, deferredFilter ์ƒํƒœ ๋ชจ๋‘ ๋นˆ ๋ฌธ์ž์—ด์„ ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ๊ฐ–๋Š”๋‹ค. ๊ทธ ํ›„ a, b, c๋ฅผ ์—ฐ์†์ ์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ์ฝ˜์†”์„ ์ถœ๋ ฅํ•œ๋‹ค. ๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋“ฏ useDeferredValue ํ›… ์—ญ์‹œ ํŠธ๋žœ์ง€์…˜ API์™€ ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.

 

์ž…๋ ฅ์ฐฝ์— a, b, c๋ฅผ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ–ˆ์„ ๋•Œ ์ฝ˜์†” ์ถœ๋ ฅ ํ™”๋ฉด

๐Ÿ’ก ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•˜๊ธฐ ์ „๊นŒ์ง„ useDeferredValue ํ›…์€ ํ•ญ์ƒ ์ด์ „๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์ž. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฆฌ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•ด์•ผ๋งŒ useDeferredValue์— ๋ณ€๊ฒฝ๋œ ๊ฐ’์ด ์ „๋‹ฌ๋œ ํ›„ ํŠธ๋ฆฌ๊ฑฐ๋ผ์„œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋ฆฌ๋ Œ๋”๋ง์„ ์‹œ์ž‘ํ•œ๋‹ค.

 

  1. a ์ž…๋ ฅ : setFilter ํ˜ธ์ถœ → ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์—…๋ฐ์ดํŠธ ํŠธ๋ฆฌ๊ฑฐ
    • ๋†’์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์‹œ์ž‘ ~ ์ข…๋ฃŒ
      • filter ์ƒํƒœ """a" ๋ณ€๊ฒฝ
      • filter ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด useDeferredValue ํ›…์˜ ์ธ์ž๋กœ "a" ๊ฐ€ ์ „๋‹ฌ๋˜์ง€๋งŒ, ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•˜๊ธฐ ์ „๊นŒ์ง„ ์ด์ „ ๊ฐ’์ธ "" ๋นˆ๋ฌธ์ž์—ด ๋ฐ˜ํ™˜ โšก๏ธ
    • ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์‹œ์ž‘ (์ด์ „๊ณผ ๋‹ค๋ฅธ ๊ฐ’์ธ "a" ๋ฅผ ์ „๋‹ฌ ๋ฐ›์•„ ํŠธ๋ฆฌ๊ฑฐ๋จ)
  2. b ์ž…๋ ฅ
    • ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์ค‘์ง€
    • ๋†’์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์‹œ์ž‘ ~ ์ข…๋ฃŒ
    • ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ Œ๋”๋ง ์‹œ์ž‘
  3. ๋ฐ˜๋ณต…

 

useDeferredValue๋Š” ๋‹ค๋ฅธ ์—…๋ฐ์ดํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š๋Š”๋‹ค

์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋ฆฌ๋ Œ๋”๋ง์ด ์ง„ํ–‰์ค‘์ผ ๋•Œ ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’์ด, ์ฒ˜์Œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•œ ๊ฐ’๊ณผ ๋‹ค๋ฅด๋”๋ผ๋„ useDeferredValue๋Š” ํ•ญ์ƒ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

๋•Œ๋ฌธ์— ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ง„ํ–‰์ค‘์ผ ๋•Œ useDeferredValue ํ›…์ด ์ƒˆ ๊ฐ’์„ ์ „๋‹ฌ๋ฐ›์•„๋„, ํ›…์„ ๊ทธ๋Œ€๋กœ ํ†ต๊ณผํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๋‹ค๋ฅธ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ์—…๋ฐ์ดํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ด๋Ÿฌํ•œ ์ž‘๋™ ๋ฐฉ์‹์„ ํ†ตํ•ด ํ•ญ์ƒ ์—…๋ฐ์ดํŠธ๋œ ์ตœ์‹  ๊ฐ’์„ ๋ฆฌ๋ Œ๋”๋ง์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

export default function App() {
  const [filter, setFilter] = useState('');
  const [delayedFilter, setDelayedFilter] = useState('');
  const [isPending, startTransition] = useTransition();

  // ๋†’์€ ์šฐ์„ ์ˆœ์œ„์™€ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ฆฌ๋ Œ๋”๋ง์— ๋”ฐ๋ผ ๊ฐ๊ฐ ๋‹ค๋ฅธ ๊ฐ’์„ ์ „๋‹ฌ๋ฐ›์Œ
  // isPending์€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ๋ฆฌ๋ Œ๋”๋ง์ด ์ง„ํ–‰์ค‘์ผ ๋•Œ๋งŒ true ๋ฐ˜ํ™˜
  const deferredFilter = useDeferredValue(
    isPending ? filter.toUpperCase() : delayedFilter,
  );

  useDebug({ filter, delayedFilter, deferredFilter });

  return (
    <div className="container">
      <input
        value={filter}
        onChange={(e) => {
          setFilter(e.target.value);
          startTransition(() => {
            setDelayedFilter(e.target.value);
          });
        }}
      />

      <List filter={deferredFilter} />
    </div>
  );
}

// List ์ปดํฌ๋„ŒํŠธ ์ƒ๋žต

 

๋†’์€ ์šฐ์„ ์ˆœ์œ„์™€ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ฆฌ๋ Œ๋”๋ง ๋„์ค‘ useDeferredValue ํ›…์— ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ’์„ ์ „๋‹ฌํ•˜๋”๋ผ๋„ ์ถ”๊ฐ€์ ์ธ ๋ Œ๋”๋ง์„ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

์ž…๋ ฅ ํ•„๋“œ์— a, b, c๋ฅผ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ–ˆ์„ ๋•Œ ์ฝ˜์†” ์ถœ๋ ฅ ํ™”๋ฉด

 

์—ฌ๋Ÿฌ ์—…๋ฐ์ดํŠธ๋Š” ๋‹จ์ผ ๋ฆฌ๋ Œ๋”๋ง์—์„œ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•œ๋‹ค

๋ชจ๋“  ํŠธ๋žœ์ง€์…˜์ด ๋‹จ์ผ ๋ฆฌ๋ Œ๋”๋ง์—์„œ ์ผ๊ด„ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ์—ฌ๋Ÿฌ useDeferredValue ํ˜ธ์ถœ๋„ ๋‹จ์ผ ๋ฆฌ๋ Œ๋”๋ง์—์„œ ์ผ๊ด„ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ฆ‰, ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋‚ด ์—ฌ๋Ÿฌ useDeferredValue ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ•˜๋”๋ผ๋” ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€ ๋‹จ์ผ ๋ฆฌ๋ Œ๋”๋ง์—์„œ ์ผ๊ด„ ์ฒ˜๋ฆฌ๋œ๋‹ค.

 

ํŠธ๋žœ์ง€์…˜๊ณผ ๋งˆ์ฒœ๊ฐ€์ง€๋กœ ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•˜์ง€ ์•Š๊ณ  ์ค‘๋‹จ๋˜๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ์ •์ฑ…๋„ ์ ์šฉ๋œ๋‹ค.

 

๋ ˆํผ๋Ÿฐ์Šค


 

Everything you need to know about Concurrent React (with a little bit of Suspense) And why it's a game changer - The Miners

Table of Contents Intro The Problem Synchronous Rendering The Solution Concurrent List Filtering Concurrent Rendering ~ Branching Workflow Concurrent Features Transitions Deferred Values Suspense Additional Considerations Suspension Points Low Priority and

blog.codeminer42.com

 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•