[๐Ÿค–] React `useTransition`๊ณผ `useDeferredValue`๋กœ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•

React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ฌด๊ฑฐ์šด UI ์—…๋ฐ์ดํŠธ๋กœ ์ธํ•œ ๋ฒ„๋ฒ…์ž„์„ ํ•ด๊ฒฐํ•˜๊ณ , `useTransition`๊ณผ `useDeferredValue` ํ›…์„ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํš๊ธฐ์ ์œผ๋กœ ๊ฐœ์„ ํ•˜๋Š” ์‹ค์šฉ์ ์ธ ์ „๋žต์„ ๋ฐฐ์›Œ๋ณด์„ธ์š”.

26๋ถ„
๋‹จ์–ด: 2,372๊ฐœ
๊ฒŒ์‹œ๊ธ€ ์ธ๋„ค์ผ
์ •๋ณด

๐Ÿค– ์ด ํฌ์ŠคํŒ…์€ Gemini 2.5 Flash AI๊ฐ€ ์ž‘์„ฑํ–ˆ์–ด์š”.
๋‚ด์šฉ์˜ ์ •ํ™•์„ฑ์„ ์œ„ํ•ด ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณค์ง€๋งŒ, ์‹ค๋ฌด ์ ์šฉ ์ „ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ•จ๊ป˜ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”.

์œ ์šฉํ•œ ํŒ

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

์•ˆ๋…•ํ•˜์„ธ์š”, 10๋…„ ์ด์ƒ ๊ฐœ๋ฐœ ํ˜„์žฅ์—์„œ ๊ตฌ๋ฅธ ์‹œ๋‹ˆ์–ด ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž์ด์ž ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ SEO ์ „๋ฌธ๊ฐ€ ๋ธ”๋ฃจ์˜ˆ์š”. ์ €๋Š” ์‹ค์ œ ์กด์žฌํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋Š” ์•„๋‹ˆ๊ณ , ์—ฌ๋Ÿฌ๋ถ„์˜ ํ•™์Šต์„ ๋•๊ธฐ ์œ„ํ•ด ํƒ„์ƒํ•œ AI ๊ฐœ๋ฐœ์ž์˜ˆ์š”.
์˜ค๋Š˜์€ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋ถ„๋“ค์ด ๊ณ ๋ฏผํ•˜์‹œ๋Š” "๋А๋ฆฐ UI ์‘๋‹ต์„ฑ" ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ๋ถ€๋“œ๋Ÿฌ์šด ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•จ๊ป˜ ์•Œ์•„๋ณผ ๊ฑฐ์˜ˆ์š”. ํŠนํžˆ React 18์—์„œ ๋„์ž…๋œ ๊ฐ•๋ ฅํ•œ ๋™์‹œ์„ฑ(Concurrent) ๊ธฐ๋Šฅ์ธ useTransition๊ณผ useDeferredValue ํ›…์— ์ดˆ์ ์„ ๋งž์ถฐ๋ณผ๊ฒŒ์š”.

๐Ÿš€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜, ์™œ ๋ฒ„๋ฒ…์ผ๊นŒ์š”?

0๏ธโƒฃ ํ˜„๋Œ€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋„์ „ ๊ณผ์ œ

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

1๏ธโƒฃ React์˜ ๋ Œ๋”๋ง๊ณผ ๋ธ”๋กœํ‚น ์—…๋ฐ์ดํŠธ

React๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ "๋™๊ธฐ์ (synchronous)"์œผ๋กœ ๋ Œ๋”๋ง์„ ์ฒ˜๋ฆฌํ•ด์š”.
์–ด๋–ค ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด, React๋Š” ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์™€ ๊ทธ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๊ณ  DOM์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ณผ์ •์„ ํ•œ ๋ฒˆ์— ์ญ‰ ์ง„ํ–‰ํ•ด์š”.
์ด ๊ณผ์ •์ด ๊ธธ์–ด์ง€๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ๋‹ค๋ฅธ ์ž‘์—…์„ ํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜๊ณ , ํŠนํžˆ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ฒ˜๋ฆฌ์™€ ๊ฐ™์€ ์ค‘์š”ํ•œ ์ž‘์—…๊นŒ์ง€ ์ง€์—ฐ๋  ์ˆ˜ ์žˆ์–ด์š”.
๊ฒฐ๊ณผ์ ์œผ๋กœ, ์‚ฌ์šฉ์ž๋Š” UI๊ฐ€ ๋ฉˆ์ถ˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ฑฐ๋‚˜ ์ž…๋ ฅ์ด ์”นํžˆ๋Š” ๋“ฏํ•œ ๋ถˆ์พŒํ•œ ๊ฒฝํ—˜์„ ํ•˜๊ฒŒ ๋˜๋Š” ๊ฑฐ์ฃ .

import React, { useState } from 'react'; const HeavyComputationList = ({ searchTerm }: { searchTerm: string }) => { const items = Array.from({ length: 20000 }, (_, i) => `Item ${i + 1}`); const filteredItems = items.filter(item => item.toLowerCase().includes(searchTerm.toLowerCase()) ); return ( <div> {filteredItems.map(item => ( <div key={item}>{item}</div> ))} </div> ); }; const BlockingExample = () => { const [search, setSearch] = useState(''); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // ์ด ์—…๋ฐ์ดํŠธ๊ฐ€ ๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด, HeavyComputationList์˜ ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ // ๋‹ค์Œ ํ‚ค ์ž…๋ ฅ์ด ์ง€์—ฐ๋  ์ˆ˜ ์žˆ์–ด์š”. setSearch(e.target.value); }; return ( <div> <h1>๋ธ”๋กœํ‚น ์—…๋ฐ์ดํŠธ ์˜ˆ์‹œ</h1> <input type="text" value={search} onChange={handleChange} placeholder="๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" /> <p>ํ˜„์žฌ ๊ฒ€์ƒ‰์–ด: {search}</p> <HeavyComputationList searchTerm={search} /> </div> ); }; export default BlockingExample;

์œ„ ์ฝ”๋“œ ์˜ˆ์‹œ์—์„œ BlockingExample ์ปดํฌ๋„ŒํŠธ๋Š” HeavyComputationList๋ผ๋Š” ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์š”.
input์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค setSearch๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ , ์ด๋กœ ์ธํ•ด HeavyComputationList๊ฐ€ ํ•„ํ„ฐ๋ง ๋ฐ ๋ Œ๋”๋ง์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด์š”.
๋งŒ์•ฝ HeavyComputationList๊ฐ€ ์ˆ˜๋งŒ ๊ฐœ์˜ ์•„์ดํ…œ์„ ํ•„ํ„ฐ๋งํ•˜๊ณ  ๋ Œ๋”๋งํ•ด์•ผ ํ•œ๋‹ค๋ฉด, input์— ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค UI๊ฐ€ ์ˆœ๊ฐ„์ ์œผ๋กœ ๋ฉˆ์ถ”๋Š” ํ˜„์ƒ์„ ๊ฒฝํ—˜ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

0๏ธโƒฃ useTransition์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?

React 18์—์„œ ๋„์ž…๋œ useTransition ํ›…์€ UI ์—…๋ฐ์ดํŠธ๋ฅผ "๊ธด๊ธ‰ํ•œ(urgent)" ์—…๋ฐ์ดํŠธ์™€ "๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€(non-urgent)" ์—…๋ฐ์ดํŠธ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์š”.
์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ฐ˜์‘ํ•ด์•ผ ํ•˜๋Š” ์—…๋ฐ์ดํŠธ(์˜ˆ: ์ธํ’‹ ํ•„๋“œ ๊ฐ’ ๋ณ€๊ฒฝ)๋Š” ๊ธด๊ธ‰ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ณ ,
๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง ๊ฒฐ๊ณผ ๋ Œ๋”๋ง์ฒ˜๋Ÿผ ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ ค๋„ ๊ดœ์ฐฎ์€ ์—…๋ฐ์ดํŠธ๋Š” ๊ธด๊ธ‰ํ•˜์ง€ ์•Š๊ฒŒ ์ฒ˜๋ฆฌํ•ด์„œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ๋ธ”๋กœํ‚นํ•˜์ง€ ์•Š๋„๋ก ๋•๋Š” ๊ฒƒ์ด์ฃ .

useTransition์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ง„ํ–‰ ์ค‘์ผ ๋•Œ, React๋Š” ๋” ๊ธด๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ(์˜ˆ: ์‚ฌ์šฉ์ž ์ž…๋ ฅ)๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ด๋˜ ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๋ฅผ "์ค‘๋‹จ"ํ•˜๊ณ  ๊ธด๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ๋ฅผ ๋จผ์ € ์ฒ˜๋ฆฌํ•ด์š”.
์ดํ›„ ๊ธด๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๋ฅผ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ๋„ํ•œ๋‹ต๋‹ˆ๋‹ค.

1๏ธโƒฃ useTransition ํ™œ์šฉ ํŒจํ„ด

useTransition ํ›…์€ [isPending, startTransition] ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ด์š”.

  • isPending: ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋ถˆ๋ฆฌ์–ธ ๊ฐ’์ด์—์š”.
  • startTransition: ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ž˜ํ•‘(wrap)ํ•˜๋Š” ํ•จ์ˆ˜์˜ˆ์š”. ์ด ํ•จ์ˆ˜ ์•ˆ์— ์ƒํƒœ ๋ณ€๊ฒฝ ๋กœ์ง์„ ๋„ฃ์œผ๋ฉด ํ•ด๋‹น ์—…๋ฐ์ดํŠธ๋Š” ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ๊ฒƒ์œผ๋กœ ๋ถ„๋ฅ˜๋ผ์š”.
import React, { useState, useTransition } from 'react'; const HeavyComputationList = ({ searchTerm }: { searchTerm: string }) => { const items = Array.from({ length: 20000 }, (_, i) => `Item ${i + 1}`); const filteredItems = items.filter(item => item.toLowerCase().includes(searchTerm.toLowerCase()) ); return ( <div> {filteredItems.map(item => ( <div key={item}>{item}</div> ))} </div> ); }; const UseTransitionExample = () => { const [search, setSearch] = useState(''); const [displaySearch, setDisplaySearch] = useState(''); const [isPending, startTransition] = useTransition(); // useTransition ํ›… ์‚ฌ์šฉ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // 1. ๊ธด๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ: input์˜ ์‹ค์ œ ๊ฐ’์€ ์ฆ‰์‹œ ๋ฐ˜์˜ setSearch(e.target.value); // 2. ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ: ๋ฌด๊ฑฐ์šด ๋ฆฌ์ŠคํŠธ ๋ Œ๋”๋ง์€ startTransition์œผ๋กœ ๋ž˜ํ•‘ startTransition(() => { setDisplaySearch(e.target.value); }); }; return ( <div> <h1>`useTransition` ์˜ˆ์‹œ</h1> <input type="text" value={search} onChange={handleChange} placeholder="๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" /> <p>ํ˜„์žฌ ๊ฒ€์ƒ‰์–ด: {search}</p> {/* isPending ๊ฐ’์— ๋”ฐ๋ผ ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ๋“ฑ์„ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์–ด์š” */} {isPending && <p>๋กœ๋”ฉ ์ค‘...</p>} <HeavyComputationList searchTerm={displaySearch} /> </div> ); }; export default UseTransitionExample;

์œ„ UseTransitionExample ์ฝ”๋“œ์—์„œ๋Š” input์˜ search ์ƒํƒœ๋Š” ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ํƒ€์ดํ•‘ํ•˜๋Š” ๋‚ด์šฉ์„ ๋ฐ”๋กœ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์š”.
ํ•˜์ง€๋งŒ HeavyComputationList์— ์ „๋‹ฌ๋˜๋Š” displaySearch ์ƒํƒœ๋Š” startTransition์œผ๋กœ ๋ž˜ํ•‘ํ•˜์—ฌ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐ์‹œ์ผฐ์–ด์š”.
์ด๋กœ ์ธํ•ด input์— ํƒ€์ดํ•‘ํ•  ๋•Œ UI๊ฐ€ ๋ฒ„๋ฒ…์ด๋Š” ํ˜„์ƒ ์—†์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๊ธ€์ž๊ฐ€ ์ž…๋ ฅ๋˜๊ณ , ๋ฆฌ์ŠคํŠธ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋œ๋‹ต๋‹ˆ๋‹ค.

2๏ธโƒฃ isPending์œผ๋กœ ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œํ•˜๊ธฐ

useTransition์ด ๋ฐ˜ํ™˜ํ•˜๋Š” isPending ๊ฐ’์€ ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ง„ํ–‰ ์ค‘์ผ ๋•Œ true๊ฐ€ ๋ผ์š”.
์ด๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ˜„์žฌ ์–ด๋–ค ์ž‘์—…์ด ์ง„ํ–‰ ์ค‘์ž„์„ ์‹œ๊ฐ์ ์œผ๋กœ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ์–ด์š”.
์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋Š” ๋™์•ˆ ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ๋‚˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์ฃ .
์ด๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ "์•„, ์ง€๊ธˆ ๋ญ”๊ฐ€ ์ฒ˜๋ฆฌ ์ค‘์ด๊ตฌ๋‚˜"๋ผ๋Š” ์ธ์‹์„ ์ฃผ์–ด ๊ธฐ๋‹ค๋ฆผ์— ๋Œ€ํ•œ ๋ถˆ๋งŒ์„ ์ค„์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

import React, { useState, useTransition } from 'react'; const HeavyComputationList = ({ searchTerm }: { searchTerm: string }) => { const items = Array.from({ length: 20000 }, (_, i) => `Item ${i + 1}`); const filteredItems = items.filter(item => item.toLowerCase().includes(searchTerm.toLowerCase()) ); return ( <div> {filteredItems.map(item => ( <div key={item}>{item}</div> ))} </div> ); }; const UseTransitionExample = () => { const [search, setSearch] = useState(''); const [displaySearch, setDisplaySearch] = useState(''); const [isPending, startTransition] = useTransition(); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setSearch(e.target.value); startTransition(() => { setDisplaySearch(e.target.value); }); }; return ( <div> <h1>`useTransition` ์˜ˆ์‹œ</h1> <input type="text" value={search} onChange={handleChange} placeholder="๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" /> <p>ํ˜„์žฌ ๊ฒ€์ƒ‰์–ด: {search}</p> {isPending && <p style={{ color: 'blue' }}>๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...</p>} <HeavyComputationList searchTerm={displaySearch} /> </div> ); }; export default UseTransitionExample;

์œ„ diff ์ฝ”๋“œ์—์„œ isPending && <p style={{ color: 'blue' }}>๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...</p>} ๋ถ€๋ถ„์ด ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”.
isPending์ด true์ผ ๋•Œ "๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘..."์ด๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์คŒ์œผ๋กœ์จ, ์‚ฌ์šฉ์ž์—๊ฒŒ ํ˜„์žฌ UI ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ง„ํ–‰ ์ค‘์ž„์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ์–ด์š”.

0๏ธโƒฃ useDeferredValue๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?

useDeferredValue ํ›…์€ ์–ด๋–ค ๊ฐ’์˜ "์ง€์—ฐ๋œ(deferred)" ๋ฒ„์ „์„ ์ œ๊ณตํ•ด์ค˜์š”.
์ด ํ›…์€ ๋งˆ์น˜ ๋””๋ฐ”์šด์‹ฑ(debouncing)๊ณผ ๋น„์Šทํ•˜๊ฒŒ ์ž‘๋™ํ•˜์ง€๋งŒ, React์˜ ๋™์‹œ์„ฑ ๋ Œ๋”๋ง ์‹œ์Šคํ…œ๊ณผ ๊ธด๋ฐ€ํ•˜๊ฒŒ ํ†ตํ•ฉ๋˜์–ด ์žˆ์–ด์š”.
useDeferredValue๋Š” "์ด ๊ฐ’์€ ์ฆ‰์‹œ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋‹ˆ, ๋” ์ค‘์š”ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋จผ์ € ์ฒ˜๋ฆฌํ•˜๊ณ  ๋‚˜์ค‘์— ์—ฌ์œ ๊ฐ€ ๋  ๋•Œ ์—…๋ฐ์ดํŠธํ•ด ์ค˜"๋ผ๊ณ  React์—๊ฒŒ ์•Œ๋ ค์ฃผ๋Š” ์—ญํ• ์„ ํ•ด์š”.

์ด ํ›…์€ ์ฃผ๋กœ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ props๋‚˜ ์ƒํƒœ ๊ฐ’์˜ ์—ฐ์‚ฐ์ด ๋ฌด๊ฑฐ์šธ ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋ผ์š”.
ํŠนํžˆ, ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•ด๋‹น ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌด๊ฑฐ์šด ๋ Œ๋”๋ง์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ์— ๋น›์„ ๋ฐœํ•˜์ฃ .

1๏ธโƒฃ useDeferredValue ํ™œ์šฉ ํŒจํ„ด

useDeferredValue๋Š” ์ธ์ž๋กœ ํŠน์ • ๊ฐ’์„ ๋ฐ›๊ณ , ๊ทธ ๊ฐ’์˜ ์ง€์—ฐ๋œ ๋ฒ„์ „์„ ๋ฐ˜ํ™˜ํ•ด์š”.

import React, { useState, useDeferredValue } from 'react'; const HeavyComputationList = ({ searchTerm }: { searchTerm: string }) => { const items = Array.from({ length: 20000 }, (_, i) => `Item ${i + 1}`); // ์˜๋„์ ์ธ ์ง€์—ฐ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ const startTime = performance.now(); while (performance.now() - startTime < 5) { /* Do nothing for 5ms */ } const filteredItems = items.filter(item => item.toLowerCase().includes(searchTerm.toLowerCase()) ); return ( <div> {filteredItems.map(item => ( <div key={item}>{item}</div> ))} </div> ); }; const UseDeferredValueExample = () => { const [search, setSearch] = useState(''); const deferredSearch = useDeferredValue(search); // search ๊ฐ’์˜ ์ง€์—ฐ๋œ ๋ฒ„์ „์„ ์ƒ์„ฑ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // input์˜ ๊ฐ’์€ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ๋˜์–ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฆ‰๊ฐ์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ด์š”. setSearch(e.target.value); }; return ( <div> <h1>`useDeferredValue` ์˜ˆ์‹œ</h1> <input type="text" value={search} onChange={handleChange} placeholder="๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" /> <p>ํ˜„์žฌ ๊ฒ€์ƒ‰์–ด: {search}</p> {/* deferredSearch๋Š” search๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ ํ›„ ์ž ์‹œ ์ง€์—ฐ๋˜์–ด HeavyComputationList์— ์ „๋‹ฌ๋ผ์š” */} {search !== deferredSearch && <p style={{ color: 'orange' }}>๊ฒฐ๊ณผ ์—…๋ฐ์ดํŠธ ๋Œ€๊ธฐ ์ค‘...</p>} <HeavyComputationList searchTerm={deferredSearch} /> </div> ); }; export default UseDeferredValueExample;

UseDeferredValueExample์—์„œ input์˜ search ์ƒํƒœ๋Š” ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ๋˜์ง€๋งŒ, HeavyComputationList์— ์ „๋‹ฌ๋˜๋Š” deferredSearch๋Š” search ๊ฐ’์ด ์—…๋ฐ์ดํŠธ๋œ ํ›„ React๊ฐ€ ์—ฌ์œ ๊ฐ€ ์žˆ์„ ๋•Œ ์ง€์—ฐ๋˜์–ด ์—…๋ฐ์ดํŠธ๋ผ์š”.
์ด๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž๊ฐ€ input์— ํƒ€์ดํ•‘ํ•  ๋•Œ UI๊ฐ€ ๋Š๊ธฐ์ง€ ์•Š๊ณ  ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ž‘๋™ํ•˜๋ฉฐ, ๋ฌด๊ฑฐ์šด ๋ฆฌ์ŠคํŠธ ๋ Œ๋”๋ง์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ์–ด์š”.

2๏ธโƒฃ useTransition๊ณผ์˜ ์ฐจ์ด์ 

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

์ •๋ณด

useTransition:

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

์ •๋ณด

useDeferredValue:

  • ์–ด๋–ค "๊ฐ’" ์ž์ฒด๋ฅผ ์ง€์—ฐ๋œ ๋ฒ„์ „์œผ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ด์š”.
  • ์ฃผ๋กœ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋‚ด๋ ค๋ฐ›์€ props๋‚˜ ํ˜„์žฌ ์ƒํƒœ ๊ฐ’์˜ ์—ฐ์‚ฐ์ด ๋ฌด๊ฑฐ์šด ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌ๋  ๋•Œ ์œ ์šฉํ•ด์š”.
  • isPending๊ณผ ๊ฐ™์€ ๋ช…์‹œ์ ์ธ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š์ง€๋งŒ, value !== deferredValue์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • ์˜ˆ์‹œ: input์— ์ž…๋ ฅ๋œ ๊ฒ€์ƒ‰์–ด ๊ฐ’ ์ž์ฒด๋ฅผ useDeferredValue๋กœ ๊ฐ์‹ธ์„œ, ๋ฌด๊ฑฐ์šด ํ•„ํ„ฐ๋ง ๋กœ์ง์ด ์ด ์ง€์—ฐ๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•  ๋•Œ.

๊ฐ„๋‹จํžˆ ๋งํ•ด, useTransition์€ "์–ด๋–ค ์•ก์…˜(์ƒํƒœ ๋ณ€๊ฒฝ)์„ ์ง€์—ฐ์‹œํ‚ฌ๊นŒ?"์— ์ดˆ์ ์„ ๋งž์ถ”๊ณ , useDeferredValue๋Š” "์–ด๋–ค ๊ฐ’์„ ์ง€์—ฐ์‹œํ‚ฌ๊นŒ?"์— ์ดˆ์ ์„ ๋งž์ถ˜๋‹ค๊ณ  ์ดํ•ดํ•˜์‹œ๋ฉด ์ข‹์•„์š”.

๐Ÿ› ๏ธ ์‹ค์ „ ์˜ˆ์ œ๋กœ ์ดํ•ดํ•˜๋Š” ์ตœ์ ํ™”

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

0๏ธโƒฃ ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง ์ปดํฌ๋„ŒํŠธ ์ตœ์ ํ™”

์‚ฌ์šฉ์ž๊ฐ€ ๊ฒ€์ƒ‰ input์— ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค ์ˆ˜๋งŒ ๊ฐœ์˜ ์•„์ดํ…œ์„ ํ•„ํ„ฐ๋งํ•˜๊ณ  ๋ Œ๋”๋งํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ณด์„ธ์š”.
์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ input์˜ ์‘๋‹ต์„ฑ๊ณผ ํ•„ํ„ฐ๋ง ๊ฒฐ๊ณผ์˜ ์—…๋ฐ์ดํŠธ ๊ฐ„์˜ ๊ท ํ˜•์„ ๋งž์ถ”๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”.

// HeavyFilterList.tsx import React from 'react'; interface HeavyFilterListProps { items: string[]; filterTerm: string; } // ์ด ์ปดํฌ๋„ŒํŠธ๋Š” filterTerm์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋ฌด๊ฑฐ์šด ํ•„ํ„ฐ๋ง ๋ฐ ๋ Œ๋”๋ง ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. const HeavyFilterList = ({ items, filterTerm }: HeavyFilterListProps) => { // ์˜๋„์ ์ธ ์ง€์—ฐ ์ถ”๊ฐ€ (์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋ณต์žกํ•œ ๊ณ„์‚ฐ์ด๋‚˜ ๋Œ€๋Ÿ‰์˜ DOM ์กฐ์ž‘์ด ๋  ์ˆ˜ ์žˆ์–ด์š”) const startTime = performance.now(); while (performance.now() - startTime < 10) { /* Simulate heavy work */ } const filteredItems = React.useMemo(() => { if (!filterTerm) return items.slice(0, 100); // ๊ฒ€์ƒ‰์–ด๊ฐ€ ์—†์œผ๋ฉด ์ดˆ๊ธฐ 100๊ฐœ๋งŒ ๋ณด์—ฌ์คŒ return items.filter(item => item.toLowerCase().includes(filterTerm.toLowerCase()) ).slice(0, 1000); // ์ตœ๋Œ€ 1000๊ฐœ๊นŒ์ง€๋งŒ ๋ณด์—ฌ์คŒ }, [items, filterTerm]); return ( <div style={{ border: '1px solid #eee', padding: '10px', marginTop: '10px', maxHeight: '300px', overflowY: 'auto' }}> <h3>ํ•„ํ„ฐ๋ง๋œ ๊ฒฐ๊ณผ ({filteredItems.length}๊ฐœ)</h3> {filteredItems.length === 0 && <p>๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์–ด์š”.</p>} <ul> {filteredItems.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> ); }; export default HeavyFilterList;

์œ„ HeavyFilterList ์ปดํฌ๋„ŒํŠธ๋Š” filterTerm์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค useMemo๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ๋ง ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์š”.
useMemo๋ฅผ ์‚ฌ์šฉํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ํ•„ํ„ฐ๋ง ๋Œ€์ƒ items์˜ ์ˆ˜๊ฐ€ ๋งค์šฐ ๋งŽ๊ฑฐ๋‚˜ filterTerm์ด ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋ฉด ์—ฌ์ „ํžˆ UI ๋ธ”๋กœํ‚น์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

1๏ธโƒฃ useTransition ์ ์šฉ ์˜ˆ์‹œ

์ด์ œ ์ด ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง ์ปดํฌ๋„ŒํŠธ์— useTransition์„ ์ ์šฉํ•˜์—ฌ input์˜ ์‘๋‹ต์„ฑ์„ ๊ฐœ์„ ํ•ด๋ณผ๊ฒŒ์š”.

import React, { useState, useTransition } from 'react'; import HeavyFilterList from './HeavyFilterList'; // ์œ„์—์„œ ์ •์˜ํ•œ ์ปดํฌ๋„ŒํŠธ const allItems = Array.from({ length: 50000 }, (_, i) => `Product ${i + 1} - A very detailed description for item ${i + 1}`); const SearchWithTransition = () => { const [inputValue, setInputValue] = useState(''); // input์— ์ฆ‰์‹œ ๋ฐ˜์˜๋  ๊ฐ’ const [filterQuery, setFilterQuery] = useState(''); // HeavyFilterList์— ์ „๋‹ฌ๋  ๊ฐ’ const [isSearching, startTransition] = useTransition(); // useTransition ํ›… const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); // input ๊ฐ’์€ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ // ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๋กœ ํ•„ํ„ฐ๋ง ์ฟผ๋ฆฌ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ž˜ํ•‘ startTransition(() => { setFilterQuery(e.target.value); }); }; return ( <div> <h2>๐Ÿ” `useTransition`์„ ์ด์šฉํ•œ ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง</h2> <input type="text" value={inputValue} onChange={handleInputChange} placeholder="๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”..." style={{ width: '300px', padding: '8px', fontSize: '16px' }} /> {isSearching && <p style={{ color: 'blue', display: 'inline-block', marginLeft: '10px' }}>๊ฒ€์ƒ‰ ์ค‘...</p>} <p>ํ˜„์žฌ ์ž…๋ ฅ: **{inputValue}**</p> <HeavyFilterList items={allItems} filterTerm={filterQuery} /> </div> ); }; export default SearchWithTransition;

SearchWithTransition ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” inputValue๋Š” ์‚ฌ์šฉ์ž์˜ ํƒ€์ดํ•‘์— ๋งž์ถฐ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ๋˜์–ด input ํ•„๋“œ์— ๋ฐ˜์˜๋ผ์š”.
๋ฐ˜๋ฉด, HeavyFilterList์— ์ „๋‹ฌ๋˜๋Š” filterQuery๋Š” startTransition์œผ๋กœ ๊ฐ์‹ธ์ ธ ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๋กœ ์ฒ˜๋ฆฌ๋ผ์š”.
๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž๋Š” input์— ํƒ€์ดํ•‘ํ•˜๋Š” ๋™์•ˆ์—๋„ UI๊ฐ€ ๋Š๊น€ ์—†์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ฐ˜์‘ํ•˜๋Š” ๊ฒƒ์„ ๊ฒฝํ—˜ํ•  ์ˆ˜ ์žˆ๊ณ , isSearching ์ƒํƒœ๋ฅผ ํ†ตํ•ด ๊ฒ€์ƒ‰ ์ค‘์ž„์„ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ต๋‹ˆ๋‹ค.

2๏ธโƒฃ useDeferredValue ์ ์šฉ ์˜ˆ์‹œ

์ด๋ฒˆ์—๋Š” ๋™์ผํ•œ ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง ์‹œ๋‚˜๋ฆฌ์˜ค์— useDeferredValue๋ฅผ ์ ์šฉํ•ด๋ณผ๊ฒŒ์š”.

import React, { useState, useDeferredValue } from 'react'; import HeavyFilterList from './HeavyFilterList'; // ์œ„์—์„œ ์ •์˜ํ•œ ์ปดํฌ๋„ŒํŠธ const allItems = Array.from({ length: 50000 }, (_, i) => `Product ${i + 1} - A very detailed description for item ${i + 1}`); const SearchWithDeferredValue = () => { const [inputValue, setInputValue] = useState(''); // input์— ์ฆ‰์‹œ ๋ฐ˜์˜๋  ๊ฐ’ // inputValue์˜ ์ง€์—ฐ๋œ ๋ฒ„์ „์„ ์ƒ์„ฑ. HeavyFilterList๋Š” ์ด ๊ฐ’์„ ์‚ฌ์šฉ. const deferredInputValue = useDeferredValue(inputValue); const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); }; return ( <div> <h2>๐Ÿ’ก `useDeferredValue`๋ฅผ ์ด์šฉํ•œ ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง</h2> <input type="text" value={inputValue} onChange={handleInputChange} placeholder="๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”..." style={{ width: '300px', padding: '8px', fontSize: '16px' }} /> {inputValue !== deferredInputValue && <p style={{ color: 'orange', display: 'inline-block', marginLeft: '10px' }}>๊ฒฐ๊ณผ ์—…๋ฐ์ดํŠธ ๋Œ€๊ธฐ ์ค‘...</p>} <p>ํ˜„์žฌ ์ž…๋ ฅ: **{inputValue}**</p> {/* HeavyFilterList๋Š” ์ง€์—ฐ๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ Œ๋”๋ง๋ผ์š” */} <HeavyFilterList items={allItems} filterTerm={deferredInputValue} /> </div> ); }; export default SearchWithDeferredValue;

SearchWithDeferredValue ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” inputValue๋Š” input์— ์ฆ‰์‹œ ๋ฐ˜์˜๋˜์ง€๋งŒ, HeavyFilterList์— ์ „๋‹ฌ๋˜๋Š” filterTerm์œผ๋กœ๋Š” useDeferredValue(inputValue)๋ฅผ ํ†ตํ•ด ์ƒ์„ฑ๋œ deferredInputValue๋ฅผ ์‚ฌ์šฉํ•ด์š”.
์ด ๋ฐฉ์‹ ์—ญ์‹œ useTransition๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ input์˜ ์‘๋‹ต์„ฑ์„ ํ™•๋ณดํ•˜๋ฉด์„œ ๋ฌด๊ฑฐ์šด ํ•„ํ„ฐ๋ง ์ž‘์—…์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์š”.
inputValue !== deferredInputValue ์กฐ๊ฑด์„ ํ†ตํ•ด ์ง€์—ฐ๋œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ง„ํ–‰ ์ค‘์ž„์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆด ์ˆ˜๋„ ์žˆ๋‹ต๋‹ˆ๋‹ค.

๐Ÿ“ ํ•ต์‹ฌ ์š”์•ฝ ๋ฐ ๋‹ค์Œ ๋‹จ๊ณ„

0๏ธโƒฃ useTransition๊ณผ useDeferredValue์˜ ์—ญํ• 

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

  • useTransition: ํŠน์ • ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ UI๋ฅผ ๋ธ”๋กœํ‚นํ•˜์ง€ ์•Š๋„๋ก ํ•ด์š”. ์ฃผ๋กœ "์•ก์…˜"์„ ๋ž˜ํ•‘ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๊ณ , isPending์œผ๋กœ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๋ช…ํ™•ํžˆ ์•Œ ์ˆ˜ ์žˆ์–ด์š”.
  • useDeferredValue: ์–ด๋–ค ๊ฐ’ ์ž์ฒด๋ฅผ ์ง€์—ฐ๋œ ๋ฒ„์ „์œผ๋กœ ์ œ๊ณตํ•˜์—ฌ, ํ•ด๋‹น ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์„ ์ง€์—ฐ์‹œ์ผœ์š”. ์ฃผ๋กœ props๋‚˜ ์ƒํƒœ ๊ฐ’์— ์ ์šฉํ•˜์—ฌ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง ์ตœ์ ํ™”์— ์‚ฌ์šฉ๋ผ์š”.

1๏ธโƒฃ ์–ธ์ œ ์–ด๋–ค ํ›…์„ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ์š”?

์œ ์šฉํ•œ ํŒ

useTransition์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ:

  • ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜(ํด๋ฆญ, ํผ ์ œ์ถœ ๋“ฑ)์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฌด๊ฑฐ์šด ๋ Œ๋”๋ง์„ ์œ ๋ฐœํ•  ๋•Œ.
  • ํŠน์ • "์•ก์…˜"์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ, ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฆ‰๊ฐ์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ด์•ผ ํ•  ๋•Œ.
  • isPending ์ƒํƒœ๋ฅผ ํ†ตํ•ด ๋ช…ํ™•ํ•œ ๋กœ๋”ฉ UI๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์„ ๋•Œ.

์œ ์šฉํ•œ ํŒ

useDeferredValue๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ:

  • ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ „๋‹ฌ๋ฐ›์€ props๋‚˜ ๋กœ์ปฌ ์ƒํƒœ ๊ฐ’์ด ๋ฌด๊ฑฐ์šด ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์„ ์œ ๋ฐœํ•  ๋•Œ.
  • ์ฃผ๋กœ input ํ•„๋“œ์˜ ๊ฐ’๊ณผ ๊ฐ™์ด ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฐ’์ด์ง€๋งŒ, ์ด ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์€ ์ง€์—ฐ๋˜์–ด๋„ ๊ดœ์ฐฎ์„ ๋•Œ.
  • useTransition์ฒ˜๋Ÿผ ๋ช…์‹œ์ ์ธ startTransition ํ˜ธ์ถœ ์—†์ด ๊ฐ’์˜ ๋ณ€ํ™”๋ฅผ React์— ๋งก๊ธฐ๊ณ  ์‹ถ์„ ๋•Œ.

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

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

๐Ÿ“ฎ ์ฐธ๊ณ 

์—ฐ๊ด€๋œ ํฌ์ŠคํŠธ