๐ React 18 ๋ณ๊ฒฝ์
๐ useId
โ
useId
๋ ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ์ hydration์ mismatch๋ฅผ ํผํ๋ฉด์ ์ ๋ํฌํ ์์ด๋๋ฅผ ์์ฑํ ์ ์๋ ์๋ก์ด hook์
๋๋ค. ์ด๋ ์ฃผ๋ก ๊ณ ์ ํ id
๊ฐ ํ์ํ ์ ๊ทผ์ฑ API์ ์ฌ์ฉ๋๋ ์ปดํฌ๋ํธ์ ์ ์ฉํ ๊ฒ์ผ๋ก ๊ธฐ๋๋ฉ๋๋ค.
์์ด๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํธ๋ฆฌ ๋ด๋ถ์ ๋ ธ๋์ ์์น๋ฅผ ๋ํ๋ด๋ base 32 ๋ฌธ์์ด์ ๋๋ค. ํธ๋ฆฌ๊ฐ ์ฌ๋ฌ children์ผ๋ก ๋ถ๊ธฐ๋ ๋๋ง๋ค, ํ์ฌ ๋ ๋ฒจ์์ ์์ ์์ค์ ๋ํ๋ด๋ ๋นํธ๋ฅผ ์ํธ์ค ์ผ์ชฝ์ ์ถ๊ฐํ๊ฒ ๋ฉ๋๋ค,.
useId
๋ ๋ชฉ๋ก์์ ํค๋ฅผ ์์ฑํ๊ธฐ ์ํ ๊ฒ์ด ์๋๋๋ค. ํค๋ ๋ฐ์ดํฐ์์ ์์ฑ๋์ด์ผ ํฉ๋๋ค.
๐ useTransition
โ
์ด ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ์ผ๋ถ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ๊ธด๊ธํ์ง ์์ ๊ฒ(not urgent)๋ก ํ์ํ ์ ์์ต๋๋ค. ์ด๊ฒ์ผ๋ก ํ์๋์ง ์์ ์ํ ์ ๋ฐ์ดํธ๋ ๊ธด๊ธํ ๊ฒ์ผ๋ก ๊ฐ์ฃผ๋ฉ๋๋ค. ๊ธด๊ธํ ์ํ ์ ๋ฐ์ดํธ๊ฐ ๊ธด๊ธํ์ง ์์ ์ํ ์ ๋ฐ์ดํธ์ ์ค๋จํ ์ ์์ต๋๋ค.
์ํ ์ ๋ฐ์ดํธ๋ฅผ ๊ธด๊ธํ ๊ฒ๊ณผ ๊ธด๊ธํ์ง ์์ ๊ฒ์ผ๋ก ๋๋์ด ๊ฐ๋ฐ์์๊ฒ ๋ ๋๋ง ์ฑ๋ฅ์ ํ๋ํ๋๋ฐ ๋ง์ ์์ ๋ฅผ ์ฃผ์๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค.
function App() {
const [resource, setResource] = useState(initialResource)
const [isPending, startTransition] = useTransition({ timeoutMs: 3000 })
return (
<>
<button
disabled={isPending}
onClick={() => {
startTransition(() => {
const nextUserId = getNextId(resource.userId)
setResource(fetchProfileData(nextUserId))
})
}}
>
Next
</button>
{isPending ? 'Loading...' : null}
<ProfilePage resource={resource} />
</>
)
}
startTransition
๋ ํจ์๋ก, ๋ฆฌ์กํธ์ ์ด๋ค ์ํ๋ณํ๋ฅผ ์ง์ฐ์ํค๊ณ ์ถ์์ง ์ง์ ํ ์ ์์ต๋๋ค.isPending
์ ์งํ ์ฌ๋ถ๋ก, ํธ๋์ง์ ์ด ์งํ์ค์ธ์ง ์ ์ ์์ต๋๋ค.timeoutMs
ํ๋กํผํฐ๋ ํธ๋์ง์ ์ด ์๋ฃ๋ ๋๊น์ง ์ผ๋ง๋ ์ค๋ซ๋์ ๊ธฐ๋ค๋ฆด ๊ฒ์ธ์ง ๊ฒฐ์ ํฉ๋๋ค.{timeoutMs: 3000}
๋ฅผ ์ ๋ฌํ๋ค๋ฉด โ๋ค์ ํ๋กํ์ ๋ถ๋ฌ์ค๋ ๋ฐ 3์ด๋ณด๋ค ์ค๋ ๊ฑธ๋ฆฐ๋ค๋ฉด ๋ก๋ฉ ์ํ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ๊ทธ์ ๊น์ง ๊ณ์ ์ด์ ํ๋ฉด์ ๋ณด์ฌ์ค๋ ๊ด์ฐฎ์โ๋ผ๋ ์๋ฏธ์ ๋๋ค.
useTransition
๊ฐ์ API๋ฅผ ์ฌ์ฉํ๋ฉด ์ํ๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ด์ ์ ๋ง์ถ ์ ์๊ณ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์๊ฐํ์ง ์์๋ ๋ฉ๋๋ค.
const initialResource = fetchUserAndPosts();
function ProfilePage() {
const [resource, setResource] = useState(initialResource);
function handleRefreshClick() {
setResource(fetchUserAndPosts());
}
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails resource={resource} />
<button onClick={handleRefreshClick}>
Refresh
</button>
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline resource={resource} />
</Suspense>
</Suspense>
);
}
์ด ์์์์ ํ์ด์ง๊ฐ ๋ก๋๋๊ฑฐ๋ โRefreshโ ๋ฒํผ์ ๋๋ฅผ ๋ ๋ง๋ค ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
fetchUserAndPosts()
์ ๋ฐํ๊ฐ์ ์ํ์ ์ ์ฅํ์ฌ ํ์ ์ปดํฌ๋ํธ๋ค์ด ์์ฒญ์์ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ์ ์๊ฒ ํ๊ฒ ์ต๋๋ค.
<ProfileDetails>
๋ฐ <ProfileTimeline>
์ปดํฌ๋ํธ๋ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋ํ๋ด๋ ์๋ก์ด ๋ฆฌ์์ค prop์ ์์ ํ๊ณ ์์ง ์๋ต์ด ์๊ธฐ ๋๋ฌธ์ "suspend"๋๊ณ fallback์ด ํ์๋ฉ๋๋ค.
ํ์ง๋ง ์ ๊ฒฝํ์ ์์ฐ์ค๋ฝ์ง ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ํ ํ์ด์ง๋ฅผ ๋ธ๋ผ์ฐ์งํ๊ณ ์์๋๋ฐ ๋ฒํผ์ ํด๋ฆญํ ์งํ์ ๋ฐ๋ก ๋ก๋ฉ ์ํ๋ก ์ ํ๋์ด ์ฌ์ฉ์๋ฅผ ํผ๋์ค๋ฝ๊ฒ ํฉ๋๋ค. ์ด์ ์ฒ๋ผ, ์๋์น ์์ ๋ก๋ฉ ์ํ๋ฅผ ์จ๊ธฐ๊ธฐ ์ํด์ ์ํ ๊ฐฑ์ ์ ํธ๋์ง์
์ ๋ํํ ์ ์์ต๋๋ค.
function ProfilePage() {
const [isPending, startTransition] = useTransition({
// Wait 10 seconds before fallback
timeoutMs: 10000
});
const [resource, setResource] = useState(initialResource);
function handleRefreshClick() {
startTransition(() => {
setResource(fetchProfileData());
});
}
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails resource={resource} />
<button
onClick={handleRefreshClick}
disabled={isPending}
>
{isPending ? "Refreshing..." : "Refresh"}
</button>
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline resource={resource} />
</Suspense>
</Suspense>
);
}
โRefreshโ ๋ฒํผ์ ํด๋ฆญํด๋ ์ฐ๋ฆฌ๊ฐ ๋ธ๋ผ์ฐ์งํ๊ณ ์๋ ํ์ด์ง๊ฐ ์ฌ๋ผ์ง์ง ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ธ๋ผ์ธ์ผ๋ก ๋ญ๊ฐ ๋ก๋ฉ๋๊ณ ์๋ค๋ ๊ฒ์ ๋ณด๊ณ ๋ฐ์ดํฐ๊ฐ ์ค๋น๋ ์ดํ์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ๋ณด์ ๋๋ค.
์ด์ useTransition
์ ํ์์ฑ์ด ๋งค์ฐ ์ผ๋ฐ์ ์ด๋ผ๋ ๊ฑธ ์ ์ ์์ต๋๋ค. ์ฌ์ฉ ์๊ฐ ์ํธ์์ฉํ๋ ๋์์ ์ค์๋ก ์จ๊ธฐ์ง ์๋๋ก ์ปดํฌ๋ํธ๋ฅผ ์์คํ๋ ์ํ๋ก ๋ง๋ค ์ ์๋ ๋๋ถ๋ถ ๋ฒํผ ํด๋ฆญ์ด๋ ์ํธ์์ฉ์ useTransition
์ผ๋ก ๋ํํด์ผ ํฉ๋๋ค.
์ ์์
์ ์ปดํฌ๋ํธ ์ฌ์ด์ ๋ง์ ๋ฐ๋ณต์ ์ธ ์ฝ๋ ์์ฐ์ผ๋ก ์ด์ด์ง ์ ์์ต๋๋ค. ์ด๊ฒ์ด ์ผ๋ฐ์ ์ผ๋ก ๋์์ธ ์์คํ
์ useTransition
์ฌ์ฉํ๋ ๊ฒ์ ์ถ์ฒํ๋ ์ด์ ์
๋๋ค. ์๋ฅผ ๋ค์ด ํธ๋์ง์
๋ก์ง์ ์ปค์คํ
<Button>
์ปดํฌ๋ํธ๋ก ์ถ์ถํ ์ ์์ต๋๋ค.
function Button({ children, onClick }) {
const [isPending, startTransition] = useTransition({
timeoutMs: 10000
});
function handleClick() {
startTransition(() => {
onClick();
});
}
const spinner = (
<span className="DelayedSpinner">
{/* ... */}
</span>
);
return (
<>
<button
onClick={handleClick}
disabled={isPending}
>
{children}
</button>
{isPending ? spinner : null}
</>
);
}
๋ช
์ฌํ์ธ์. ๋ฒํผ์ ์ด๋ค ์ํ๋ฅผ ๊ฐฑ์ ํ๋์ง ๊ด์ฌํ์ง ์์ต๋๋ค. ์ด๊ฒ์ onClick
์ด๋ฒคํธ ํธ๋ค๋ฌ์์ ๋ฐ์ํ๋ ๋ชจ๋ ์ํ ๊ฐฑ์ ์ transition
์ ํฌํจํฉ๋๋ค. ์ด์ <Button>
์ด ํธ๋์ง์
์ค์ ์ ๋์ ํด ์ฃผ๊ธฐ ๋๋ฌธ์ <ProfilePage>
์ปดํฌ๋ํธ์ ํธ๋์ง์
์ค์ ์ ํด์ค ํ์๊ฐ ์์ต๋๋ค.
function ProfilePage() {
const [resource, setResource] = useState(initialResource);
function handleRefreshClick() {
setResource(fetchProfileData());
}
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails resource={resource} />
<Button onClick={handleRefreshClick}>
Refresh
</Button>
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline resource={resource} />
</Suspense>
</Suspense>
);
}
๋ฒํผ์ ํด๋ฆญํ๋ฉด ํธ๋์ง์
์ด ์์๋๊ณ ๊ทธ ์์ props.onClick()
์ด ํธ์ถ๋์ <ProfilePage>
์ปดํฌ๋ํธ์์ handleRefreshClick
ํจ์๊ฐ ์คํ๋ฉ๋๋ค. ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์์ํ์ง๋ง ํธ๋์ง์
๋ด๋ถ๋ผ์ ํด๋ฐฑ์ด ๋ณด์ฌ์ง์ง ์์ผ๋ฉฐ useTransition
ํธ์ถ์ ์ง์ ๋ 10์ด๊ฐ ์ง๋์ง ์์์ต๋๋ค. ํธ๋์ง์
์ด ๋ณด๋ฅ์ค์ธ ๋์ ๋ฒํผ์ ์ธ๋ผ์ธ์ผ๋ก ๋ก๋ฉ ์ธ๋์ผ์ดํฐ๋ฅผ ๋ด
๋๋ค.
์ด์ ์ปจ์ปค๋ฐํธ ๋ชจ๋๊ฐ ์ปดํฌ๋ํธ์ ๊ฒฉ๋ฆฌ ์์ค ๋ฐ ๋ชจ๋์ฑ์ ํฌ์ํ์ง ์๊ณ ๋ ์ฐ์ํ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ง๋๋์ง ๋ฐฐ์ ์ต๋๋ค. React๋ ํธ๋์ง์ ์ ์กฐ์ ํฉ๋๋ค.
๐ useDeferredValue
โ
useDeferredValue
๋ฅผ ์ฌ์ฉํ๋ฉด, ํธ๋ฆฌ์์ ๊ธํ์ง ์์ ๋ถ๋ถ์ ์ฌ๋๋๋ง์ ์ง์ฐํ ์ ์์ต๋๋ค. ์ด๋ debounce
์ ๋น์ทํ์ง๋ง, ๋ช๊ฐ์ง ๋ ์ฅ์ ์ด ์์ต๋๋ค. ๊ณ ์ ๋ ์ง์ฐ์๊ฐ์ด ์์ผ๋ฏ๋ก, ๋ฆฌ์กํธ๋ ์ฒซ๋ฒ์งธ ๋ ๋๋ง์ด ๋ฐ์๋๋ ์ฆ์ ์ง์ฐ ๋ ๋๋ง์ ์๋ํฉ๋๋ค. ์ด ์ง์ฐ๋ ๋ ๋๋ง์ ์ธํฐ๋ฝํธ๊ฐ ๊ฐ๋ฅํ๋ฉฐ, ์ฌ์ฉ์ ์
๋ ฅ์ ์ฐจ๋จํ์ง ์์ต๋๋ค.
const deferredValue = useDeferredValue(value);
useDeferredValue
๊ฐ์ ์๋ฝํ๊ณ ๋ ๊ธด๊ธํ ์
๋ฐ์ดํธ๋ฅผ ์ฐ๊ธฐํ ๊ฐ์ ์ ๋ณต์ฌ๋ณธ์ ๋ฐํํฉ๋๋ค. ํ์ฌ ๋ ๋๋ง์ด ์ฌ์ฉ์ ์
๋ ฅ๊ณผ ๊ฐ์ ๊ธด๊ธ ์
๋ฐ์ดํธ์ ๊ฒฐ๊ณผ์ธ ๊ฒฝ์ฐ React๋ ์ด์ ๊ฐ์ ๋ฐํํ ๋ค์ ๊ธด๊ธ ๋ ๋๋ง์ด ์๋ฃ๋ ํ ์ ๊ฐ์ ๋ ๋๋งํฉ๋๋ค.
์ด hook์ ๋๋ฐ์ด์ฑ ๋๋ throttling์ ์ฌ์ฉํ์ฌ ์ ๋ฐ์ดํธ๋ฅผ ์ฐ๊ธฐํ๋ user-space hooks์ ์ ์ฌํฉ๋๋ค.
useDeferredValue
์ฌ์ฉ์ ์ด์ ์ React๊ฐ ๋ค๋ฅธ ์์
์ด ์๋ฃ๋๋ ์ฆ์ ์
๋ฐ์ดํธ ์์
์ ์ํํ๊ณ (์์์ ์๊ฐ์ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ) startTransition
๊ณผ ๊ฐ์ด ์ง์ฐ๋ ๊ฐ์ด ๊ธฐ์กด ์ฝํ
์ธ ์ ๋ํ ์๊ธฐ์น ์์ ๋์ฒด๋ฅผ ํธ๋ฆฌ๊ฑฐํ์ง ์๊ณ ์ผ์ ์ค๋จ๋ ์ ์๋ค๋ ๊ฒ์
๋๋ค.
Memoizing deferred childrenโ
useDeferredValue
๋ ์ ๋ฌํ ๊ฐ๋ง ์ฐ๊ธฐํฉ๋๋ค. ๊ธด๊ธ(urgent) ์
๋ฐ์ดํธ ์ค์ ์์ ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ ๋๋ง๋๋ ๊ฒ์ ๋ฐฉ์งํ ๋ ค๋ฉด ํด๋น ์ปดํฌ๋ํธ๋ React.memo
๋๋ React.useMemo
๋ก memoizeํด์ผ ํฉ๋๋ค.
function Typeahead() {
const query = useSearchQuery('');
const deferredQuery = useDeferredValue(query);
// Memoizing tells React to only re-render when deferredQuery changes,
// not when query changes.
const suggestions = useMemo(() =>
<SearchSuggestions query={deferredQuery} />,
[deferredQuery]
);
return (
<>
<SearchInput query={query} />
<Suspense fallback="Loading results...">
{suggestions}
</Suspense>
</>
);
}
์์์ memoizeํ๋ฉด React๋ query
๊ฐ ๋ณ๊ฒฝ๋ ๋๊ฐ ์๋๋ผ deferredQuery
๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ๋ค์ ๋ ๋๋งํ๋ฉด ๋ฉ๋๋ค.
์ด ์ฃผ์ ์ฌํญ์ useDeferredValue
์๋ง ์๋ ๊ฒ์ด ์๋๋ฉฐ ๋๋ฐ์ด์ฑ ๋๋ throttling์ ์ฌ์ฉํ๋ ์ ์ฌํ hook์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํ ํจํด์
๋๋ค.
๐ useSyncExternalStore
(Library Hooks)โ
Library Hooks๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ฑ์๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ React ๋ชจ๋ธ์ ๊น์ด ํตํฉํ ์ ์๋๋ก ์ ๊ณต๋๋ฉฐ ์ผ๋ฐ์ ์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์์๋ ์ฌ์ฉ๋์ง ์์ต๋๋ค.
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);
useSyncExternalStore
์ ์ ํ์ hydration ๋ฐ ์๊ฐ ๋ถํ ๊ณผ ๊ฐ์ concurrent rendering ๊ธฐ๋ฅ๊ณผ ํธํ๋๋ ๋ฐฉ์์ผ๋ก ์ธ๋ถ ๋ฐ์ดํฐ ์์ค์์ ์ฝ๊ณ subscribingํ๋ ๋ฐ ๊ถ์ฅ๋๋ hook์
๋๋ค.
์ธ๋ถ ๋ฐ์ดํฐ์ ๋ํ ์๋ณธ์ ๋ํ subscription์ ํ์๋ก ํ ๋ ๋ ์ด์ useEffect
๊ฐ ํ์ํ์ง ์๊ณ , ์ด๋ ๋ฆฌ์กํธ ์ธ๋ถ ์ํ์ ํตํฉ๋๋ ๋ชจ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ถ์ฅ๋๋ค.
์ด ๋ฉ์๋๋ store ๊ฐ์ ๋ฐํํ๊ณ ์ธ ๊ฐ์ง ์ธ์๋ฅผ ํ์ฉํฉ๋๋ค.
subscribe
: ์คํ ์ด๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ํธ์ถ๋๋ ์ฝ๋ฐฑ์ ๋ฑ๋กํ๋ ํจ์์ ๋๋ค.getSnapshot
: store์ ํ์ฌ ๊ฐ์ ๋ฐํํ๋ ํจ์์ ๋๋ค.getServerSnapshot
: ์๋ฒ ๋ ๋๋ง ์ค์ ์ฌ์ฉ๋ ์ค๋ ์ท์ ๋ฐํํ๋ ํจ์์ ๋๋ค.
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์๋ ๋จ์ํ ์ ์ฒด store๋ฅผ subscriptionํ๋ ๊ฒ์ ๋๋ค.
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
๋ค์๊ณผ ๊ฐ์ด ํน์ ํ๋๋ฅผ ๊ตฌ๋ ํ ์๋ ์์ต๋๋ค.
const selectedField = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().selectedField,
);
์๋ฒ ๋ ๋๋ง ์ ์๋ฒ์์ ์ฌ์ฉํ๋ ์คํ ์ด ๊ฐ์ ์ง๋ ฌํํ์ฌ useSyncExternalStore
์ ์ ๊ณตํด์ผ ํฉ๋๋ค. React๋ ์๋ฒ ๋ถ์ผ์น๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด hydration ์ค์ ์ด ์ค๋
์ท์ ์ฌ์ฉํฉ๋๋ค.
const selectedField = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().selectedField,
() => INITIAL_SERVER_SNAPSHOT.selectedField,
);
- External Store: ์ธ๋ถ ์คํ ์ด๋ผ๋ ๊ฒ์ ์ฐ๋ฆฌ๊ฐ subscribeํ๋ ๋ฌด์ธ๊ฐ๋ฅผ ์๋ฏธํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ฆฌ๋์ค ์คํ ์ด, ๊ธ๋ก๋ฒ ๋ณ์, dom ์ํ ๋ฑ์ด ๋ ์ ์์ต๋๋ค.
- Internal Store:
props
,context
,useState
,useReducer
๋ฑ ๋ฆฌ์กํธ๊ฐ ๊ด๋ฆฌํ๋ ์ํ๋ฅผ ์๋ฏธํฉ๋๋ค. - Tearing: ์๊ฐ์ ์ธ ๋น์ผ์น๋ฅผ ์๋ฏธํ๋ค. ์๋ฅผ ๋ค์ด, ํ๋์ ์ํ์ ๋ํด UI๊ฐ ์ฌ๋ฌ ์ํ๋ก ๋ณด์ฌ์ง๊ณ ์๋, (= ๊ฐ ์ปดํฌ๋ํธ ๋ณ๋ก ์ ๋ฐ์ดํธ ์๋๊ฐ ๋ฌ๋ผ์ ๋ฐ์ํ๋) UI๊ฐ ์ฐข์ด์ง ์ํ๋ฅผ ์๋ฏธํฉ๋๋ค.
๋ฆฌ์กํธ 18 ์ด์ ์๋ ์ด๋ฌํ ๋ฌธ์ ๊ฐ ์์๋ค. ๊ทธ๋ฌ๋ ๋ฆฌ์กํธ 18๋ถํฐ ๋์ ๋ concurrent ๋ ๋๋ง์ด ๋ฑ์ฅํ๋ฉฐ์ ๋ ๋๋ง์ด ๋ ๋๋ง์ ์ ์ ์ผ์์ค์งํ ์ ์๊ฒ ๋๋ฉด์ ์ด ๋ฌธ์ ๊ฐ ๋๋๋๊ธฐ ์์ํ๋ค. ์ผ์์ค์ง๊ฐ ๋ฐ์ํ๋ ์ฌ์ด์ ์ ๋ฐ์ดํธ๋ ๋ ๋๋ง์ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ์ ์ด์ ๊ด๋ จ๋ ๋ณ๊ฒฝ์ฌํญ์ ๊ฐ์ ธ์ฌ ์ ์๊ฒ ๋์๋ค. ์ด๋ก ์ธํด UI๋ ๋์ผํ ๋ฐ์ดํฐ์ ๋ค๋ฅธ ๊ฐ์ ํ์ํ ์ ์๊ฒ ๋๋ฒ๋ ธ๋ค.
์๋์ ๊ฐ์ด ๊ธฐ์กด์ ๋๊ธฐ ๋ ๋๋ง ์์๋ UI๋ ํญ์ ์ผ๊ด์ฑ์ ์ ์งํ ์ ์์์ต๋๋ค.
๊ทธ๋ฌ๋ concurrent ๋ ๋๋ง์์๋ ์ด๊ธฐ์๋ ์๋ ๊ทธ๋ฆผ์ฒ๋ผ ํ๋์์
๋๋ค. ๋ฆฌ์กํธ๋ ์ธ๋ถ ์คํ ์ด๊ฐ ๋ฐ๋๋ฉด์ ๋นจ๊ฐ์์ผ๋ก ์
๋ฐ์ดํธ ํฉ๋๋ค. ๋ฆฌ์กํธ๋ ๊ณ์ํด์ ์ปดํฌ๋ํธ๋ฅผ ๋นจ๊ฐ์์ผ๋ก ๋ฐ๊พธ๋ ค๊ณ ์๋ํ ๊ฒ์
๋๋ค. ์ด ๊ณผ์ ์์ ๋ฐ์ํ๋ UI์ ๋ถ์ผ์น๋ฅผ tearing
์ด๋ผ๊ณ ํฉ๋๋ค.
import React, { useState, useEffect, useCallback } from 'react'
// library code
const createStore = (initialState) => {
let state = initialState
const getState = () => state
const listeners = new Set()
const setState = (fn) => {
state = fn(state)
listeners.forEach((l) => l())
}
const subscribe = (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
}
return { getState, setState, subscribe }
}
const useStore = (store, selector) => {
const [state, setState] = useState(() => selector(store.getState()))
useEffect(() => {
const callback = () => setState(selector(store.getState()))
const unsubscribe = store.subscribe(callback)
callback()
return unsubscribe
}, [store, selector])
return state
}
//Application code
const store = createStore({ count: 0, text: 'hello' })
const Counter = () => {
const count = useStore(
store,
useCallback((state) => state.count, []),
)
const inc = () => {
store.setState((prev) => ({ ...prev, count: prev.count + 1 }))
}
return (
<div>
{count} <button onClick={inc}>+1</button>
</div>
)
}
const TextBox = () => {
const text = useStore(
store,
useCallback((state) => state.text, []),
)
const setText = (event) => {
store.setState((prev) => ({ ...prev, text: event.target.value }))
}
return (
<div>
<input value={text} onChange={setText} className="full-width" />
</div>
)
}
const App = () => {
return (
<div className="container">
<Counter />
<Counter />
<TextBox />
<TextBox />
</div>
)
}
useState
, useEffect
๋ฅผ ์ฌ์ฉํ๊ณ ์๋ useStore
hook์ useSyncExternalStore
๋ก ๋ณ๊ฒฝํด๋ณผ ์ ์์ต๋๋ค.
import { useSyncExternalStore } from 'react'
const useStore = (store, selector) => {
return useSyncExternalStore(
store.subscribe,
useCallback(() => selector(store.getState(), [store, selector])),
)
}
์ฝ๋๊ฐ ํจ์ฌ ๊ฐ๊ฒฐํด์ก์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ์ด๋ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์ด๋ฌํ concurrent rendering์ ์ํฅ์ ๋ฐ์๊น?
๋ ๋๋ง ์ค์ ์ธ๋ถ ๊ฐ๋ณ ๋ฐ์ดํฐ์ ์ ๊ทผํ์ง ์๊ณ , react props, state, context ๋ง์ ์ฌ์ฉํ์ฌ ์ ๋ณด๋ฅผ ์ ๋ฌํ๋ ์ปดํฌ๋ํธ์ ํ
๋ง ๊ฐ์ง๊ณ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ผ๋ฉด ์ํฅ์ ๋ฐ์ง ์์ ๊ฒ์
๋๋ค.
๋ฐ์ดํฐ fetch, ์ํ๊ด๋ฆฌ, redux, mobx, relay ๋ฑ์ ์ํฅ์ ๋ฐ์ ๊ฒ์
๋๋ค. ์ด๋ ๋ฆฌ์กํธ ์ธ๋ถ์ ์ํ๋ฅผ ์ ์ฅํ๊ธฐ ๋๋ฌธ์
๋๋ค. concurrent ๋ ๋๋ง ์์๋ react๊ฐ ๋ชจ๋ฅด๊ฒ ๋ ๋๋ง ์ค์ ์ด๋ฌํ ๊ฐ์ด ์
๋ฐ์ดํธ ๋ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
์ฐธ๊ณ : https://github.com/reactwg/react-18/discussions/86
์ฐธ๊ณ : https://www.youtube.com/watch?v=oPfSC5bQPR8&t=694s&ab_channel=ReactConf2021getSnapShot
์ ์บ์๋ ๊ฐ์ ๋ฐํํด์ผ ํฉ๋๋ค.getSnapshot
์ด ์ฐ์์ผ๋ก ์ฌ๋ฌ ๋ฒ ํธ์ถ๋๋ฉด ๊ทธ ์ฌ์ด์ ์คํ ์ด ์ ๋ฐ์ดํธ๊ฐ ์๋ ํ ์ ํํ ๋์ผํ ๊ฐ์ ๋ฐํํด์ผ ํฉ๋๋ค.
๐ useInsertionEffect
(Library Hooks)โ
useInsertionEffect(didUpdate);
signature๋ useEffect
์ ๋์ผํ์ง๋ง, ๋ชจ๋ DOM ๋ณํ ์ ์ ๋๊ธฐ์ ์ผ๋ก ์คํ๋ฉ๋๋ค. useLayoutEffect
์์ ๋ ์ด์์์ ์ฝ๊ธฐ ์ ์ DOM์ ์คํ์ผ์ ์ฝ์
ํ๋ ค๋ฉด ์ด๊ฒ์ ์ฌ์ฉํ์ญ์์ค. ์ด hook๋ ๋ฒ์๊ฐ ์ ํ๋์ด ์์ผ๋ฏ๋ก ์ด hook๋ refs์ ์ ๊ทผํ ์ ์์ผ๋ฉฐ ์
๋ฐ์ดํธ๋ฅผ ์์ฝํ ์ ์์ต๋๋ค.
useInsertionEffect
๋ css-in-js ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ฑ์๋ก ์ ํ๋์ด์ผ ํฉ๋๋ค. ๋์useEffect
๋๋useLayoutEffect
๋ฅผ ์ฌ์ฉํ์ธ์.
Breaking Changeโ
๐ Automatic batchingโ
batching์ ๋ ๋์ ์ฑ๋ฅ์ ์ํด React๊ฐ ์ฌ๋ฌ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ๋จ์ผ ์ฌ๋ ๋๋ง์ผ ๋ก ๊ทธ๋ฃนํํ๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด ๋์ผํ ํด๋ฆญ ์ด๋ฒคํธ ๋ด์ ๋ ๊ฐ์ ์ํ ์ ๋ฐ์ดํธ๊ฐ ์๋ ๊ฒฝ์ฐ React๋ ํญ์ ์ด๋ฅผ ํ๋์ ์ฌ๋ ๋๋ง์ผ๋ก ์ผ๊ด ์ฒ๋ฆฌํฉ๋๋ค.
React Batch ์ ๋ฐ์ดํธ ๋ฐฉ์์ ๋ณ๊ฒฝํ์ฌ ์๋์ผ๋ก ๋ ๋ง์ ๋ฐฐ์น๋ฅผ ์ํํ ์ ์๋๋ก ์ฑ๋ฅ์ด ํฅ์๋์๋ค. ์ฌ๊ธฐ์ batching์ด๋ ์ฌ๋ฌ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ํ๋์ ๋ฆฌ๋ ๋๋ง์ผ๋ก ์ฒ๋ฆฌํ์ฌ ์ฑ๋ฅ์ ํฅ์์ํค๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด, ๋ฒํผ ํ๋ ํด๋ฆญ์ด ๋๊ฐ์ ์ํ๋ฅผ ์ ๋ฐ์ดํธ (useState๊ฐ ๋๋ฒ ์ํ) ํ๋ค๋ฉด, ๋ฆฌ์กํธ๋ ์ด๋ฅผ ํ๋์ ๋ฆฌ๋ ๋๋ง์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ํด์ฃผ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
๊ทธ๋ฌ๋ ๋ฆฌ์กํธ๋ ์ธ์ ์ ๋ฐ์ดํธ๋ฅผ ๋ฐฐ์น๋ก ์ฒ๋ฆฌํ๋์ง๊ฐ ์ผ๊ด์ฑ์๊ฒ ์ด๋ค์ง๊ณ ์์ง ์์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ฐ์ดํฐ๋ฅผ fetch ํ ๋ค์, handleClick ์์ ์ํ๋ฅผ ์ ๋ฐ์ดํธ ํ๋ ๊ฒฝ์ฐ, ๋ฆฌ์กํธ๋ ์ ๋ฐ์ดํธ๋ฅผ ๋ฐฐ์นํ์ง ์๊ณ ๊ฐ๋ณ ์ ๋ฐ์ดํธ ๋๊ฐ๋ฅผ ์ํํ๊ณค ํ์์ต๋๋ค. ๊ทธ ์ด์ ๋ ๋ธ๋ผ์ฐ์ ์ด๋ฒคํธ ์ค์๋ ๋ฐฐ์น๋ก ์ผ๊ด ์ฒ๋ฆฌ ํ์ง๋ง, ์ด๋ฒคํธ๊ฐ ์ด๋ฏธ ์ฒ๋ฆฌ๋ ํ(์ฝ๋ฐฑ)์์ ์ํ๋ฅผ ์ ๋ฐ์ดํธ ์ฒ๋ฆฌํ๊ณ ์์๊ธฐ ๋๋ฌธ์ด์์ต๋๋ค.
๋ฆฌ์กํธ 18์ automatic batching์ createRoot๊ฐ ์๋ React 18๋ถํฐ ๋ชจ๋ ์ ๋ฐ์ดํธ๋ ์ถ์ฒ์ ๊ด๊ณ์์ด ์๋์ผ๋ก ์ผ๊ด ์ฒ๋ฆฌ๋ฉ๋๋ค. ์ด๋์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋์ง์ ์๊ด์์ด ์๋์ผ๋ก ๋ชจ๋ ์ ๋ฐ์ดํธ๊ฐ ๋ฐฐ์น๋์ด ์ด๋ค์ง๋๋ค.
์ฆ, timeouts, promises, native ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋๋ ๊ธฐํ ์ด๋ฒคํธ ๋ด๋ถ์ ์ ๋ฐ์ดํธ๋ React ์ด๋ฒคํธ ๋ด๋ถ์ ์ ๋ฐ์ดํธ์ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ผ๊ด ์ฒ๋ฆฌ๋ฉ๋๋ค. ์ด๋ก ์ธํด ๋ ๋๋ง ์์ ์ด ์ค์ด๋ค๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ด ํฅ์๋ ๊ฒ์ผ๋ก ๊ธฐ๋ํฉ๋๋ค.
function App() {
const [count, setCount] = useState(0)
const [flag, setFlag] = useState(false)
function handleClick() {
fetchSomething().then(() => {
// React 18 and later DOES batch these:
setCount((c) => c + 1)
setFlag((f) => !f)
// React will only re-render once at the end (that's batching!)
})
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? 'blue' : 'black' }}>{count}</h1>
</div>
)
}
๋ง์ฝ ์ด๋ฌํ ๋์์ ์์น ์๋๋ค๋ฉด flushSync
๋ฅผ ์ฐ๋ฉด ๋ฉ๋๋ค.
import { flushSync } from 'react-dom'; // Note: react-dom, not react
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React has updated the DOM by now
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now
}
๐ New Suspense Featuresโ
Suspense๋ฅผ ์ฌ์ฉํ๋ฉด ์์ง ํ์ํ ์ค๋น๊ฐ ๋์ง ์์ ๊ฒฝ์ฐ ์ปดํฌ๋ํธ ํธ๋ฆฌ์ ์ผ๋ถ์ ๋ํ ๋ก๋ ์ํ๋ฅผ ์ ์ธ์ ์ผ๋ก ์ง์ ํ ์ ์์ต๋๋ค.
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
Suspense๋ React ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์์ "UI ๋ก๋ฉ ์ํ"๋ฅผ ์ผ๊ธ ์ ์ธ์ ๊ฐ๋ (first-class declarative concept)์ผ๋ก ๋ง๋ญ๋๋ค.
์ฐ๋ฆฌ๋ ๋ช ๋
์ ์ ์ ํ๋ ๋ฒ์ ์ Suspense
๋ฅผ ๋์
ํ์ต๋๋ค. ๊ทธ๋ฌ๋ ์ง์๋๋ ์ ์ผํ ์ฌ์ฉ ์ฌ๋ก๋ React.lazy
๋ก ์ฝ๋ ๋ถํ (splitting)์ด์๊ณ ์๋ฒ์์ ๋ ๋๋งํ ๋ ์ ํ ์ง์๋์ง ์์์ต๋๋ค.
React 18์์๋ ์๋ฒ์์ Suspense
์ ๋ํ ์ง์์ ์ถ๊ฐํ๊ณ concurrent rendering ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ๊ธฐ๋ฅ์ ํ์ฅํ์ต๋๋ค. React 18์ Suspense
๋ ์ ํ(transition) API์ ๊ฒฐํฉ๋ ๋ ๊ฐ์ฅ ์ ์๋ํฉ๋๋ค. ๋ง์ฝ ์ ํ(transition) ์ค์ ์ผ์ ์ค๋จํ๋ฉด React๋ ์ด๋ฏธ ๋ณด์ด๋ ์ฝํ
์ธ ๊ฐ fallback์ผ๋ก ๋์ฒด๋๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. ๋์ React๋ ์๋ชป๋ ๋ก๋ ์ํ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ถฉ๋ถํ ๋ฐ์ดํฐ๊ฐ ๋ก๋๋ ๋๊น์ง ๋ ๋๋ง์ ์ง์ฐํฉ๋๋ค.
์์ธํ ๋ด์ฉ์ React 18์ Suspense์ ๋ํ RFC๋ฅผ ์ฐธ์กฐํ์ธ์.
https://reactjs.org/blog/2022/03/29/react-v18.html
https://yceffort.kr/2022/04/react-18-changelog
https://ko.reactjs.org/docs/concurrent-mode-patterns.html
๐ New Strict Mode Behaviorsโ
์์ผ๋ก React๊ฐ ์ํ๋ฅผ ์ ์งํ๋ฉด์ UI์ ์น์ ์ ์ถ๊ฐ ๋ฐ ์ ๊ฑฐํ ์ ์๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ณ ์ถ์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ ํ๋ฉด์์ ๋ฉ์ด์ก๋ค๊ฐ ๋ค๋ก ํญํ๋ฉด React๋ ์ฆ์ ์ด์ ํ๋ฉด์ ํ์ํ ์ ์์ด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด React๋ ์ด์ ๊ณผ ๋์ผํ component ์ํ๋ฅผ ์ฌ์ฉํ์ฌ ํธ๋ฆฌ๋ฅผ ๋ง์ดํธ ํด์ ํ๊ณ ๋ค์ ๋ง์ดํธํฉ๋๋ค.
์ด ๊ธฐ๋ฅ์ React ์ฑ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ ๋์ ์ฑ๋ฅ์ ์ ๊ณตํ์ง๋ง component๊ฐ ์ฌ๋ฌ ๋ฒ ๋ง์ดํธ๋๊ณ ํ๊ดด๋๋ ํจ๊ณผ์ ํ๋ ฅ์ ์ด์ด์ผ ํฉ๋๋ค. ๋๋ถ๋ถ์ ํจ๊ณผ๋ ๋ณ๊ฒฝ ์์ด ์๋ํ์ง๋ง ์ผ๋ถ ํจ๊ณผ๋ ํ ๋ฒ๋ง ์ฅ์ฐฉ๋๊ฑฐ๋ ํ๊ดด๋๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํ์ํ๋ ๋ฐ ๋์์ด ๋๋๋ก React 18์ Strict Mode์ ๋ํ ์๋ก์ด ๊ฐ๋ฐ ์ ์ฉ ๊ฒ์ฌ๋ฅผ ๋์ ํ์ต๋๋ค. ์ด ์๋ก์ด ๊ฒ์ฌ๋ component๊ฐ ์ฒ์์ผ๋ก ๋ง์ดํธ๋ ๋๋ง๋ค ๋ชจ๋ component๋ฅผ ์๋์ผ๋ก ๋ง์ดํธ ํด์ ํ๋ค๊ฐ ๋ค์ ๋ง์ดํธํ์ฌ ๋ ๋ฒ์งธ ๋ง์ดํธ์์ ์ด์ ์ํ๋ฅผ ๋ณต์ํฉ๋๋ค.
https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state
๐ ์ผ๊ด๋ useEffect
ํ์ด๋ฐโ
์์์ ์ธ๊ธํ Automatic Batching์์ ์ด์ด์ง๋ ๋งฅ๋ฝ์ ๋๋ค. click, keydown event์ ๊ฐ์ ๊ฐ๋ณ ์ฌ์ฉ์ ์ ๋ ฅ ์๋ฒคํธ ์ค์ ์ ๋ฐ์ดํธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ, ํญ์ ๋๊ธฐ์์ผ๋ก effect ํจ์๋ฅผ ํ๋ฌ์ฌํฉ๋๋ค. ์ด์ ์๋ ์ด ๊ธฐ๋ฅ์ด ์์ธก๊ฐ๋ฅํ๊ฑฐ๋, ์ผ๊ด์ ์ด์ง ๋ชปํ์ต๋๋ค.
๐ ์๊ฒฉํด์ง hydration ์๋ฌโ
ํ
์คํธ ์ ์ฉ ๋๋ฝ, ํ
์คํธ ๋ด์ฉ ๋ถ์ผ์น ๋ฑ์ ์ด์ ๊ฒฝ๊ณ ๋์ ์ค๋ฅ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค. ๋ฆฌ์กํธ๋ ์๋ฒ ๋ง์ผ์ปต์ ์ผ์น์ํค๊ธฐ ์ํด ํด๋ผ์ด์ธํธ ๋
ธ๋์ ์ฝ์
์ด๋ ์ญ์ ๋ฅผ ํจ์ผ๋ก์ ๊ฐ๋ณ ๋
ธ๋๋ฅผ ์์ ํด์ฃผ์ง ์๊ณ , ์ด์ ๋ ํธ๋ฆฌ์์ ๊ฐ์ฅ ๊ฐ๊น์ด <Suspense>
boundary ๊น์ง ํด๋ผ์ด์ธํธ ๋ ๋๋ง์ผ๋ก ๋์๊ฐ๋๋ค. ์ด๋ฅผ ํตํด hydration ํธ๋ฆฌ์ ์ผ๊ด์ฑ์ ํ๋ณดํ๊ณ , ๋ถ์ผ์น๋ก ์ธํด ๋ฐ์ํ ์ ์๋ ์ ์ฌ์ ์ธ ๋ณด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ค๋น๋ค.
๐ Suspense ๊ฐ ์ด์ ํญ์ ์ผ๊ด๋๊ฒ ์ ์ฉ๋จโ
ํธ๋ฆฌ์ ์์ ํ ์ถ๊ฐ๋๊ธฐ ์ ์, ์ปดํฌ๋ํธ๊ฐ suspend๋ ๊ฒฝ์ฐ, ๋ฆฌ์กํธ๋ ๋ถ์์ ํ ์ํ๋ก ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ effect๋ฅผ ๋ฐ์์ํค์ง ์์ต๋๋ค. ๋์ ๋ฆฌ์กํธ๋ ์ ํธ๋ฆฌ๋ฅผ ์์ ํ ๋ฒ๋ฆฌ๊ณ ๋น๋๊ธฐ ์์ ์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ๋ค์, ๋ค์ ์ฒ์๋ถํฐ ๋ ๋๋ง์ ์๋ํฉ๋๋ค. ๋ฆฌ์กํธ๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฐจ๋จํ์ง ์๊ณ ๋์์ ๋ ๋๋ง์ ์ฌ์๋ํฉ๋๋ค.
๐ Suspense์ layout effectโ
ํธ๋ฆฌ๊ฐ suspend๋์๋ค๊ฐ fallback์ผ๋ก ๋์๊ฐ๋ฉด, ๋ฆฌ์กํธ ๋ ์ด์์ effect๋ฅผ ์ ๋ฆฌํ ๋ค์, ๋ฐ์ด๋๋ฆฌ ๋ด๋ถ์ ๋ด์ฉ์ด ๋ค์ ํ์ ๋ ๋๊น์ง ๋ง๋ญ๋๋ค. ์ด๋ก ์ธํด ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ suspense์ ํจ๊ป ์ฌ์ฉ๋ ๋ ๋ ์ด์์์ ์ฌ๋ฐ๋ฅด๊ฒ ์ธก์ ํ ์ ์์๋ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋ฉ๋๋ค.
๐ ์๋ก์ด js ํ๊ฒฝ (polyfill ํ์)โ
๋ฆฌ์กํธ๋ ์ด์ ๋ชจ๋ ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฅ์ธ Promise
Symbol
Object.assign
์ ์์กดํฉ๋๋ค. ์ต์ ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง ์๊ฑฐ๋, ํน์ ํธํ๋์ง ์๋ ์ธํฐ๋ท ์ต์คํ๋ก๋ฌ ๋ฑ ์ค๋๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ง์ํด์ผ ํ๋ ๊ฒฝ์ฐ, ์ ํ๋ฆฌ์ผ์ด์
์ ๊ธ๋ก๋ฒ ํ๋กํ์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ด์ผ ํฉ๋๋ค.
๋์ ๋๋ ๋ณํโ
๐ undefined
๋ ๋ ๋๋ง ๊ฐ๋ฅโ
์ด์ ์ปดํฌ๋ํธ๊ฐ undefined
๋ฅผ ๋ฆฌํดํด๋ ์๋ฌ๋ฅผ ๋ฆฌํดํ์ง ์์ต๋๋ค.