useDeferredValue๋Š” UI ์ผ๋ถ€ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” React Hook์ž…๋‹ˆ๋‹ค.

const deferredValue = useDeferredValue(value)

๋ ˆํผ๋Ÿฐ์Šค

useDeferredValue(value)

์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ useDeferredValue๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ง€์—ฐ๋œ ๋ฒ„์ „์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

์•„๋ž˜์—์„œ ๋” ๋งŽ์€ ์˜ˆ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

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

  • value: ์ง€์—ฐ์‹œํ‚ค๋ ค๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ํƒ€์ž…์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’

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

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

  • useDeferredValue์— ์ „๋‹ฌํ•˜๋Š” ๊ฐ’์€ ๋ฌธ์ž์—ด ๋ฐ ์ˆซ์ž์™€ ๊ฐ™์€ ์›์‹œ๊ฐ’์ด๊ฑฐ๋‚˜, ์ปดํฌ๋„ŒํŠธ์˜ ์™ธ๋ถ€์—์„œ ์ƒ์„ฑ๋œ ๊ฐ์ฒด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ์ค‘์— ์ƒˆ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฆ‰์‹œ useDeferredValue์— ์ „๋‹ฌํ•˜๋ฉด ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค ๊ฐ’์ด ๋‹ฌ๋ผ์ ธ ๋ถˆํ•„์š”ํ•œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • useDeferredValue๊ฐ€ ํ˜„์žฌ ๋ Œ๋”๋ง(์—ฌ์ „ํžˆ ์ด์ „ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ) ์™ธ์— ๋‹ค๋ฅธ ๊ฐ’(Object.is๋กœ ๋น„๊ต)์„ ๋ฐ›์œผ๋ฉด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ƒˆ ๊ฐ’์œผ๋กœ ๋ฆฌ๋ Œ๋”๋งํ•˜๋„๋ก ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค. ๊ฐ’์— ๋Œ€ํ•œ ๋˜ ๋‹ค๋ฅธ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌ๋ Œ๋”๋ง์€ ์ค‘๋‹จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌ๋ Œ๋”๋ง์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ฐจํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๊ฐ€๋Šฅํ•œ ์ง€์—ฐ๋œ ๊ฐ’์„ ๋ฐ›๋Š” ์†๋„๋ณด๋‹ค ์‚ฌ์šฉ์ž๊ฐ€ input์— ๊ฐ’์„ ์ž…๋ ฅํ•˜๋Š” ์†๋„๊ฐ€ ๋” ๋น ๋ฅธ ๊ฒฝ์šฐ, ์ฐจํŠธ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์„ ๋ฉˆ์ถ˜ ํ›„์—๋งŒ ๋ฆฌ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.

  • useDeferredValue๋Š” <Suspense>์™€ ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ์ธํ•œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋ฐ์ดํŠธ๋กœ ์ธํ•ด UI๊ฐ€ ์ผ์‹œ ์ค‘๋‹จ๋˜๋ฉด ์‚ฌ์šฉ์ž๋Š” ํด๋ฐฑ์„ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋”ฉ๋  ๋•Œ๊นŒ์ง€ ์ด์ „ ์ง€์—ฐ๋œ ๊ฐ’์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

  • useDeferredValue๋Š” ๊ทธ ์ž์ฒด๋กœ ์ถ”๊ฐ€ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๋ฐฉ์ง€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

  • useDeferredValue๋กœ ์ธํ•œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌ๋ Œ๋”๋ง์€ ํ™”๋ฉด์— ์ปค๋ฐ‹๋  ๋•Œ๊นŒ์ง€ Effects๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌ๋ Œ๋”๋ง์ด ์ผ์‹œ ์ค‘๋‹จ๋˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋”ฉ๋˜๊ณ  UI๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ ํ›„์— ํ•ด๋‹น Effects๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.


์‚ฌ์šฉ๋ฒ•

์ƒˆ ์ฝ˜ํ…์ธ ๊ฐ€ ๋กœ๋”ฉ๋˜๋Š” ๋™์•ˆ ์˜ค๋ž˜๋œ ์ฝ˜ํ…์ธ  ํ‘œ์‹œํ•˜๊ธฐ

์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ useDeferredValue๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ UI ์ผ๋ถ€ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

์ดˆ๊ธฐ ๋ Œ๋”๋ง ์ค‘์— ์ง€์—ฐ๋œ ๊ฐ’์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ๊ฐ’๊ณผ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ง€์—ฐ๋œ ๊ฐ’์€ ์ตœ์‹  ๊ฐ’๋ณด๋‹ค โ€œ๋’ค์ณ์ง€๊ฒŒโ€ ๋ฉ๋‹ˆ๋‹ค. React๋Š” ๋จผ์ € ์ง€์—ฐ๋œ ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์€ ์ฑ„๋กœ ๋ Œ๋”๋งํ•œ ๋‹ค์Œ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ƒˆ๋กœ ๋ฐ›์€ ๊ฐ’์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด ์–ธ์ œ ์œ ์šฉํ•œ์ง€ ์˜ˆ์‹œ๋ฅผ ํ†ตํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

์ด ์˜ˆ์ œ์—์„œ๋Š” Suspense ์ง€์› ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค

  • Relay ๋ฐ Next.js์™€ ๊ฐ™์€ Suspense ์ง€์› ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐ์ดํ„ฐ ํŽ˜์นญ

  • lazy๋ฅผ ์‚ฌ์šฉํ•œ ์ง€์—ฐ ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ

Suspense์™€ ๊ทธ ํ•œ๊ณ„ ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ

์ด ์˜ˆ์‹œ์—์„œ๋Š” ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋™์•ˆ SearchResults ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ผ์‹œ ์ค‘์ง€๋ฉ๋‹ˆ๋‹ค. "a"๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฐ ๋‹ค์Œ "ab"๋กœ ์ˆ˜์ •ํ•ด ๋ณด์„ธ์š”. "a"์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋กœ๋”ฉ ํด๋ฐฑ์œผ๋กœ ๋Œ€์ฒด๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

์ผ๋ฐ˜์ ์ธ ๋Œ€์ฒด UI ํŒจํ„ด์€ ๊ฒฐ๊ณผ ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐํ•˜๊ณ  ์ƒˆ ๊ฒฐ๊ณผ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ์ด์ „ ๊ฒฐ๊ณผ๋ฅผ ๊ณ„์† ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. useDeferredValue๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ฟผ๋ฆฌ์˜ ์ง€์—ฐ๋œ ๋ฒ„์ „์„ ์ „๋‹ฌํ•˜์„ธ์š”.

export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}

query๋Š” ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ๋˜๋ฏ€๋กœ input์— ์ƒˆ ๊ฐ’์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ deferredQuery๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋”ฉ๋  ๋•Œ๊นŒ์ง€ ์ด์ „ ๊ฐ’์„ ์œ ์ง€ํ•˜๋ฏ€๋กœ SearchResults๋Š” ์ž ์‹œ ๋™์•ˆ ์˜ค๋ž˜๋œ ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ์—์„œ "a"๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๊ฒฐ๊ณผ๊ฐ€ ๋กœ๋”ฉ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ ๋‹ค์Œ, ์ž…๋ ฅ๊ฐ’์„ "ab"๋กœ ์ˆ˜์ •ํ•ด๋ณด์„ธ์š”. ์ด์ œ ์ƒˆ ๊ฒฐ๊ณผ๊ฐ€ ๋กœ๋”ฉ๋  ๋•Œ๊นŒ์ง€ Suspense ํด๋ฐฑ ๋Œ€์‹  ์˜ค๋ž˜๋œ ๊ฒฐ๊ณผ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

Deep Dive

๊ฐ’์„ ์ง€์—ฐ์‹œํ‚ค๋Š” ๊ฒƒ์€ ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋‚˜์š”?

๋‘ ๋‹จ๊ณ„๋กœ ์ง„ํ–‰๋œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค

  1. ๋จผ์ €, React๋Š” ์ƒˆ๋กœ์šด query("ab")๋กœ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€๋งŒ ์ด์ „ deferredQuery(์—ฌ์ „ํžˆ "a")๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ ๋ชฉ๋ก์— ์ „๋‹ฌํ•˜๋Š” deferredQuery ๊ฐ’์€ โ€œ์ง€์—ฐโ€๋œ query ๊ฐ’์ž…๋‹ˆ๋‹ค.

  2. ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ React๋Š” query์™€ deferredQuery๋ฅผ ๋ชจ๋‘ "ab"๋กœ ์—…๋ฐ์ดํŠธํ•œ ์ƒํƒœ๋กœ ๋ฆฌ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฆฌ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋˜๋ฉด React๋Š” ์ด๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ผ์‹œ ์ค‘๋‹จ๋˜๋Š” ๊ฒฝ์šฐ("ab"์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์•„์ง ๋กœ๋”ฉ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ) React๋Š” ์ด ๋ Œ๋”๋ง ์‹œ๋„๋ฅผ ํฌ๊ธฐํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋”ฉ๋œ ํ›„ ์ด ๋ฆฌ๋ Œ๋”๋ง์„ ๋‹ค์‹œ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ์˜ค๋ž˜๋œ ์ง€์—ฐ๋œ ๊ฐ’์„ ๊ณ„์† ๋ณด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ง€์—ฐ๋œ โ€œbackgroundโ€ ๋ Œ๋”๋ง์€ ์ค‘๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด input์„ ๋‹ค์‹œ ์ž…๋ ฅํ•˜๋ฉด React๋Š” ์ง€์—ฐ๋œ ๊ฐ’์„ ๋ฒ„๋ฆฌ๊ณ  ์ƒˆ ๊ฐ’์œผ๋กœ ๋‹ค์‹œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. React๋Š” ํ•ญ์ƒ ๊ฐ€์žฅ ์ตœ๊ทผ์— ์ œ๊ณต๋ฐ›์€ ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์—ฌ์ „ํžˆ ๊ฐ ํ‚ค ์ž…๋ ฅ๋งˆ๋‹ค ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์žˆ๋‹ค๋Š” ์ ์— ์ฃผ์˜ํ•˜์„ธ์š”. ์—ฌ๊ธฐ์„œ ์ง€์—ฐ๋˜๋Š” ๊ฒƒ์€ ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์ž์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฒฐ๊ณผ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๊ณ„์† ์ž…๋ ฅํ•˜๋”๋ผ๋„ ๊ฐ ํ‚ค ์ž…๋ ฅ์— ๋Œ€ํ•œ ์‘๋‹ต์€ ์บ์‹œ ๋˜๋ฏ€๋กœ ๋ฐฑ์ŠคํŽ˜์ด์Šค๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ฆ‰์‹œ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.


์ฝ˜ํ…์ธ ๊ฐ€ ์˜ค๋ž˜๋˜์—ˆ์Œ์„ ํ‘œ์‹œํ•˜๊ธฐ

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

<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>

์ด๋ ‡๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ฉด ์ž…๋ ฅ์„ ์‹œ์ž‘ํ•˜์ž๋งˆ์ž ์ƒˆ ๊ฒฐ๊ณผ ๋ชฉ๋ก์ด ๋กœ๋”ฉ๋  ๋•Œ๊นŒ์ง€ ์˜ค๋ž˜๋œ ๊ฒฐ๊ณผ ๋ชฉ๋ก์ด ์•ฝ๊ฐ„ ์–ด๋‘์›Œ์ง‘๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ์—์„œ์™€ ๊ฐ™์ด ์ ์ง„์ ์œผ๋กœ ์–ด๋‘์›Œ์ง„๋‹ค๊ณ  ๋Š๊ปด์ง€๋„๋ก CSS transition์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํ๋ฆฌ๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์„ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{
          opacity: isStale ? 0.5 : 1,
          transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
        }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}


UI ์ผ๋ถ€์— ๋Œ€ํ•ด ๋ฆฌ๋ Œ๋”๋ง ์ง€์—ฐํ•˜๊ธฐ

useDeferredValue๋ฅผ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋กœ ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. UI ์ผ๋ถ€๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ์†๋„๊ฐ€ ๋Š๋ฆฌ๊ณ , ์ด๋ฅผ ์ตœ์ ํ™”ํ•  ์‰ฌ์šด ๋ฐฉ๋ฒ•์ด ์—†์œผ๋ฉฐ, ๋‚˜๋จธ์ง€ UI๋ฅผ ์ฐจ๋‹จํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

ํ‚ค ์ž…๋ ฅ ์‹œ๋งˆ๋‹ค ๋ฆฌ๋ Œ๋”๋ง๋˜๋Š” ํ…์ŠคํŠธ ํ•„๋“œ์™€ ์ปดํฌ๋„ŒํŠธ(์˜ˆ: ์ฐจํŠธ ๋˜๋Š” ๊ธด ๋ชฉ๋ก)๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ณด์„ธ์š”

function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}

๋จผ์ €, props๊ฐ€ ๊ฐ™์€ ๊ฒฝ์šฐ ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๋„๋ก SlowList๋ฅผ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด, memo๋กœ ๊ฐ์‹ธ์ฃผ์„ธ์š”.

const SlowList = memo(function SlowList({ text }) {
// ...
});

๊ทธ๋Ÿฌ๋‚˜ ์ด๋Š” SlowList props๊ฐ€ ์ด์ „ ๋ Œ๋”๋ง ๋•Œ์™€ ๋™์ผํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ์ง€๊ธˆ ์ง๋ฉดํ•˜๊ณ  ์žˆ๋Š” ๋ฌธ์ œ๋Š” props๊ฐ€ ๋‹ค๋ฅด๊ณ  ์‹ค์ œ๋กœ ๋‹ค๋ฅธ ์‹œ๊ฐ์  ์ถœ๋ ฅ์„ ๋ณด์—ฌ์ค˜์•ผ ํ•  ๋•Œ ์†๋„๊ฐ€ ๋Š๋ฆฌ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ตฌ์ฒด์ ์œผ๋กœ, ์ฃผ์š” ์„ฑ๋Šฅ ๋ฌธ์ œ๋Š” input์— ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค SlowList๊ฐ€ ์ƒˆ๋กœ์šด props๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ์ „์ฒด ํŠธ๋ฆฌ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•˜๋ฉด ํƒ€์ดํ•‘์ด ๋Š๊ธฐ๋Š” ๋Š๋‚Œ์ด ๋“ ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ useDeferredValue๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž…๋ ฅ ์—…๋ฐ์ดํŠธ(๋นจ๋ผ์•ผ ํ•˜๋Š”)๋ฅผ ๊ฒฐ๊ณผ ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ(๋Š๋ ค๋„ ๋˜๋Š”)๋ณด๋‹ค ๋†’์€ ์šฐ์„ ์ˆœ์œ„์— ๋‘˜ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}

์ด๋ ‡๊ฒŒ ํ•œ๋‹ค๊ณ  ํ•ด์„œ SlowList์˜ ๋ฆฌ๋ Œ๋”๋ง ์†๋„๊ฐ€ ๋นจ๋ผ์ง€์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ‚ค ์ž…๋ ฅ์„ ์ฐจ๋‹จํ•˜์ง€ ์•Š๋„๋ก ๋ชฉ๋ก ๋ฆฌ๋ Œ๋”๋ง์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ React์— ์•Œ๋ ค์ค๋‹ˆ๋‹ค. ๋ชฉ๋ก์€ ์ž…๋ ฅ๋ณด๋‹ค โ€œ์ง€์—ฐโ€๋˜์—ˆ๋‹ค๊ฐ€ โ€œ๋”ฐ๋ผ์žก์„โ€ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์ „๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ React๋Š” ๊ฐ€๋Šฅํ•œ ํ•œ ๋นจ๋ฆฌ ๋ชฉ๋ก์„ ์—…๋ฐ์ดํŠธํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜์ง€๋งŒ, ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ์„ ์ฐจ๋‹จํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

useDeferredValue์™€ ์ตœ์ ํ™”๋˜์ง€ ์•Š์€ ๋ฆฌ๋ Œ๋”๋ง์˜ ์ฐจ์ด์ 

์˜ˆ์ œ 1 of 2:
๋ชฉ๋ก ๋ฆฌ๋ Œ๋”๋ง ์ง€์—ฐ

์ด ์˜ˆ์‹œ์—์„œ๋Š” SlowList ์ปดํฌ๋„ŒํŠธ์˜ ๊ฐ ํ•ญ๋ชฉ์„ ์ธ์œ„์ ์œผ๋กœ ๋Š๋ ค์ง€๋„๋ก ํ•˜์—ฌ useDeferredValue๋ฅผ ํ†ตํ•ด input์˜ ๋ฐ˜์‘์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. input์— ํƒ€์ดํ•‘ํ•˜๋ฉด ์ž…๋ ฅ์€ ๋น ๋ฅด๊ฒŒ ๋Š๊ปด์ง€๋Š” ๋ฐ˜๋ฉด ๋ชฉ๋ก์€ โ€œ์ง€์—ฐโ€๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

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

์ด ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด์„œ๋Š” SlowList๋ฅผ memo๋กœ ๊ฐ์‹ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. text๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค React๋Š” ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ฆฌ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋ฆฌ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ deferredText๋Š” ์—ฌ์ „ํžˆ ์ด์ „ ๊ฐ’์„ ๊ฐ€์ง€๋ฏ€๋กœ SlowList๋Š” ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(props๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค). memo๊ฐ€ ์—†์œผ๋ฉด ์–ด์จŒ๋“  ๋ฆฌ๋ Œ๋”๋งํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ตœ์ ํ™”์˜ ์ทจ์ง€๊ฐ€ ๋ฌด์ƒ‰ํ•ด์ง‘๋‹ˆ๋‹ค.

Deep Dive

๊ฐ’์„ ์ง€์—ฐํ•˜๋Š” ๊ฒƒ์€ ๋””๋ฐ”์šด์‹ฑ ๋ฐ ์Šค๋กœํ‹€๋ง๊ณผ ์–ด๋–ค ์ ์ด ๋‹ค๋ฅธ๊ฐ€์š”?

์ด ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์ด์ „์— ์‚ฌ์šฉํ–ˆ์„ ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ์ผ๋ฐ˜์ ์ธ ์ตœ์ ํ™” ๊ธฐ์ˆ ์ด ์žˆ์Šต๋‹ˆ๋‹ค

  • ๋””๋ฐ”์šด์‹ฑ์€ ํƒ€์ดํ•‘์„ ๋ฉˆ์ถœ ๋•Œ๊นŒ์ง€(์˜ˆ: 1์ดˆ ๋™์•ˆ) ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ๋ชฉ๋ก์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  • ์Šค๋กœํ‹€๋ง์€ ๊ฐ€๋”์”ฉ(์˜ˆ: ์ตœ๋Œ€ 1์ดˆ์— ํ•œ ๋ฒˆ) ๋ชฉ๋ก์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ธฐ๋ฒ•๋“ค์€ ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ์œ ์šฉํ•˜์ง€๋งŒ, useDeferredValue๋Š” React ์ž์ฒด์™€ ๊นŠ๊ฒŒ ํ†ตํ•ฉ๋˜์–ด ์žˆ๊ณ  ์‚ฌ์šฉ์ž์˜ ๊ธฐ๊ธฐ์— ๋งž๊ฒŒ ์กฐ์ •๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ Œ๋”๋ง์„ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐ ๋” ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

๋””๋ฐ”์šด์‹ฑ์ด๋‚˜ ์Šค๋กœํ‹€๋ง๊ณผ ๋‹ฌ๋ฆฌ ๊ณ ์ •๋œ ์ง€์—ฐ์„ ์„ ํƒํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ๋””๋ฐ”์ด์Šค๊ฐ€ ๋น ๋ฅธ ๊ฒฝ์šฐ(์˜ˆ: ๊ณ ์„ฑ๋Šฅ ๋…ธํŠธ๋ถ) ์ง€์—ฐ๋œ ๋ฆฌ๋ Œ๋”๋ง์€ ๊ฑฐ์˜ ์ฆ‰์‹œ ๋ฐœ์ƒํ•˜๋ฉฐ ๋ˆˆ์— ๋„์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ๋””๋ฐ”์ด์Šค๊ฐ€ ๋Š๋ฆฐ ๊ฒฝ์šฐ, ๊ธฐ๊ธฐ ์†๋„์— ๋น„๋ก€ํ•˜์—ฌ ๋ชฉ๋ก์ด input์— โ€˜์ง€์—ฐโ€™๋ฉ๋‹ˆ๋‹ค.

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

์ตœ์ ํ™”ํ•˜๋ ค๋Š” ์ž‘์—…์ด ๋ Œ๋”๋ง ์ค‘์— ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋„ ๋””๋ฐ”์šด์‹ฑ๊ณผ ์Šค๋กœํ‹€๋ง์€ ์—ฌ์ „ํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋””๋ฐ”์šด์‹ฑ๊ณผ ์Šค๋กœํ‹€๋ง์„ ์‚ฌ์šฉํ•˜๋ฉด ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๋” ์ ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ธฐ์ˆ ์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.