useTransition & useDeferredValue
๋ฆฌ์กํธ๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ฉด ์์ฃผ ์ฐ๋ ํ ๋ค๋ง ์ต์ํด์ง๊ณ , ์๊ฐ๋ณด๋ค ๋ค์ํ ํ ๋ค์ ๋์น๊ฒ ๋๋ ๊ฒ ๊ฐ๋ค.
useTransition๊ณผ useDeferredValue ๊ฐ์ ํ ๋ค์ ๊ฐ๊ณผํ๊ณ ์์๋๋ฐ, ์ด๋ฒ์ ์๊ฒ ๋์ด ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํด ๋ณด์๋ค.
1๏ธโฃ useTransition
useTransition์ ๋น๋๊ธฐ UI ์ ๋ฐ์ดํธ๋ฅผ ์ฒ๋ฆฌํ ๋, UI๋ฅผ ์ฐจ๋จํ์ง ์๊ณ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ ์ ์๋ ํ ์ด๋ค.
์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๊ธฐ ์ํด ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ์์ ์ "์ ํ(transition)"์ผ๋ก ๊ตฌ๋ถํ๊ณ , ์ด๋ฅผ ๋ฎ์ ์ฐ์ ์์๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ ํด ์ค๋ค.
const [isPending, startTransition] = useTransition();
์ด ํ ์ ๋ ๊ฐ์ง ๊ฐ์ ๋ฐํํ๋๋ฐ, ํ๋๋ ์์ ์ด ์งํ ์ค์ธ์ง ๋ํ๋ด๋ isPending ์ด๊ณ , ๋ค๋ฅธ ํ๋๋ ์ ํ์ ์์ํ๋ ํจ์์ธ startTransition ์ด๋ค.
import { useState, useTransition } from 'react';
import TabButton from './TabButton.js';
import AboutTab from './AboutTab.js';
import PostsTab from './PostsTab.js';
import ContactTab from './ContactTab.js';
export default function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<>
<TabButton
isActive={tab === 'about'}
onClick={() => selectTab('about')}
>
About
</TabButton>
<TabButton
isActive={tab === 'posts'}
onClick={() => selectTab('posts')}
>
Posts (slow)
</TabButton>
<TabButton
isActive={tab === 'contact'}
onClick={() => selectTab('contact')}
>
Contact
</TabButton>
<hr />
{isPending && <p>Loading...</p>}
{tab === 'about' && <AboutTab />}
{tab === 'posts' && <PostsTab />}
{tab === 'contact' && <ContactTab />}
</>
);
}
- useState์ useTransition ์ฌ์ฉ: tab ์ํ๋ ํ์ฌ ์ ํ๋ ํญ์ ๊ด๋ฆฌํ๋ฉฐ, useTransition์ ์ฌ์ฉํ์ฌ ์ ํ ์์ ์ ์ฒ๋ฆฌํ๋ค. isPending ๊ฐ์ ์ ํ์ด ์งํ ์ค์ธ์ง ๋ํ๋ด๊ณ , startTransition ํจ์๋ ํญ ์ ํ์ ๋ฎ์ ์ฐ์ ์์๋ก ์ฒ๋ฆฌํ๊ฒ ํ๋ค.
- selectTab ํจ์: ํด๋น ํจ์๋ ์๋ก์ด ํญ์ ์ ํํ ๋ ํธ์ถ๋๋๋ฐ, startTransition์ผ๋ก ๊ฐ์ธ์ ธ ์์ด ํญ ์ ํ ์์
์ด ๋น๋๊ธฐ์ ์ผ๋ก, ์ฐ์ ์์๊ฐ ๋ฎ์ ์์
์ผ๋ก ์ฒ๋ฆฌ๋๋ค. ๋ฐ๋ผ์ UI๊ฐ ๋น ๋ฅด๊ฒ ๋ฐ์ํ ์ ์๋ค.
ํด๋น ์์ ์ฝ๋๋ ๋ฆฌ์กํธ ๊ณต์๋ฌธ์์์ ๊ฐ์ ธ์จ ๊ฑฐ๋ผ ์ง์ ๋์์์ผ ๋ณผ ์ ์๋๋ฐ, PostsTab์ฒ๋ผ ๋ฌด๊ฑฐ์ด ์์ ์ด ํฌํจ๋ ํญ์ ์ ํํ ๋ ์ฐ์ ์์๊ฐ ๋ฎ๊ธฐ ๋๋ฌธ์ Posts ํด๋ฆญ ํ Contact ํญ์ ํด๋ฆญํ๋ค๋ฉด Posts ํญ์ด ๋์๋ ๋๊น์ง UI ์ ๋ฐ์ดํธ๊ฐ ๋ฉ์ถ๋ ๊ฒ์ด ์๋๋ผ ๋น ๋ฅด๊ฒ Contact ํญ์ผ๋ก ์ ํ๋จ์ ๋ณผ ์ ์๋ค! - ๋ก๋ฉ ์ํ ํ์: isPending ๊ฐ์ด true์ผ ๋, ์ ํ์ด ์๋ฃ๋๊ธฐ ์ ๊น์ง "Loading..." ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ค๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉ์๋ ์ ํ์ด ์งํ ์ค์์ ์ ์ ์๋ค.
2๏ธโฃ useDeferredValue
useDeferredValue๋ UI ์ผ๋ถ ์ ๋ฐ์ดํธ๋ฅผ ์ง์ฐ์ํฌ ์ ์๋ ํ ์ด๋ค.
useTransition ์ ๋น์ทํ๊ฒ ๋๊ปด์ง ์ ์๋๋ฐ, useTransition ๋ ํจ์ ์คํ์ ์ฐ์ ์์๋ฅผ ์ง์ ํ๋ ํ ์ด๋ผ๋ฉด, useDefferdValue๋ ๊ฐ์ ์ ๋ฐ์ดํธ ์ฐ์ ์์๋ฅผ ์ง์ ํ๋ค.
const deferredValue = useDeferredValue(value)
value๋ ์ฆ์ ๋ฐ์๋๋ ๊ฐ์ด์ง๋ง, deferredValue๋ ์ฐ์ ์์๊ฐ ๋ฎ์ ์์ ์ผ๋ก ์ฒ๋ฆฌ๋์ด, React๊ฐ ๋ ๋๋ง ์ฑ๋ฅ์ ๋ถ๋ด์ ์ฃผ์ง ์๋ ์์ ์ ์ ๋ฐ์ดํธ๋๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ์๊ฐ ์ ๋ ฅ์ ๋น ๋ฅด๊ฒ ํ๋๋ผ๋ UI๋ ์ฆ๊ฐ ๋ฐ์ํ๊ณ , ๊ฒฐ๊ณผ๋ ์ฝ๊ฐ์ ์ง์ฐ ํ ํ์๋๋ค.
import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';
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>
</>
);
}
๋จผ์ useState ํ์ฉํด์ ๋ง๋ ๊ฒ์ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ปดํฌ๋ํธ๋ก ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋๋ง๋ค query๊ฐ ์ฆ์ ์ ๋ฐ์ดํธ๋๊ธฐ ๋๋ฌธ์, ๊ฒ์์ด๊ฐ ๋ฐ๋ ๋๋ง๋ค ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋ก๋ฉํ๋๋ผ UI๊ฐ ์ ์ ๋์ ๋ฉ์ถ๊ฑฐ๋ Suspense ํด๋ฐฑ ๋ฉ์์ง("Loading...")๊ฐ ๋ํ๋ ์ ์๋ค.
๊ทธ๋ผ ์ฌ๊ธฐ์ useDeferredValue ๋ฅผ ์ด๋ป๊ฒ ํ์ฉํ ์ ์์๊น?
https://codesandbox.io/p/sandbox/lsmcyq
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>
</>
);
}
- query๋ ์ฆ์ ์ ๋ฐ์ดํธ: ์ฌ์ฉ์๊ฐ ๊ฒ์์ด๋ฅผ ์ ๋ ฅํ ๋ query๋ ์ฆ์ ์ ๋ฐ์ดํธ๋๋ค. ๋ฐ๋ผ์ ์ ๋ ฅ ํ๋์ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๊ฐ์ด ๋ฐ๋ก ๋ฐ์๋๋ค.
- deferredQuery๋ ์ง์ฐ๋ ๊ฐ: SearchResults ์ปดํฌ๋ํธ์ ์ ๋ฌ๋๋ ๊ฐ์ ์ง์ฐ๋ deferredQuery์ด๋ค.
์ฆ, React๋ ์ฌ์ ๊ฐ ์์ ๋๋ง deferredQuery๋ฅผ ์ ๋ฐ์ดํธํ์ฌ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋ก๋ฉํ๋๋ก ํ๋ค. useDeferredValue ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ UI์์๋ Loading UI ๊ฐ ์ ์ ๋ณด์๋ค๋ฉด, useDeferredValue ๋ฅผ ํ์ฉํ UI์์๋ ์ด์ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์ ์ง๋๋ค.
๐คจ ๋ก๋ฉ ํ์๊ฐ ์๋๋ฉด ๋ถ์น์ ํ UI ์๋๊ฐ...
useDeferredValue๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฒ์์ด๊ฐ ๋น ๋ฅด๊ฒ ์ ๋ ฅ๋ ๋ ์ด์ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์ ์ง๋๋ฏ๋ก, ๋ก๋ฉ UI๊ฐ ํ์๋์ง ์๋ ์ ์ด ์ฅ์ ์ผ ์ ์๋ค. ๋ค๋ง, ๋ก๋ฉ ์ํ๊ฐ ์๊ฐ์ ์ผ๋ก ๋๋ฌ๋์ง ์์ผ๋ฉด ์ฌ์ฉ์์๊ฒ ๋ถ์น์ ํ UI๊ฐ ๋ ์ ์๋ค๋ ์๊ฐ์ด ๋ค์๋๋ฐ..
์ด๋ด ๋, ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์ต์ ์ฟผ๋ฆฌ์ ์ผ์นํ์ง ์์ ๊ฒฝ์ฐ(์ฆ, ์ค๋๋ ๊ฒฐ๊ณผ์ผ ๋) CSS๋ฅผ ํ์ฉํด ํ๋ฆฌ๊ฒ ํ์ํจ์ผ๋ก์จ, ๋ก๋ฉ ์ค์์ ์๊ฐ์ ์ผ๋ก ์๋ฆฌ๋ ๋ฐฉ๋ฒ์ ์ ์ฉํ ์ ์๋ค! ๐
const isStale = query !== deferredQuery;
<div style={{
opacity: isStale ? 0.5 : 1,
transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
}}>
<SearchResults query={deferredQuery} />
</div>