New features in React 18
๋ค์ด๊ฐ๋ฉด์
React 18์ด ์ ์์ผ๋ก Release ๋์๊ณ ๋ง์ ๊ธฐ๋ฅ๋ค์ด ์๊ฐ๋์์ต๋๋ค.
๊ทธ ๊ธฐ๋ฅ๋ค์ ์กฐ๊ธ ์์ธํ ์ดํด๋ณด๊ณ ๊ฐ๋ฅํ๋ค๋ฉด ์ค์ต๊น์ง ํด๋ณด๋ ค๊ณ ํฉ๋๋ค.
๋จผ์ ์ด๋ค ๊ธฐ๋ฅ๋ค์ด ์ถ๊ฐ ๋์๋์ง ์ดํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
React Concurrency ๊ฐ๋ ์ ์ดํดํ๊ธฐ ์ํด ๋ค์ ๋งํฌ๋ฅผ ์ด์ฉํ์๊ธธ ๊ฐ๋ ฅํ๊ฒ ์ถ์ฒ๋๋ฆฝ๋๋ค.
Inside React - ๋์์ฑ์ ๊ตฌํํ๋ ๊ธฐ์
๋ฐํ์๋ฃ
new features
React 18 ๋ณํ๋ฅผ ํ ๋จ์ด๋ก ์ ๋ฆฌํ์๋ฉด Concurrency ์
๋๋ค.
๋๋ถ๋ถ์ ์๋ก์ด ๊ธฐ๋ฅ์ React์์ ๋์์ฑ์ ์ ๊ณตํ๊ธฐ ์ํ ๊ธฐ๋ฅ์
๋๋ค.
automatic batching
Batching์ React๊ฐ ๋ค์์ state๋ฅผ ๊ฐ์ง๊ณ ์์ ๋ ๋ ๋์ ์ฑ๋ฅ์ ์ํด single re-renderํ๋๋ก ์ ๋ฐ์ดํธํ๋ ๊ฒ์ ๋๋ค.
promises, setTimeout, native event handlers, React์์ batch๋์ง ์๋ event๋ค์ ๊ฒฝ์ฐ์ ๊ธฐ์กด์๋ ๊ฐ state๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค re-render๊ฐ ์ผ์ด๋ฌ๋ค๋ฉด, react 18์์๋ automatic batching์ผ๋ก ์ธํด Batching
ํ์ฌ ์๋์ผ๋ก ํ ๋ฒ๋ง re-renderํ๋๋ก ๋์์ต๋๋ค.
// Before: only React events were batched.
setTimeout(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
// React will render twice, once for each state update (no batching)
}, 1000);
// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched.`
setTimeout(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);
transitions
- Urgent updates(๊ธด๊ธํ ์ ๋ฐ์ดํธ)์ ํ์ดํ, ํด๋ฆญ, ํค์ ๋ ฅ๊ณผ ๊ฐ์ ๊ฒ๋ค ๋ฐ๋ก ๋ฐ์ํด์ผ ํ๋ ๊ฒ๋ค์ ๋ฐ์ํฉ๋๋ค.
- Transition updates(ํธ๋์ง์ ์ ๋ฐ์ดํธ)์ ํ๋์ view์์ ๋ ๋ค๋ฅธ view๋ก UI๋ฅผ ์ ํํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
ํ์ดํ, ํด๋ฆญ, ํค์
๋ ฅ๊ณผ ๊ฐ์ Urgent Update๋ ๋ฌผ๋ฆฌ์ ๊ฐ์ฒด๊ฐ ์ด๋ป๊ฒ ๋์ํ๋์ง์ ๋ํ ์ฐ๋ฆฌ์ ์ง๊ด๊ณผ ๊ด๋ จ๋์ด ์์ด์ ์ฆ๊ฐ์ ์ธ ์๋ต์ด ํ์ํฉ๋๋ค. ๊ทธ๋ ๊ฒ ์์ง์ด์ง ์๋๋ค๋ฉด "์๋ชป๋" ๋๋์ ๋ฐ์ต๋๋ค.
๊ทธ๋ฌ๋ ํธ๋์ง์
์ ์ฌ์ฉ์๊ฐ ์คํฌ๋ฆฐ์ ํธ๋์ง์
์ค๊ฐ์ ๋ชจ๋ ๊ฐ์ ๋ณผ ๊ฒ์ผ๋ก ๊ธฐ๋ํ์ง ์๊ธฐ ๋๋ฌธ์ urgent update์ ๋ค๋ฆ
๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ์ต์์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ํด์๋ ๋จ์ผ user input์ urgent update์ non-urgent update ๋ชจ๋ ๋ฐ์ํด์ผ ํฉ๋๋ค. input event ๋ด์ startTransition API๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ค update๊ฐ urgentํ์ง, ์ด๋ค ๊ฒ์ด "transition"์ธ์ง React์๊ฒ ์๋ ค์ค ์ ์์ต๋๋ค.
with transition : concurrent |
without transition : blocked |
transition์ ์ฌ์ฉํ๋ ์ ์ฌ์ง์์๋ task๊ฐ ์๊ฒ ์ชผ๊ฐ์ ธ์ concurrent ํ๊ฒ ๋์๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ transition์ด ์๋ ์๋ ์ฌ์ง์ long task๊ฐ block๋ ๋ชจ์ต์ ๋ณผ ์ ์์ต๋๋ค. input์ ํ์ดํ์ ํ๋๋ผ๋ block์ผ๋ก ์ธํด ํ๋ฉด์ ๋ ๋๋ง๋์ง ์์ ์ด์ ์
๋๋ค.
import { startTransition } from 'react';
const handleInput = () => {
// Urgent: Show what was typed
setInputValue(input);
// Mark any state updates inside as transitions
startTransition(() => {
// Transition: Show the results
setSearchQuery(input);
});
};
startTransition์ผ๋ก ๊ฐ์ธ์ง update๋ non-urgent(๊ธํ์ง ์์ ๊ฒ)์ผ๋ก ๋ค๋ค์ง๋๋ค.
๋ง์ฝ click์ด๋ ํค์
๋ ฅ๊ณผ ๊ฐ์ ๋ urgent(๊ธด๊ธํ) ์
๋ฐ์ดํธ๊ฐ ์๋ค๋ฉด interrupt ๋ฉ๋๋ค.
๋ง์ฝ ์ฌ์ฉ์๊ฐ transition์ ์ผ์์ค๋จํ๋ฉด(์๋ฅผ ๋ค์ด, ์ฌ๋ฌ ๋ฌธ์๋ฅผ ์ฐ์์ผ๋ก ์
๋ ฅ), React๋ ์๋ฃ๋์ง ์์ ์ค๋๋ rendering ์์
์ throw out(๋ฒ๋ฆผ)ํ๊ณ ์ต์ ์
๋ฐ์ดํธ๋ง ๋ ๋๋งํฉ๋๋ค.
๋ค์ ๋งํด, ๋ค์์ ๊ธ์๋ฅผ ์
๋ ฅํ ๋ ๋ ๋๋ง์ ๊ธฐ๋ค๋ ธ๋ ์ค๊ฐ ์ํ๋ค์ ๋ฒ๋ ค์ง๊ณ ๋ง์ง๋ง ์ํ๋ง ๋ ๋๋งํฉ๋๋ค.
useTransition
: transition์ ์์ํ๊ธฐ ์ํ pending ์ํ๋ฅผ ํฌํจํ hookstartTransition
: hook์ด ์ฌ์ฉ๋์ง ์์ ๋ transition์ ์์ํ๊ธฐ ์ํ method
transition์ concurrent rendering์์ ํด๋น update๋ฅผ ์ผ์์ค๋จํ ์ ์์ต๋๋ค. content๊ฐ ๋ค์ ์ผ์์ค๋จ(re-suspend) ์ํ๊ฐ ๋๋ค๋ฉด, transition์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ trantition content๋ฅผ ๋ ๋๋งํ๋ ๋์ ํ์ฌ content๋ฅผ ๊ณ์ ๋ณด์ฌ์ฃผ๋๋ก React์๊ฒ ๋งํฉ๋๋ค.
Note:
transition์์ update๋ ํด๋ฆญ๊ณผ ๊ฐ์ ๋ณด๋ค ๊ธด๊ธํ(urgent) update๋ฅผ ์ํด ์ผ์์ค๋จํฉ๋๋ค.
transition์์ update๋ ๋ค์ ์ผ์์ค๋จ(re-suspend)๋ content๋ฅผ ์ํด fallback๋ฅผ ๋ณด์ฌ์ฃผ์ง ์์ต๋๋ค.
update๊ฐ ๋ ๋๋ง๋๋ ๋์์ ์ฌ์ฉ์์๊ฒ ํ์ฌ content์ ๊ณ์ํด์ ์ํธ์์ฉํ๋๋ก ํด์ค๋๋ค.
๊ธฐ์กด์ ๋ ๋๋ง block์ด ํ ๋ฌถ์์ผ๋ก ์ด๋ฃจ์ด์ ธ ๋ฌด๊ฑฐ์ด ๋ ๋๋ง์ด ์์ ๊ฒฝ์ฐ ๋์์ฑ ๋ณด์ฅ์ด ํ๋ค์๋ค๋ฉด, useTransition์ ํตํด ์์ ์ ๋๋๊ณ ์ฐ์ ์์๋ฅผ ๋์ด ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
์ ๋ฆฌํ์๋ฉด input์ ๊ฐ์ ์
๋ ฅํ๋ ๊ฒ๊ณผ ๊ฐ์ urgent update๋ ์ฆ๊ฐ์ ์ผ๋ก ๋ฐ์์ ํด์ผํฉ๋๋ค.
๊ทธ๋ฐ๋ฐ heavyํ render๋ก ์ธํด urgent update๊ฐ prevent ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์ด๊ฒ์ UX์ ์ ์ข์ ์ํฅ์ ์ค๋๋ค. useTransition์ ์ด๋ฌํ ๊ฒฝ์ฐ๋ฅผ ๋ง๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. non-urgent update๋ฅผ startTransition์ผ๋ก ๊ฐ์ผ๋ค๋ฉด react๋ '์ด๊ฒ์ non-urgent update๊ตฌ๋'๋ผ๊ณ ์ดํดํ๊ณ render๋ฅผ urgnet update๋ณด๋ค ํ์์๋ก ๋ฏธ๋ฃน๋๋ค. ๊ทธ๋์ ์ฌ๋์ ์ธ์ง์ ๊ด๋ จ์๋ urgent update๋ฅผ ๋ ๋น ๋ฅด๊ฒ ๋ ๋๋งํ์ฌ ๋ ๋์ UX๋ฅผ ์ ๊ณตํฉ๋๋ค.
useTransition vs useDeferredValue
react 18์์ ๊ณต๊ฐ๋ useDeferredValue ๋ผ๋ hook์ด ์์ต๋๋ค.
๋์์ฑ์ ์ ๊ณตํ๊ธฐ ์ํด์ ์ฌ์ฉ๋๊ธฐ์ useTransition๊ณผ ์ผํ ๋น์ทํด๋ณด์ด๊ธฐ๋ ํฉ๋๋ค.
์ ๋ํ ์ฐจ์ด๊ฐ ์ด๋ค ์ ์์ ๋ฐ์ํ๋์ง ๋๋์ผ๋ก๋ง ์๊ณ ์๋๋ฐ ์์ธํ ๋ณด๊ธฐ ์ํด ์กฐ๊ธ ์ดํด๋ณด์์ต๋๋ค.
useTransition์ ์์๋ ์ดํด๋ณด์๋ฏ์ด React์๊ฒ ์ฐ์ ์์๊ฐ ๋ฎ์ update๋ฅผ ์๋ ค์ฃผ์ด ํด๋น non-urgent update๋ฅผ urgent update ๋ค๋ก rebaseํ๊ฒ ํด์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
๋ํ useTransition์ ์ด๋ค ์ฝ๋๋ฅผ ๋ฎ์ ์ฐ์ ์์๋ก ๋ค๋ฃฐ์ง, ์ด๋ค ์ฝ๋๋ฅผ wrappingํ ์ง ๊ฒฐ์ ํ๋๋ก ๋ชจ๋ ๊ถํ์ ์ค๋๋ค.
function App() {
const [isPending, startTransition] = useTransition();
const [filterTerm, setFilterTerm] = useState('');
const filteredProducts = filterProducts(filterTerm);
function updateFilterHandler(event) {
startTransition(() => {
setFilterTerm(event.target.value);
});
}
return (
<div id="app">
<input type="text" onChange={updateFilterHandler} />
{isPending && <p>Updating List...</p>}
<ProductList products={filteredProducts} />
</div>
);
}
๊ทธ๋ฌ๋ ์ฝ๋๋ฅผ ์ ๋ฐ์ดํธํ๋ ์ค์ ์ํ์ ์ ๊ทผํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ์์ ์ ์์ต๋๋ค(์๋ฅผ ๋ค์ด third-party library์์ ์ํ๋๋ ๊ฒฝ์ฐ ๋ฑ). ๋๋ ๋ช๊ฐ์ง ์ด์ ๋ก ์ธํด useTransition์ ์ฌ์ฉ ๋ชปํ๊ฒ ๋ ์๋ ์์ต๋๋ค.
๊ทธ๋ฐ ๊ฒฝ์ฐ์ useDeferredValue๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
useDeferredValue๋ ์ํ๋ฅผ wrapping ํ์ง ์์ต๋๋ค. ๋์ ์ ์ํ ์ ๋ฐ์ดํธ ๋๋ฌธ์ ๋ณ๊ฒฝ๋๊ฑฐ๋ ์ฌ์์ฑ๋ value๋ฅผ ๋ค๋ฃน๋๋ค(๊ทธ ์ํ ๊ทธ๋๋ก ์ฐ๊ฑฐ๋ ๋๋ ๊ธฐ๋ณธ ์ํ์ computed ๊ฐ).
function ProductList({ products }) {
const deferredProducts = useDeferredValue(products);
return (
<ul>
{deferredProducts.map((product) => (
<li>{product}</li>
))}
</ul>
);
}
๊ทธ๋์ ์ฐ๋ฆฌ๋ ๋ฌด์์ ์จ์ผํฉ๋๊น?
์์์ ์
๊ธํ๋ฏ์ด useTransition์ ์ํ ์
๋ฐ์ดํธ ์ฝ๋๋ฅผ wrappingํ๋ ๋ฐ๋ฉด useDeferredValue๋ ์ํ ์
๋ฐ์ดํธ์ ์ํฅ์ ๋ฐ๋ ๊ฐ(์ํ ๊ทธ๋๋ก์ ๊ฐ ๋๋ ์ํ์์ computed๋ ๊ฐ)์ ์ด๋ค๋ ์ ์
๋๋ค.
๊ฒฐ๊ตญ ๋ ๋ค ๊ฐ์ ๋ชฉํ๋ฅผ ๋ฌ์ฑํ๊ธฐ ๋๋ฌธ์ ํจ๊ป ์ฌ์ฉํ ํ์๋ ์์ต๋๋ค.
๋ ๋ฎ์ ์ฐ์ ์์๋ก ์ฒ๋ฆฌํด์ผ ํ๋ ์ํ ์
๋ฐ์ดํธ๊ฐ ์๊ณ ์ํ ์
๋ฐ์ดํธ ์ฝ๋์ ์ ๊ทผํ ์ ์๋ ๊ฒฝ์ฐ useTransition์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข์ต๋๋ค.
ํด๋น ์ฝ๋์ ์ ๊ทผ ๊ถํ์ด ์๋ค๋ฉด useDeferredValue๋ฅผ ์ฌ์ฉํ์ธ์.
๋ฐ๋์ ์จ์ผํ ๊น์?
๋ชจ๋ ์ํ ์
๋ฐ์ดํธ๋ฅผ useTransition์ด๋ useDeferredValue๋ก wrappingํ์ง ๋ง์ธ์.
๋ค๋ฅธ ์๋จ์ผ๋ก ์ต์ ํํ ์ ์๋ ๋ณต์กํ UI๋ component๊ฐ ์์ ๋ ์ด๋ฌํ hook์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
lazy loading ์ฌ์ฉ, pagination ์ฌ์ฉ, worker thread์์ ์ฒ๋ฆฌํ๋ ๊ฒ, backend ์๋ฒ์์ ์ฒ๋ฆฌํ๋ ๊ฒ ๋ฑ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋์ง๋ฅผ ํญ์ ์ผ๋ํด ๋์ด์ผ ํฉ๋๋ค.
Suspense
Suspense๋ ์์ง ํ๋ฉด์ ๋ณด์ฌ์ค ์ค๋น๊ฐ ๋์ง ์์ ๊ฒฝ์ฐ์ component ํธ๋ฆฌ์ ์ผ๋ถ์ ๋ํ loading ์ํ๋ฅผ ์ ์ธ์ ์ผ๋ก ์ง์ ํ ์ ์์ต๋๋ค.
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
Suspense๋ React ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์์ "UI loading ์ํ"๋ฅผ first-class ์ ์ธ์ ๊ฐ๋
์ผ๋ก ๋ง๋ญ๋๋ค.
(์ฆ, <Component>...</Component>
ํํ๋ก ์ธ ์ ์๋ค๋ ๋ง์
๋๋ค.)
fisrt-class(์ผ๊ธ๊ฐ์ฒด)
๋ค๋ฅธ ๊ฐ์ฒด๋ค์ ์ผ๋ฐ์ ์ผ๋ก ์ ์ฉ ๊ฐ๋ฅํ ์ฐ์ฐ์ ๋ชจ๋ ์ง์ํ๋ ๊ฐ์ฒด๋ฅผ ๋งํฉ๋๋ค. ๋ณดํต ํจ์์ ์ธ์๋ก ๋๊ธฐ๊ธฐ, ์์ ํ๊ธฐ, ๋ณ์์ ๋์ ํ๊ธฐ์ ๊ฐ์ ์ฐ์ฐ์ ์ง์ํ ๋ ์ผ๊ธ ๊ฐ์ฒด๋ผ๊ณ ํฉ๋๋ค.
์ฝ๊ฒ ๋งํ๋ฉด, ์๋ฐ์คํฌ๋ฆฝํธ์์๋ ํจ์๊ฐ ์ผ๊ธ๊ฐ์ฒด์ ๋๋ค. ๋ฆฌ์กํธ ์ ์ธ์ ๊ฐ๋ ์ผ๋ก ๋ค๋ฃจ๋ฉด<Component>
ํํ๋ก ์ธ ์ ์์ต๋๋ค.(React.createElement(...)
์ ๋์ผํ๋ฉฐ ์ด ํํ์์ ๋ช ๋ นํ ๊ฐ๋ ์ ๋๋ค.)
์ด๋ฅผ ํตํด ๋ ๋์ ์์ค์ ๊ธฐ๋ฅ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
๋ช ๋ ์ ์ ์ ํ๋ ๋ฒ์ ์ Suspense๋ฅผ ๋์ ํ์ต๋๋ค. ๊ทธ๋ฌ๋ ์ ์ผํ ์ฌ์ฉ ์ฌ๋ก๋ React.lazy ์ฝ๋ ์คํ๋ฆฌํ ์ ์ฌ์ฉ๋ ๊ฒฝ์ฐ์์ต๋๋ค. ์๋ฒ์์ ๋ ๋๋งํ ๋๋ ์ ํ ์ง์๋์ง ์์์ต๋๋ค.
React 18์์๋ ์๋ฒ์์ Suspense์ ๋ํ ์ง์์ ์ถ๊ฐํ๊ณ Concurrent rendering ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ๊ธฐ๋ฅ์ ํ์ฅํ์ต๋๋ค.
Suspense๋ transition API(useTransition, startTransition)์ ํจ๊ป ๋์ํฉ๋๋ค.
transitionํ๋ ๋์์ suspend(์ผ์์ค์ง) ๋๋ค๋ฉด React๋ ์ด๋ฏธ ๋ณด์ด๋(already-visible) ์ปจํ
์ธ ๊ฐ fallback์ผ๋ก ๋์ฒด๋๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. ๋์ React๋ ์๋ชป๋ loading ์ํ๋ฅผ ๋ง๊ธฐ ์ํด data๊ฐ ์ถฉ๋ถํ ๋ก๋๋ ๋๊น์ง ๋ ๋๋ง์ ์ง์ฐํฉ๋๋ค.
Streaming HTML and Selective Hydration
https://github.com/reactwg/react-18/discussions/37
https://codesandbox.io/s/kind-sammet-j56ro?file=/src/App.js
React์์ SSR์ ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ํญ์ ์งํ๋ฉ๋๋ค.
- ์๋ฒ์์: App์ ํ์ํ data๋ฅผ fetch ํฉ๋๋ค.
- ์๋ฒ์์: App์ HTML๋ก renderํ๊ณ response๋ก ๋ณด๋ ๋๋ค.
- ํด๋ผ์ด์ธํธ์์: App์ ์ํ JavaScript ์ฝ๋๋ฅผ ๋ก๋ํฉ๋๋ค.
- ํด๋ผ์ด์ธํธ์์: ์๋ฒ์์ ์์ฑ๋ HTML์ JavaScript ๋ก์ง์ ์ฐ๊ฒฐํฉ๋๋ค.(์ด๊ฒ์ "hydration"์ด๋ผ๊ณ ํฉ๋๋ค.)
์ฌ๊ธฐ์ ๋ฌธ์ ๋ ๋ค์ ๋จ๊ณ ์์ ์ ์ ๊ฐ ๋จ๊ณ๊ฐ ์ ์ฒด ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ํ ์์ ์ ํ ๋ฒ์ ์๋ฃ๋์ด์ผ ํ๋ค๋ ๊ฒ์ ๋๋ค. ์ฑ์ ๊ฑฐ์ ๋ชจ๋ ๋ถ๋ถ์ด non-trival์ธ ๊ฒฝ์ฐ, ์ฑ์ ์ผ๋ถ๊ฐ ๋ค๋ฅธ ๋ถ๋ถ๋ณด๋ค ๋๋ฆฐ ๊ฒฝ์ฐ, ์ด๋ฐ ๊ฒฝ์ฐ๋ ํจ์จ์ ์ด์ง ์์ต๋๋ค.
1. CSR: ๋น HTML |
SSR์ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ JavaScript๊ฐ ๋ก๋๋๋ ๋์ ์ฌ์ฉ์์๊ฒ ํ์๋๋ ๊ฒ์ ๋น ํ์ด์ง๋ฟ์ ๋๋ค. ์ด๊ฒ์ด ๊ถ์ฅ๋์ง ์์ผ๋ฉฐ ์ธํฐ๋ท ์๋๊ฐ ๋๋ฆฐ ๊ฒฝ์ฐ(์ผ๋ฐ์ ์ผ๋ก JavaScript ํฌ๊ธฐ๊ฐ ํฌ๊ธฐ ๋๋ฌธ์) ํนํ ๋ ์ฌํด์ง๋๋ค. ๊ทธ๋์ SSR์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
2. SSR + hydration X: ์๋ฒ์์ ์์ฑ๋ HTML, but ์ด๋ฒคํธ๊ฐ X |
SSR์ ์ฌ์ฉํ์ฌ React components๋ฅผ HTML๋ก ๋ ๋๋งํ์ฌ ์ฌ์ฉ์์๊ฒ ๋ณด๋ ๋๋ค. ๊ทธ๋ฌ๋ HTML์ ๋งค์ฐ ์ํธ์์ฉ์ ์ด์ง ์์ต๋๋ค.(link, form, ๋ฑ ๊ฐ๋จํ built-in Web interactive ๋ฑ ์ ์ธ, ๋ง์ฝ build-in Web interactive๋ก๋ง ์์ฑ๋์๋ค๋ฉด ์ด๋ฐ ๋ฌธ์ ๋ ์์ต๋๋ค.) ํ์ง๋ง ์ฅ์ ์ผ๋ก๋ JavaScript๊ฐ ๋ก๋๋๋ ๋์(hydration) ์ฌ์ฉ์๋ ํ๋ฉด์ ๋ฌด์ธ๊ฐ๋ฅผ ๋ณผ ์ ์๋ค๋ ๊ฒ์ ๋๋ค.
์ ์ฌ์ง์์ ํ์ ํ์๋ ์์ ํ ์ํธ์์ฉํ์ง ์๋๋ค๋ ๊ฒ์ ๋ํ๋
๋๋ค. ๋ง์ฝ ๊ทธ ๋ถ๋ถ์ ์ํธ์์ฉ์ด ํ์ํ JavaScript ์ฝ๋๊ฐ ์๋ค๋ฉด ์ด๋ฒคํธ๋ฅผ ํธ์ถ(ํด๋ฆญ๊ณผ ๊ฐ์)ํ๋๋ผ๋ ์๋ฌด ์์
๋ ์ํ๋์ง ์์ต๋๋ค.
๊ทธ๋ฌ๋ฉด CSR๊ณผ ์ฐจ์ด๊ฐ ์๋๊ฑฐ ์๋๊น์? JavaScript๋ฅผ ๋ก๋ํ๋ ์๊ฐ์ CSR๊ณผ ๋์ผํ๋ฐ SSR ์๊ฐ์ด ์ถ๊ฐ๊ฐ ๋ ๊ฑฐ ์๋๊ฐ์?
๊ทธ๋ฌ๋ ํนํ ์ฝํ
์ธ ๊ฐ ๋ง์ ์น ์ฌ์ดํธ์ ๊ฒฝ์ฐ SSR์ JavaScript๊ฐ ๋ก๋๋๋ ๋์ ์ฐ๊ฒฐ ์ํ๊ฐ ์ข์ง ์์ ์ฌ์ฉ์๊ฐ ์ฝํ
์ธ ๋ฅผ ์ฝ๊ฑฐ๋ ๋ณผ ์ ์๋๋ก ํ๊ธฐ ๋๋ฌธ์ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
๋ํ CSR๊ณผ ๋ฌ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ์ ์ปดํฌ๋ํธ ํธ๋ฆฌ๋ฅผ ๋ ๋๋ง์ ํ์ง๋ง ์ด์ ๋ํ DOM ๋
ธ๋๋ฅผ ์์ฑํ๋ ๋์ ๊ธฐ์กด HTML DOM ๋
ธ๋์ ์ด๋ฒคํธ๋ฅผ ๋ถ์ด๋ ํํ๋ก ์์
๋ฉ๋๋ค. ์ด ์์
์ hydration์ด๋ผ๊ณ ๋ถ๋ฆ
๋๋ค. ๊ฑด์กฐํ HTML(SSR๋ก ์์ฑ๋ HTML)์ ๋ฌผ์ ์ฃผ๋ ๊ฒ๊ณผ ๊ฐ๋ค๊ณ ํด์ ๊ทธ๋ ์ต๋๋ค.
3. SSR + hydration O: ์๋ฒ์์ ์์ฑ๋ HTML + ์ด๋ฒคํธ |
SSR์ ์ผ์ข ์ "๋ง์ ํธ๋ฆญ"์ ๋๋ค. ์ฑ์ด ์์ ํ ์ํธ์์ฉํ๋ ์๋๊ฐ ๋นจ๋ผ์ง์ง๋ ์์ต๋๋ค. ๋์ ๋น๋ํํ(non-interactive) ๋ฒ์ ์ ์ฑ์ ๋ ๋นจ๋ฆฌ ํ์ํ ์ ์์ผ๋ฏ๋ก ์ฌ์ฉ์๋ JavaScript๊ฐ ๋ก๋๋๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ์ ์ ์ฝํ ์ธ ๋ฅผ ๋ณผ ์ ์์ต๋๋ค. ์ด๊ฒ์ ๋คํธ์ํฌ ์ฐ๊ฒฐ์ํ๊ฐ ์ข์ง ์์ ์ฌ๋๋ค์๊ฒ ํฐ ์ฐจ์ด๋ฅผ ๋ง๋ค๊ณ ์ ๋ฐ์ ์ผ๋ก ์ธ์ง์ ์ธ ์ฑ๋ฅ์ ํฅ์์ํต๋๋ค. ๋ํ ๋ ์ฌ์ด ์ธ๋ฑ์ฑ๊ณผ ๋ ๋์ ์๋ ๋๋ถ์ SEO์๋ ๋์์ด ๋ฉ๋๋ค.
Note:
SSR์ Server Component์ ํท๊ฐ๋ฆฌ์ง ๋ง์ธ์. Server Component๋ ํ์ฌ ์คํ์ ์ธ ๊ธฐ๋ฅ์ด๋ฉฐ ์ฌ์ ํ ์ฐ๊ตฌ์ค์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๊ธฐ React 18 ๋ฆด๋ฆฌ์ฆ์๋ ํฌํจ๋์ง ์์ ๊ฒ์ ๋๋ค. ์ฌ๊ธฐ์ Server Component๋ฅผ ๋ฐฐ์ธ ์ ์์ต๋๋ค. Server Component๋ SSR์ ๋ณด์ํ๊ณ , data fetching ์ ๊ทผ๋ฒ์ค ์ถ์ฒ๋๋ ๋ฐฉ๋ฒ ์ค ํ๋์ ๋๋ค. ๊ทธ๋ฌ๋ ์ฌ๊ธฐ์ ๋ค๋ฃจ์ง ์์ต๋๋ค.
์์ ์ ๊ทผ ๋ฐฉ์์ ํจ๊ณผ๊ฐ ์์ง๋ง ์ฌ๋ฌ ๋ฉด์์ ์ต์ ์ด ์๋๋๋ค.
- ์ด๋ค ๊ฒ์ ํ์ํ๊ธฐ ์ํด์ ๋ชจ๋ ๊ฒ์ fetch ํด์ผํ๋ ๊ฒ
- ์ด๋ค ๊ฒ์ hydrateํ๊ธฐ ์ํด์ ๋ชจ๋ ์ฝ๋๋ฅผ load ํด์ผํ๋ ๊ฒ
- ์ด๋ค ๊ฒ์ ์ํธ์์ฉํ๊ธฐ ์ํด์ ๋ชจ๋ ๊ฒ์ hydrate ํด์ผํ๋ ๊ฒ
React 18์์ ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด <Suspense>
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๊ธฐ์กด Suspense๋ Reacy.lazy๋ฅผ ์ด์ฉํ Code Splitting์์๋ง ์ฌ์ฉ๋์์ต๋๋ค๋ง React 18์์๋ Server Side์์๋ ์ ๋์ํ๋ฉฐ, ์ ๋ฌธ์ ๋ฅผ ๋ ๋์ ๋ฐฉํฅ์ผ๋ก ํด๊ฒฐํ๋๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
React 18์์ <Suspense>
๋ฅผ ์ฌ์ฉํ๋ฉด ์ฑ์ ์๊ณ ๋
๋ฆฝ์ ์ธ ๋จ์๋ก ๋๋์ด์ฃผ์ด ์์ ๋จ๊ณ๋ค์์ ๋
๋ฆฝ์ ์ผ๋ก ์คํ๋๋๋ก ํ ๊ฒ์
๋๋ค. ๊ทธ๋์ ์ฑ์ ๋๋จธ์ง ๋ถ๋ถ์ด block๋์ง ์๋๋ก ํด์ค ๊ฒ์
๋๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฑ ์ฌ์ฉ์๋ content๋ฅผ ๋ ๋นจ๋ฆฌ ๋ณด๊ณ ํจ์ฌ ๋ ๋นจ๋ฆฌ ์ํธ์์ฉํ ์ ์์ต๋๋ค.
๋ํ React.lazy
๊ฐ SSR ํ๊ฒฝ์์ "์๋"ํ๊ฒ ๋๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
(ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค๋ฉด, ์๋ฒ์์ HTML generateํ๋ ๋ถ๋ถ์ ๋ฐ๊ฟ์ผ๋ง ํฉ๋๋ค.์์)
Suspense๊ธฐ๋ฅ์ผ๋ก ์ธํด unlock๋ React 18์์ 2๊ฐ์ง ์ค์ํ feature๊ฐ ์์ต๋๋ค.
- Streaming HTML on the server: switch
renderToString
to newrenderToPipeableStream
- Selective hydration on the client: switch to new
hydrateRoot
. and wrapping parts of your app with<Suspense>
์ฌ๋ด: server-side rendering ์ง์ ๋ง๋ค์ด์ ํ ์คํธํด๋ณด๋ ค๋ค๊ฐ ๋ฐฐ๋ณด๋ค ๋ฐฐ๊ผฝ์ด ๋ ์ปค์ง ์ผ์ด์ค๋๊น... ์ฒ์ React SSR ์๋ฒ๋ฅผ ์ง์ ์์ฑ(์ด๋ผ๊ณค ํ์ง๋ง ๊ตฌ๊ธ์์ ๋๋ถ๋ถ ๊ฐ์ ธ์ด)ํด๋ณด์๋๋ฐ ์๋นํ ๋ฒ๊ฑฐ์ด ๋๋์ด์์ต๋๋ค.
๊ทธ๋๋ ๋์?์ ๋์๊ณ Suspense๊ฐ server-side์์๋ ์ ๋์ํ๋๋ก ์ ๋ฐ์ดํธ ๋์์๋๊ฑธ ํ์ธํ์ต๋๋ค. loadable-component์ ๊ฐ์ ๋ณ๋์ tool์์ด Suspense SSR์ด ์ ์ฉ์ด ๊ฐ๋ฅํ ๊ฒ์ ํ์ธํ์ต๋๋ค.
์ด๋ค ๊ฒ์ ํ์ํ๊ธฐ ์ํด์ ๋ชจ๋ ๊ฒ์ fetch ํด์ผํ๋ ๊ฒ
๋ชจ๋ fetchํ๊ธฐ ์ ์ HTML Streaming |
Streaming HTML์ด ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
<Layout>
<NavBar />
<Sidebar />
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>
Suspense๋ฅผ ํตํด Comments๊ฐ ์๋ฃ๋๋์ง ์๋๋์ง ์๊ด์์ด HTML์ ๋ณด๋ผ ์ ์์ต๋๋ค.
ํด๋ผ์ด์ธํธ๋ ์ฒ์ HTML์ ๋ฐ๊ณ ๋ ํ๋ถํฐ Comments๊ฐ ๋ง๋ค์ด์ง๊ธฐ ๊น์ง Suspense๊ฐ fallback์ ์ ์ธํด๋ component๋ฅผ ๋ณด์ฌ์ฃผ๊ฒ ๋๊ณ , Comments๊ฐ ์๋ฃ๋ ์์ ์ ์ถ๊ฐ HTML์ ๋์ผ ์คํธ๋ฆผ์ผ๋ก ๋ณด๋ด๊ณ ํด๋น HTML์ ์ฌ๋ฐ๋ฅธ ์์น์ ๋ฃ์ ์ ์๊ฒ ์์ ์ธ๋ผ์ธ <script>
ํ๊ทธ๋ฅผ ๋ณด๋ด์ฃผ์ด ์ฌ๋ฐ๋ฅธ ์์น์ ๋ฃ๊ฒ ํด์ค๋๋ค.
๋ํ ๊ธฐ์กด HTML Streaming ๋ฐฉ์๊ณผ ๋ค๋ฅด๊ฒ ํ๋ค์ด ์์๋ก ์งํ๋ ํ์๋ ์์ต๋๋ค.
๋ฐ์ดํฐ๊ฐ ํน์ ์์์ ๋ง์ถฐ ๋ก๋๋์ด์ผ ํ๋ค๋ ์๊ตฌ์ฌํญ๋ ์์ต๋๋ค.
์ด๋ค ๊ฒ์ hydrateํ๊ธฐ ์ํด์ ๋ชจ๋ ์ฝ๋๋ฅผ load ํด์ผํ๋ ๊ฒ
์ ํด๊ฒฐ๋ก ์ธํด ์ด๊ธฐ HTML์ ๋ ์ผ์ฐ ๋ณด๋ผ ์ ์์ง๋ง ์ฌ์ ํ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. JavaScript ์ฝ๋๊ฐ ๋ชจ๋ ๋ก๋๋ ๋๊น์ง ํด๋ผ์ด์ธํธ์์ hydration์ ์์ํ ์ ์์ต๋๋ค.
ํฐ ๋ฒ๋ค ์ฌ์ด์ฆ๋ฅผ ํผํ๊ธฐ ์ํด ์ฃผ๋ก "์ฝ๋ ์คํ๋ฆฌํ
"์ด ์ฌ์ฉ๋ฉ๋๋ค. ํน์ ์ฝ๋ ๋ถ๋ถ์ด ๋๊ธฐ์ ์ผ๋ก ๋ก๋๋ ํ์๊ฐ ์๋ค๋ผ๊ณ ๋ช
์ํด์ฃผ๋ฉด ๋ฒ๋ค๋ฌ๊ฐ ์ด๋ฅผ ๋ณ๋์ <script>
ํํฌ๋ก ๋ถํ ํฉ๋๋ค.
React์์๋ React.lazy
๋ฅผ ์ด์ฉํ์ฌ ๋ช
์ํด์ค ์ ์๊ณ ๋ฉ์ธ ๋ฒ๋ค์์ <Comments>
๋ฅผ ์ฝ๋ ์คํ๋ฆฌํ
ํ ์ ์์ต๋๋ค.
import { lazy } from 'react';
const Comments = lazy(() => import('./Comments.js'));
// ...
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>;
์ด์ ์๋ server rendering์์ ์๋ํ์ง ์์์ต๋๋ค. (์ฐ๋ฆฌ๊ฐ ์๋ํ, ์ ๋ช ํ ํด๊ฒฐ๋ฐฉ๋ฒ์กฐ์ฐจ๋ ์ฝ๋ ์คํ๋ฆฌํ component์ ๋ํด SSR์์ ์ ์ธํ๊ฑฐ๋ ๊ทธ ์ฝ๋๊ฐ ๋ชจ๋ ๋ก๋๋ ์ดํ์ hydrateํ๋ ๊ฒ ์ค ํ๋๋ฅผ ์ ํํ๋๋ก ๊ฐ์ํ์ฌ ์ฝ๋ ์คํ๋ฆฌํ ๋ชฉ์ ์ ๋ค์ ๋ฌด๋ ฅํํ์ต๋๋ค.)
๊ทธ๋ฌ๋ React 18์์๋ <Comments>
๊ฐ ๋ก๋๋๊ธฐ ์ ์ hydrate ํ ์ ์์ต๋๋ค.
๋ชจ๋ ์ฝ๋๊ฐ load๋๊ธฐ ์ ์ hydrate |
์ด๊ฒ์ Selective hydration์ ํ ์์
๋๋ค. <Comments>
๊ฐ ์์ง ๋ก๋๋์ง ์์์ด๋ ๋๋จธ์ง ๋ถ๋ถ์ด hydrateํ๋ ๊ฒ์ ๋ง์ง ์์ต๋๋ค. ๊ทธ ํ <Comments>
๊ฐ ๋ก๋๋๋ฉด ํด๋น ๋ถ๋ถ์ hydrateํ๊ธฐ ์์ํฉ๋๋ค.
selective hydration ๋๋ถ์ JavaScript์ ํ์ด์ง์ ๋ฌด๊ฑฐ์ด ๋ถ๋ถ์ด ๋๋จธ์ง ๋ถ๋ถ์ interactive๋๋ ๊ฒ์ ๋ง์ง ์์ต๋๋ค.
์ด๋ค ๊ฒ์ ์ํธ์์ฉํ๊ธฐ ์ํด์ ๋ชจ๋ ๊ฒ์ hydrate ํด์ผํ๋ ๊ฒ
streaming HTML๊ณผ selective hydrateion์ผ๋ก ์ธํด hydrate ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ค๋ฅธ ์์ ์ ์ํํ๋ ๊ฒ์ ๋์ด์ ์ฐจ๋จํ์ง ์์ต๋๋ค.
๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ hydrate๋๊ธฐ ์ ์ interactive |
React 18์์๋ Suspense ๋ด๋ถ์ hydrate๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋๋ก ์์ ๊ฐ๊ฒฉ์ผ๋ก ๋ฐ์ํฉ๋๋ค. ๋๋ถ์ ํด๋ฆญ์ด ์ฆ์ ์ฒ๋ฆฌ๋ ์ ์๊ณ low-end ์ฅ์น์์๋ ์ค๋ hydrate ์๊ฐ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฉ์ถ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ๋ ์ด์ ๊ด์ฌ์ด ์๋ ํ์ด์ง์์ block๋๋ฌธ์ ๋ ์ด์ ๋จธ๋ฌด๋ฅด์ง ์๊ณ ๋ค๋ฅธ ๊ณณ์ผ๋ก ์ด๋ํ ์ ์์ต๋๋ค.
์์ ์์๋ <Comments>
๋ถ๋ถ๋ง Suspense๋ก wrapping๋์ด ์์ด์ ๋๋จธ์ง ๋ถ๋ถ์ ํ ๋ฒ์ hydrate๋ฉ๋๋ค. ๋ ๋ง์ ๊ณณ์์ Suspense๋ฅผ ์ฌ์ฉํ์ฌ ์ด ๋ถ๋ถ์ ํด๊ฒฐํ ์ ์์ต๋๋ค.
<Layout>
<NavBar />
<Suspense fallback={<Spinner />}>
<Sidebar />
</Suspense>
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>
Suspense๋ฅผ ์ ์ธํ ๋ถ๋ถ์ด ์ด๊ธฐ HTML์ ํฌํจ๋๊ณ ๊ทธ ํ <Sidebar>
, <Comments>
๊ฐ hydrate๋ฉ๋๋ค. React๋ ๋ ๊ฐ ๋ชจ๋ hydrate๋ฅผ ์งํํ๋๋ฐ ํธ๋ฆฌ์์ ์๋ถ๋ถ์ ์๋ Suspense๋ถํฐ ์งํํฉ๋๋ค. ์ฌ๊ธฐ์๋ <Sidebar>
๋ถํฐ ์งํ๋ฉ๋๋ค.
click on hydrate <Sidebar> |
๊ทธ๋ฐ๋ฐ ๋ง์ฝ ์ ์ด๋ฏธ์ง์ฒ๋ผ <Sidebar>
๊ฐ hydrate๋ฅผ ์งํํ๊ณ ์๋ ์ํ์์ <Comments>
์์ญ์ ์ํธ์์ฉ(์ฌ๊ธฐ์๋ ํด๋ฆญ)์ ํ๋ค๊ณ ๊ฐ์ ํด๋ด
์๋ค.
React๋ ํด๋น ํด๋ฆญ ์ด๋ฒคํธ์ capture phase ๋์์ <Comments>
๋ฅผ ๋๊ธฐ์ ์ผ๋ก hydrate ํ ๊ฒ์
๋๋ค.
๊ทธ ๊ฒฐ๊ณผ <Comments>
๊ฐ ์ ์์ hydrateํ์ฌ ์ํธ์์ฉ์ ์๋ตํ ์ ์๊ณ ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ๋ค๋ฃจ์ ์๊ฒ ๋ฉ๋๋ค. ๊ทธ ํ์ React๋ ๊ธด๊ธํ(urgent) ์์
์ด ์๊ธฐ ๋๋ฌธ์ <Sidebar>
๋ฅผ hydrate ํฉ๋๋ค.
์ด๊ฒ์ ์ฐ๋ฆฌ 3๋ฒ์งธ ๋ฌธ์ ์ธ "์ด๋ค ๊ฒ์ ์ํธ์์ฉํ๊ธฐ ์ํด์ ๋ชจ๋ ๊ฒ์ hydrate ํด์ผํ๋ ๊ฒ"์ ํด๊ฒฐํฉ๋๋ค.
React๋ ๊ฐ๋ฅํ ๋นจ๋ฆฌ ๋ชจ๋ ๊ฒ์ hydrateํ๊ธฐ ์์ํ๊ณ ์ฌ์ฉ์ ์ํธ์์ฉ์ ๊ธฐ๋ฐํ ํ๋ฉด์ ๊ฐ์ฅ ๊ธด๊ธํ(urgent) ๋ถ๋ถ์ ์ฐ์ ์์๋ฅผ ์ ํฉ๋๋ค. ์ฑ ์ ์ฒด์ Suspense๋ฅผ ์ฑํํ๋ค๋ฉด ๊ทธ ๊ฒฝ๊ณ๊ฐ ๋ ์ธ๋ถํ๋๊ธฐ ๋๋ฌธ์ Selective Hydration์ ์ฅ์ ์ด ๋ ๋ช ํํด์ง ๊ฒ์ ๋๋ค.
more granualr |
Note:
์์ ํ๊ฒ hydrate๊ฐ ์๋ฃ๋์ง ์์ ์ํ์์ ์ฑ์ด ์ด๋ป๊ฒ ์๋ํ๋์ง ๊ถ๊ธํ ์ ์์ต๋๋ค.
์ด๊ฒ์ด ๋์ํ๋๋ก ํ๋ ๋์์ธ์๋ ๋ช ๊ฐ์ง ์ธ๋ฐํ ๋ํ ์ผ์ด ์์ต๋๋ค.
- ๊ฐ๊ฐ์ component๊ฐ ๊ฐ๋ณ์ ์ผ๋ก hydrationํ์ง ์๊ณ
<Suspense>
boundary ์ ์ฒด์ ๋ํด hydration์ด ๋ฐ์ํฉ๋๋ค.<Suspense>
๋ ๋ฐ๋ก ๋ํ๋์ง ์๋ ์ฝํ ์ธ ์ ์ฌ์ฉ๋๊ธฐ ๋๋ฌธ์, ์ฝ๋๋ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ children์ ํ๋ ฅ์ น์ผ๋ก ์์ฑ๊ฐ๋ฅํฉ๋๋ค.- React๋ ํญ์ ๋ถ๋ชจ๋ถํฐ hydrate๋๋ฏ๋ก component์๋ ํญ์ props๋ฅผ ๊ฐ์ง๋๋ค.
- React๋ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์ง์ ์ผ๋ก๋ถํฐ ๋ถ๋ชจtree ์ ์ฒด๊ฐ hydrate๋ ๋๊น์ง ์ด๋ฒคํธ ์ ๋ฌ์ ๋ณด๋ฅํฉ๋๋ค.
- React๋ ๋ง์ฝ parent๊ฐ not-yet-hydrated HTML์ staleํ๊ฒ ๋ง๋๋ ๋ฐฉ์์ผ๋ก ์ ๋ฐ์ดํธ๋๋ค๋ฉด ํด๋น ๋ถ๋ถ์ด load๋ ๋๊น์ง ์ด๋ฅผ ์จ๊ธฐ๊ณ ์ง์ ๋
fallback
์ ๋ณด์ฌ์ค๋๋ค.์ด๋ ๊ฒ ํ๋ฉด ํธ๋ฆฌ๊ฐ ์ฌ์ฉ์์๊ฒ ์ผ๊ด๋๊ฒ ๋ํ๋ฉ๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก
React 18์ SSR์ 2๊ฐ์ง ์ฃผ์ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
- Streaming HTML: ์ํ๋ ์ฆ์ HTML์ ๋ด๋ณด๋ด๊ณ ์ ์ ํ ์์น์ ๋ฐฐ์น๋์ด ์ถ๊ฐ ์ฝํ ์ธ ์ ๋ํ HTML์ Streamingํ ์ ์์ต๋๋ค.
- Selective Hydration: ์ด๊ธฐ ์ดํ์ HTML, JavaScript ์ฝ๋๊ฐ ์์ ํ ๋ค์ด๋ก๋ ๋๊ธฐ ์ ์ ์ต๋ํ ๋นจ๋ฆฌ ์ฑ์ hydratation์ ์์ํ ์ ์์ต๋๋ค. ๋ํ ์ฌ์ฉ์๊ฐ ์ํธ์์ฉํ๋ ๋ถ๋ถ์ ์ฐ์ ์์๋ฅผ ๋์ด ์ฆ๊ฐ์ ์ธ hydration์ ํ์์ ๋ง๋ญ๋๋ค.
<Suspense>
์ปดํฌ๋ํธ๋ ์ด ๋ชจ๋ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
React ๋ด๋ถ์์ ์๋์ผ๋ก ์ด๋ฃจ์ด์ง๋ฉฐ ๋๋ถ๋ถ์ ๊ธฐ์กด React ์ฝ๋์ ํจ๊ป ์๋ํ ๊ฒ์ผ๋ก ๊ธฐ๋ํฉ๋๋ค. ์ด๊ฒ์ loading state๋ฅผ ์ ์ธ์ ์ผ๋ก ํํํ๋ ํ์ ๋ณด์ฌ์ค๋๋ค.
if (isloading)
์์ <Suspense>
๋ก ๋ฐ๊พธ๋ ๊ฒ์ด ํฐ ์ฐจ์ด๋ก ๋ณด์ด์ง ์์ง๋ง, ์ด๋ฌํ ๋ชจ๋ ๊ฐ์ ์ฌํญ์ ์ ๊ธํด์ ํ๋ ์ค์ํ ๋ณํ์
๋๋ค.
new client and server rendering APIs
React 18์์ client์ server์์ ๋ ๋๋งํ๊ธฐ ์ํ APIs๊ฐ ์๋กญ๊ฒ ๋์์ธ๋์์ต๋๋ค.
React DOM Client
import ... from 'react-dom/client';
react-dom/client
์์ ์ ๊ณต๋๋ ์๋ก์ด APIs๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
createRoot
:render
๋๋unmount
๋ฅผ ์ํ root๋ฅผ ๋ง๋๋ ์๋ก์ด method์ ๋๋ค.ReactDOM.render
๋์ ์createRoot
๋ฅผ ์ฌ์ฉํ์ธ์. React 18์ ์๋ก์ด ๊ธฐ๋ฅ๋ค์ ์ด๊ฒ ์์ด๋ ๋์ํ์ง ์์ต๋๋ค.hydrateRoot
: ์๋ฒ ๋ ๋๋ง ์ดํ๋ฆฌ์ผ์ด์ ์ hydrateํ๋ ์๋ก์ด ๋ฐฉ๋ฒ์ ๋๋ค.ReactDOM.hydrate
๋์ ์ ์๋ก์ด React DOM Server APIs์ ํจ๊ป ๋์ํhydrateRoot
๋ฅผ ์ฌ์ฉํ์ธ์. ๋ง์ฐฌ๊ฐ์ง๋ก React 18์ ์๋ก์ด ๊ธฐ๋ฅ๋ค์ ์ด๊ฒ ์์ด ๋์ํ์ง ์์ต๋๋ค.
createRoot
import { createRoot } from 'react-dom/client';
const container = document.getElementById('...');
const root = createRoot(container);
root.render(element);
createRoot(container[, options]);
root.umount();
createRoot()
๋ ์ ๋ฌ๋ container node์ content๋ฅผ ์ ์ดํฉ๋๋ค. ๋ด๋ถ์ ๊ธฐ์กด DOM element๋ render๊ฐ ํธ์ถ๋ ๋ ๊ต์ฒด๋ฉ๋๋ค. ์ดํ ํธ์ถ์ ํจ์จ์ ์ธ ์
๋ฐ์ดํธ๋ฅผ ์ํด React์ DOM diffing ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํฉ๋๋ค.
container node๋ ์์ ๋ ์ ์์ผ๋ฉฐ ์ค์ง container์ chilren๋ง ์์ ๊ฐ๋ฅํฉ๋๋ค. ๊ธฐ์กด children์ ๋ฎ์ด์ฐ์ง ์๊ณ ๊ธฐ์กด DOM node์ component์ ์ฝ์
ํ ์ ์์ต๋๋ค.
createRoot()
๋ ์๋ฒ์์ ๋ ๋๋ง๋ container๋ฅผ hydrateํ๋๋ฐ ์ฌ์ฉํ๋ ๊ฒ์ ์ง์๋์ง ์์ต๋๋ค. ๋์ hydrateRoot()
๋ฅผ ์ฌ์ฉํ์ธ์.
createRoot
๋ ๋ ๊ฐ์ง ์ต์
์ ์ ๊ณตํ๋๋ฐ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
onRecoverableError
: ๋ ๋๋ง ์์ ์ค๋ฅ๋ก ์ธํด React๊ฐ ์๋์ผ๋ก recoverํ ๋ ํธ์ถ๋๋ callback ์ ๋๋ค.identifierPrefix
:React.useId
hook์ ์ํด ์์ฑ๋ ids๋ฅผ ์ํด ์ฌ์ฉ๋๋ prefix์ ๋๋ค. ๊ฐ์ ํ์ด์ง์ ๋ค์์ root๋ฅผ ์ฌ์ฉํ ๋ conflict๋ฅผ ํผํ ๋ ์ ์ฉํฉ๋๋ค. ์๋ฒ์์ ์ฌ์ฉ๋ ๊ฒ๊ณผ ๋์ผํ prefix์ฌ์ผ ํฉ๋๋ค.
root์์๋ unmount
๋ฅผ ํธ์ถํ์ฌ ๋ง์ดํธ ํด์ ํ ์๋ ์์ต๋๋ค.
hydrateRoot
import { hydrateRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('...');
hydrateRoot(container, <App />);
hydrateRoot(element, container[, options])
hydrateRoot()
๋ createRoot()
์ ๊ฑฐ์ ๊ฐ์ต๋๋ค. ๊ทธ๋ฌ๋ ReactDOMServer
์์ ๋ ๋๋ง๋ container์ HTML contents๋ฅผ hydrateํ๋๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. React๋ ๊ธฐ์กด markup์ ์ด๋ฒคํธ ๋ฆฌ์คํฐ๋ฅผ ๋ถ์ด๋ ค๊ณ ์๋ํฉ๋๋ค.
createRoot
, hydrateRoot
๋ ๋ค onRecoverableError
๋ผ๊ณ ๋ถ๋ฆฌ๋ ์ต์
์ ๊ฐ์ง๋๋ค. ์ด ์ต์
์ React๊ฐ rendering์ด๋ hydration ํ๋ ๋์์ ๋ฐ์ํ ์ค๋ฅ๋ก๋ถํฐ recoverํ ๋ ๋ก๊น
์ ์ํด ์๋ฆผ์ ๋ฐ์ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก React๋ WEB APIs์์ ์ ๊ณตํ๋ reportError๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ค๋๋ ๋ธ๋ผ์ฐ์ ์์๋ console.error
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
reportError
createRoot
์ ๋ง์ฐฌ๊ฐ์ง๋ก hydrateRoot
๋ ๋ ๊ฐ์ง ์ต์
์ ๋์ผํ๊ฒ ์ ๊ณตํฉ๋๋ค.
onRecoverableError
: ๋ ๋๋ง ์์ ์ค๋ฅ๋ก ์ธํด React๊ฐ ์๋์ผ๋ก recoverํ ๋ ํธ์ถ๋๋ callback ์ ๋๋ค.identifierPrefix
:React.useId
hook์ ์ํด ์์ฑ๋ ids๋ฅผ ์ํด ์ฌ์ฉ๋๋ prefix์ ๋๋ค. ๊ฐ์ ํ์ด์ง์ ๋ค์์ root๋ฅผ ์ฌ์ฉํ ๋ conflict๋ฅผ ํผํ ๋ ์ ์ฉํฉ๋๋ค. ์๋ฒ์์ ์ฌ์ฉ๋ ๊ฒ๊ณผ ๋์ผํ prefix์ฌ์ผ ํฉ๋๋ค.
Note:
React๋ ๋ ๋๋ง๋ content๊ฐ ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ์ ๋์ผํ ๊ฒ์ผ๋ก ์์ํฉ๋๋ค. ํ ์คํธ ๋ด์ฉ์ ์ฐจ์ด์ ์ ํด๊ฒฐํ ์ ์์ง๋ง ๋ถ์ผ์น๋ฅผ ๋ฒ๊ทธ๋ก ์ฒ๋ฆฌํ๊ณ ์์ ํด์ผ ํฉ๋๋ค. ๊ฐ๋ฐ ๋ชจ๋์์ React๋ hydrate ์ค ๋ถ์ผ์น์ ๋ํด ๊ฒฝ๊ณ ํฉ๋๋ค. ์ผ์นํ์ง ์๋ ๊ฒฝ์ฐ attribute ์ฐจ์ด๊ฐ ํด๊ฒฐ๋๋ค๋ ๋ณด์ฅ์ด ์์ต๋๋ค. ์ด๋ ๋๋ถ๋ถ์ ์ฑ์์ ๋ถ์ผ์น๊ฐ ๋๋ฌผ๊ณ ๋ชจ๋ markup์ ๊ฒ์ฆํ๋๋ฐ ์์ฒญ๋ ๋น์ฉ์ด ๋ค๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ์์ ์ด์ ๋ก ์ค์ํฉ๋๋ค.
React DOM Server
react-dom/server
์์ ์ ๊ณต๋๋ ์๋ก์ด APIs๋ ์๋ฒ์์ streaming Suspense๋ฅผ ์์ ํ ์ง์ํฉ๋๋ค.
renderToPipeableStream
: Node์์ streaming์ ์ํด์.renderToReadableStream
: Deno์ Cloudflare workes์ฒ๋ผ modern edge runtim์ ์ํด์.
๊ธฐ์กด renderToString
method๋ ์ฌ์ ํ ๋์ํ์ง๋ง ๊ถ์ฅ๋์ง ์์ต๋๋ค.
๋ค์ method๋ server์ browserํ๊ฒฝ์์ ๋ ๋ค ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
renderToString()
: HTML string์ ๋ฐํํฉ๋๋ค.renderToStaticMarkup()
:renderToString
๊ณผ ํก์ฌํฉ๋๋ค๋ง React๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉํ๋data-reactroot
๊ฐ์ ์ถ๊ฐ DOM attribute๋ฅผ ๋ง๋ค์ง ์์ต๋๋ค.
๋ค์ method๋ stream
package์ ์์กดํ๊ณ ์์ด์ ์ค์ง ์๋ฒ์์๋ง ์ฌ์ฉํ ์ ์๊ณ browser์์๋ ๋์ํ์ง ์์ต๋๋ค.
renderToPipeableStream()
: React 18์์ ์ถ๊ฐ๋์์ผ๋ฉฐ ์๋ฒ์์ streaming์ ์ํด์ ์ฌ์ฉํฉ๋๋ค.renderToReadableStream()
:renderToReadableStream
๊ณผ ํก์ฌํ๋ Deno์ Cloudflare workes์ฒ๋ผ modern edge runtim์ ์ํด์ ์ฌ์ฉํฉ๋๋ค.renderToNodeStream()
(Deprecated)renderToStaticNodeStream()
renderToString
import ReactDOMServer from 'react-dom/server';
ReactDOMServer.renderToString(element);
React element๋ฅผ ์ด๊ธฐ HTML๋ก ๋ ๋๋งํฉ๋๋ค. React๋ HTML string์ ๋ฐํํฉ๋๋ค. ์ด ๋ฐฉ๋ฒ์ ์๋ฒ์์ HTML์ ์์ฑํ๊ณ , ์ต์ด ์์ฒญ์์ ๋ ๋น ๋ฅธ ํ์ด์ง ๋ก๋๋ฅผ makrup์ ๋ด๋ ค๋ณด๋ด๊ณ , SEO ๋ชฉ์ ์ผ๋ก ํ์ด์ง๋ฅผ ํฌ๋กค๋ง๋๋๋ก ํ๋๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋ง์ฝ ์ด๋ฏธ ์๋ฒ์์ ๋ ๋๋งํ markup์ ๊ฐ์ง node์์ ReactDOM.hydrateRoot()
๋ฅผ ํธ์ถํ๋ค๋ฉด, React๋ ์ด๋ฅผ ๋ณด์กดํ๊ณ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ง ์ฐ๊ฒฐํ๋ฏ๋ก ๋งค์ฐ ์ฑ๋ฅ์ด ์ข์ first-load ๊ฒฝํ์ ํ ์ ์์ต๋๋ค.
renderToStaticMarkup
import ReactDOMServer from 'react-dom/server';
ReactDOMServer.renderToStaticMarkup(element);
renderToString
๊ณผ ํก์ฌํ๋ React๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉํ๋ data-reactroot
๊ฐ์ ์ถ๊ฐ DOM attribute๋ฅผ ๋ง๋ค์ง ์์ต๋๋ค. ์ถ๊ฐ attribute๋ฅผ ์ ๊ฑฐํ๋ฉด ์ผ๋ถ ๋ฐ์ดํธ๋ฅผ ์ ์ฝํ ์ ์์ด React๋ฅผ ๊ฐ๋จํ ์ ์ ํ์ด์ง ์์ฑ๊ธฐ๋ก ์ฌ์ฉํ ๋ ์ ์ฉํฉ๋๋ค.
์ํธ์์ฉ๊ฐ๋ฅํ markup์ ๋ง๋ค ๋๋ ์ฌ์ฉํ์ง ๋ง์ธ์. ๋์ ์ ์๋ฒ์์ renderToString
๋ฅผ ์ฌ์ฉํ๊ณ ํด๋ผ์ด์ธํธ์์ ReactDOM.hydrateRoot()
๋ฅผ ์ฌ์ฉํ์ธ์
renderToPipeableStream
import ReactDOMServer from 'react-dom/server';
ReactDOMServer.renderToPipeableStream(element, options);
type Controls = {
// Cancel any pending I/O and put anything remaining into
// client rendered mode.
abort(): void,
pipe<T: Writable>(destination: T): T,
};
type Options = {
identifierPrefix?: string,
namespaceURI?: string,
nonce?: string,
bootstrapScriptContent?: string,
bootstrapScripts?: Array<string>,
bootstrapModules?: Array<string>,
progressiveChunkSize?: number,
onShellReady?: () => void,
onShellError?: () => void,
onAllReady?: () => void,
onError?: (error: mixed) => void,
};
function renderToPipeableStream(
children: ReactNodeList,
options?: Options,
): Controls {
// ...
}
const { pipe, abort } = renderToPipeableStream(<App />, {
onAllReady() {
res.statusCode = 200;
res.setHeader('Content-type', 'text/html');
pipe(res);
},
onShellError(x) {
res.statusCode = 500;
res.send(
'<!doctype html><p>Loading...</p><script src="clientrender.js"></script>'
);
},
});
React element๋ฅผ ์ด๊ธฐ HTML๋ก ๋ ๋๋งํฉ๋๋ค. output์ pipeํ๊ฑฐ๋ abortํ ์ ์๋ Control object๋ฅผ ๋ฐํํฉ๋๋ค. ์ดํ์ JavaScript ์คํ์ ํตํด์ ๋ค์ด๊ฐ "์ง์ฐ๋" content block์ด ๋์ค์ JavaScript ์คํ์ ํตํด "Popping in"๋๋ Suspense์ HTML streaming์ ์๋ฒฝํ๊ฒ ์ง์ํฉ๋๋ค.
Note:
Node.js์ ํนํ๋ API์ ๋๋ค. modern server(edge runtime)์์๋renderToReadableStream
์ ์ฌ์ฉํ์ธ์.
renderToReadableStream
ReactDOMServer.renderToReadableStream(element, options);
let controller = new AbortController();
try {
let stream = await renderToReadableStream(
<html>
<body>Success</body>
</html>,
{
signal: controller.signal,
}
);
// This is to wait for all suspense boundaries to be ready. You can uncomment
// this line if you don't want to stream to the client
// await stream.allReady;
return new Response(stream, {
headers: { 'Content-Type': 'text/html' },
});
} catch (error) {
return new Response(
'<!doctype html><p>Loading...</p><script src="clientrender.js"></script>',
{
status: 500,
headers: { 'Content-Type': 'text/html' },
}
);
}
React element๋ฅผ ์ด๊ธฐ HTML๋ก ๋ ๋๋งํฉ๋๋ค. Readable Stream์ ๋ฐํํฉ๋๋ค. Suspense์ HTML streaming์ ์๋ฒฝํ๊ฒ ์ง์ํฉ๋๋ค.
renderToNodeStream(depreacated)
React 18์ด ๋๋ฉด์ depcreated ๋์๊ณ , renderToPipeableStream
๋ก ์
๊ทธ๋ ์ด๋ํ์ฌ ์ฌ์ฉํ๋ผ๊ณ ๊ถ์ฅํฉ๋๋ค.
renderToStaticNodeStream
ReactDOMServer.renderToStaticNodeStream(element);
renderToPipeableStream
์ด๋ renderToReadableStream
๊ณผ ๋น์ทํ์ง๋ง, renderToStaticMarkup
์ฒ๋ผ React๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉํ๋ data-reactroot
๊ฐ์ ์ถ๊ฐ DOM attribute๋ฅผ ๋ง๋ค์ง ์์ต๋๋ค.
์ด stream์ ์ํ HTML output์ renderToStaticMarkup
๊ฒฐ๊ณผ์ ์์ ํ๊ฒ ๋์ผํฉ๋๋ค.
new hooks
useId
: hydrate ๋ถ์ผ์น๋ฅผ ํผํ๋ฉด์ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋์์ unique ID๋ฅผ ์์ฑํ๊ธฐ ์ํ hook ์ ๋๋ค.useTransition
: ์ผ๋ถ ์ํ ์ ๋ฐ์ดํธ๋ฅผ non-urgentํ ๊ฒ์ผ๋ก ๋ค๋ฃจ๊ธฐ ์ํ hook ์ ๋๋ค.useDeferredValue
: non-urgentํ ๋ถ๋ถ์ re-renderingํ๋ ๊ฒ์ ์ง์ฐํ ๋ ์ฌ์ฉํ๋ hook ์ ๋๋ค.useSyncExternalStroe
: store ์ํ๋ฅผ ๋๊ธฐ์์ผ๋ก ์ ๋ฐ์ดํธ ํ๋๋ก ๊ฐ์ ํ์ฌ ํด๋น store(redux store๊ฐ์)๊ฐ concurrent mode์์๋ ์ ๋์ํ ์ ์๋๋ก ํ๋ hook์ ๋๋ค.useInsertionEffect
: CSS-in-JS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ ๋๋งํ ๋ ์คํ์ผ์ ์ฝ์ ํ๋ ๊ฒ์์์ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋๋ก ํ๋ hook์ ๋๋ค.
useId
hydrate ๋ถ์ผ์น๋ฅผ ํผํ๋ฉด์ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋์์ unique ID๋ฅผ ์์ฑํ๊ธฐ ์ํ hook ์ ๋๋ค.
unique ID๊ฐ ํ์ํ accessibility API๋ฅผ ์ฌ์ฉํ๋ component ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ฃผ๋ก ์ ์ฉํฉ๋๋ค.
React 17์ดํ์์ ๊ธฐ์กด์ ์กด์ฌํ๋ ์ด์๋ค์ ํด๊ฒฐํฉ๋๋ค. ๊ทธ๋ฌ๋ ์๋ก์ด streaming ์๋ฒ renderer๊ฐ HTML์ ์์ ์์ด ์ ๋ฌํ๋ ๋ฐฉ์ ๋๋ฌธ์ React 18์์ ํจ์ฌ ๋ ์ค์ํฉ๋๋ค.
function Checkbox() {
const id = useId();
return (
<>
<label htmlFor={id}>Do you like React?</label>
<input id={id} type="checkbox" name="react" />
</>
);
}
function NameFields() {
const id = useId();
return (
<div>
<label htmlFor={id + '-firstName'}>First Name</label>
<div>
<input id={id + '-firstName'} type="text" />
</div>
<label htmlFor={id + '-lastName'}>Last Name</label>
<div>
<input id={id + '-lastName'} type="text" />
</div>
</div>
);
}
Note:
useId
๋:
ํ ํฐ์ ํฌํจํ๋ ๋ฌธ์์ด์ ์์ฑํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด token์ด ๊ณ ์ ํ์ง ํ์ธํ๋๋ฐ ๋์์ด ๋์ง๋ง CSS selector๋ querySelectorAll๊ณผ ๊ฐ์ API์์๋ ์ง์๋์ง ์์ต๋๋ค.
useId
๋ multi-root ์ฑ์์ ์ถฉ๋์ ํผํ๊ธฐ ์ํดidentifierPrefix
๋ฅผ ์ง์ํฉ๋๋ค.hydrateRoot
์ต์ ์ ํ์ธํ์ธ์.
useTransition
useTransition
๊ณผ startTransition
์ non-urgentํ ์ํ ์
๋ฐ์ดํธ๋ฅผ mark ํ ์ ์๋๋ก ํด์ค๋๋ค.
์์ธํ ๋ด์ฉ์ ์์ ๋ด์ฉ์ ์ฐธ๊ณ ํ์ธ์.
useDeferredValue
useDeferredValue
๋ ๋ ๊ธด๊ธํ ์
๋ฐ์ดํธ๋ฅผ ์ํด ์ง์ฐ๋ ๊ฐ์ ๋ณต์ฌ๋ณธ์ ๋ฐํํฉ๋๋ค.
ํ์ฌ ๋ ๋๋ง์ด ์ฌ์ฉ์ input๊ณผ ๊ฐ์ ๊ธด๊ธํ(urgent) ์
๋ฐ์ดํธ์ธ ๊ฒฝ์ฐ React๋ ์ด์ ๊ฐ์ ๋ฐํํ ๋ค์ ๊ธด๊ธํ ๋ ๋๋ง์ด ์๋ฃ๋ ํ ์๋ก์ด ๊ฐ์ ๋ ๋๋งํฉ๋๋ค.
์ง์ฐ ์
๋ฐ์ดํธ๋ฅผ ์ํด debouncing์ด๋ throttling๋ฅผ ์ฌ์ฉํ๋ ์ฌ์ฉ์ hook๊ณผ ๋น์ทํฉ๋๋ค.
React์์ useDeferredValue
๋ฅผ ์ฌ์ฉํ๋ ์ด์ ์ ๋ค๋ฅธ ์์
์ด ์๋ฃ๋์๋ง์(์ผ์ ํ ์๊ฐ์ ๊ธฐ๋ค๋ ค์ผํ๋ ๊ฒ๊ณผ ๋ฌ๋ฆฌ) ์
๋ฐ์ดํธ๊ฐ ๋ฐ๋ก ๋์ํ ๊ฒ์ด๋ผ๋ ์ ์
๋๋ค. ๊ทธ๋ฆฌ๊ณ startTransition
๊ณผ ๊ฐ์ด, ๊ธฐ์กด content๋ฅผ ์ํด ์๊ธฐ์น ์์ fallback์ ํธ๋ฆฌ๊ฑฐํ์ง ์๊ณ deferred ๊ฐ์ suspenseํ ์ ์์ต๋๋ค.
Memoizing deferred children
useDeferredValue
์์ ์ ๋ฌํ ๊ฐ๋ง ์ง์ฐํ๊ธฐ ๋๋ฌธ์ urgent update ์ค์ children์ด re-rendering๋๋ ๊ฒ์ ๋ฐฉ์งํ๋ ค๋ฉด ํด๋น ๋ถ๋ถ์ React.memo
๋๋ React.useMemo
๋ก memoizing ํด์ผ ํฉ๋๋ค.
function Typeahead() {
const query = useSearchQuery('');
const deferredQuery = useDeferredValue(query);
// memoizing์ React์๊ฒ query๊ฐ ๋ฐ๋ ๋๊ฐ ์๋
// dependency๋ก ๋ฑ๋ก๋ deferredQuery๊ฐ ๋ฐ๋ ๋ ๋ ๋๋งํ๋๋ก ์๋ ค์ค๋๋ค.
const suggestions = useMemo(
() => <SearchSuggestions query={deferredQuery} />,
[deferredQuery]
);
return (
<>
<SearchInput query={query} />
<Suspense fallback="Loading results...">{suggestions}</Suspense>
</>
);
}
useSyncExternalStore
store ์ํ๋ฅผ ๋๊ธฐ์์ผ๋ก ์ ๋ฐ์ดํธ ํ๋๋ก ๊ฐ์ ํ์ฌ ํด๋น store(redux store๊ฐ์)๊ฐ concurrent mode์์๋ ์ ๋์ํ ์ ์๋๋ก ํ๋ hook์ ๋๋ค. ์ด๊ฒ์ ์ธ๋ถ data source์ ๊ตฌ๋ ์ ๊ตฌํํ ๋ useEffect๊ฐ ํ์ํ์ง ์๊ณ React ์ธ๋ถ์ ์ํ์ ํตํฉ๋๋ ๋ชจ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ถ์ฅ๋ฉ๋๋ค.
Note:
useSyncExternalStore
๋ ์ฑ ์ฝ๋๊ฐ ์๋๋ผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ฌ์ฉํ๊ธฐ ์ํ ๊ฒ์ ๋๋ค.
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);
// ์ ์ฒด ์คํ ์ด ๊ตฌ๋
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
// ํน์ ํ๋ ๊ตฌ๋
const selectedField = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().selectedField,
);
// ์๋ฒ ๋ ๋๋ง์์ ์๋ฒ์์ ์ฌ์ฉ๋๋ store ๊ฐ์ serialize ๊ฐ ํ์ํ๊ณ ๊ทธ๊ฒ์ ์ ๊ณต ํด์ผ ํฉ๋๋ค.
// hydration ๋์ server ๋ถ์ผ์น๋ฅผ ๋ง๊ธฐ ์ํด์ ์ฌ์ฉ๋ฉ๋๋ค.
const selectedField = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().selectedField,
() => INITIAL_SERVER_SNAPSHOT.selectedField,
);
- subscribe: ์คํ ์ด๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ํธ์ถ๋๋ ์ฝ๋ฐฑ์ ๋ฑ๋กํ๋ ํจ์
- getSnapshot: ์คํ ์ด์ ํ์ฌ ๊ฐ์ ๋ฐํํ๋ ํจ์
- getServerSnapshot: ์๋ฒ ๋ ๋๋ง ์ค์ ์ฌ์ฉ๋ snapshot์ ๋ฐํํ๋ ํจ์
useInsertionEffect
CSS-in-JS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ ๋๋งํ ๋ ์คํ์ผ์ ์ฝ์
ํ๋ ๊ฒ์์์ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋๋ก ํ๋ hook์
๋๋ค. CSS-in-JS๋ฅผ ์ด๋ฏธ ๋ง๋ค์ง ์์๋ค๋ฉด ์ด๊ฒ์ ์ฌ์ฉํ์ง ์์ ๊ฒ์
๋๋ค. ์ด hook์ DOM์ด ๋ณ๊ฒฝ๋ ์ดํ์ ์คํ๋์ง๋ง layout effect๊ฐ ์๋ก์ด layout์ ์ฝ๊ธฐ ์ ์ ์คํ๋ฉ๋๋ค.
์ด๊ฒ์ React 17์ดํ์ ์ด๋ฏธ ์กด์ฌํ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค. ๊ทธ๋ฌ๋ React๊ฐ concurrent ๋ ๋๋ง ๋์์ ๋ธ๋ผ์ฐ์ ์๊ฒ layout์ ์ฌ๊ณ์ฐํ ์ ์๋๋ก ํ๊ธฐ ๋๋ฌธ์ React 18์์ ๋ ์ค์ํฉ๋๋ค.
Note:
useSyncExternalStore
๋ ์ฑ ์ฝ๋๊ฐ ์๋๋ผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ฌ์ฉํ๊ธฐ ์ํ ๊ฒ์ ๋๋ค.
useInsertionEffect(didUpdate);
signature๋ useEffect
์ ๋์ผํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ชจ๋ DOM ๋ณ๊ฒฝ ์ ์ ๋๊ธฐ์ ์ผ๋ก ์คํ๋ฉ๋๋ค. useLayoutEffect
์์ layout์ ์ฝ๊ธฐ ์ ์ DOM์์ ์คํ์ผ์ ์ฝ์
ํ๊ธฐ ์ํด ์ด๊ฒ์ ์ฌ์ฉํ์ธ์. ์ด hook์ ๋ฒ์๊ฐ ์ ํ๋์ด ์์ด์ ref์ ์์ธ์คํ ์ ์์ผ๋ฉฐ ์
๋ฐ์ดํธ๋ฅผ ์์ฝ ํ ์๋ ์์ต๋๋ค.
Note:
useInsertionEffect
๋ css-in-js ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ฑ์๋ง ์ฌ์ฉํด์ผ ํฉ๋๋ค.useEffect
๋useLayoutEffect
๋์ ์ ๋ ์ ํธ๋ฉ๋๋ค.
์ฒดํ์ฉ ํ๋ก์ ํธ
React 18 ์๋ก์ด ๊ธฐ๋ฅ๋ค ์ค ๋ช ๊ฐ์ง๋ฅผ ์ง์ ๊ตฌํํด๋ณด๊ณ ์ถ์ด์ ๋ฒ๋ค๋ง ์ค์ ๋ถํฐ ํ๋ํ๋ ์ฐพ์๊ฐ๋ฉด์ ํด๋ณธ ํ๋ก์ ํธ ์ ๋๋ค.
๊ธ๋ก ์์ฑ๋ ๋ด์ฉ ์ค suspense๊น์ง ์ฌ์ฉ์ ํด๋ณด์์ต๋๋ค.
CSR, SSR ๋ก ๋๋ค ์์ฑํด๋ณด์๊ณ ์์ค๋ ๋ชจ๋ ๊ณต๊ฐ๋์ด์์ต๋๋ค.
๋ง๋ฌด๋ฆฌํ๋ฉฐ
React 18 ์ฒซ ๋ฑ์ฅ์ด ์๋ ์ด์๋ ๊ฒ์ผ๋ก ๊ธฐ์ตํ๋๋ฐ ์ ๋ฒ์ฃผ์ ๋๋์ด ๊ณต์ ๋ฆด๋ฆฌ์ฆ๊ฐ ๋์์ต๋๋ค.
๊ทธ ๋์ ๋ฏธ๋ฃจ๊ณ ๋ฏธ๋ฃจ์๋ React 18 ๋ด์ฉ์ ์ด๋ฒ ๊ธฐํ์ ์ ๋๋ก ๋ณด๋ ๊ณ๊ธฐ๊ฐ ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์๋ก์ด ๊ธฐ๋ฅ๋ค์ ์ตํ๊ณ ๋ฐฐ์ฐ๋ฉด์ CSR, SSR ๊ฐ๋ ์ ์ข ๋ ๋ช ํํ๊ฒ ์์๊ฐ ๊ฒ ๊ฐ์์ ๋ ์ข์์ต๋๋ค.
์ฐ์ต์ฉ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค ๋ CSR ์์ฑ์ ๋ณ๋ก ์ด๋ ต์ง ์์๋๋ฐ SSR์ ์ง์ ๋ง๋ค๋ ค๊ณ ํ๋ ์ ๋ถ์กฑํจ์ด ๋ญ์ง ํ์คํ ๋๊ปด์ง ๊ฒ ๊ฐ์์ต๋๋ค.(์ด๋ ๊ฒ ๊ฐ๋จํ ์์ค์ธ๋ฐ๋ ๋ถ๊ตฌํ๊ณ ... ๊ทธ๋ฆฌ๊ณ ์ฌ์ค ์ ๋๋ก ์์ฑ๋ ๊ฑด์ง๋ ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค...)
React Server Component๊ฐ 18.0.0์๋ ํฌํจ๋์ง ์๊ณ experimental๋ก ๊ณต๊ฐ๋์๋๋ฐ ์ถํ ๊ณง ํฌํจ๋์ง ์์๊น ์ถ์ต๋๋ค.
์ด์ ๋ง ์์ํ ๊ฒ ๊ฐ์ ๋๋์ด๋ผ์ ๋ ๋ง์ด ์จ๋ณด๋ฉด์ ์ตํ๋๋ก ๋ ธ๋ ฅํด๋ด์ผ๊ฒ ์ต๋๋ค.