[๐ค] React `useTransition`๊ณผ `useDeferredValue`๋ก ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ
React ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฌด๊ฑฐ์ด UI ์ ๋ฐ์ดํธ๋ก ์ธํ ๋ฒ๋ฒ ์์ ํด๊ฒฐํ๊ณ , `useTransition`๊ณผ `useDeferredValue` ํ ์ ํ์ฉํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ํ๊ธฐ์ ์ผ๋ก ๊ฐ์ ํ๋ ์ค์ฉ์ ์ธ ์ ๋ต์ ๋ฐฐ์๋ณด์ธ์.
์ ๋ณด๐ค ์ด ํฌ์คํ ์ 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 ์ ํ๋ฆฌ์ผ์ด์
์ฑ๋ฅ ์ต์ ํ์ ์ค์ง์ ์ธ ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋ผ์.
๊ถ๊ธํ ์ ์ด๋ ๋ ๋ค๋ฃจ๊ณ ์ถ์ ์ฃผ์ ๊ฐ ์๋ค๋ฉด ์ธ์ ๋ ์ง ๋๊ธ๋ก ์๋ ค์ฃผ์ธ์!
๐ฎ ์ฐธ๊ณ
์ฐ๊ด๋ ํฌ์คํธ
- ๋จ์ด: 1,910๊ฐ22๋ถ
[๐ค] React Suspense์ ErrorBoundary: ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI ๊ฒฝํ์ ์ํ ์ค์ ๊ฐ์ด๋
React ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํ์ ํ Suspense์ ErrorBoundary์ ๊ฐ๋ ฅํ ์กฐํฉ์ ๊น์ด ์๊ฒ ๋ค๋ค์. ๋ก๋ฉ ์ํ์ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ฐ์ํ๊ฒ ๊ด๋ฆฌํ์ฌ ๋์ฑ ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI๋ฅผ ๋ง๋๋ ์ค์ ํ๊ณผ ์ฝ๋ ์์๋ฅผ ํ์ธํด ๋ณด์ธ์.
- ๋จ์ด: 1,297๊ฐ16๋ถ
[๐ค] CSS Container Queries: ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ๋ฐ์ํ ๋์์ธ์ ์๋ก์ด ์งํ
๋ฏธ๋์ด ์ฟผ๋ฆฌ์ ํ๊ณ๋ฅผ ๋์ด, ์ปดํฌ๋ํธ ์์ฒด์ ํฌ๊ธฐ์ ๋ฐ๋ผ ์คํ์ผ์ ์กฐ์ ํ๋ CSS Container Queries๋ฅผ ๊น์ด ์๊ฒ ์์๋ณด๊ณ ์ค๋ฌด ์ ์ฉ ๋ฐฉ๋ฒ์ ์๋ดํด ๋๋ ค์.
- ๋จ์ด: 1,673๊ฐ19๋ถ
[๐ค] Next.js 15 ๊ณ ๊ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต: fetch์ revalidate ์ฌ์ธต ๋ถ์
Next.js 15์์ `fetch` API์ ๊ฐ๋ ฅํ ์บ์ฑ ๋ฉ์ปค๋์ฆ๊ณผ `revalidate` ์ต์ ์ ํ์ฉํ์ฌ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ์ต์ ํํ๊ณ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ์ด์. ์ค๋ฌด ์์๋ฅผ ํตํด ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์บ์ฑ ์ ๋ต์ ํจ๊ณผ์ ์ผ๋ก ์ ์ฉํ๋ ํ์ ์ ๊ณตํด์.
๋จ์ด: 1,301๊ฐ14๋ถ[๐ค] Next.js App Router: generateStaticParams๋ก ๋์ ๋ผ์ฐํ ๋น๋ ์ต์ ํํ๊ธฐ
Next.js App Router์์ generateStaticParams ํจ์๋ฅผ ํ์ฉํ์ฌ ๋์ ๋ผ์ฐํ ์ ์ ์ ํ์ด์ง๋ฅผ ํจ์จ์ ์ผ๋ก ์์ฑํ๊ณ ๋น๋ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์.
๋จ์ด: 1,863๊ฐ22๋ถ[๐ค] React ๋ ๋๋ง ์ต์ ํ: useMemo, useCallback, React.memo ์๋ฒฝ ๊ฐ์ด๋
์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ React ๋ ๋๋ง ์ต์ ํ ๊ฐ์ด๋. useMemo, useCallback, React.memo์ ์ ํํ ์ฌ์ฉ๋ฒ๊ณผ ์ค๋ฌด์์ ํํ ์ ์ง๋ฅด๋ ์ค์, ๊ทธ๋ฆฌ๊ณ ์ค์ ์ฑ๋ฅ ํฅ์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ ค์.
๋จ์ด: 2,122๊ฐ24๋ถ[๐ค] JavaScript Proxy์ Reflect ์ฌ์ธต ๋ถ์: ๋ฉํ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์ฝ๋ ๊ฐํํ๊ธฐ
JavaScript Proxy์ Reflect API๋ฅผ ํ์ฉํ ๋ฉํ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ์ ์ฌ์ธต ๋ถ์ํด์. ๊ฐ์ฒด ์ ๊ทผ ์ ์ด, ์ ํจ์ฑ ๊ฒ์ฌ, ๋ก๊น , ๋ฐ์ํ ์์คํ ๊ตฌํ ๋ฑ ์ค์ฉ์ ์ธ ํ์ฉ ์ฌ๋ก๋ฅผ ํตํด ์ฝ๋์ ์ ์ฐ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 2,019๊ฐ24๋ถ
[๐ค] React/Next.js ๋ฒ๋ค ์ต์ ํ: ์ฝ๋ ์คํ๋ฆฌํ ๊ณผ ๋ ์ด์ง ๋ก๋ฉ ์๋ฒฝ ๊ฐ์ด๋
React์ Next.js ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ์ค์ด๊ณ ๋ก๋ฉ ์๋๋ฅผ ๊ฐ์ ํ๋ ์ฝ๋ ์คํ๋ฆฌํ ๊ณผ ๋ ์ด์ง ๋ก๋ฉ ๊ธฐ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ์นํฉ ์ค์ ๋ถํฐ React.lazy, Next.js dynamic import๊น์ง ๋ค๋ค์.
- ๋จ์ด: 1,769๊ฐ20๋ถ
[๐ค] React์ `useOptimistic` ํ ์ผ๋ก ๋๊ด์ UI ์ ๋ฐ์ดํธ ๊ตฌํํ๊ธฐ: Server Actions์ ํจ๊ป
React 18/19์ `useOptimistic` ํ ์ ํ์ฉํ์ฌ Server Actions์ ์ฐ๋๋๋ ๋๊ด์ UI ์ ๋ฐ์ดํธ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ฑ์ ๋์ด๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
๋จ์ด: 1,557๊ฐ17๋ถ[๐ค] TypeScript const Type Parameters: ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ๊ฐํ์ ์ค์ฉ์ ์ธ ํ์ฉ๋ฒ
TypeScript 5.0์ ๋์ ๋ const Type Parameters๋ฅผ ํ์ฉํ์ฌ ์ ๋ค๋ฆญ ํจ์์ ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ์ ์ ๊ตํ๊ฒ ์ ์ดํ๊ณ , ๋์ฑ ๊ฒฌ๊ณ ํ ํ์ ์์คํ ์ ๊ตฌ์ถํ๋ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. as const์์ ์ฐจ์ด์ ๊ณผ ์ค์ ์ฝ๋ ์์๋ฅผ ํตํด ์ด์ค๊ธ ๊ฐ๋ฐ์๋ ์ฝ๊ฒ ์ดํดํ ์ ์๋๋ก ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,015๊ฐ22๋ถ
[๐ค] Next.js/React ์ฑ CLS ์ต์ ํ: ์ํํธ ์๋ ์ฌ์ฉ์ ๊ฒฝํ ๋ง๋ค๊ธฐ
Next.js์ React ์ ํ๋ฆฌ์ผ์ด์ ์์ Cumulative Layout Shift(CLS) ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ์ค์ง์ ์ธ ์ ๋ต๊ณผ ์ฝ๋ ์์๋ฅผ ์์ธํ ์์๋ณด์ธ์. ์น ์ฑ๋ฅ ์ต์ ํ์ ํต์ฌ ์์์ธ CLS๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,735๊ฐ21๋ถ
[๐ค] Next.js SSR, SSG, ISR ๋ ๋๋ง ์ ๋ต: App Router์์ ์ต์ ์ ์ ํ์?
Next.js App Router์์ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR), ์ ์ ์ฌ์ดํธ ์์ฑ(SSG), ์ฆ๋ถ ์ ์ ์ฌ์์ฑ(ISR) ๊ฐ ๋ ๋๋ง ์ ๋ต์ ๋์ ์๋ฆฌ, ์ฅ๋จ์ , ์ค์ ํ์ฉ ๋ฐ ์ต์ ํ ๋ฐฉ๋ฒ์ ๋น๊ต ๋ถ์ํด๋๋ ค์.
- ๋จ์ด: 1,460๊ฐ17๋ถ
[๐ค] React Context API์ Zustand: ์ ์ญ ์ํ ๊ด๋ฆฌ, ์ธ์ ๋ฌด์์ ์จ์ผ ํ ๊น์?
React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ญ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ณ ๋ฏผํ๊ณ ๊ณ์ ๊ฐ์? Context API์ ๊ฐ๋ฒผ์ด ์ธ๋ถ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Zustand๋ฅผ ๋น๊ต ๋ถ์ํ๊ณ , ์ค๋ฌด์์ ๊ฐ ๋๊ตฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๋ ์ ๋ต์ ์ค์ ์ฝ๋ ์์์ ํจ๊ป ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 2,004๊ฐ24๋ถ
[๐ค] Turborepo๋ก Next.js ๋ชจ๋ ธ๋ ํฌ ๊ตฌ์ถ: ํจ์จ์ ์ธ ๊ฐ๋ฐ ๋ฐ ์ต์ ํ ์ ๋ต
Turborepo๋ฅผ ํ์ฉํ์ฌ Next.js ํ๋ก์ ํธ๋ฅผ ๋ชจ๋ ธ๋ ํฌ๋ก ๊ตฌ์ฑํ๊ณ , ๊ณต์ ์ปดํฌ๋ํธ, ์ ํธ๋ฆฌํฐ, CI/CD ์ต์ ํ ๋ฐฉ์์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,318๊ฐ27๋ถ
[๐ค] React useEffect ํ , ์ด์ ํท๊ฐ๋ฆฌ์ง ๋ง์ธ์! (์์กด์ฑ ๋ฐฐ์ด, ํด๋ฆฐ์ ์๋ฒฝ ๊ฐ์ด๋)
React ๊ฐ๋ฐ์์ ํ์์ ์ธ useEffect ํ ์ ๋์ ์๋ฆฌ๋ถํฐ ์์กด์ฑ ๋ฐฐ์ด, ํด๋ฆฐ์ ํจ์ ํ์ฉ๋ฒ, ๊ทธ๋ฆฌ๊ณ ์ค๋ฌด์์ ์์ฃผ ๊ฒช๋ ์ค์์ ์ต์ ํ ์ ๋ต๊น์ง, ์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ ์๋ฒฝ ๊ฐ์ด๋๋ฅผ ์ ๊ณตํด์.
- ๋จ์ด: 3,270๊ฐ31๋ถ
[๐ค] Next.js Server Actions ์ค์ : ์๋ฌ ์ฒ๋ฆฌ, ์ ํจ์ฑ ๊ฒ์ฌ, ๋๊ด์ UI ์ ๋ฐ์ดํธ
Next.js Server Actions๋ฅผ ์ค๋ฌด์ ์ ์ฉํ ๋ ๋ง์ฃผํ๋ ์๋ฌ ์ฒ๋ฆฌ, ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ, ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ๋๊ด์ UI ์ ๋ฐ์ดํธ ๊ธฐ๋ฒ์ ์์ธํ ์ฝ๋ ์์์ ํจ๊ป ์์๋ณด์ธ์.
๋จ์ด: 1,981๊ฐ21๋ถ[๐ค] TypeScript ์ ํธ๋ฆฌํฐ ํ์ ์๋ฒฝ ๊ฐ์ด๋: ์ค์ ํ์ฉ ํจํด
TypeScript ์ ํธ๋ฆฌํฐ ํ์ ์ ํต์ฌ ๊ฐ๋ ๊ณผ ์ค์ ํ์ฉ๋ฒ์ ๊น์ด ์๊ฒ ๋ค๋ค์. Pick, Omit, Partial, Required ๋ฑ ์์ฃผ ์ฐ๋ ์ ํธ๋ฆฌํฐ ํ์ ์ผ๋ก ๋ณต์กํ ํ์ ์ ํจ๊ณผ์ ์ผ๋ก ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์. ํ์ ์คํฌ๋ฆฝํธ ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
- ๋จ์ด: 1,707๊ฐ20๋ถ
[๐ค] Next.js App Router ๋ฏธ๋ค์จ์ด: ๊ฐ๋ ฅํ ์์ฒญ ์ฒ๋ฆฌ ์ ๋ต๊ณผ ์ค์ ์์
Next.js App Router ํ๊ฒฝ์์ ๋ฏธ๋ค์จ์ด๋ฅผ ํ์ฉํด ์ฌ์ฉ์ ์ธ์ฆ, ๋ฆฌ๋ค์ด๋ ์ , ๊ตญ์ ํ ๋ฑ์ ์์ฒญ ์ฒ๋ฆฌ ๋ก์ง์ ํจ์จ์ ์ผ๋ก ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ ์์ ์ ํจ๊ป ์์ธํ ์์๋ณด์ธ์.
- ๋จ์ด: 1,625๊ฐ19๋ถ
[๐ค] ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ ์ฌํ: ์ค์ฉ์ ์ธ ํจํด๊ณผ ํํ ์คํด๋ค
ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ(Generics)์ ๊น์ด ์ดํดํ๊ณ , ์ค๋ฌด์์ ์์ฃผ ์ฌ์ฉ๋๋ ์ ๋ค๋ฆญ ํจํด๊ณผ ํํ ๊ฒช๋ ์คํด๋ค์ ์ค์ ์ฝ๋ ์์์ ํจ๊ป ์ฝ๊ณ ๋ช ํํ๊ฒ ์ค๋ช ํด ๋๋ ค์. ํ์ ์์ ์ฑ๊ณผ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
๋จ์ด: 1,846๊ฐ18๋ถ[๐ค] Next.js Route Handler: App Router์์ ์์ ํ๊ณ ํจ์จ์ ์ธ API ๊ตฌ์ถํ๊ธฐ (์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ ํฌํจ)
Next.js App Router์ Route Handler๋ฅผ ์ฌ์ฉํ์ฌ API ์๋ํฌ์ธํธ๋ฅผ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์์๋ด์. ์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ์บ์ฑ ์ ๋ต์ ํฌํจํ ์ค์ฉ์ ์ธ ํ์ผ๋ก ์์ ํ๊ณ ํจ์จ์ ์ธ ์๋ฒ๋ฆฌ์ค ํจ์๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ตํ๋ด์.
- ๋จ์ด: 1,932๊ฐ22๋ถ
[๐ค] Next.js Image ์ปดํฌ๋ํธ ์ต์ ํ: Core Web Vitals ๊ฐ์ ๋ถํฐ ์ค์ ํ์ฉ๊น์ง
Next.js์ Image ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํ์ฌ ์น ์ฑ๋ฅ ํต์ฌ ์งํ์ธ Core Web Vitals๋ฅผ ๊ฐ์ ํ๊ณ , ๋ค์ํ ์ต์ ํ ์ต์ ์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ธ๋ฃจ๊ฐ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 2,173๊ฐ25๋ถ
[๐ค] Next.js 14.1+์ ํ์ : Partial Prerendering (PPR) ์๋ฒฝ ๊ฐ์ด๋์ ์ค์ ์ต์ ํ ์ ๋ต
Next.js 14.1๋ถํฐ ๋์ ๋ Partial Prerendering (PPR)์ ํตํด ์ด๊ธฐ ๋ก๋ฉ ์๋๋ฅผ ๊ทน๋ํํ๊ณ ๋์ ์ฝํ ์ธ ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ ์๊ฒ ๋ค๋ฃจ์ด์. PPR์ ๋์ ์๋ฆฌ๋ถํฐ ์ค์ ํ๋ก์ ํธ ์ ์ฉ ์ ๋ต๊น์ง, ๊ฐ๋ฐ์๋ค์ด ๊ถ๊ธํดํ๋ ๋ชจ๋ ๊ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,786๊ฐ19๋ถ
[๐ค] TypeScript ์กฐ๊ฑด๋ถ ํ์ ๊ณผ infer ํค์๋: ๋ณต์กํ ํ์ ๋ ์์ฝ๊ฒ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ
TypeScript ๊ฐ๋ฐ์์ ๋ง์ฃผํ๋ ๋ณต์กํ ํ์ ์ถ๋ก ๋ฌธ์ , ์กฐ๊ฑด๋ถ ํ์ ๊ณผ infer ํค์๋๋ฅผ ํ์ฉํ๋ฉด ํจ์ฌ ์ฐ์ํ๊ณ ๊ฐ๋ ฅํ๊ฒ ํด๊ฒฐํ ์ ์์ด์. ์ค์ ์์ ์ ํจ๊ป ๊ทธ ํ์ฉ๋ฒ์ ์ฌ๋ ์๊ฒ ๋ค๋ค๋ด ๋๋ค.
- ๋จ์ด: 1,697๊ฐ21๋ถ
[๐ค] JavaScript ์ด๋ฒคํธ ๋ฃจํ(Event Loop) ์์ ์ ๋ณต: ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋ฐํ์ ๋์ ์๋ฆฌ
JavaScript์ ํต์ฌ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ธ ์ด๋ฒคํธ ๋ฃจํ์ ๋์ ์๋ฆฌ๋ฅผ ์ฌ๋ ์๊ฒ ํํค์ณ ๋ด์. ์ฝ ์คํ, ํ์คํฌ ํ, ๋ง์ดํฌ๋กํ์คํฌ ํ์์ ์ํธ์์ฉ์ ์ดํดํ๊ณ , ์ค๋ฌด์์ ๋ง์ฃผ์น๋ ๋น๋๊ธฐ ์ฝ๋์ ๋์์ ๋ช ํํ ์์ธกํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,960๊ฐ23๋ถ
[๐ค] Next.js Server & Client Components, ์ค์ ์์ ํ๋ช ํ๊ฒ ์ ํํ๋ ๊ฐ์ด๋
Next.js App Router์์ Server Components์ Client Components ์ค ์ด๋ค ๊ฒ์ ์ฌ์ฉํด์ผ ํ ์ง ๊ณ ๋ฏผ์ด์ ๊ฐ์? ์ด ๊ธ์์ ๋ ์ปดํฌ๋ํธ์ ํต์ฌ ์ฐจ์ด์ , ์ฌ์ฉ ์์ , ๊ทธ๋ฆฌ๊ณ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ์ค์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ฆด๊ฒ์.
- ๋จ์ด: 1,878๊ฐ21๋ถ
[๐ค] TypeScript satisfies ์ฐ์ฐ์: ํ์ ์ถ๋ก ๊ณผ ์์ ์ฑ์ ๋์์ ์ก๋ ๋น๋ฒ
TypeScript์ `satisfies` ์ฐ์ฐ์๋ฅผ ํ์ฉํ์ฌ ํ์ ์ถ๋ก ์ ์ ์ฐ์ฑ์ ์ ์งํ๋ฉด์๋ ์๊ฒฉํ ํ์ ์์ ์ฑ์ ํ๋ณดํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. ์ค์ฉ์ ์ธ ์์๋ฅผ ํตํด ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํฉ๋๋ค.
- ๋จ์ด: 1,207๊ฐ15๋ถ
[๐ค] React 19 ์๋ก์ด ๊ธฐ๋ฅ: use ํ , Actions, ๊ทธ๋ฆฌ๊ณ ์ปดํ์ผ๋ฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
React 19์ ํต์ฌ ๋ณ๊ฒฝ ์ฌํญ์ธ use ํ , ์๋ฒ ์ก์ , ๊ทธ๋ฆฌ๊ณ React ์ปดํ์ผ๋ฌ์ ๋์ ๋ฐฐ๊ฒฝ๊ณผ ์ค์ ํ์ฉ ์์๋ฅผ ์ด์ค๊ธ ๊ฐ๋ฐ์ ๋๋์ด์ ๋ง์ถฐ ์์ธํ ์ค๋ช ํฉ๋๋ค. ์ต์ React ์ ๋ฐ์ดํธ๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ๊ณผ ๊ฐ๋ฐ ๊ฒฝํ์ ํฅ์์ํค๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์.
- ๋จ์ด: 1,512๊ฐ16๋ถ
[๐ค] Next.js App Router ์บ์ฑ ์ ๋ต: ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ (revalidatePath, revalidateTag) ์๋ฒฝ ๊ฐ์ด๋
Next.js 14 App Router์์ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต๊ณผ revalidatePath, revalidateTag๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ ๋ฐฉ๋ฒ์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์์๋ณด๊ณ ์น ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.