Concurrent mode

๋™์‹œ์„ฑ ๋ชจ๋“œ(concurrent mode)๋Š” ํ•œ ๊ฐ€์ง€ ์ผ์ด ๋๋‚  ๋•Œ ๊นŒ์ง€ ๋ฌด์กฐ๊ฑด ๋‹ค์Œ ์ž‘์—…์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ํ•œ ๊ฐ€์ง€ ์ผ์ด ์‹คํ–‰ ์ค‘์ผ ๋™์•ˆ ๋‹ค๋ฅธ ์ผ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ด ๋™์‹œ์„ฑ(concurrency)์ด ๋ณ‘ํ–‰์„ฑ(parallelism)๊ณผ ๊ฐ™์€ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง„ ๋™์˜์–ด๊ฐ€ ์•„๋‹Œ๊ฐ€? ๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ ๋‘˜์ด ๋งํ•˜๋Š” ์ž‘์—…์˜ ๋™์‹œ์„ฑ์€ ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค.

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

๋ฆฌ์•กํŠธ์—์„œ์˜ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๋Š” ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž‘์—…๋“ค์„ ์ž‘์€ ์กฐ๊ฐ๋“ค๋กœ ๋‚˜๋ˆ„๊ณ , ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ํ†ตํ•˜์—ฌ ๊ฐ ์ž‘์—…๋“ค์˜ ์ค‘์š”๋„์— ๋”ฐ๋ฅธ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ถ€์—ฌํ•œ๋‹ค(time-slicing).

์™œ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ถ„๋ฆฌํ• ๊นŒ?

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

๋ฆฌ์•กํŠธ๊ฐ€ ์ด๋ ‡๊ฒŒ ๋‚˜๋ˆˆ ์ž‘์—…๋“ค์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ฉ”์ธ์Šค๋ ˆ๋“œ๋Š” ๋ธ”๋ก๋˜์ง€ ์•Š์œผ๋ฉฐ, ๋™์‹œ์— ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ ์šฐ์„  ์ˆœ์œ„์— ๋”ฐ๋ผ ๊ฐ ์ž‘์—…๋“ค ๊ฐ„์— ์ „ํ™˜์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

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

๋™์‹œ์„ฑ์˜ ๋„์ž… ๋ฐฐ๊ฒฝ

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

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

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ฐœ๋ฐœ์ž๋“ค์€ Debounce์™€ Throttle ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋А์ •๋„ ํ•ด์†Œํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

Debounce ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ด ์—ฐ์†์œผ๋กœ ๋“ค์–ด์˜ฌ ๋•Œ ๋งˆ์ง€๋ง‰ ์ž…๋ ฅ ํ›„ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚œ ๋‹ค์Œ์— ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ์‹

Throttle ํŠน์ • ์‹œ๊ฐ„ ๋™์•ˆ ํ•œ ๋ฒˆ๋งŒ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•˜๋Š” ๋ฐฉ์‹.

ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ ๋ฐฉ์‹ ๋˜ํ•œ Debounce์˜ ๊ฒฝ์šฐ์—๋Š” ์„ฑ๋Šฅ์ด ์ข‹์•„๋„ ๋ชจ๋“  ๊ธฐ๊ธฐ์—์„œ ๊ฐ™์€ ์‹œ๊ฐ„๋™์•ˆ ๋Œ€๊ธฐ ํ›„์— ์ž‘์—… ์ˆ˜ํ–‰์„ ํ•ด์•ผํ–ˆ๊ณ , Throttle์˜ ๊ฒฝ์šฐ์—๋Š” Throttle ์ฃผ๊ธฐ๋ฅผ ์งง๊ฒŒ ๊ฐ€์ ธ๊ฐˆ์ˆ˜๋ก ์„ฑ๋Šฅ์€ ์ ์  ๋–จ์–ด์ง„๋‹ค๋Š” ํ•œ๊ณ„๊ฐ€ ๋ณด์˜€๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ๋™๊ธฐ์  ๋ Œ๋”๋ง์˜ ํ•œ๊ณ„๋ฅผ ํ•ด์†Œํ•˜๊ณ ์ž ๋™์‹œ์„ฑ์˜ ํ•„์š”์„ฑ์ด ๋Œ€๋‘๋˜์–ด ๋‚˜์˜ค๊ฒŒ ๋˜์—ˆ๋‹ค.

Concurrent Mode ์„ค์ •

๊ธฐ์กด์˜ ReactDOM ํ•จ์ˆ˜์˜ ํ”„๋กœํ† ํƒ€์ž… ํ•จ์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๋˜ render๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ReactDOM์˜ ํ”„๋กœํ† ํƒ€์ž… ํ•จ์ˆ˜์ธ createRoot๋ฅผ ํ†ตํ•ด์„œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ ๋’ค, ํ•ด๋‹น ๊ฐ์ฒด์˜ render ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ๋ฅผ ๋ Œ๋”๋ง์‹œํ‚จ๋‹ค.

import ReactDOM from 'react-dom';
import App from 'App'; 
 
const container = document.getElementById('app'); 
 
// ์ด์ „ ๋ฒ„์ „(React 17)
const container = document.getElementById('app'); 
 
ReactDOM.render(<App />, container);
 
// Concurrent Mode ๋„์ž… ์ดํ›„(React 18)
// ๋ฃจํŠธ ์ƒ์„ฑ
const root = ReactDOM.createRoot(container); 
 
// ๋ฃจํŠธ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๋กœ ์•ฑ์„ ๋ Œ๋”๋ง
root.render(<App />);

{๋ฃจํŠธ๊ฐ์ฒด}.render๋ฅผ ํ†ตํ•ด์„œ ์•ฑ์„ ๋ Œ๋”๋ง์‹œํ‚ค๊ฒŒ ๋˜๋ฉด ๊ฐœ์„ ๋œ ๊ธฐ๋Šฅ๋“ค๊ณผ ๋™์‹œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ startTransition, useTransition, useDeferredValue ํ›…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Concurrent Mode์˜ ํ™œ์šฉ

Automatic Batching(์ƒํƒœ ์ผ๊ด„์ฒ˜๋ฆฌ)

์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๊ฒฝ์šฐ, ๊ธฐ์กด์—๋Š” ํ•˜๋‚˜์˜ state์˜ ์—…๋ฐ์ดํŠธ -> ๋ณ€๊ฒฝ๋œ ์ƒํƒœ๋ฅผ ๋ฆฌ๋ Œ๋”๋ง -> ๋‹ค์Œ state์˜ ์—…๋ฐ์ดํŠธ์˜ ๋‹จ๊ณ„๋กœ ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ๋ฒˆ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜์˜€๊ณ , ์ด์— ์„ฑ๋Šฅ์ ์œผ๋กœ ์ข‹์ง€ ์•Š์€ ํšจ๊ณผ๋ฅผ ๊ฐ€์ ธ์™”๋‹ค.

๊ธฐ์กด์˜ React 17๊นŒ์ง€๋„ Automatic Batching์ด ์ ์šฉ์€ ๋˜์–ด ์žˆ์—ˆ์ง€๋งŒ, ์ ์šฉ๋˜๋Š” ๊ณณ์ด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜ ๋‚ด๋ถ€๋กœ ํ•œ์ •์ ์ธ ํŠน์„ฑ์„ ๊ฐ€์กŒ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค๋ณด๋‹ˆ ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ(Promise)์— ๋Œ€ํ•œ .then ๋ฉ”์„œ๋“œ์˜ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ๋‹ค๊ฑฐ๋‚˜ setTimeout์˜ ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ์•ˆ์˜ ์—ฌ๋Ÿฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋“ฑ์—์„œ๋Š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ผ๊ด„์ฒ˜๋ฆฌ๊ฐ€ ์•„๋‹Œ, ์ˆœ์ฐจ์ฒ˜๋ฆฌ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋ฉด์„œ ๋ณ€๊ฒฝ๋œ state์˜ ์ˆ˜๋งŒํผ ๋ฆฌ๋ Œ๋”๋ง์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค๋ณด๋‹ˆ ์„ฑ๋Šฅ์ ์œผ๋กœ ๋–จ์–ด์ง€๋Š” ํšจ๊ณผ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋œ ๊ฒƒ์ด๋‹ค.

์ด์— React18๋ถ€ํ„ฐ Concurrent Mode๊ฐ€ ํ™œ์„ฑํ™”๋˜๋ฉด, ๋ชจ๋“  Promise๋‚˜ setTimeout, ์ด๋ฒคํŠธ ์ฝœ๋ฐฑ ๋“ฑ์—์„œ ๋‹ค์ˆ˜ ๊ฐœ์˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ์ผ๊ด„๋กœ ์ฒ˜๋ฆฌ๋˜๋„๋ก ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

Transition ๊ด€๋ จ ํ›…๋“ค

Transition ๊ด€๋ จ ํ›…๋“ค์€ ์ „๋ถ€ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌํ•˜์—ฌ ๋ Œ๋”๋ง ๊ณผ์ •์—์„œ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋œ ํ›…๋“ค์ด๋‹ค. ์œ„์—์„œ ๋งํ•œ ๊ฒƒ๊ณผ ๊ฐ™์ด ์ด์ „์˜ ๋ Œ๋”๋ง ๋ฐฉ์‹์€ ๋™๊ธฐ์ ์œผ๋กœ ๊ณ„์†ํ•ด์„œ UI์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ ์‹คํ–‰ -> ๋ฆฌ๋ Œ๋”๋ง์˜ ๋ฐ˜๋ณต์ด์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋™์‹œ์„ฑ ๋ Œ๋”๋ง ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ์ฒซ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ถ€ํ„ฐ ์ตœ์ข…์ ์œผ๋กœ ๋ณด์—ฌ์•ผ ํ•˜๋Š” ํ™”๋ฉด์˜ ๋ Œ๋”๋ง๊นŒ์ง€ ๊ฐ€๋Š” ๊ณผ์ •์—์„œ ์ค‘๊ฐ„์— ๊ณ„์†ํ•ด์„œ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์€, ์ฆ‰ ๊ฐ€๋ฒผ์šด ์—…๋ฐ์ดํŠธ๋ฅผ ๋ผ์›Œ ๋น„๊ต์  ๊ฐ€๋ฒผ์šด ์—…๋ฐ์ดํŠธ -> ๋ฌด๊ฑฐ์šด ์—…๋ฐ์ดํŠธ์ˆœ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฅผ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋„๋ก ์ฒ˜๋ฆฌํ•˜์—ฌ UI blocking ์—†์ด ๋™์‹œ์— ๋‹ค๋ฅธ ์ž‘์—…์ด ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

useTransition

useTransition์€ ์ด๋Ÿฌํ•œ ๋™์‹œ์„ฑ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ํ›…์ด๋‹ค. ์ด ํ›…์€ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ ๋™์‹œ์„ฑ ๋ชจ๋“œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค.

import { useState } from 'react';
 
export function FilterList({ names }) {
  const [query, setQuery] = useState('');
 
  const changeHandler = ({ target: { value } }) => setQuery(value);
 
  return (
    <div>
      <input onChange={changeHandler} value={query} type="text" />
      {names.map((name, i) => (
        <ListItem key={i} name={name} highlight={query} />
      ))}
    </div>
  );
}
 
function ListItem({ name, highlight }) {
  const index = name.toLowerCase().indexOf(highlight.toLowerCase());
  if (index === -1) {
    return <div>{name}</div>;
  }
  return (
    <div>
      {name.slice(0, index)}
      <span className="highlight">
        {name.slice(index, index + highlight.length)}
      </span>
      {name.slice(index + highlight.length)}
    </div>
  );
}

ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด input์˜ ๋‚ด์šฉ์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค setState๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์žˆ๊ณ , state๊ฐ€ ๋ฐ”๋€Œ๊ฒŒ ๋˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง์ด ์ด๋ฃจ์–ด์ง€๋ฉฐ, names๋ฅผ mapํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋•Œ๋งˆ๋‹ค ์‹คํ–‰๋˜๋ฉฐ UI๋ฅผ ๋‹ค์‹œ๊ธˆ ํ™”๋ฉด์— ๋„์šด๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ name๋“ค์ด ์ ์  ๋งŽ์•„์งˆ ์ˆ˜๋ก, input์— ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ๋ฆฌ๋ Œ๋”๋ง ์†๋„๊ฐ€ input ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ์˜ setState๊ฐ€ ์‹คํ–‰๋˜๋Š” ์†๋„๋ฅผ ๋”ฐ๋ผ๊ฐ€์ง€ ๋ชปํ•˜๊ฒŒ ๋˜๊ณ , ๊ฒฐ๊ทน input์˜ value๊ฐ€ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅ๋  ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค. ์˜ˆ์‹œ ๋ณด๊ธฐ

์ด๋Ÿด๋•Œ, ๋ฌด๊ฑฐ์šด ์ž‘์—…์€ List์˜ ๋ฆฌ๋ Œ๋”๋ง์ด ๋  ๊ฒƒ์ด๊ณ , ๋น„๊ต์  ๊ฐ€๋ฒผ์šด ์ž‘์—…์€ input์˜ eventHandler ์•ˆ์— ๋“ค์–ด์žˆ๋Š” setState์— ๋Œ€ํ•œ input์˜ value ๋ฆฌ๋ Œ๋”๋ง์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ด input์˜ ๋ฆฌ๋ Œ๋”๋ง์— ๋Œ€ํ•ด์„œ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋†’์—ฌ input๋จผ์ € ๊ณ„์† ๋จผ์ € ๋ฆฌ๋ Œ๋”๋ง ๋  ์ˆ˜ ์žˆ๊ฒŒ๋งŒ ํ•œ๋‹ค๋ฉด input์ด ๋ฐ€๋ฆฌ๊ฒŒ ๋˜๋Š” ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿด ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํ›…์ด useTransition ํ›…์ด๋‹ค.

[isPending, startTransition] = useTransition()

useTransition์„ ์‚ฌ์šฉํ•˜๋ฉด isPending, startTranstion์˜ ๋ฆฌํ„ด๊ฐ’์„ ๋ฐ›๋Š”๋‹ค.

  • isPending: transition์ด pending ์ƒํƒœ์ธ์ง€ ์•Œ๋ ค์ฃผ๋Š” boolean ๊ฐ’
  • startTransition(callbackFn): UI ์—…๋ฐ์ดํŠธ์— ๊ด€ํ•œ ๋กœ์ง์„ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋กœ ๋„˜๊ฒจ์คŒ ์ด startTransition์„ ํ†ตํ•ด์„œ ์ฝœ๋ฐฑํ•จ์ˆ˜์˜ setState๋ฅผ ํ†ตํ•œ ๋ฆฌ๋ Œ๋”๋ง ๋กœ์ง์„ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ๋„๋ก ์„ค์ •ํ•˜์—ฌ ํ›„์ˆœ์œ„๋กœ ๋ Œ๋”๋ง์ด ์ด๋ฃจ์–ด์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ฉด์„œ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•œ๋‹ค.
`startTransition` lets you update the state without blocking the UI.

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

import { startTransition } from 'react';
 
function TabContainer() {
  const [tab, setTab] = useState('about');
 
  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }
  // ...
}

์ด๋Ÿฐ ์‹์œผ๋กœ setState๊ฐ€ ์žˆ๋Š” ๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๋ Œ๋”๋ง ๊ณผ์ •์—์„œ ๋ง‰ํžˆ์ง€ ์•Š๊ณ  ๋”ฐ๋กœ ์ง„ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋ฒผ์šด UI์˜ ์—…๋ฐ์ดํŠธ ๋“ฑ์ด ๋ง‰ํžˆ์ง€ ์•Š๊ณ  ๋ฐ”๋กœ๋ฐ”๋กœ ๋ Œ๋”๋ง์ด ๋  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•œ๋‹ค.

useDeferredValue

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

useDeferredValue๋กœ ๋ž˜ํ•‘ํ•œ ์ƒํƒœ์˜ ๊ฒฝ์šฐ์—๋Š” ๋‹ค๋ฅธ ์ƒํƒœ๊ฐ’์ด ๋ชจ๋‘ ์ƒํƒœ ๋ณ€๊ฒฝ์ด ์ด๋ฃจ์–ด์ง„ ์ดํ›„์— ์ž์‹ ์ด ๋ฐ”๋€Œ๊ฒŒ ๋œ๋‹ค. ๊ฐ’ ๋ณ€ํ™”์˜ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋‚ฎ์•„์ง€๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ์ƒํƒœ๋“ค์˜ ์—…๋ฐ์ดํŠธ ์ดํ›„์— ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด๋‹ค.

๊ทธ๋ ‡๊ธฐ์— ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์— ๋”ฐ๋ผ useTransition๊ณผ useDeferredValue๋ฅผ ์ทจ์‚ฌ ์„ ํƒํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.

  • useTransition : ์ƒํƒœ ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ชจ๋‘ ์ด๋ฃจ์–ด์ง„ ํ›„ ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ์‹คํ–‰
    • ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ์•ˆ์— setState๋ฅผ ๋„ฃ์–ด ์‚ฌ์šฉ
  • useDeferredValue: ์ƒํƒœ ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ชจ๋‘ ์ด๋ฃจ์–ด์ง„ ํ›„์— ๋ณ€ํ•œ ๊ฐ’์— ๋Œ€ํ•œ ๋ฆฌ๋ Œ๋”๋ง

์ถ”๊ฐ€์ ์œผ๋กœ useDeferredValue๋Š” Suspense์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค . ๋งŒ์•ฝ useDeferredValue์˜ ์ธ์ž๋กœ ์„ค์ •ํ•œ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝํ•˜๊ฒŒ ๋˜๋ฉด ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ์ธํ•œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋ฐ์ดํŠธ ๋™์•ˆ ์ด์ „์˜ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค. ์—…๋ฐ์ดํŠธ ์ด์ „์˜ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉด์„œ ๋‹ค์Œ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๋ Œ๋”๋ง์ด ๋ชจ๋‘ ์ด๋ฃจ์–ด์ง„ ํ›„์— ํ•ด๋‹น ์ƒํƒœ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ ๋‹ค์‹œ๊ธˆ ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•œ๋‹ค. ์ด ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•˜๋Š” ๊ณผ์ •์—์„œ ๊ธฐ์กด์—๋Š” ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กญ๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ fetchingํ•˜๋Š” ๋™์•ˆ ๋‚˜์˜ฌ ui๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ Suspense๋ฅผ ํ™œ์šฉํ•˜์—ฌ fallback UI๋ฅผ ๋ณด์—ฌ์คŒ์œผ๋กœ์จ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋กœ๋”ฉ์ค‘์ž„์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.

Suspense ์ฝ˜ํ…์ธ ๊ฐ€ ๋ Œ๋”๋งํ•  ์ค€๋น„๊ฐ€ ๋˜๊ธฐ ์ „๊นŒ์ง€ ๋Œ€์ฒด UI๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ํƒœ๊ทธ

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>
    </>
  );
}
 

๊ธฐ๋ณธ์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ผ์‹œ ์ค‘๋‹จ๋์„ ๋•Œ lazy loading์ด๋‚˜ use, Next.js์™€ ๊ฐ™์€ suspense ์ง€์›ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๋ฐ์ดํ„ฐ ํŽ˜์นญ ๋“ฑ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ƒ์œ„ Suspense ์ปดํฌ๋„ŒํŠธ๊ฐ€ fallback ui๋ฅผ ๋„์›Œ์ฃผ๊ฒŒ ๋œ๋‹ค.

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>
    </>
  );
}

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

๋˜ํ•œ ์ด ๋ฐฉ์‹์„ ์กฐ๊ธˆ ๋” ํ™œ์šฉํ•˜์—ฌ ๊ธฐ์กด์˜ ๊ฐ’๊ณผ ๋งŒ์•ฝ ๊ฐ’์ด ๋ฐ”๋€Œ์—ˆ๋‹ค๋ฉด ์ด๋ฅผ ๋น„๊ตํ•˜๋Š” ๋ณ€์ˆ˜ ํ•˜๋‚˜๋ฅผ ๋งŒ๋“ค์–ด refetchingํ•˜๊ณ  ์žˆ๋Š” ์ƒํƒœ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋Š” ui๋กœ ๋ณด์—ฌ์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.

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>
    </>
  );
}

์œ„ ์˜ˆ์‹œ์—์„œ๋Š” ์ƒˆ๋กญ๊ฒŒ ์—…๋ฐ์ดํŠธ๋œ deferredQuery์™€ ์ด์ „์˜ ๊ฐ’์ด์–ด๋˜ query๋ฅผ ๋น„๊ตํ•œ ๋ณ€์ˆ˜์ธ isStale์„ ๋”ฐ๋กœ ์„ ์–ธํ•˜์—ฌ ์ด์— ๋”ฐ๋ผ transition์„ ์ฃผ๋ฉฐ refetching์ค‘์ธ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด์—ˆ๋‹ค. ์ด๋ฅผ ๋น„๊ตํ•˜์—ฌ ui๋ฅผ ๋„์šธ ๋•Œ๋Š” suspense์˜ fallback ui๋ณด๋‹ค๋Š” ๊ธฐ์กด์˜ ๊ฐ’์„ ๋„์šฐ๋Š” ๋ฐฉ์‹์œผ๋กœ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

https://dmitripavlutin.com/react-usetransition/ https://velog.io/@heelieben/React-18-Concurrent-Rendering