[React] ๋ฆฌ์กํธ ๋์์ฑ ๋ ๋๋ง(Concurrent) ํบ์๋ณด๊ธฐ
์ฌ์ฉ์๊ฐ ์ฌ์ฉํ๋ UI๋ ์ฌ๋ฌ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋์ด ์๋ค. ์ผ๋ฐ์ ์ผ๋ก ํผ ์ ๋ ฅ ํ๋๋ ์ํธ ์์ฉ์ ์ฆ๊ฐ์ ์ผ๋ก ์๋ตํ์ง๋ง, ๋ง์ ์์ ํํฐ๋ง ๋ชฉ๋ก ๊ฐ์ ๋ถ๋ถ์ ์๋ตํ๋๋ฐ ์ค๋ ์๊ฐ์ด ์์๋ ์ ์๋ค. React๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋๊ธฐ์ ์ธ ๋ฐฉ์์ผ๋ก ๋ ๋๋งํ๋๋ฐ, UI์ ๋๋ฆฐ ์์ (ํํฐ๋ง ๋ชฉ๋ก ๋ฑ)์ด ๋ค๋ฅธ ๋น ๋ฅธ ์์ (์ ๋ ฅ ํผ ๋ฑ)์ ์ฐจ๋จํ์ฌ ์๋ต์ด ๋๋ ค์ง๋ ํ์์ด ๋ฐ์ํ๋ค.
๐ก Render-blocking: Concurrent ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ง ์์ ๋ React๋ ์ปดํฌ๋ํธ๋ฅผ ๋๊ธฐ์ ์ผ๋ก ๋ ๋๋ง ํ๋ฉฐ, ๋ ๋๋ง์ ์์ํ๋ฉด ์์ธ๊ฐ ์๋ ์ด์ ๋ ๋๋ง์ ์ค๋จํ ์ ์๋ค. ์ฆ, ๋ ๋๋ง์ด ๋๋์ผ๋ง ๋ค๋ฅธ ์์ ์ ์ํํ ์ ์๋ค.
React 18 ๋ฒ์ ์ ๊ณต๊ฐํ Concurrent ๊ธฐ๋ฅ์ ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๊ฐ๋ฐ๋๋ค. Concurrent๋ ์ฌ๋ฌ ์์ ์ ์ฐ์ ์์๋ฅผ ๋ถ์ฌํ์ฌ ์ค์ํ ๋ถ๋ถ์ ๋จผ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ผ๋ก, ๋น ๋ฅธ ์์ ๊ณผ ๋๋ฆฐ ์์ ์ ์ค์ง์ ์ผ๋ก ๋ถ๋ฆฌํ์ฌ ๊ฐ๊ฐ ์์ ์ ์๋๋ก ์์ ์ ์งํํ ์ ์๋๋ก ํ๋ค. ์ฆ, ๋ค์ View๋ฅผ ๋ ๋๋งํ๋ ๋์ ํ์ฌ View์ ๋ฐ์์ฑ์ ์ ์งํ๋๋ก ๋ ๋๋ง ํ๋ก์ธ์ค๋ฅผ ์ฌ์์ ํ๋ค. ์ด๋ฌํ ๋ฐฉ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ๋๊น ์์ด ์ ์งํ๋ฉด์ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ฑ์ ํฅ์์ํค๋ ํจ๊ณผ๋ฅผ ๊ฐ์ ธ์จ๋ค.
๋๊ธฐ์ ๋ ๋๋ง์ ๋ฌธ์ ์
์๋๋ โ ์ ์ด(controlled) ์ปดํฌ๋ํธ <input />
, โก๋ ๋๋งํ๋๋ฐ 2์ด๊ฐ ์์๋๋ <Slow />
์ปดํฌ๋ํธ, โข๊ฐ์ ๋ก <Slow />
์ปดํฌ๋ํธ๋ฅผ ๋ฆฌ๋ ๋๋งํ๋ ๋ฒํผ์ผ๋ก ๊ตฌ์ฑ๋์ด ์๋ค.
<Slow />
์ปดํฌ๋ํธ๋ props๋ฅผ ๋ฐ์ง ์๊ณ ๋ฉ๋ชจ์ด์ง๋ผ์ input ๊ฐ์ด ๋ณ๊ฒฝ๋ผ๋ ๋ฆฌ๋ ๋๋งํ์ง ์๋๋ค. ํ์ง๋ง ๋ฒํผ์ ํด๋ฆญํ ๋๋ง๋ค key
๊ฐ์ด ๋ณ๊ฒฝ๋๋ฏ๋ก <Slow />
์ปดํฌ๋ํธ๋ ๋ฆฌ๋ ๋๋ง ๋๋ค.
// ์ฝ๋ ์ถ์ฒ via The Miners
export default function App() {
const [value, setValue] = useState('');
const [key, setKey] = useState(Math.random());
return (
<div className="container">
<input
value={value}
onChange={(e) => {
console.log(
`%c Input changed! -> "${e.target.value}"`,
'color: yellow;',
);
setValue(e.target.value);
}}
/>
<button onClick={() => setKey(Math.random())}>Render Slow</button>
<Slow key={key} />
</div>
);
}
const Slow = memo(() => {
sleep(2000);
console.log('%c Slow rendered!', 'color: teal;');
return <></>;
});
const sleep = (ms) => {
const start = performance.now();
while (performance.now() - start < ms);
};
๋ฒํผ์ ํด๋ฆญํด์ <Slow />
์ปดํฌ๋ํธ๋ฅผ ๋ฆฌ๋ ๋๋งํ๋ ๋์ input ํ๋์ ๊ฐ์ ์
๋ ฅํด๋ ํ๋ฉด์ ์๋ฌด๋ฐ ๋ฐ์์ด ์๋ค๊ฐ, ๋ฆฌ๋ ๋๋ง์ด ๋๋์ผ๋ง ์
๋ ฅํ ๊ฐ์ด ๋ฐ์๋๋ค. ๋๋ฆฐ ์ปดํฌ๋ํธ์ ๋ ๋๋ง์ ๋ง์น ํ์์ผ ๋ค๋ฅธ UI์ ์
๋ฐ์ดํธ๋ฅผ ์ฒ๋ฆฌํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๋๋ฆฐ ์์
์ด ๋น ๋ฅธ ์์
์ ์ฐจ๋จํ๋ ํ์์ด ๋ฐ์ํ ๊ฒ์ด๋ค.
a
์ ๋ ฅ → ํ๋ฉด์ ๋ฐ๋ก ๋ฐ์- ๋ฒํผ ํด๋ฆญ →
<Slow />
์ปดํฌ๋ํธ ๋ฆฌ๋ ๋๋ง ์์ b
์ ๋ ฅ → ๋ฐ์ ์์- (๋ฆฌ๋ ๋๋ง ์ข ๋ฃ) ์ ๋ ฅํ ๋ด์ฉ ๋ฐ์
๋ ๋๋ง ์ฐ์ ์์
Concurrent ๋ ๋๋ง ๋งฅ๋ฝ์์ ๋งํ๋ ์ ๋ฐ์ดํธ๋ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ ์ํฉ์ ๋งํ๋ค. ์ ๋ฐ์ดํธ๋ ๋ ๊ฐ์ง ์ฐ์ ์์๋ก ๋๋๋ฉฐ, ์ฐ์ ์์๊ฐ ๋ฎ์ ์ ๋ฐ์ดํธ๋ ์ฐ์ ์์๊ฐ ๋์ ๋ ๋๋ง์ ์๋ฃํด์ผ๋ง ์คํํ๋ค. ๋ํ, ์ฐ์ ์์๊ฐ ๋ฎ์ ์ ๋ฐ์ดํธ๋ ์ฐ์ ์์๊ฐ ๋์ ์ ๋ฐ์ดํธ์ ์ํด ์ค๋จ๋ ์ ์๋ค. ์ฐ์ ์์๊ฐ ๋ฎ์ ๋ฆฌ๋ ๋๋ง์ด ์ค๋จ๋๋ฉด, ์ฐ์ ์์๊ฐ ๋์ ๋ฆฌ๋ ๋๋ง์ ์๋ฃํ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ํ ์ฒ์๋ถํฐ ๋ค์ ์์ํ๋ค.
- ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ
- setState
- dispatch (useReducer)
- useSyncExternalStore (์ธ๋ถ ์คํ ์ด ๊ตฌ๋ )
- UI๊ฐ ์ธ๋ถ ์คํ ์ด(Redux ๋ฑ)์ ์ผ์นํ์ง ์๋ Tearing ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ํ
- ReactDOM.createRoot (์ฑ์ ์ฒ์ ๋ ๋๋งํ ๋ ํธ์ถ)
- ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ
- startTransition
- useDeferredValue
๋์ผํ ํธ์ถ ์คํ์์ ๋ฐ์ํ ๋ชจ๋ ์ ๋ฐ์ดํธ๋ ์ผ๊ด ์ฒ๋ฆฌ๋ผ์ ๋จ์ผ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ค. ๋๋ฌธ์ ์๋ ์ฝ๋ ๊ธฐ์ค์ผ๋ก ๋์ ์ฐ์ ์์์ ๋ฎ์ ์ฐ์ ์์์ ๋ํ ๋จ์ผ ์ ๋ฐ์ดํธ๊ฐ ์ด 2ํ ๋ฐ์ํ๋ค.
const Component = () => {
const [filter, setFilter] = useState('');
const [otherFilter, setOtherFilter] = useState('');
const [delayedFilter, setDelayedFilter] = useState('');
const [delayedOtherFilter, setDelayedOtherFilter] = useState('');
const [isPending, startTransition] = useTransition();
const handleInputChanged = (e) => {
// ๋ ์ํ ์
๋ฐ์ดํธ ์ผ๊ด ์ฒ๋ฆฌ(batching) -> ์ฐ์ ์์๊ฐ ๋์ ๋จ์ผ ์
๋ฐ์ดํธ ๋ฐ์
setFilter(e.target.value);
setOtherFilter(e.target.value.toUpperCase());
startTransition(() => {
// ๋ ์ํ ์
๋ฐ์ดํธ ์ผ๊ด ์ฒ๋ฆฌ(batching) -> ์ฐ์ ์์๊ฐ ๋ฎ์ ๋จ์ผ ์
๋ฐ์ดํธ ๋ฐ์
setDelayedFilter(e.target.value);
setDelayedOtherFilter(e.target.value.toUpperCase());
});
};
return (
<>
<input value={filter} onChange={handleInputChanged} />
</>
);
};
์ด ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋๋ฆฐ ๋ถ๋ถ์ ๋ ๋๋งํ๊ณ ์๋๋ผ๋ ๋น ๋ฅธ ๋ถ๋ถ์ ์ ๋ฐ์ดํธ๊ฐ ์๋ค๋ฉด ๋๋ฆฐ ๋ถ๋ถ์ ์ค๋จ์ํค๊ณ ๋น ๋ฅธ ๋ถ๋ถ์ ์ ๋ฐ์ดํธํ์ฌ ๋ฐ์์ฑ์ ์ ์งํ๋ค. ๋น ๋ฅธ ๋ถ๋ถ์ ๋ ๋๋ง์ ์๋ฃํ๋ฉด ๋ค์ ๋๋ฆฐ ๋ถ๋ถ์ ๋ ๋๋ง์ผ๋ก ๋์๊ฐ๋ค.
Concurrent ์งํ ํ๋ฆ
Concurrent ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ๋น ๋ฅธ ์์ (์ ๋ ฅ)๊ณผ ๋๋ฆฐ ์์ (๋ชฉ๋ก ํํฐ๋ง)์ ๋ถ๋ฆฌํ์ฌ ๋น ๋ฅธ ์์ ์ ์๋ต์ฑ์ ์ ์งํ ์ ์๋ค.
์๋ ์์ ์ฝ๋์์ ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ์ ์ฌ์ฉํ filter
์ํ ์ธ์, ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ์ ์ฌ์ฉํ delayedFilter
์ํ๋ฅผ ์ ์ํ์ฌ <List />
์ปดํฌ๋ํธ์ prop์ผ๋ก ์ ๋ฌํ๊ณ ์๋ค. <List />
์ปดํฌ๋ํธ๋ ๋ฉ๋ชจ์ด์ง ๋ผ์ ์ด์ ๋ ๋๋ง ๋ ์ ๋ฌํ props(delayedFilter
)๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ๋ฆฌ๋ ๋๋ง ๋๋ค. ์
๋ ฅ์ ์์ํ๋ฉด onChange
ํธ๋ค๋ฌ์ ์ํด ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ์ ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ๋ฅผ ๋ชจ๋ ํธ๋ฆฌ๊ฑฐํ๋ค.
export default function App() {
// ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ
const [filter, setFilter] = useState('');
// ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ
const [delayedFilter, setDelayedFilter] = useState('');
const [isPending, startTransition] = useTransition();
useDebug({ filter, delayedFilter });
return (
<div className="container">
<input
value={filter}
onChange={(e) => {
// ๋์&๋ฎ์ ์ฐ์ ์์ ์
๋ฐ์ดํธ๋ฅผ ๋ชจ๋ ํธ๋ฆฌ๊ฑฐํ๋ ํธ๋ค๋ฌ
setFilter(e.target.value); // ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ
startTransition(() => {
setDelayedFilter(e.target.value); // ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ
});
}}
/>
<List filter={delayedFilter} /> {/* delayedFilter ์ํ ๋ณ๊ฒฝ์ ๋ฆฌ๋ ๋๋ง */}
</div>
);
}
const List = memo(({ filter }) => {
const filteredList = list.filter((entry) =>
entry.name.toLowerCase().includes(filter.toLowerCase()),
);
sleep(100);
return (
<ul>
{filteredList.map((item) => (
<li key={item.id}>
{item.name} - ${item.price}
</li>
))}
</ul>
);
});
<App />
์ ์ฒ์ ๋ ๋๋งํ ๋ ReactDOM.createRoot
์ ์ํด ์ฒซ๋ฒ์งธ ์ฐ์ ์์๊ฐ ๋์ ๋ ๋๋ง์ ํธ๋ฆฌ๊ฑฐํ๋ค. ์ด๋ filter
, delayedFilter
๋ชจ๋ ๋น ๋ฌธ์์ด์ ์ด๊ธฐ๊ฐ์ผ๋ก ๊ฐ๋๋ค. ๊ทธ ํ a
, b
, c
๋ฅผ ๋น ๋ฅด๊ฒ ์
๋ ฅํ๋ฉด ์๋์ ๊ฐ์ด ์ฝ์์ ์ถ๋ ฅํ๋ค. ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ ๋์ค์ ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ๋ฅผ ์งํํ์ง ์๊ณ , ์ฐ์ ์์๊ฐ ๋ฎ์ ๋ฆฌ๋ ๋๋ง์ ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ์ ์ํด ์ฆ์ ์ค๋จ๋๊ณ ๋ฒ๋ ค์ง ์ ์๋ค๋ ์ ์ ์ฃผ๋ชฉํ์.
Interrupted ์์ด ์ฐ์ ์์๊ฐ ๋์ ๋ ๋๋ง ์์/์ข ๋ฃ → ์ฐ์ ์์๊ฐ ๋ฎ์ ๋ ๋๋ง ์์/์ข ๋ฃ ์์ผ๋ก ๋ฐ๋ณตํด์ ์งํ
- [High Priority Start]
a
์ ๋ ฅ → ์ฐ์ ์์๊ฐ ๋์ ๋ฆฌ๋ ๋๋ง ์์- filter :
""
→"a"
- delayedFilter :
""
— ๋ณ๊ฒฝ ์์- ์ด ๋จ๊ณ์์ ์ฐ์ ์์๊ฐ ๋ฎ์ ์ ๋ฐ์ดํธ๋ฅผ ์งํํ์ง ์์
<List />
๋ ์ด์ ๋ ๋๋ง๊ณผ ๋์ผํdelayedFilter
๊ฐ์ ์ ๋ฌ๋ฐ์ผ๋ฏ๋ก ๋ฆฌ๋ ๋๋งํ์ง ์์
- filter :
- [High Priority End] ์ฐ์ ์์๊ฐ ๋์ ๋ฆฌ๋ ๋๋ง ์ข
๋ฃ → VDOM์ ์ปค๋ฐ → ์๋ช
์ฃผ๊ธฐ ์งํ
์๋ช ์ฃผ๊ธฐ(Lifecycle) : ์ดํํธ ์ฃผ์ → DOM ๋ณ๊ฒฝ → ๋ ์ด์์ → ๋ ์ด์์ ์ดํํธ → ํ์ธํธ → ์ดํํธ - [Low Priority Start]
a
์ ๋ ฅ์ ๋ํ ์ฐ์ ์์๊ฐ ๋ฎ์ ๋ฆฌ๋ ๋๋ง ์์- filter :
"a"
— ๋ณ๊ฒฝ ์์ - delayedFilter :
""
→"a"
- filter :
- [Low Priority Interrupted]
b
์ ๋ ฅ → ์ฐ์ ์์๊ฐ ๋ฎ์ ๋ฆฌ๋ ๋๋ง ์ค์ง- ์ฐ์ ์์๊ฐ ๋์ ์ ๋ฐ์ดํธ์ ์ํด ์ฐ์ ์์๊ฐ ๋ฎ์ ๋ ๋๋ง์ด ์ค๋จ๋จ (์ปค๋ฐ ์๋จ)
- ๋ณ๊ฒฝ ์ฌํญ์ ์ปค๋ฐํ์ง ์์์ผ๋ฏ๋ก
delayedFilter
์ํ ๊ฐ์ ์ด์ ๊ณผ ๋์ผ - ์ด์ ๊ณผ ๋์ผํ ์ํ๋ฅผ ๊ฐ์ง๋ฏ๋ก
<List />
์ญ์ ๋ฆฌ๋ ๋๋งํ์ง ์์
- [High Priority Start]
b
์ ๋ ฅ์ ๋ํ ์ฐ์ ์์๊ฐ ๋์ ๋ฆฌ๋ ๋๋ง ์์- filter :
"a"
→"ab"
- delayedFilter :
""
— ๋ณ๊ฒฝ ์์
- filter :
- ์ ๋ ฅ์ ์ค๋จํ ๋๊น์ง ์ ์ฌ์ดํด ๋ฐ๋ณต…
์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ(์
๋ ฅ)์์ delayedFilter
์ํ๊ฐ ๋ณ๊ฒฝ๋์ง ์๊ธฐ ๋๋ฌธ์ <List />
์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋์ง ์๊ณ , ๊ทธ๋ก ์ธํด UI์ ๋น ๋ฅธ ๋ถ๋ถ์ ์๋ต์ฑ์ ์ ์งํ ์ ์๊ฒ๋ ๊ฒ์ด๋ค.
Branch ์ ๋ต๊ณผ Concurrent ๋ฎ์๊ผด
Concurrent ๊ธฐ๋ฅ์์ ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ๋ ๊ธด๊ธํ ๋ฒ๊ทธ๋ฅผ ์์ ํ๋ hotfix
๋ธ๋์น ์์
, ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ๋ feature
๋ธ๋์น ์์
๊ณผ ์ ์ฌํ๋ค.
๊ธฐ๋ฅ ๊ฐ๋ฐ์ค ํซํฝ์ค๋ฅผ ๋ฐฐํฌํด์ผ ํ๋ ์ํฉ์ด ๋ฐ์ํ ์ ์๋ค. ์ผ๋ฐ์ ์ผ๋ก ํซํฝ์ค ๋ฐฐํฌ๊ฐ ๊ธฐ๋ฅ ๋ฐฐํฌ๋ณด๋ค ๋ ์๊ธํ๋ฏ๋ก ๊ธฐ๋ฅ ๊ฐ๋ฐ์ ์ค๋จํ๊ณ ํซํฝ์ค ๋ธ๋์น์์ ์์
์ ์์ํ๋ค. ํซํฝ์ค ์์
์ ์๋ฃํ๋ฉด main
๋ธ๋์น์ ์์
๋ด์ฉ์ ๋ณํฉํ๋ค.
๋ค์ ๊ธฐ๋ฅ ์์
์ ์์ํ๊ธฐ ์ํด์ main
๋ธ๋์น์ ๋ฐ์๋ ํซํฝ์ค ๋ด์ฉ์ feature
๋ธ๋์น๋ก ๊ฐ์ ธ์์ผ ํ๋ค. ์ด๋ฅผ ์ํด feature
๋ธ๋์น๋ก ์ด๋ํด์ main
๋ธ๋์น๋ฅผ ๋ณํฉํ๊ฑฐ๋ ๋ฆฌ๋ฒ ์ด์ค๋ฅผ ํตํด main
๋ธ๋์น๋ฅผ ๋ฐ๋ผ ์ก๋๋ค.
๋ง์ฝ ๋ค๋ฅธ ๊ธด๊ธํ ์์ ์ ์ํด ์ถ๊ฐ์ ์ธ ํซํฝ์ค ์์ ์ด ํ์ํ๋ค๋ฉด ๊ธฐ๋ฅ ์์ ์ ๋ค์ ์ค๋จํ๊ณ ์ ๊ณผ์ ์ ๋ฐ๋ณตํ๋ค. ์ด์ฒ๋ผ ๊ธฐ๋ฅ ๋ธ๋์น ์์ ์ ์ฐ์ ์์๊ฐ ๋์ ํซํฝ์ค ์์ ์ผ๋ก ์ธํด ์ธ์ ๋ ์ง ์ค๋จ๋ ์ ์๋ ์ ์์ ์ฐ์ ์์๊ฐ ๋ฎ์ ์ ๋ฐ์ดํธ์ ์ ์ฌํ๋ค.
๋ํ, ๊ธฐ๋ฅ ๋ธ๋์น ์์ ์ ์์ํ๊ธฐ ์ ํซํฝ์ค ๋ณ๊ฒฝ ์ฌํญ์ ๊ธฐ๋ฅ ๋ธ๋์น๋ก ๊ฐ์ ธ์ค๋ ๋ถ๋ถ์, ์ฐ์ ์์๊ฐ ๋ฎ์ ๋ ๋๋ง ์์ ์ ๋ค์ ์์ํ ๋, ์ฐ์ ์์๊ฐ ๋์ ์ ๋ฐ์ดํธ์ ๋ณ๊ฒฝ ๋ด์ฉ์ ํตํฉํ๊ธฐ ์ํด ์ฒ์๋ถํฐ ๋ ๋๋ง์ ๋ค์ ์์ํ๋ ๊ฒ๊ณผ ๋น์ทํ๋ค.
Concurrent ๊ธฐ๋ฅ
Concurrent ๋ ๋๋ง์ Transition ํน์ DeferredValue ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ํ์ฑํํ ์ ์๋ค. ๋ ๊ฐ์ง ๋ชจ๋ ํน์ ์ ๋ฐ์ดํธ๋ฅผ ๋ฎ์ ์ฐ์ ์์๋ก ํ์ํ๋ฉด์ Concurrent ๋ ๋๋ฌ๊ฐ ๋๋จธ์ง๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ผ๋ก ๋์ํ๋ค.
ํธ๋์ง์ | useTransition
๋ ๋ฆฝ์ ์ผ๋ก ์๋ํ๋
startTransition
์ importํด์ ์ฌ์ฉํ ์๋ ์๋ค
์ํ ๋ณํ์ ์ฐ์ ์์๋ฅผ ์ง์ ํ๊ธฐ ์ํด useTransition
ํ
์ ์ฌ์ฉํ๋ค. ์ด ํ
์ isPending
, startTransition
๋ ๊ฐ์ง ๊ฐ์ ๋ฐํํ๋ค. isPending
์ ๋ฎ์ ์ฐ์ ์์ ์์
์ด ์ง์ฐ ์ค์์ ์๋ฆฌ๋ boolean
๊ฐ์ด๋ฉฐ(์ฐ์ ์์๊ฐ ๋์ ๋ฆฌ๋ ๋๋ง ์งํ ์ค), startTransition
์ ๋ฎ์ ์ฐ์ ์์๋ก ์คํํ ํจ์๋ฅผ ์ธ์๋ก ๋ฐ๋๋ค(๋ช
๋ นํ ๋ฐฉ๋ฒ).
const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);
function handleClick() {
startTransition(() => {
setCount((c) => c + 1);
});
}
startTransition์ ๋์/๋ฎ์ ์ฐ์ ์์ ์ ๋ฐ์ดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ค
startTransition
์ ํญ์ ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ์ ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ๋ฅผ ๋ชจ๋ ํธ๋ฆฌ๊ฑฐํ๋ค. ์ฆ, startTransition
์ฝ๋ฐฑ ๋ด๋ถ์ ๋ฐ๋ก ์
๋ฐ์ดํธ๋ฅผ ๋ช
์ํ์ง ์์๋ ํญ์ ๋์/๋ฎ์ ์ฐ์ ์์์ ์
๋ฐ์ดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ค.
export default function App() {
const [isPending, startTransition] = useTransition();
useDebug();
return (
<button
onClick={() => {
startTransition(() => {});
}}
>
Start Transition
</button>
);
}
startTransition์ ์ฝ๋ฐฑ์ ์ฆ์ ์คํ๋๋ค
startTransition
์ธ์๋ก ์ ๋ฌํ ํจ์๋ ๋๊ธฐ์ ์ผ๋ก ์ฆ์ ์คํ๋๋ค. ๋๋ฌธ์ ์ฝ๋ฐฑ ๋ด๋ถ์ ๋น์ฉ์ด ๋ง์ด ๋๋ ์์
์ ์ํํด์๋ ์๋๋ค. ๋ ๋๋ง์ ์ฐจ๋จํ ์๋ ์๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ฉด, startTransition
์ฝ๋ฐฑ์ ์ํด ํธ๋ฆฌ๊ฑฐ๋ ์
๋ฐ์ดํธ๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํํ๋ฏ๋ก ๋น์ฉ์ด ๋ง์ด ๋๋ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํด๋ ๋ฌด๋ฐฉํ๋ค.
export default function App() {
const [isPending, startTransition] = useTransition();
useDebug();
return (
<button
onClick={() => {
console.log('Clicked!');
startTransition(() => {
console.log('Callback ran!');
});
}}
>
Start Transition
</button>
);
}
startTransition ์ฝ๋ฐฑ๊ณผ ๊ฐ์ ํธ์ถ ์คํ์ ์์ด์ผ ํ๋ค
์ํ ์
๋ฐ์ดํธ๊ฐ ๋ฎ์ ์ฐ์ ์์๋ก ์ง์ ๋๋ ค๋ฉด startTransition
์ฝ๋ฐฑ ์์ฒด์ ๋์ผํ ํธ์ถ ์คํ์ ์์ด์ผ ํ๋ค. ์๋ 3๊ฐ์ง ์์ ๋ชจ๋ ๋ฎ์ ์ฐ์ ์์๋ก ์ง์ ๋๋ ๊ฒ์ ๊ธฐ๋ํ์ง๋ง, startTransition
์ฝ๋ฐฑ๊ณผ ๋ค๋ฅธ ํธ์ถ ์คํ์ ์์ด์ ๋์ ์ฐ์ ์์ ์
๋ฐ์ดํธ๋ก ์ง์ ๋๋ค.
startTransition(() => {
setTimeout(() => {
// ๋ค๋ฅธ ํธ์ถ ์คํ
setCount((count) => count + 1);
}, 1000);
});
startTransition(async () => {
await asyncWork();
// ๋ค๋ฅธ ํธ์ถ ์คํ
setCount((count) => count + 1);
});
startTransition(() => {
asyncWork().then(() => {
// ๋ค๋ฅธ ํธ์ถ ์คํ
setCount((count) => count + 1);
});
});
์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด ๋น๋๊ธฐ ํจ์๋ฅผ startTransition
์ฝ๋ฐฑ ๋ฐ์ผ๋ก ๋นผ๊ฑฐ๋ ๋น๋๊ธฐ ํจ์ ๋ด๋ถ์์ startTransition
์ ํธ์ถํ๋๋ก ์์ ํด์ผ ํ๋ค.
setTimeout(() => {
startTransition(() => {
setCount((count) => count + 1);
});
}, 1000);
await asyncWork();
startTransition(() => {
setCount((count) => count + 1);
});
asyncWork().then(() => {
startTransition(() => {
setCount((count) => count + 1);
});
});
๋ชจ๋ ํธ๋์ง์ ์ ๋จ์ผ ๋ฆฌ๋ ๋๋ง์ผ๋ก ์ผ๊ด ์ฒ๋ฆฌํ๋ค
์ฐ์ ์์๊ฐ ๋ฎ์ ์ฌ๋ฌ ์ ๋ฐ์ดํธ๋ ๋จ์ผ ๋ฆฌ๋ ๋๋ง์ ํตํด ์ผ๊ด ์ฒ๋ฆฌ๋๋ค. ์๋ฅผ๋ค์ด ์ฐ์ ์์๊ฐ ๋ฎ์ A ์ ๋ฐ์ดํธ๊ฐ ๋ณด๋ฅ์ค์ผ ๋ ๋ค๋ฅธ ์ฐ์ ์์๊ฐ ๋ฎ์ B ์ ๋ฐ์ดํธ๊ฐ ํธ๋ฆฌ๊ฑฐ๋๋ฉด, A ์ ๋ฐ์ดํธ๋ฅผ ํฌํจํ ๋ชจ๋ ๋ฎ์ ์ฐ์ ์์ ์ ๋ฐ์ดํธ๊ฐ ๋์ผํ ๋ฆฌ๋ ๋๋ง์์ ์ผ๊ด ์ฒ๋ฆฌ๋๋ค.
๋ํ ๋ ๋๋ง์ ์๋ฃํ์ง ์๊ณ ์ค๋จ๋๋ฉด ์ฒ์๋ถํฐ ๋ค์ ๋ ๋๋งํ๋ ์ ์ฑ ์ด ์ ์ฉ๋๋ค.
export default function App() {
return (
<div style={{ display: 'flex' }}>
<Component name="First" />
<Component name="Second" />
</div>
);
}
export const Component = ({ name }) => {
const [filter, setFilter] = useState('');
const [delayedFilter, setDelayedFilter] = useState('');
const [isPending, startTransition] = useTransition();
useDebug({ name });
return (
<div className="container">
<input
value={filter}
onChange={(e) => {
setFilter(e.target.value);
startTransition(() => {
setDelayedFilter(e.target.value);
});
}}
/>
{isPending && 'Recalculating...'}
<List filter={delayedFilter} />
</div>
);
};
// List ์ปดํฌ๋ํธ ์๋ต
- ์ฒซ๋ฒ์ฌ ํ๋
a
์ ๋ ฅ- ๋์ ์ฐ์ ์์ ๋ ๋๋ง ์์ ~ ์ข ๋ฃ
- ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง ์์
- ๋๋ฒ์งธ ํ๋
a
์ ๋ ฅ- ์ฒซ๋ฒ์งธ ํ๋์ ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง ์ค์ง
- ๋๋ฒ์งธ ํ๋์ ๋์ ์ฐ์ ์์ ๋ ๋๋ง ์์ ~ ์ข ๋ฃ
- ์ฒซ๋ฒ์งธ / ๋๋ฒ์งธ ํ๋์ ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง ์์
- ์ฒซ๋ฒ์งธ / ๋๋ฒ์งธ ํ๋์ ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง ์ข ๋ฃ
ํธ๋์ง์ ์ ์ํ์๋ง ์ ์ฉ๋๋ค
startTransition
์ฝ๋ฐฑ ์์์ ref
๋ฅผ ์์ ํ ์ ์์ง๋ง, ref
๋ ํธ๋์ง์
์ ์ฌ์ฉํ์ง ์๊ณ ๋ณ๊ฒฝํ ๊ฒ๊ณผ ๋์ผํ๊ฒ ๋์ํ๋ค. ๋๋ฌธ์ ์๋ ์์ ์์ ์ฐ์ ์์๊ฐ ๋์ ๋ฆฌ๋ ๋๋ง์ผ ๋ delayedFilter
๊ฐ๋ ๋ณ๊ฒฝ๋ผ์ filter
์ํ์ ํญ์ ๋์ผํ๋ค.
export default function App() {
const [filter, setFilter] = useState('');
const delayedFilterRef = useRef(filter);
const [isPending, startTransition] = useTransition();
const delayedFilter = delayedFilterRef.current;
useDebug({ filter, delayedFilter });
return (
<div className="container">
<input
value={filter}
onChange={(e) => {
setFilter(e.target.value);
startTransition(() => {
delayedFilterRef.current = e.target.value;
});
}}
/>
{isPending && 'Recalculating...'}
<List filter={delayedFilter} />
</div>
);
}
// List ์ปดํฌ๋ํธ ์๋ต
์ง์ฐ๋ ๊ฐ | useDeferredValue
๋ช
๋ นํ์ผ๋ก ์๋ํ๋ ํธ๋์ง์
(Transition) API์ ๋ฌ๋ฆฌ, useDeferredValue
ํ
์ ์ ์ธ์ ์ธ ๋ฐฉ์์ผ๋ก ์๋ํ๋ค. useDeferredValue
ํ
์ ์ธ์๋ก ์ ๋ฌํ ๊ฐ์ ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ์ ๊ฒฐ๊ณผ(์ํ)๊ฐ ๋๋ค. ์ฐ์ ์์๊ฐ ๋์ ๋ ๋๋ง ์ข
๋ฃ ์์ ์ useDeferredValue
ํ
์ ์ ๋ฌํ ๊ฐ๊ณผ ์ด์ ๊ฐ์ด ๋ค๋ฅด๋ฉด ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ค.
export default function App() {
// ์ฐ์ ์์๊ฐ ๋์ ์
๋ฐ์ดํธ
const [filter, setFilter] = useState('');
// ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ
const deferredFilter = useDeferredValue(filter);
useDebug({ filter, deferredFilter });
return (
<div className="container">
<input
value={filter}
onChange={(e) => {
setFilter(e.target.value);
}}
/>
<List filter={deferredFilter} />
</div>
);
}
// List ์ปดํฌ๋ํธ ์๋ต
์ดํ๋ฆฌ์ผ์ด์
์ ์ฒ์ ๋ ๋๋งํ๋ฉด ํญ์ ์ฐ์ ์์๊ฐ ๋์ ๋ ๋๋ง์ ํธ๋ฆฌ๊ฑฐํ๋ค. useDeferredValue
๋ฅผ ์ฒ์ ํธ์ถํ ๋ ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ์ง ์๊ณ ์ด๊ธฐํ๋ ๊ฐ๋ง ๋ฐํํ๋ค.
๋ฐ๋ผ์ filter
, deferredFilter
์ํ ๋ชจ๋ ๋น ๋ฌธ์์ด์ ์ด๊ธฐ๊ฐ์ผ๋ก ๊ฐ๋๋ค. ๊ทธ ํ a
, b
, c
๋ฅผ ์ฐ์์ ์ผ๋ก ๋น ๋ฅด๊ฒ ์
๋ ฅํ๋ฉด ์๋์ฒ๋ผ ์ฝ์์ ์ถ๋ ฅํ๋ค. ๋ก๊ทธ๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ฏ useDeferredValue
ํ
์ญ์ ํธ๋์ง์
API์ ์ ์ฌํ๊ฒ ๋์ํ๋ค.
๐ก ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง์ ์๋ฃํ๊ธฐ ์ ๊น์ง useDeferredValue
ํ
์ ํญ์ ์ด์ ๊ฐ์ ๋ฐํํ๋ ์ ์ ์ฃผ๋ชฉํ์. ์ฐ์ ์์๊ฐ ๋์ ๋ฆฌ๋ ๋๋ง์ ์๋ฃํด์ผ๋ง useDeferredValue
์ ๋ณ๊ฒฝ๋ ๊ฐ์ด ์ ๋ฌ๋ ํ ํธ๋ฆฌ๊ฑฐ๋ผ์ ์ฐ์ ์์๊ฐ ๋ฎ์ ๋ฆฌ๋ ๋๋ง์ ์์ํ๋ค.
a
์ ๋ ฅ :setFilter
ํธ์ถ → ์ฐ์ ์์๊ฐ ๋์ ์ ๋ฐ์ดํธ ํธ๋ฆฌ๊ฑฐ- ๋์ ์ฐ์ ์์ ๋ ๋๋ง ์์ ~ ์ข
๋ฃ
filter
์ํ""
→"a"
๋ณ๊ฒฝfilter
์ํ๊ฐ ๋ณ๊ฒฝ๋๋ฉดuseDeferredValue
ํ ์ ์ธ์๋ก"a"
๊ฐ ์ ๋ฌ๋์ง๋ง, ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง์ ์๋ฃํ๊ธฐ ์ ๊น์ง ์ด์ ๊ฐ์ธ""
๋น๋ฌธ์์ด ๋ฐํ โก๏ธ
- ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง ์์ (์ด์ ๊ณผ ๋ค๋ฅธ ๊ฐ์ธ
"a"
๋ฅผ ์ ๋ฌ ๋ฐ์ ํธ๋ฆฌ๊ฑฐ๋จ)
- ๋์ ์ฐ์ ์์ ๋ ๋๋ง ์์ ~ ์ข
๋ฃ
b
์ ๋ ฅ- ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง ์ค์ง
- ๋์ ์ฐ์ ์์ ๋ ๋๋ง ์์ ~ ์ข ๋ฃ
- ๋ฎ์ ์ฐ์ ์์ ๋ ๋๋ง ์์
- ๋ฐ๋ณต…
useDeferredValue๋ ๋ค๋ฅธ ์ ๋ฐ์ดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ์ง ์๋๋ค
์ฐ์ ์์๊ฐ ๋ฎ์ ๋ฆฌ๋ ๋๋ง์ด ์งํ์ค์ผ ๋ ์ ๋ฌ๋ฐ์ ๊ฐ์ด, ์ฒ์ ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ ๊ฐ๊ณผ ๋ค๋ฅด๋๋ผ๋ useDeferredValue
๋ ํญ์ ๋ง์ง๋ง์ผ๋ก ์ ๋ฌ๋ฐ์ ๊ฐ์ ๋ฐํํ๋ค.
๋๋ฌธ์ ์ฐ์ ์์๊ฐ ๋ฎ์ ์
๋ฐ์ดํธ๊ฐ ์งํ์ค์ผ ๋ useDeferredValue
ํ
์ด ์ ๊ฐ์ ์ ๋ฌ๋ฐ์๋, ํ
์ ๊ทธ๋๋ก ํต๊ณผํ์ฌ ๋ฐํํ๋ฏ๋ก ๋ค๋ฅธ ๋ฎ์ ์ฐ์ ์์ ์
๋ฐ์ดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ์ง ์๋๋ค. ์ด๋ฌํ ์๋ ๋ฐฉ์์ ํตํด ํญ์ ์
๋ฐ์ดํธ๋ ์ต์ ๊ฐ์ ๋ฆฌ๋ ๋๋ง์ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค.
export default function App() {
const [filter, setFilter] = useState('');
const [delayedFilter, setDelayedFilter] = useState('');
const [isPending, startTransition] = useTransition();
// ๋์ ์ฐ์ ์์์ ๋ฎ์ ์ฐ์ ์์ ๋ฆฌ๋ ๋๋ง์ ๋ฐ๋ผ ๊ฐ๊ฐ ๋ค๋ฅธ ๊ฐ์ ์ ๋ฌ๋ฐ์
// isPending์ ์ฐ์ ์์๊ฐ ๋์ ๋ฆฌ๋ ๋๋ง์ด ์งํ์ค์ผ ๋๋ง true ๋ฐํ
const deferredFilter = useDeferredValue(
isPending ? filter.toUpperCase() : delayedFilter,
);
useDebug({ filter, delayedFilter, deferredFilter });
return (
<div className="container">
<input
value={filter}
onChange={(e) => {
setFilter(e.target.value);
startTransition(() => {
setDelayedFilter(e.target.value);
});
}}
/>
<List filter={deferredFilter} />
</div>
);
}
// List ์ปดํฌ๋ํธ ์๋ต
๋์ ์ฐ์ ์์์ ๋ฎ์ ์ฐ์ ์์ ๋ฆฌ๋ ๋๋ง ๋์ค useDeferredValue
ํ
์ ์๋ก ๋ค๋ฅธ ๊ฐ์ ์ ๋ฌํ๋๋ผ๋ ์ถ๊ฐ์ ์ธ ๋ ๋๋ง์ ํธ๋ฆฌ๊ฑฐํ์ง ์๋๋ค.
์ฌ๋ฌ ์ ๋ฐ์ดํธ๋ ๋จ์ผ ๋ฆฌ๋ ๋๋ง์์ ์ผ๊ด ์ฒ๋ฆฌํ๋ค
๋ชจ๋ ํธ๋์ง์
์ด ๋จ์ผ ๋ฆฌ๋ ๋๋ง์์ ์ผ๊ด ์ฒ๋ฆฌ๋๋ ๊ฒ์ฒ๋ผ, ์ฌ๋ฌ useDeferredValue
ํธ์ถ๋ ๋จ์ผ ๋ฆฌ๋ ๋๋ง์์ ์ผ๊ด ์ฒ๋ฆฌํ๋ค. ์ฆ, ์ปดํฌ๋ํธ ํธ๋ฆฌ๋ด ์ฌ๋ฌ useDeferredValue
ํธ์ถ์ด ๋ฐ์ํ๋๋ผ๋ ์ฐ์ ์์๊ฐ ๋ฎ์ ๋จ์ผ ๋ฆฌ๋ ๋๋ง์์ ์ผ๊ด ์ฒ๋ฆฌ๋๋ค.
ํธ๋์ง์ ๊ณผ ๋ง์ฒ๊ฐ์ง๋ก ๋ ๋๋ง์ ์๋ฃํ์ง ์๊ณ ์ค๋จ๋๋ฉด ์ฒ์๋ถํฐ ๋ค์ ๋ ๋๋งํ๋ ์ ์ฑ ๋ ์ ์ฉ๋๋ค.
๋ ํผ๋ฐ์ค
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Algorithm] ๋ฐ์ดํฐ ์ถ๊ฐ, ์ญ์ , ์ ๋ ฌ๋ก ๋ณด๋ BFS / DFS ํ์ ์๊ณ ๋ฆฌ์ฆ (0) | 2024.05.21 |
---|---|
[React/JS] ๋๋๊ทธํ ๋ฌธ์์ด ๋ถ๋ฆฌ(๋ฉํ)ํ๊ธฐ / Selection API (0) | 2024.05.21 |
[Express] req.query vs req.params (0) | 2024.05.19 |
[JS] ํจ์ํ / ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ์ ๋ฎ์ ๊ผด (0) | 2024.05.19 |
[TS] TypeScript ํ์ ์คํฌ๋ฆฝํธ Infer ํค์๋ ํ์ฉํ๊ธฐ (0) | 2024.05.19 |
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[Algorithm] ๋ฐ์ดํฐ ์ถ๊ฐ, ์ญ์ , ์ ๋ ฌ๋ก ๋ณด๋ BFS / DFS ํ์ ์๊ณ ๋ฆฌ์ฆ
[Algorithm] ๋ฐ์ดํฐ ์ถ๊ฐ, ์ญ์ , ์ ๋ ฌ๋ก ๋ณด๋ BFS / DFS ํ์ ์๊ณ ๋ฆฌ์ฆ
2024.05.21 -
[React/JS] ๋๋๊ทธํ ๋ฌธ์์ด ๋ถ๋ฆฌ(๋ฉํ)ํ๊ธฐ / Selection API
[React/JS] ๋๋๊ทธํ ๋ฌธ์์ด ๋ถ๋ฆฌ(๋ฉํ)ํ๊ธฐ / Selection API
2024.05.21 -
[Express] req.query vs req.params
[Express] req.query vs req.params
2024.05.19 -
[JS] ํจ์ํ / ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ์ ๋ฎ์ ๊ผด
[JS] ํจ์ํ / ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ์ ๋ฎ์ ๊ผด
2024.05.19