๐Ÿšฅ

13. useTransition

ย 

13.1 useTransition ๊ฐœ์š”

ย 

13.1.1 useTransition ์ด๋ž€?

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

13.1.2 useTransition ์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ?

์ถ”๊ฐ€์ ์œผ๋กœ useTransition์„ ์„ค๋ช…ํ•˜๊ธฐ์— ์•ž์„œ ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ์™€ ์ „ํ™˜ ์—…๋ฐ์ดํŠธ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. React 18 ์ „๊นŒ์ง€๋Š” ์•„๋ž˜์˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ๋ชจ๋“  ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ธด๊ธ‰ํ•˜๊ฒŒ ๋ Œ๋”๋ง๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋ชจ๋“  ์—…๋ฐ์ดํŠธ๊ฐ€ ๋™์‹œ์— ๋ Œ๋”๋ง ๋˜๋Š” ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์ฐจ๋‹จํ•˜๊ฒŒ ๋˜์–ด ๋ Œ๋”๋ง์ด ๋˜๋Š” ๋™์•ˆ ํŽ˜์ด์ง€๋Š” ์ง€์—ฐ ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜์—ฌ ์‘๋‹ตํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋Š๊ปด์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
ย 
setInputValue(input); // ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ : ์ž…๋ ฅํ•œ ๊ฐ’ setSearchQuery(input); // ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ: ๊ฒฐ๊ณผ ๊ฐ’
ํ•˜์ง€๋งŒ ์ตœ๊ทผ React 18์— ์ถ”๊ฐ€๋œ useTransition์„ ํ†ตํ•ด React์—๊ฒŒ ๊ธด๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ์™€ ๊ทธ๋ ‡์ง€ ์•Š์€ ์—…๋ฐ์ดํŠธ๋ฅผ ์•Œ๋ ค์คŒ์œผ๋กœ์จ ์‚ฌ์šฉ์ž์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๋น ๋ฅด๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ๊ณผ ๋™์‹œ์— ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง์„ ์ค„์ผ ์ˆ˜ ์žˆ๋Š” ํšจ๊ณผ๋„ ์–ป์„ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
ย 
  • ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ: ์ž…๋ ฅ, ํด๋ฆญ๊ณผ ๊ฐ™์€ ์ง์ ‘์ ์ธ ์‚ฌ์šฉ์ž์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๋ฐ˜์˜ํ•˜๋Š” ์—…๋ฐ์ดํŠธ๋กœ, ์ฆ‰๊ฐ์ ์œผ๋กœ ์„œ๋น„์Šค๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋ถˆํŽธํ•จ์„ ๋Š๋‚„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ
  • ์ „ํ™˜ ์—…๋ฐ์ดํŠธ: ํ•˜๋‚˜์˜ ๋ทฐ์—์„œ ๋‹ค๋ฅธ ๋ทฐ๋กœ UI๋ฅผ ์ „ํ™˜ํ•˜๋Š” ์—…๋ฐ์ดํŠธ๋กœ, ํ™”๋ฉด์— ์ฆ‰์‹œ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์•„๋„ ์‚ฌ์šฉ์ž๊ฐ€ ๋ถˆํŽธํ•จ์„ ๋Š๋ผ์ง€ ์•Š๋Š” ์˜์—ญ
ย 
์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์ฐฝ์— ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด, API์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ํ™”๋ฉด์— ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋ชฉ๋ก์„ ๋ฏธ๋ฆฌ ๋ณด์—ฌ์ฃผ๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์ฐฝ์— ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅ(๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ)์„ ํ•˜๊ณ  ์žˆ์Œ์—๋„ ๋งŽ์€ ์–‘์˜ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ(์ „ํ™˜ ์—…๋ฐ์ดํŠธ)๊ฐ€ ๋ Œ๋”๋ง์ด ๋˜์–ด๋ฒ„๋ฆฐ๋‹ค๋ฉด ๋ Œ๋”๋ง ๋ธ”๋กœํ‚น ๋ฌธ์ œ๋กœ ์ธํ•ด ์ž…๋ ฅ์ฐฝ์ด ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋Š” ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋งค์šฐ ๋ถˆ๋งŒ์กฑ์Šค๋Ÿฌ์šด ๊ฒฝํ—˜์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ย 
import { startTransition } from 'react' setInputValue(input) // ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ : ์ž…๋ ฅํ•œ ๊ฐ’ startTransition(() => { setSearchQuery(input) // ์ „ํ™˜ ์—…๋ฐ์ดํŠธ: ๊ฒฐ๊ณผ ๊ฐ’
ย 
์œ„์˜ ์˜ˆ์ œ์™€ ๊ฐ™์ด startTransition ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ธด๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ฆญ ํ˜น์€ ํ‚ค ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ startTransition ์œผ๋กœ ๊ฐ์‹ธ์ธ ๊ธด๊ธ‰ํ•œ ์ด๋ฒคํŠธ๋ฅผ ๋จผ์ € ์ฒ˜๋ฆฌํ•œ ์ดํ›„ ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์˜ค๋ž˜๋œ ๋ Œ๋”๋ง์„ ํ๊ธฐํ•˜๊ณ  ์ตœ์‹  ์—…๋ฐ์ดํŠธ๋กœ ๋ Œ๋”๋ง์„ ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
startTransition์ด๋ž€? ๋ฆฌ์•กํŠธ์— ์–ด๋–ค ์ƒํƒœ๋ณ€ํ™”๋ฅผ ์ง€์—ฐํ•˜๊ณ  ์‹ถ์€์ง€ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
ย 
์ด์ „ ๋ฒ„์ „์—์„œ๋Š” ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋“ฑ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ Debounce, Throttle ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ์ • ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.
Debounce ์™€ Throttle ์˜ ๊ฒฝ์šฐ ์ผ์ • ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์œผ๋กœ ๋ฌธ์ œ๋ฅผ ์ž ์‹œ ๋ฏธ๋ฃจ๋Š” ๋ฐฉ์‹์ด์—ˆ๋‹ค๋ฉด, useTranstion์€ ๋ Œ๋”๋ง ์ค‘์—๋„ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋” ๋†’์€ ์ž‘์—…์ด ์ƒ๊ธด๋‹ค๋ฉด ๋จผ์ € ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์คŒ์œผ๋กœ์จย ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ฐจ๋ณ„์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰ ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜๋ฅผ ์ „ํ™˜ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋†’๊ฒŒ ์„ค์ •ํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด๋ผ ๋ณผ ์ˆ˜ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 

13.2 useTransition ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

ย 

13.2.1 useTransition์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ

ย 
const [isPending, startTransition] = useTransition();
ย 
useTransition์€ ๋‘ ๊ฐœ์˜ ์ธ์ž๋ฅผ ๋ฐ›์•„ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ธ์ž isPending์€ โ€˜๋Œ€๊ธฐโ€™, โ€˜๋ณด๋ฅ˜ ์ค‘โ€™ ์ƒํƒœ์˜ ์ž‘์—…์ด ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” Boolean ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. isPending์€ true์ผ ๋•Œ, ๋กœ๋”ฉ ์ค‘์ž„์„ ํ‘œ์‹œํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆฌ๊ฑฐ๋‚˜ ์†์„ฑ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜์—ฌ UI๋กœ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ์ธ์ž startTransition์€ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ์‹คํ–‰ํ•ด๋„ ๋˜๋Š” ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜๋ฅผ ๊ฐ์Œ‰๋‹ˆ๋‹ค.
ย 
๋‹ค์Œ์€ useTransition์„ ์‚ฌ์šฉํ•œ ๊ธฐ๋ณธ ์˜ˆ์ œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. count ์ƒํƒœ ๊ฐ’์„ 2์”ฉ ๋”ํ•ด์ฃผ๋Š” setCount ํ•จ์ˆ˜๋ฅผ startTransition์œผ๋กœ ๊ฐ์‹ธ์ฃผ์—ˆ์œผ๋ฉฐ, ์•ž์—์„œ ๊ฐ์ŒŒ๋˜ ๋ณด๋ฅ˜ ์ค‘์ธ ์ž‘์—…์ด ๋‚จ์•„์žˆ์„ ๋•Œ Loadingโ€ฆ ๋ฌธ๊ตฌ๋ฅผ ํ™”๋ฉด์— ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
function App() { const [isPending, startTransition] = useTransition(); const [count, setCount] = useState(0); function handleClick() { startTransition(() => { setCount(c => c + 2); }) } return ( <div> {isPending && "Loading..."} <button onClick={handleClick}>{count}</button> </div> ); }
App.js
ย 

13.2.2 useTransiton์˜ ์ž‘๋™ ๋ฐฉ์‹

React 18์—์„œ๋Š” concurrency(๋™์‹œ์„ฑ)์ด ๊ฐ€์žฅ ์ค‘์š”ํ•œ ํ‚ค์›Œ๋“œ๋กœ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋™์‹œ์„ฑ์€ โ€˜๋™์‹œ์— ์‹คํ–‰ํ•œ๋‹คโ€™์™€๋Š” ๋‹ค๋ฅธ ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. JavaScript๋Š” ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์—, ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ ์•ˆ์—์„œ ์—ฌ๋Ÿฌ ์ž‘์—…๋“ค์„ ์ž‘์€ ๋‹จ์œ„๋กœ ์ชผ๊ฐœ์–ด โ€˜๋น ๋ฅด๊ฒŒ ์ž‘์—…๋“ค์„ ๋ฐ”๊ฟ”๊ฐ€๋ฉฐ ์‹คํ–‰ํ•˜๋ฏ€๋กœ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹คโ€™๋Š” ์˜๋ฏธ๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 13-1๊ทธ๋ฆผ 13-1
๊ทธ๋ฆผ 13-1
ย 
๋‹ค์Œ์€ React ๊ณต์‹ ๋ฌธ์„œ์—์„œ ๋ฐœ์ทŒํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.
In a concurrent render, this is not always the case. โ€ฆ To do this, it waits to perform DOM mutations until the end, once the entire tree has been evaluated. With this capability, React can prepare new screens in the background without blocking the main thread. This means the UI can respond immediately to user input even if itโ€™s in the middle of a large rendering task, creating a fluid user experience.
React์—์„œ์˜ ๋™์‹œ์„ฑ์€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์˜ ๋™์ž‘์„ ๋ง‰์ง€ ์•Š๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ƒˆ๋กœ์šด ํ™”๋ฉด์„ ์ค€๋น„ํ•œ๋‹ค๋Š” ์˜๋ฏธ๋กœ, ๋Œ€๊ทœ๋ชจ ๋ Œ๋”๋ง ์ž‘์—… ์ค‘ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ์ฆ‰์‹œ ์‘๋‹ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

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

์ฒซ ๋ฒˆ์งธ, ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ state์˜ setํ•จ์ˆ˜์—๋งŒ startTransiton์œผ๋กœ ๊ฐ์Œ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ props ๋˜๋Š” ์ปค์Šคํ…€ ํ›…์˜ ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ „ํ™˜ํ•ด์•ผ ํ•œ๋‹ค๋ฉด, useDefferedValue๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋‘ ๋ฒˆ์งธ, startTransition์œผ๋กœ ๊ฐ์‹ผ ํ•จ์ˆ˜๋Š” ๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰๋˜๋‚˜, ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ์ „ํ™˜ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ Œ๋”๋ง ๋˜๋Š” ๋™์•ˆ ๋†’์€ ์šฐ์„ ์ˆœ์œ„์˜ ํ•จ์ˆ˜๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด ๋ Œ๋”๋ง์„ ์ค‘๋‹จํ•˜๊ณ , ์šฐ์„ ์ˆœ์œ„ ์ฒ˜๋ฆฌ ํ›„ ๋ Œ๋”๋ง ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค.
๋งˆ์ง€๋ง‰์œผ๋กœ, useTransiton์€ Hook์ด๊ธฐ ๋•Œ๋ฌธ์— ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ startTransiton ํ•จ์ˆ˜๋ฅผ ๋‹จ๋…์œผ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์‹ผ๋‹ค๋ฉด Hook ์—†์ด๋„ ์ „ํ™˜ ์ž‘์—…์œผ๋กœ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„์— ๋‘˜ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import { startTransiton } from "react"
ย 

13.3 (์‹ค์Šต1) useTransition์„ ํ†ตํ•ด ๊ฒ€์ƒ‰ ๋ฏธ๋ฆฌ๋ณด๊ธฐ UX ๊ฐœ์„ ํ•˜๊ธฐ

10,000๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ณผ์ •์—์„œ์˜ ์ง€์—ฐ์‹œ๊ฐ„์œผ๋กœ ์ธํ•ด input ์š”์†Œ์— ํƒ€์ดํ•‘ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ํ™”๋ฉด์ƒ์— ๋ฐ”๋กœ ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. 10,000๊ฐœ์˜ ์š”์†Œ๊ฐ€ ํ™”๋ฉด์ƒ์— ๋ Œ๋”๋ง์ด ๋จผ์ € ์ฒ˜๋ฆฌ๊ฐ€ ๋˜๊ณ  ๋‚œ ํ›„์— ๋ฐ˜์˜์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
์ด๋ฅผย useTransitionย Hook์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—…์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ณ€๊ฒฝํ–ˆ์„ ๋•Œ ์–ด๋–ค ์ฐจ์ด๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
App.js, Page.js, Result.js ์„ธ ๊ฐ€์ง€ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

13.3.1 useTransition์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ

import { useState } from "react"; import Page from "./pages/Page"; function App() { const [keyword, setKeyword] = useState(); return ( <div> <Page keyword={keyword} setKeyword={setKeyword}/> </div> ); } export default App;
App.js
๊ฐ€์žฅ ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์ธ App.js์—์„œ๋Š” input ๊ฐ’์— ์ž…๋ ฅ๋˜๋Š” value๋ฅผ ๋‹ด์„ state๋ฅผ ์„ ์–ธํ•˜๊ณ  props๋กœ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋„˜๊ฒจ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
ย 
import React from 'react' import Result from '../components/Result' function Page({keyword, setKeyword}) { return ( <div> <input type="text" onKeyUp={(e) => {setKeyword(e.target.value)}} /> <Result keyword={keyword}/> </div> ) } export default Page
Page.js
Page.js์—์„œ input ์š”์†Œ์˜ value๋ฅผ setKeyword๋ฅผ ์ด์šฉํ•˜์—ฌ state์— ๊ฐ’์„ ์ €์žฅํ•˜๋Š” onKeyUp ์ด๋ฒคํŠธ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.
ย 
import React from "react"; function Result({keyword}) { const bigWork = new Array(10000).fill(0); return ( <div> { bigWork.map(()=> { return <div>{keyword} {keyword} {keyword}</div> }) } </div> ) } export default Result
Result.js
state๋ฅผ ๋ฐ›์•„ div์š”์†Œ์— ์„ธ๋ฒˆ ๋ฐ˜๋ณตํ•˜๋Š” ๊ฒฐ๊ณผ๊ฐ’์„ ์ถœ๋ ฅํ•˜๋„๋ก ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. bigWork๋ผ๊ณ  ์„ ์–ธ๋œ ๋ฐฐ์—ด์ด map ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด 10,000๊ฐœ์˜ ์š”์†Œ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ณผ์ •์—์„œ ์ง€์—ฐ์‹œ๊ฐ„์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ง€์—ฐ์‹œ๊ฐ„์œผ๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž๋Š” input ์š”์†Œ์— value๋ฅผ ์ž…๋ ฅํ•  ๋•Œ์˜ ๊ฒฐ๊ณผ๊ฐ€ ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ณด์—ฌ์ง€์ง€ ์•Š์•„ ๋ถˆํŽธํ•จ์„ ๋Š๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 13-2 ๊ทธ๋ฆผ 13-2
๊ทธ๋ฆผ 13-2
์ง์ ‘ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ํ•ด๋ณด๋ฉด input์— ๊ฐ€๋‚˜๋‹ค๋ผ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ–ˆ์ง€๋งŒ, ์ง€์—ฐ์‹œ๊ฐ„์œผ๋กœ ์ธํ•ด input์— ์ž…๋ ฅํ•˜๋Š” ์ฆ‰๊ฐ์ ์ธ ๋ Œ๋”๋ง ๋ฐ˜์‘์†๋„๊ฐ€ ๋งค์šฐ ๋Š๋ฆฐ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ React Developer Tools ์ต์Šคํ…์…˜์„ ์„ค์น˜ํ•˜๋ฉด Profiler๋กœ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด useTransition์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์—๋Š” ์„ธ๊ฐœ์˜ ํŒŒ์ผ์ด ๊ฑฐ์˜ ๋™์‹œ์— ๋ Œ๋”๋ง๋˜๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ Œ๋”๋ง ์†Œ์š” ์‹œ๊ฐ„์ด 74.4ms ์ธ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆผ 13-3 ์„ฑ๋Šฅ ์ธก์ • ๊ฒฐ๊ณผ๊ทธ๋ฆผ 13-3 ์„ฑ๋Šฅ ์ธก์ • ๊ฒฐ๊ณผ
๊ทธ๋ฆผ 13-3 ์„ฑ๋Šฅ ์ธก์ • ๊ฒฐ๊ณผ
ย 

13.3.2 useTransition์„ ์ ์šฉํ•ด๋ณด๊ธฐ

import { useTransition } from "react"; import { useState } from "react"; import Page from "./pages/Page"; function App() { const [keyword, setKeyword] = useState(); const [isPending, startTransition] = useTransition(); return ( <div> <Page keyword={keyword} setKeyword={setKeyword} isPending={isPending} startTransition={startTransition}/> </div> ); } export default App;
App.js
๊ฐ€์žฅ ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์ธ App.js ์—์„œ useTransition์„ ์„ ์–ธํ•˜๊ณ  ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚ด๋ ค์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import React from 'react' import Result from '../components/Result' function Page({keyword, setKeyword, isPending, startTransition}) { return ( <div> <input type="text" onKeyUp={(e) => { startTransition(()=>{ setKeyword(e.target.value) }) }} /> <Result keyword={keyword} isPending={isPending}/> </div> ) } export default Page
Page.js
์ด๋ฒคํŠธ ํ•จ์ˆ˜๋กœ ๋™์ž‘ํ•˜๋Š” setKeyword๋ฅผ startTransition๋กœ ๋ž˜ํ•‘ํ•˜๋ฉด ๋ Œ๋”๋ง ์‹œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์„ค์ •๋˜์–ด setKeyword ํ•จ์ˆ˜๋Š” ๋‹ค๋ฅธ ์ฝ”๋“œ๋“ค๋ณด๋‹ค ๋‚˜์ค‘์— ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž๊ฐ€ input์— ์ž…๋ ฅํ•˜๋Š” ์ฆ‰์‹œ ๋ฐ˜์‘ํ•ด์•ผ ํ•˜๋Š” ๋ Œ๋”๋ง์„ ์šฐ์„ ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import React from "react"; function Result({keyword, isPending}) { const bigWork = new Array(10000).fill(0); return ( <div> { isPending ? ( <div>Loading...</div> ) : ( bigWork.map(() => { return <div>{keyword} {keyword} {keyword}</div> }) ) } </div> ) } export default Result
Result.js
isPending์„ ์ด์šฉํ•˜์—ฌ startTransition์œผ๋กœ ๊ฐ์‹ผ setKeyword ์—…๋ฐ์ดํŠธ๊ฐ€ ์ง„ํ–‰๋  ๋•Œ Loading ๋ฌธ์ž๊ฐ€ ๋ Œ๋”๋ง ๋  ์ˆ˜ ์žˆ๋„๋ก ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. useTransition์„ ์‚ฌ์šฉํ•˜๋ฉด ์ž‘์—…์˜ ์ฒ˜๋ฆฌ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ–ˆ๊ธฐ์— ์ด์ „๋ณด๋‹ค ํ™•์‹คํžˆ input์— ํƒ€์ดํ•‘ํ•  ๋•Œ ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ฐ˜์‘ํ•˜๊ณ , isPending์„ ์ด์šฉํ•˜์—ฌ ์ž‘์—… ์ฒ˜๋ฆฌ์ค‘์ด๋ผ๋Š” ์˜๋ฏธ์˜ ๋กœ๋”ฉํ™”๋ฉด๋„ ์‰ฝ๊ฒŒ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆผ 13-4 ๊ทธ๋ฆผ 13-4
๊ทธ๋ฆผ 13-4
๐Ÿ’ก
ย useTransition์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ Œ๋”๋ง์˜ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ํ™•์‹คํžˆ ๋“œ๋Ÿฌ๋‚˜๋ฉฐ ๋ฌด๊ฑฐ์šด ์ž‘์—…์ด ์žˆ๋Š” Result ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์€ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— Page์ปดํฌ๋„ŒํŠธ์— ์žˆ๋Š” input ์˜ ํƒ€์ดํ•‘์— ๋Œ€ํ•œ ๋ Œ๋”๋ง์ด ์šฐ์„ ๋˜๊ธฐ์— ์‚ฌ์šฉ์ž๋Š” ๋ฒ„๋ฒ…์ž„์ด ์ค„์–ด๋“  ๊ฒƒ ์ฒ˜๋Ÿผ ๋Š๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
useTransition์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฌด๊ฑฐ์šด ์ž‘์—…์ด ๋™๋ฐ˜๋˜์–ด ์žˆ๋Š” ์ƒํ™ฉ์—์„œ๋„ ์‚ฌ์šฉ์ž๊ฐ€ input์— ์ž…๋ ฅํ•˜๋Š” ๋ Œ๋”๋ง ๊ฒฐ๊ณผ๊ฐ€ ์šฐ์„ ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ์ ์€ย useTransition์ด ๋ฌด๊ฑฐ์šด ์ž‘์—…์œผ๋กœ ์ธํ•œ ์ง€์—ฐ์‹œ๊ฐ„ ์ž์ฒด๋ฅผ ์ค„์ด๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
๊ทธ๋ฆผ 13-5 ๊ทธ๋ฆผ 13-5
๊ทธ๋ฆผ 13-5
ย 
ย 

13.4 (์‹ค์Šต2) useTransition์„ ํ†ตํ•ด ํŽ˜์ด์ง€ ์ด๋™ UX ๊ฐœ์„ ํ•˜๊ธฐ

ย 

13.4.1 useTransition์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ

๋‹ค์Œ์€ useTransition์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ „ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. App.js, PageOne.js, PageTwo.js ์„ธ ๊ฐ€์ง€ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. App.js์—์„œ๋Š” setPage๊ฐ€ ์ง€์ •ํ•˜๋Š” state ๊ฐ’์— ๋”ฐ๋ผ ํ•ด๋‹นํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™”๋ฉด์— ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. App.js์—์„œ ๋ˆ„๋ฅด๋Š” ๋ฒ„ํŠผ์˜ state ๊ฐ’์— ๋”ฐ๋ผ ๊ฐ๊ฐ PageOne๊ณผ PageTwo์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ๊ทธ๋ ค์ง€๋Š”๋ฐ, PageOne์€ 30,000๊ฐœ๊ฐ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ Œ๋”๋ง ๋˜๋„๋ก ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ์‹œ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ƒํ™ฉ์„ ๊ฐ€์ •ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ƒ๋Œ€์ ์œผ๋กœ PageTwo๋Š” ๋ Œ๋”๋งํ•  ์š”์†Œ๊ฐ€ ์ ๊ธฐ ๋•Œ๋ฌธ์— ํ™”๋ฉด์— ๋ฐ”๋กœ ๊ทธ๋ ค์ง‘๋‹ˆ๋‹ค.
ย 
import { useState } from "react"; import PageOne from "./PageOne.js"; import PageTwo from "./PageTwo.js"; function Router() { const [page, setPage] = useState("/"); function navigate(url) { setPage(url); } let content; if (page === "/") { content = ( <> <button onClick={() => navigate("/page-one")}> ๐Ÿ‹๏ธโ€โ™€๏ธ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€ </button> <button onClick={() => navigate("/page-two")}>๐Ÿ™‚ ์ผ๋ฐ˜ ํŽ˜์ด์ง€</button> </> ); } else if (page === "/page-one") { content = <PageOne />; } else if (page === "/page-two") { content = <PageTwo />; } return ( <> <h1>{`ํ™ˆ`}</h1> <main>{content}</main> </> ); } export default function App() { return <Router />; }
App.js
import { Suspense, useEffect } from "react"; export default function PageOne() { return ( <> <h1>๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.</h1> <Suspense fallback={<h2>Loading...</h2>}> {Array(30000) .fill() .map((v, i) => ( <div key={i}>๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ</div> ))} </Suspense> </> ); }
PageOne.js
import React from "react"; function PageTwo() { return <div>์ผ๋ฐ˜ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.</div>; } export default PageTwo;
PageTwo.js
ย 
ย 
ย 
๊ทธ๋ฆผ 13-6๊ทธ๋ฆผ 13-6
๊ทธ๋ฆผ 13-6
ย 
ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, ํ™ˆ์—์„œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ๋‹ค๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ํ™”๋ฉด์ด ๋ฉˆ์ถ”๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ Œ๋”๋ง ์š”์ฒญ์ด ๋๋‚˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์ผ๋ฐ˜ ํŽ˜์ด์ง€๋ฅผ ํด๋ฆญํ•˜๋Š” ๋“ฑ์˜ ๋‹ค๋ฅธ ์ž‘์—…์„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

13.4.2 useTransition ์ ์šฉํ•ด๋ณด๊ธฐ

useTransition Hook์„ ์ ์šฉํ•ด๋ณด๊ธฐ์— ์•ž์„œ React 18์— ๋„์ž…๋œ Suspense ๊ฐœ๋…์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
๐Ÿ’ก
Suspense๋ž€ ? ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ์ „๋ถ€ ๋กœ๋“œ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋ฅผ ๋ฆฌ์•กํŠธ์—๊ฒŒ ์•Œ๋ ค์ฃผ๋Š” ๊ธฐ๋Šฅ์œผ๋กœ, Suspense ๋‚ด๋ถ€์˜ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋™์•ˆ์—๋Š” ๋ Œ๋”๋ง์ด ์ผ์‹œ ์ค‘๋‹จ๋˜๋ฉฐ fallback ํ”„๋กœํผํ‹ฐ์— ์ง€์ •๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ง‘๋‹ˆ๋‹ค.
ย 
Suspense๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
<Suspense fallback={<Spinner/>}> <Component/> {/* ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋Š” ์ปดํฌ๋„ŒํŠธ */} </Suspense>
ย 
์—ฌ๊ธฐ์„œ fallback ์ปจํ…์ธ ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ์—๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ธฐ์กด์˜ UI ๋ Œ๋”๋ง์ด ์ผ์‹œ์ ์œผ๋กœ ์ค‘๋‹จ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์—ฌ์ง‘๋‹ˆ๋‹ค. ์ด ๋•Œ ํŽ˜์ด์ง€๋ฅผ ๋ณด๋‹ค ๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ „ํ™˜ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ useTransition ์„ Suspense ์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. useTransition ๋‚ด๋ถ€์— ์ง€์ •ํ•œ ํ•จ์ˆ˜๋กœ ์ธํ•œ ์—…๋ฐ์ดํŠธ์˜ ๋ Œ๋”๋ง์ด ์ค‘๋‹จ๋˜๋Š” ๊ฒฝ์šฐ, Suspense๋Š” fallback์— ์ง€์ •๋œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋Œ€์‹  UI๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ ์ž‘์—…์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
์ด์ „ ์ฝ”๋“œ์—์„œ ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด App.js์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์ด ์ผ์–ด๋‚˜๋Š” Router ์ปดํฌ๋„ŒํŠธ๋ฅผ Suspense๋กœ ๊ฐ์‹ธ์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด์–ด์„œ useTranisiton Hook์„ ์ ์šฉํ•˜์—ฌ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์„ค์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. startTransition ์œผ๋กœ setPage๋ฅผ ๊ฐ์‹ธ ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๋ฅผ ์™„๋ฃŒํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋„๋ก React์—๊ฒŒ ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ย 
import { useState, Suspense, useTransition } from "react"; function Router() { const [page, setPage] = useState("/"); const [isPending, startTransition] = useTransition(); function navigate(url) { startTransition(() => { setPage(url); }); } //... export default function App() { return ( <Suspense fallback={<h2>๐Ÿค” Loading...</h2>}> <Router /> </Suspense> ); }
App.js
์—ฌ๊ธฐ์„œ ์‚ฌ์šฉ์ž๊ฐ€ โ€˜๐Ÿ‹๏ธโ€โ™€๏ธ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€โ€™ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”?
Suspense ๋กœ ๊ฐ์‹ผ Router ์ปดํฌ๋„ŒํŠธ์˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ผ์–ด๋‚  ๋•Œ fallback์— ์ง€์ •๋œ โ€˜๐Ÿค” Loading...โ€™์ด ๋ Œ๋”๋ง๋  ๊ฒƒ์ด๋ผ๊ณ  ์˜ˆ์ƒํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ React๋Š” startTransition์œผ๋กœ ๊ฐ์‹ผ setPageํ•จ์ˆ˜๊ฐ€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ๋‹ค๊ณ  ์ธ์ง€ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, UI์˜ ๋ณ€๊ฒฝ์— ์•ž์„œ ํ•ด๋‹น ํŽ˜์ด์ง€์˜ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ž‘์—…์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋จผ์ € ์ง„ํ–‰๋˜๋ฉฐ ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์€ ํ›„์ˆœ์œ„๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.
ย 
์—ฌ๊ธฐ์„œ startTransition ๊ณผ ํ•จ๊ป˜ ์ „๋‹ฌ๋˜๋Š” ํ”„๋กœํผํ‹ฐ์ธ isPendig ์˜ ์ƒํƒœ๋ฅผ ํ†ตํ•ด ํ™”๋ฉด ์ „ํ™˜์ด ์ผ์–ด๋‚  ๋•Œ์˜ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. isPending ์˜ ์ƒํƒœ์— ๋”ฐ๋ผ ์ถœ๋ ฅ๋˜๋Š” ๋ฌธ๊ตฌ๋ฅผ ๋ฐ”๊ฟ”๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
return ( <> <h1>{isPending ? `โณ ๋กœ๋”ฉ์ค‘(isPending...)` : `ํ™ˆ`}</h1> <main>{content}</main> </> );
App.js
ย 
๊ทธ๋ฆผ 13-7๊ทธ๋ฆผ 13-7
๊ทธ๋ฆผ 13-7
ย 
๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ ์ค‘์ผ ๋•Œ Suspense์˜ fallback ์ปจํ…์ธ ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋Œ€์‹ , ์œ„์™€ ๊ฐ™์ด ์ „ํ™˜ ์—…๋ฐ์ดํŠธ์˜ isPending์˜ ์ƒํƒœ์— ๋”ฐ๋ผ ์กฐ๊ฑด ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์—ฌ ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ์—ญํ• ์„ ํ•˜๋Š” UI๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์—ฌ๊ธฐ์„œ ์งš๊ณ  ๊ฐ€์•ผ ํ•  useTransition์˜ ํŠน์ง•์€ startTransition์œผ๋กœ ๊ฐ์‹ผ ์ „ํ™˜ ์ž‘์—…์ด ์ค‘๋‹จ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. isPending ์ด true ์ƒํƒœ์ผ ๋•Œ, ์ผ๋ฐ˜ ํŽ˜์ด์ง€๋ฅผ ํด๋ฆญํ•  ๊ฒฝ์šฐ PageOne ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ์ž‘์—…์ด ์ค‘๋‹จ๋˜๊ณ  PageTwo๋กœ์˜ ๋ Œ๋”๋ง ์ „ํ™˜์ด ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด์˜ ์ฝ”๋“œ์— console.log๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. PageOne.js์—๋Š” useEffect Hook์„ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” console.log๋„ ์ถ”๊ฐ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { Suspense, useEffect } from "react"; export default function PageOne() { console.log("๐Ÿ‹๏ธโ€โ™€๏ธ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€"); useEffect(() => { console.log("๋งˆ์šดํŠธ: ์ปดํฌ๋„ŒํŠธ ์ฒ˜์Œ ๊ทธ๋ ค์ง"); }, []); return ( <> <h1>๋งŽ์€ ๋‚ด์šฉ์ด ์žˆ๋Š” ํŽ˜์ด์ง€</h1> <Suspense fallback={<h2>Loading...</h2>}> {Array(30000) .fill() .map((v, i) => ( <div key={i}>๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ</div> ))} </Suspense> </> ); }
import React from "react"; function PageTwo() { console.log("๐Ÿ™‚ ์ผ๋ฐ˜ ํŽ˜์ด์ง€"); return <div>PageTwo</div>; } export default PageTwo;
ย 
๊ทธ๋ฆผ 13-8๊ทธ๋ฆผ 13-8
๊ทธ๋ฆผ 13-8
notion imagenotion image
ย 
ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ ์šฉํ•œ ํ›„, ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ pageOne ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์ด ๋๋‚˜๊ธฐ ์ „์— ์ผ๋ฐ˜ ํŽ˜์ด์ง€ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ฝ˜์†”์—๋Š” ์œ„์™€ ๊ฐ™์ด ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€, ์ผ๋ฐ˜ ํŽ˜์ด์ง€๊ฐ€ ๋‚˜๋ž€ํžˆ ์ฐํžˆ๋ฉฐ, ์ผ๋ฐ˜ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด startTransition์œผ๋กœ ์ง€์ •๋œ ์ „ํ™˜ ์—…๋ฐ์ดํŠธ ์ง„ํ–‰ ์ค‘์— ์‚ฌ์šฉ์ž ํด๋ฆญ์ด๋ผ๋Š” ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ ๋ฐœ์ƒ ์‹œ ๊ธฐ์กด์˜ ์ „ํ™˜ ์—…๋ฐ์ดํŠธ ์ž‘์—…์ด ์ค‘๋‹จ๋˜๋ฉฐ ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 13-9๊ทธ๋ฆผ 13-9
๊ทธ๋ฆผ 13-9
notion imagenotion image
๋งŒ์•ฝ ์ „ํ™˜ ์ž‘์—…์ด ์ค‘๋‹จ๋˜๊ธฐ ์ „ ํŽ˜์ด์ง€ ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด ์œ„์™€ ๊ฐ™์ด ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋˜์–ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ทธ๋ ค์กŒ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ useTransition์˜ ์ „ํ™˜ ์ž‘์—…์ด ์ค‘๋‹จ๋œ๋‹ค๋Š” ์„ฑ์งˆ์„ ์ด์šฉํ•˜๋ฉด, ์‚ฌ์šฉ์ž๋Š” ๋ฌด๊ฑฐ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ํŽ˜์ด์ง€๋ฅผ ํด๋ฆญํ•˜๊ณ  ๋‚˜์„œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜๊ธฐ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์„œ๋น„์Šค ์ด์šฉ ์ค‘ ๋ฐ์ดํ„ฐ ๋กœ๋“œ์— ์ œ์•ฝ๋ฐ›์ง€ ์•Š๊ฒŒ ํ•˜์—ฌ ํŽ˜์ด์ง€์˜ UX ์„ฑ๋Šฅ ๊ฐœ์„ ์— ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์—ฌ๊ธฐ๊นŒ์ง€ useTransition์„ ์ด์šฉํ•˜์—ฌ UX ๊ฐœ์„ ์„ ๋งˆ์นœ App.js์˜ ์ตœ์ข… ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
ย 
import { Suspense, useState, useTransition } from "react"; import PageOne from "./PageOne.js"; import PageTwo from "./PageTwo.js"; function Router() { const [page, setPage] = useState("/"); const [isPending, startTransition] = useTransition(); function navigate(url) { startTransition(() => { setPage(url); }); } let content; if (page === "/") { content = ( <> <button onClick={() => navigate("/page-one")}> ๐Ÿ‹๏ธโ€โ™€๏ธ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€ </button> <button onClick={() => navigate("/page-two")}>๐Ÿ™‚ ์ผ๋ฐ˜ ํŽ˜์ด์ง€</button> </> ); } else if (page === "/page-one") { content = <PageOne />; } else if (page === "/page-two") { content = <PageTwo />; } return ( <> <h1>{isPending ? `โณ ๋กœ๋”ฉ์ค‘(isPending...)` : `ํ™ˆ`}</h1> <main>{content}</main> </> ); } export default function App() { return ( <Suspense fallback={<h2>๐Ÿค” Loading...</h2>}> <Router /> </Suspense> ); }
ย