
๋ฐฑ์๋ ์์ด๋ ํ๋ก ํธ์๋ ์ฑ๋ฅ ์ต์ ํ, โOptimistic UIโ
๋ฐฑ์๋ ์์ด๋ ํ๋ก ํธ์๋ ์ฑ๋ฅ ์ต์ ํ, โOptimistic UIโ ๊ด๋ จ
์ฌ์ฉ์ ๊ฒฝํ์ ํ ๋จ๊ณ ๋์ด๋ ํ๋ก ํธ์๋ ํจํด
์ฑ๋ฅ ์ต์ ํ์ ๊ธธ์ ํ๋ํฉ๋๋ค. ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ "์ด ์์ฒญ ์ฒ๋ฆฌ๊ฐ ์ ์ด๋ ๊ฒ ๋๋ฆฐ ๊ฑฐ์ผ?!" ํ๋ฉฐ ๋ฐค์ ๋๋ฒ๊น ํฉ๋๋ค. ์์ง ํ์ฅ, ์ํ ํ์ฅ์ผ๋ก ์๋ฒ๋ฅผ ๋๋ฆฌ๊ณ , ๋ ๋์ค ์บ์ ๋ ์ด์ด๋ฅผ ์ถ๊ฐํ๊ณ , DB ์ฟผ๋ฆฌ ์ต์ ํ์ ๋ช ์ฃผ๋ฅผ ํฌ์ํฉ๋๋ค. ์ ์ ํ ์ค๋ฉ๊ณผ ํํฐ์ ๋์ ์ํด ์ํคํ ์ฒ ์ค๊ณ์ ์๋ฐฑ ์๊ฐ์ ์์๋ถ๊ณ , ์์ญ ๋ช ์ ์์ง๋์ด๊ฐ ํฌ์ ๋ ํ๋ก์ ํธ๋ ๋ถ์ง๊ธฐ์์ฃ .
๊ทธ๋ฐ๋ฐ ์ฌ๋ฌ๋ถ์ "ํ๋ก ํธ์๋"๋ฅผ ์ ์ ํํ์ จ๋์?
์ด๋ฌํ ์ง์ ์์ ํ๋ก ํธ์๋๋ ์ฐ์๋ฅผ ์ง๋๋๋ค. ์ฌ์ค ๋ชจ๋ ์น๊ณผ ์ฑ์ ๋ฐฑ์๋ ์์ง๋์ด์ ๋ ธ๊ณ ๋๋ถ์ ๋์๊ฐ์ง๋ง, ๊ฒฐ๊ตญ ์์ ๋์๋ ๊ฑด ํ๋ก ํธ์๋์ ๋๋ค. ๋ง์น ์๋ฆฌ์ฌ๊ฐ ์๋ฆฌ๋ฅผ ์ ๋ง๋ค์ด๋ ์๋นํ๋ ์ฌ๋์ด ์๋์๊ฒ ์นญ์ฐฌ๋ฐ๋ ๊ฒ์ฒ๋ผ์. ์ด๋ฐ ์ ๋๋ฌธ์ ์ ๋ ํ๋ก ํธ์๋๋ฅผ ์ ํํ์ต๋๋ค.
์ ๋ ์ฒ์์ ๋ฐฑ์๋๋ก ๊ฐ๋ฐ์ ์์ํ๋ค๊ฐ ํ๋ก ํธ์๋๋ก ์ ํฅํ์ต๋๋ค. "๋ฒํผ ์๊น๋ง ๋ฐ๊ฟ๋ '์ฐ์' ์๋ฆฌ ๋ฃ๋๋ค"๋ผ๋ ๋ง์ ํํน๋ ๊ฑฐ์ฃ . ์์งํ ๋งํ๋ฉด ์ ๋ ์ข ๊ฒ์ผ๋ฆ ๋๋ค. ์ต์ํ์ ๋ ธ๋ ฅ์ผ๋ก ์ฌ์ฅ๋๊ณผ ๊ธฐํ์๋ค์๊ฒ ๊ฐํ์ ๋์ด๋ด๊ณ ์ถ์์ด์. ์ ์ ๋ค์ ๊ฒฐ๊ตญ ๋ณด์ด๋ ๊ฒ๋ง ํ๊ฐํ๋๊น์. ์ด๋ฐ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์์๊ฒ ์ต๊ณ ์ ๊ฐ์ฑ๋น ๊ธฐ์ ์ด ๋ฐ๋ก โOptimistic UIโ์ ๋๋ค. ๋จ ๋ช ์ค์ ์ฝ๋๋ก ์ฌ์ฉ์๋ฅผ ๊ธฐ๋ถ ์ข๊ฒ ์์ด๋ ๋ง๋ฒ์ด์ฃ .
INP: ๊ตฌ๊ธ๋ ์ธ์ ํ ์ํธ์์ฉ์ ์ค์์ฑ
๊ตฌ๊ธ์ 'INP(Interaction to Next Paint)' ์งํ๋ ์ฌ์ฉ์ ์ธํฐ๋์
ํ ํ๋ฉด ์
๋ฐ์ดํธ ์๋๋ฅผ ์ธก์ ํฉ๋๋ค. 2024๋
3์๋ถํฐ ๊ตฌ๊ธ์ INP๋ฅผ Core Web Vitals์ ๊ณต์ ์งํ๋ก ์ฑํํ์ต๋๋ค. ์ด๋ ๋จ์ํ ๊ธฐ์ ์งํ๊ฐ ์๋๋๋ค. ๊ตฌ๊ธ์ ๊ณต์ ์น ๊ฐ๋ฐ ๋ธ๋ก๊ทธ web.dev
์ ๋ฐ๋ฅด๋ฉด, RedBus๋ INP ์ต์ ํ๋ก ์ ์๋ฅผ 72% ๊ฐ์ ํ๊ณ ๋งค์ถ์ 7% ์ฆ๊ฐ์์ผฐ์ผ๋ฉฐ, The Economic Times๋ INP ๊ฐ์ ์ผ๋ก ์ดํ๋ฅ ์ 50% ๊ฐ์์์ผฐ์ต๋๋ค.
'Optimistic UI' ํจํด์ ์ด INP ์งํ์์ ๋น์ ๋ฐํ๋ฉฐ ์๋ฒ ์๋ต ์์ด๋ ์ฆ์ UI๋ฅผ ์ ๋ฐ์ดํธํด ์ฌ์ฉ์์๊ฒ "๋น์ ์๋" ๊ฐ์ ๊ฒฝํ์ ์ ์ฌํฉ๋๋ค. ํ์ด์ค๋ถ์ ์ข์์ ๋ฒํผ, ์ฌ๋์ ๋ฉ์์ง ์ ์ก ๋ฑ ์ธ๊ธฐ ์๋น์ค๋ค์ด ์ด๋ฏธ ์ด ๊ธฐ๋ฒ์ ํ์ฉํ๊ณ ์์ฃ .
์ด๋ ฅ์์ "Optimistic UI ํจํด ๋์ ์ผ๋ก ์ฌ์ฉ์ ์ฒด๊ฐ ์๋ 200% ํฅ์" ๊ฐ์ ๋ฌธ๊ตฌ๋ฅผ ์ ๊ณ ์ถ์ผ์ ๊ฐ์? ๋๋ ๋ฉด์ ์์ "์ ๋ UX ํฅ์์ ์ํด ์ด๋ฐ ํจํด์ ์ ์ฉํด ๋ดค์ต๋๋ค"๋ผ๊ณ ๋งํ๊ณ ์ถ์ผ์ ๊ฐ์?
์ด์ ๋๊ด์ ์ ๋ฐ์ดํธ๊ฐ ๋ฌด์์ธ์ง, ์ ์ค์ํ์ง, ๊ทธ๋ฆฌ๊ณ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ํจ๊ป ์์๋ณด๊ฒ ์ต๋๋ค.
Optimistic UI๊ฐ ๋ญ๋ฐ?
Optimistic UI๋ ๋ฐฑ์๋์ ์์ฒญ์ ๋ณด๋ด๋ ์ฆ์, ์ฑ๊ณต ์ฌ๋ถ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ํ๋ฉด์ ์ ๋ฐ์ดํธํ๋ ํจํด์ ๋๋ค. ์ ํต์ ์ธ UI ํจํด์์๋ "์์ฒญ โ ๋๊ธฐ โ ์๋ต โ UI ์ ๋ฐ์ดํธ" ์์๋ก ์งํ๋์ง๋ง, ์ด ๋ฐฉ์์์๋ ์ฌ์ฉ์๊ฐ ํญ์ ๋คํธ์ํฌ ์ง์ฐ์ ์ฒด๊ฐํ ์๋ฐ์ ์์ต๋๋ค.
์ ํต์ ์ธ UI ํจํด:
์ฌ์ฉ์ โ ์ก์
๋ฐ์ โ API ์์ฒญ โ ๋ก๋ฉ ํ์ โ ์๋ฒ ์ฒ๋ฆฌ โ ์๋ต ์์ โ UI ์
๋ฐ์ดํธ
๋ฐ๋ฉด Optimistic UI๋ ์๋ฒ ์๋ต ์ฑ๊ณต์ ๋๊ด์ ์ผ๋ก ๊ฐ์ ํ๊ณ UI๋ฅผ ์ฆ์ ์ ๋ฐ์ดํธํฉ๋๋ค.

์ด๋ฐ ํจํด์ด ๊ฐ๋ฅํ ์ด์ ๋ ์ ์์ ์ธ ์ํฉ์์ ๋๋ถ๋ถ์ ์์ฒญ์ด ์ฑ๊ณตํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ํ ์๋ต ๊ฒฐ๊ณผ๊ฐ ์์ธก ๊ฐ๋ฅํ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ฉด ๋ด ๋ฉ์์ง๊ฐ ๊ทธ๋๋ก ์ถ๊ฐ๋ ๊ฒ์ด๊ณ , ์ข์์๋ฅผ ๋๋ฅด๋ฉด ๋ด ์ข์์๊ฐ ํผ๋์ ์ถ๊ฐ๋ ๊ฒ์ ๋๋ค. ์ด์ฒ๋ผ ๋ด๊ฐ ์์ฑํ ๋ฆฌ์์ค๋ฅผ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ์ ํนํ ์ ์ฉํฉ๋๋ค.
๋ฐ๋ฉด, ๋ด๊ฐ ์์ฒญํ์ ๋ ๋๋คํ ์ซ์๊ฐ ๋ฐํ๋๊ฑฐ๋ ๊ฒ์ฌ ์ง๋จํ๊ฐ ์์ฑ๋๋ ๋ฑ ๊ฒฐ๊ณผ๋ฅผ ์์ธกํ ์ ์๋ ๊ฒฝ์ฐ์๋ ์ด ํจํด์ ์ ์ฉํ๊ธฐ ์ด๋ ต์ต๋๋ค.
๋์ค์ฝ๋์์๋ ์ด๋ค!
๋์ค์ฝ๋๋ ์ด ํจํด์ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๋ ์ข์ ์ฌ๋ก์ ๋๋ค. ๋ฉ์์ง๋ฅผ ์ ์กํ๋ฉด ์ฆ์ ์ฑํ ์ฐฝ์ ํ์๋์ง๋ง, ์๋ฒ ํ์ธ ์ ๊น์ง๋ ์ฝ๊ฐ ํ๋ฆฟํ๊ฒ ๋ณด์ ๋๋ค. ์์ฒญ์ด ์ฑ๊ณตํ๋ฉด ํ ์คํธ๊ฐ ์ ๋ช ํด์ง๊ณ , ์คํจํ๋ฉด ์ค๋ฅ ๋ฉ์์ง์ ํจ๊ป ์ฌ์๋ ์ต์ ์ ์ ๊ณตํฉ๋๋ค.

๋ง์ฝ ๋์ค์ฝ๋๊ฐ ์ด ํจํด์ ์ฌ์ฉํ์ง ์์๋ค๋ฉด? ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ณ ์๋ฒ ์๋ต์ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ์๋ฌด ๋ณํ๊ฐ ์๊ฑฐ๋, ๋ก๋ฉ ์คํผ๋๋ง ๋์๊ฐ๋ ๋ต๋ตํ ๊ฒฝํ์ ํ์ ๊ฒ์ ๋๋ค. ํนํ ๋คํธ์ํฌ ์ํ๊ฐ ์ข์ง ์์ ํ๊ฒฝ์์๋ ๋ช ์ด์์ ๋ช ์ญ ์ด๊น์ง ๊ธฐ๋ค๋ ค์ผ ํ ์๋ ์์ต๋๋ค. ์ด๋ฐ ์ํฉ์์ ๋ง์ ์ฌ์ฉ์๋ค์ "๋ด ๋ฉ์์ง๊ฐ ์ ์ก๋๋?" ํ๋ ์๋ฌธ์ ๊ฐ์ง๋ฉฐ ๊ฐ์ ๋ฉ์์ง๋ฅผ ์ฌ๋ฌ ๋ฒ ๋ณด๋ด๋ ์ค์๋ฅผ ํ๊ฒ ๋ฉ๋๋ค.
UI ๊ด์ ์์๋ Optimistic ์ํ๋ ๊ตฌ๋ถ์ด ํ์ํฉ๋๋ค. ๋ณดํต ์ฝ๊ฐ ํ๋ฆฟํ๊ฒ ํ์ํ๊ฑฐ๋, ๋ฐฐ๊ฒฝ์์ ๋ค๋ฅด๊ฒ ํ๋ ๋ฑ์ ๋ฐฉ๋ฒ์ผ๋ก "์์ง ํ์ ๋์ง ์์ ์ํ"์์ ๋ฏธ๋ฌํ๊ฒ ํํํฉ๋๋ค. ์ด๋ฐ ๋ํ ์ผ์ด ์ฌ์ฉ์์๊ฒ ์์คํ ์ ์ํ๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ์ ๋ฌํ๋ ๋์์, ์ฆ๊ฐ์ ์ธ ๋ฐ์์ฑ๋ ์ ๊ณตํฉ๋๋ค.
์ธ์คํ๊ทธ๋จ์ ์ข์์ ๋ฒํผ๋ ์ด์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ์ข์์๋ฅผ ๋๋ฅด๋ฉด ์ฆ์ ํํธ๊ฐ ์ฑ์์ง๊ณ ์ซ์๊ฐ ์ฆ๊ฐํ๋ฉฐ, ๋คํธ์ํฌ ์ํ์ ๋ฌด๊ดํ๊ฒ ์ฌ์ฉ์์๊ฒ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ ๋ฐฑ์๋์ ๊ฐ์ ์ด ์ง์ฐ๋๋ ์ํฉ์์๋ ์ฌ์ฉ์์๊ฒ ์ต์ ํ๋ ๊ฒฝํ์ ๋ฏธ๋ฆฌ ์ ๊ณตํ ์ ์์ต๋๋ค. ์ด๊ฒ์ด ๋ฐ๋ก ํ๋ก ํธ์๋ ์ฑ๋ฅ ์ต์ ํ์ ํ ํํ๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค. ์ด์ ์ด๋ค ์กฐ๊ฑด์์ ์ด ํจํด์ ์ ์ฉํ ์ ์๋์ง, ๊ทธ๋ฆฌ๊ณ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
Optimistic UI ๊ตฌํ์ ์ํ ํต์ฌ ์ ๋ต๊ณผ ํจํด
Optimistic UI๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ตฌํํ๊ธฐ ์ํด์๋ ๋ช ๊ฐ์ง ํต์ฌ ์ ๋ต๊ณผ ํจํด์ ์ดํดํด์ผ ํฉ๋๋ค. ์ด ํจํด์ ๊ฐ์ฅ ์ค์ํ ์์น์ ์๋ณธ ์ํ(์๋ฒ ์ํ)์ ๋๊ด์ ์ํ๋ฅผ ๋ช ํํ ๋ถ๋ฆฌํ์ฌ ํ์์ ๋กค๋ฐฑํ ์ ์๋ ๊ธฐ๋ฐ์ ๋ง๋ จํ๋ ๊ฒ์ ๋๋ค.
1. ์๋ณธ ์ํ ๋ฐฑ์ ๊ณผ ๋๊ด์ ์ ๋ฐ์ดํธ
Optimistic UI ๊ตฌํ์ ์ฒซ ๋ฒ์งธ ๋จ๊ณ๋ ํญ์ ์๋ณธ ์ํ๋ฅผ ๋ฐฑ์ ํด ๋๋ ๊ฒ์ ๋๋ค. ์ด๋ ๋ง์น ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํธ๋์ญ์ ๊ณผ ์ ์ฌํฉ๋๋ค. ์์ฒญ์ด ์คํจํ ๊ฒฝ์ฐ ์๋ ์ํ๋ก ๋๋๋ฆด ์ ์์ด์ผ ํฉ๋๋ค.
// ์ข์์ ๋ฒํผ ๊ตฌํ ์์
function LikeButton({ postId, initialIsLiked }) {
const [isLiked, setIsLiked] = useState(initialIsLiked);
const [isUpdating, setIsUpdating] = useState(false);
const handleLike = async () => {
// ์๋ณธ ์ํ ๋ฐฑ์
const originalState = isLiked;
// ๋๊ด์ ์
๋ฐ์ดํธ (์ฆ์ UI ๋ณ๊ฒฝ)
setIsLiked(!isLiked);
setIsUpdating(true);
try {
// ๋ฐฑ๊ทธ๋ผ์ด๋์์ API ์์ฒญ
const response = await api.toggleLike(postId, !isLiked);
// ์์ฒญ ์ฑ๊ณต, ์
๋ฐ์ดํธ ์ํ ์ข
๋ฃ
setIsUpdating(false);
} catch (error) {
// ์์ฒญ ์คํจ, ์๋ณธ ์ํ๋ก ๋กค๋ฐฑ
setIsLiked(originalState);
setIsUpdating(false);
alert('์ข์์ ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.');
}
};
return (
<button
onClick={handleLike}
disabled={isUpdating}
className={`like-button ${isLiked ? 'active' : ''} ${isUpdating ? 'updating' : ''}`}
>
{isLiked ? 'โฅ' : 'โก'} ์ข์์
</button>
);
}
์ด ์์์์ ์ฌ์ฉ์๊ฐ ์ข์์ ๋ฒํผ์ ํด๋ฆญํ๋ฉด UI๋ ์ฆ์ ์ ๋ฐ์ดํธ๋์ง๋ง, ๋ฐฑ๊ทธ๋ผ์ด๋์์ API ์์ฒญ์ด ์คํจํ ๊ฒฝ์ฐ ์๋ ์ํ๋ก ๋๋๋ฆฝ๋๋ค.
2. ๋ฆฌ์คํธ ๋ด ํญ๋ชฉ ์ ๋ฐ์ดํธ ์ฒ๋ฆฌ
๋ฆฌ์คํธ์์ ํญ๋ชฉ์ ์ถ๊ฐํ๊ฑฐ๋ ์ญ์ ํ ๋๋ ์์ ID๋ฅผ ์ฌ์ฉํ๋ ์ ๋ต์ด ํจ๊ณผ์ ์ ๋๋ค. ์์ฒญ ์ ์ ์์ ID๋ก ํญ๋ชฉ์ ์์ฑํ๊ณ , ์๋ฒ ์๋ต ํ ์ค์ ID๋ก ๊ต์ฒดํฉ๋๋ค.
function TodoList() {
const [todos, setTodos] = useState([]);
const [newTodoText, setNewTodoText] = useState('');
const handleAddTodo = async () => {
if (!newTodoText.trim()) return;
// ์์ ID ์์ฑ (ํด๋ผ์ด์ธํธ์์๋ง ์ฌ์ฉ)
const tempId = `temp-${Date.now()}`;
// ๋๊ด์ ์
๋ฐ์ดํธ๋ก ์์ ํญ๋ชฉ ์ถ๊ฐ
const tempTodo = {
id: tempId,
text: newTodoText,
completed: false,
isTemporary: true // ์์ ํญ๋ชฉ ํ์
};
setTodos([...todos, tempTodo]);
setNewTodoText('');
try {
// ๋ฐฑ๊ทธ๋ผ์ด๋์์ API ์์ฒญ
const response = await api.createTodo(newTodoText);
// ์์ ID๋ฅผ ์ค์ ์๋ฒ ID๋ก ๊ต์ฒด
setTodos(prevTodos => prevTodos.map(todo =>
todo.id === tempId
? { ...response.data, isTemporary: false }
: todo
));
} catch (error) {
// ์์ฒญ ์คํจ, ์์ ํญ๋ชฉ ์ ๊ฑฐ
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== tempId));
alert('ํ ์ผ ์ถ๊ฐ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.');
}
};
return (
<div>
<ul>
{todos.map(todo => (
<li key={todo.id} className={todo.isTemporary ? 'temporary' : ''}>
{todo.text}
</li>
))}
</ul>
<input
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
placeholder="์ ํ ์ผ"
/>
<button onClick={handleAddTodo}>์ถ๊ฐ</button>
</div>
);
}
์ด ์์์์๋ ์ฌ์ฉ์๊ฐ ์ ํ ์ผ์ ์ถ๊ฐํ ๋ ์์ ID๋ก ํญ๋ชฉ์ ์ฆ์ ํ์ํ๊ณ , ์๋ฒ ์๋ต์ ๋ฐ์ผ๋ฉด ์ค์ ID๋ก ์ ๋ฐ์ดํธํฉ๋๋ค.
3. ์ค๋ณต ์์ฒญ ๋ฐ ๊ฒฝ์ ์ํ(Race Condition) ์ฒ๋ฆฌ
์ฌ์ฉ์๊ฐ ๊ฐ์ ํญ๋ชฉ์ ๋น ๋ฅด๊ฒ ์ฌ๋ฌ ๋ฒ ์ก์ ์ ์ทจํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ข์์ ๋ฒํผ์ ๋น ๋ฅด๊ฒ ์ฌ๋ฌ ๋ฒ ๋๋ฅด๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ์ด๋ฐ ์ํฉ์์๋ ๋ง์ง๋ง ์์ฒญ๋ง ๋ฐ์ํ๊ฑฐ๋, ์์ฒญ ์์ฒด๋ฅผ ๋๋ฐ์ด์ค ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
function LikeButtonWithDebounce({ postId, initialIsLiked }) {
const [isLiked, setIsLiked] = useState(initialIsLiked);
const [pendingState, setPendingState] = useState(null);
// ๋๋ฐ์ด์ค ์ฒ๋ฆฌ๋ฅผ ์ํ useRef
const timerRef = useRef(null);
const latestRequestRef = useRef(null);
const handleLike = () => {
// ํ์ฌ ์๋ํ ์ํ (ํ ๊ธ)
const newLikedState = !isLiked;
// UI ์ฆ์ ์
๋ฐ์ดํธ
setIsLiked(newLikedState);
// ์ด์ ํ์ด๋จธ ์ทจ์
if (timerRef.current) {
clearTimeout(timerRef.current);
}
// ๋๋ฐ์ด์ค ์ ์ฉ (300ms)
timerRef.current = setTimeout(async () => {
// ๊ณ ์ ์์ฒญ ์๋ณ์ ์์ฑ
const requestId = Date.now();
latestRequestRef.current = requestId;
setPendingState(newLikedState);
try {
const response = await api.setLikeStatus(postId, newLikedState);
// ์ต์ ์์ฒญ๋ง ์ฒ๋ฆฌ (๊ฒฝ์ ์ํ ๋ฐฉ์ง)
if (requestId === latestRequestRef.current) {
setPendingState(null);
}
} catch (error) {
// ์ต์ ์์ฒญ๋ง ์ฒ๋ฆฌ
if (requestId === latestRequestRef.current) {
// ์คํจ ์ ๋กค๋ฐฑ
setIsLiked(!newLikedState);
setPendingState(null);
alert('์ข์์ ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.');
}
}
}, 300); // 300ms ๋๋ฐ์ด์ค
};
return (
<button
onClick={handleLike}
className={`like-button ${isLiked ? 'active' : ''} ${pendingState !== null ? 'pending' : ''}`}
>
{isLiked ? 'โฅ' : 'โก'} ์ข์์
</button>
);
}
์ด ๊ตฌํ์์๋ ์ฌ์ฉ์๊ฐ ์ข์์ ๋ฒํผ์ ๋น ๋ฅด๊ฒ ์ฌ๋ฌ ๋ฒ ๋๋ฅผ ๊ฒฝ์ฐ, ๋ง์ง๋ง ์ํ๋ง ์๋ฒ์ ๋ฐ์๋ฉ๋๋ค. ๋ํ ์์ฒญ ์๋ณ์๋ฅผ ์ฌ์ฉํด ๊ฒฝ์ ์ํ๋ฅผ ๋ฐฉ์งํฉ๋๋ค. Optimistic UI๋ ์ด๋ฌํ ํจํด๋ค์ ํจ๊ณผ์ ์ผ๋ก ์กฐํฉํ์ฌ ์ฌ์ฉ์์๊ฒ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋ฉด์๋, ๋ฐ์ดํฐ ์ ํฉ์ฑ์ ์ ์งํ๋ ๋ฐฉํฅ์ผ๋ก ๊ตฌํ๋์ด์ผ ํฉ๋๋ค.
๋ฆฌ์กํธ ํ๋ Optimistic UI๋ฅผ ์ค์ํ๊ฒ ์๊ฐํฉ๋๋ค
๋ฆฌ์กํธ ํ์ Optimistic UI์ ์ค์์ฑ์ ์ธ์ํ๊ณ ๋ณ๋์ ํ
์ ๋์
ํ์ต๋๋ค. React 19์์ ์ถ๊ฐ๋ useOptimistic
ํ
์ ๋๊ด์ ์
๋ฐ์ดํธ๋ฅผ ์ ์ธ์ ์ด๊ณ ์์ ํ๊ฒ ๊ตฌํํ ์ ์๊ฒ ํด์ค๋๋ค.
useOptimistic ํ ์ ๊ธฐ๋ณธ ๊ฐ๋
useOptimistic
ํ
์ ์๋ฒ ์ํ์ ๋๊ด์ ์ํ๋ฅผ ๋ช
ํํ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํฉ๋๋ค. ์ด ํจํด์ ์ํคํ
์ฒ๋ ๋ ๊ฐ์ ๋ ์ด์ด๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
์๋ฒ ์์ฒญ <-------> ๋ฆฌ์กํธ ์ํ(๋๊ธฐํ)
|
|
v
์ตํฐ๋ฏธ์คํฑ ์ํ(๋๊ธฐํ)
์ฒซ ๋ฒ์งธ ๋ ์ด์ด๋ ์๋ฒ์ ๋ฆฌ์กํธ ์ํ ๊ฐ์ ๋๊ธฐํ๋ฅผ ๋ด๋นํ๋ฉฐ, ๋ ๋ฒ์งธ ๋ ์ด์ด๋ ๋ฆฌ์กํธ ์ํ์ ๋๊ด์ ์ํ ๊ฐ์ ๋๊ธฐํ๋ฅผ ๋ด๋นํฉ๋๋ค. ์ด๋ ๊ฒ ๋ถ๋ฆฌํจ์ผ๋ก์จ ์๋ฒ์์ ํต์ ๋ก์ง๊ณผ ๋๊ด์ UI ์ ๋ฐ์ดํธ ๋ก์ง์ ์์ ํ ๋ถ๋ฆฌํ ์ ์์ต๋๋ค.
๋ฆฌ์กํธ ํ์ด ์ด๋ฐ ๋ฐฉ์์ผ๋ก ์ค๊ณํ ์ด์ ๋ฅผ ์ถ์ธกํด ๋ณด๋ฉด, ์ด๋ฏธ ์กด์ฌํ๋ ์๋ฒ์ ํด๋ผ์ด์ธํธ ์ํ ๋๊ธฐํ ์ฝ๋์ ๋๊ด์ ์ ๋ฐ์ดํธ ๋ก์ง์ด ๊ฐํ๊ฒ ๊ฒฐํฉ๋์ง ์๋๋ก ํ๊ธฐ ์ํจ์ ๋๋ค. ์ด๋ฐ ๋ถ๋ฆฌ๋ ์ฝ๋๋ฅผ ๋ ์ ์ฐํ๊ฒ ๋ง๋ค๊ณ , ๊ธฐ์กด ์ฝ๋๋ฅผ ํฌ๊ฒ ์์ ํ์ง ์๊ณ ๋ ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ์ ์ฉํ ์ ์๊ฒ ํด์ค๋๋ค.
์ด ์ค๊ณ์ ํต์ฌ์ ์๋ณธ ์ํ(์๋ฒ ์ํ)๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋๊ด์ ์ํ๋ ์๋์ผ๋ก ๋๊ธฐํ๋๋ค๋ ์ ์ ๋๋ค. ๋์์ ์ฌ์ฉ์ ์ก์ ์ ๋ฐ๋ฅธ ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ์๋ฒ ์๋ต ์ ์ ์ฆ์ ์ ์ฉํ ์ ์์ต๋๋ค.
useOptimistic์ ๋ด๋ถ ๊ตฌํ ์์
๋จ์ํ ์๊ฐํ๋ฉด useOptimistic
์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํ๋ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
function useOptimisticSimple(state, updateFn) {
const [optimisticValue, setOptimisticValue] = useState(null);
// ์๋ฒ ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋๊ด์ ์ํ๋ ์
๋ฐ์ดํธ
useEffect(() => {
// state๊ฐ ๋ฐ๋๋ฉด ๋๊ธฐํ ๋ก์ง ์คํ
}, [state]);
const optimisticState =
optimisticValue !== null ? updateFn(state, optimisticValue) : state;
return [optimisticState, setOptimisticValue];
}
ํ์ง๋ง ์ด๋ฌํ ๋จ์ ๊ตฌํ์๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋น ๋ฅด๊ฒ ๋ฐ๋ณต๋๋ ์์ฒญ์์ ๋๊ด์ ์ํ์ ์๋ฒ ์ํ ๊ฐ์ ๋๊ธฐํ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ์ง ์์ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ ๋น ๋ฅด๊ฒ 5๊ฐ์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ ์๋๋ฆฌ์ค๋ฅผ ์๊ฐํด ๋ณด์ธ์.
์ฌ์ฉ์๊ฐ 5๊ฐ์ ๋ฉ์์ง๋ฅผ ์ฐ์์ผ๋ก ๋ณด๋ ๋๋ค.
๋๊ด์ ์ํ๋ ์ฆ์ 5๊ฐ ๋ฉ์์ง๊ฐ ๋ชจ๋; ๋ค์ด์๋ ์ํ์ ๋๋ค.
์๋ฒ ์๋ต์ด ์์ฐจ์ ์ผ๋ก ๋์ฐฉํฉ๋๋ค (๋ฉ์์ง 1, ๋ฉ์์ง 2, ...)
๋จ์ ๊ตฌํ์์๋ ์๋ฒ ์ํ๊ฐ ์ ๋ฐ์ดํธ๋ ๋๋ง๋ค
useEffect
๊ฐ ๋ฐ๋๋์ด ๋๊ด์ ์ํ๋ฅผ ์ด๊ธฐํํ๊ฒ ๋ฉ๋๋ค.๊ฒฐ๊ณผ์ ์ผ๋ก UI๋ ๋ฉ์์ง 1 โ ๋ฉ์์ง 1,2 โ ๋ฉ์์ง 1, 2, 3 โ ... ์์ผ๋ก ๋ค๋ฆ๊ฒ ๋ฐ๋๊ฒ ๋์ด, ์ด๋ฏธ UI์ ํ์๋ ๋๊ด์ ์ ๋ฐ์ดํธ(๋ฉ์์ง 4, ๋ฉ์์ง 5)๊ฐ ์ฌ๋ผ์ก๋ค๊ฐ ๋ํ๋๋ ๊น๋นก์์ด ๋ฐ์ํฉ๋๋ค.
์ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ํดํ๋ ์ฌ๊ฐํ ๋ฌธ์ ์ ๋๋ค.
๋ฆฌ์กํธ์ ์ค์ ๊ตฌํ
์ค์ useOptimistic
ํ
์ ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋จ์ํ useEffect
๋ณด๋ค ํจ์ฌ ๋ณต์กํ ๊ตฌํ์ ๊ฐ๊ณ ์์ต๋๋ค. ๋ฆฌ์กํธ์ Fiber ์ํคํ
์ฒ์ ํตํฉ๋์ด ์๋ฒ ์ํ์ ๋๊ด์ ์ํ์ ์
๋ฐ์ดํธ๋ฅผ ์ ๊ตํ๊ฒ ํ์ํ๊ณ ๋ณํฉํฉ๋๋ค.
๋ฆฌ์กํธ ํ์ ๋๊ด์ ์ ๋ฐ์ดํธ์ ์๋ฒ ์ํ ์ ๋ฐ์ดํธ ๊ฐ์ ๊ด๊ณ๋ฅผ ์ถ์ ํ๊ณ , ์๋ฒ ์ํ๊ฐ ์ ๋ฐ์ดํธ๋ ๋ ์ด๋ฏธ ์ ์ฉ๋ ๋๊ด์ ์ ๋ฐ์ดํธ๊ฐ ์์ค๋์ง ์๋๋ก ํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฌ๋ฌ ๊ฐ์ ์ ๋ฐ์ดํธ๊ฐ ์งํ ์ค์ผ ๋๋ UI๊ฐ ์ผ๊ด๋๊ณ ์์ฐ์ค๋ฝ๊ฒ ์ ์ง๋ฉ๋๋ค.
์ด๋ ๊ฒ useOptimistic
์ ๋จ์ํ ์ํ ๊ด๋ฆฌ ์ด์์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ, ๋ณต์กํ ๋น๋๊ธฐ ์ํ ์ ํ์ ์ ์ธ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ ํด์ค๋๋ค. ๊ฐ๋ฐ์๋ ๋๊ด์ ์ํ์ ๋ชจ์๋ง ์ ์ํ๋ฉด ๋๊ณ , ๋ณต์กํ ๋๊ธฐํ ๋ก์ง์ ๋ฆฌ์กํธ๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.

๋ ๋์ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ก ๊ฐ๊ธฐ
Optimistic UI๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฒ์ด์ง๋ง, ๋ชจ๋ ์ํฉ์ ์ ํฉํ ๊ฒ์ ์๋๋๋ค. ๊ฒฐ์ , ๊ณ์ ์ญ์ , ์ค์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ๊ณผ ๊ฐ์ ์ค์ํ ์์ ์์๋ ์ฌ์ฉ์์๊ฒ ์ ํํ ์งํ ์ํฉ์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ์ ๋ขฐ๋๋ฅผ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋๋ค. ๋ํ ํ์ผ ์ ๋ก๋๋ ๋ณต์กํ ์๋ฒ ์ฒ๋ฆฌ์ ๊ฐ์ด ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์์๋ ๋ก๋ฉ ์ธ๋์ผ์ดํฐ๋ฅผ ํ์ํ๋ ๊ฒ์ด ๋ ์ ์ ํ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ์ข์์, ๋๊ธ, ๊ฐ๋จํ ์ค์ ๋ณ๊ฒฝ ๋ฑ ๋ง์ ์ผ์์ ์ธ ์ํธ์์ฉ์์ ๋๊ด์ ์ ๋ฐ์ดํธ๋ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค. ์ฌ์ฉ์๋ ๋ฒํผ์ ํด๋ฆญํ๊ณ ์ฆ์ ๋ฐ์ํ๋ ์ธํฐํ์ด์ค์ ๋ง์กฑ๊ฐ์ ๋๋ผ๋ฉฐ, ์ด๋ ์๋น์ค์ ๋ํ ์ ๋ฐ์ ์ธ ์ธ์์ ์ข๊ฒ ๋ง๋ญ๋๋ค. ์ ๋ ํ์ฌ ํ๋ก์ ํธ์์๋ ์ด๋ฐ ๋ถ๋ถ์ ์ฑ๊ธฐ๋ ค๊ณ ํญ์ ๋ ธ๋ ฅํ๊ณ ์์ต๋๋ค. ํนํ ์ฌ์ฉ์ ์ธํฐ๋์ ์ด ๋ง์ ๊ธฐ๋ฅ์์๋ ๋๊ด์ ์ ๋ฐ์ดํธ๋ฅผ ์ ์ฉํ์ฌ ์ฒด๊ฐ ์๋๋ฅผ ๋์ด๋ ์์ ์ ์งํํ๊ณ , ์ค์ ๋ก ์ฌ์ฉ์ ๋ง์กฑ๋ ํฅ์์ผ๋ก ์ด์ด์ก์ต๋๋ค.
ํ๋ ์น ๊ฐ๋ฐ ์ํ๊ณ์์๋ ์ด๋ฌํ ํจํด์ ์ฝ๊ฒ ์ ์ฉํ ์ ์๋๋ก ๋์์ฃผ๋ ๋๊ตฌ๋ค์ด ๋ง์ด ๋ฑ์ฅํ์ต๋๋ค. React Query(TanStack Query)์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ๋๊ด์ ์ ๋ฐ์ดํธ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ผ๋ก ์ง์ํ๋ฉฐ, ๋ณต์กํ ๋น๋๊ธฐ ์ํ ๊ด๋ฆฌ๋ฅผ ๋จ์ํํด ์ค๋๋ค. ๋จ์ํ ํํ๋ถํฐ ์์ํ์ฌ ์ ์ฐจ ๋ณต์กํ ์ํฉ์ผ๋ก ํ์ฅํด ๋๊ฐ๋ฉด์, ๊ธฐ๋ฅ ์๋์ ๋์ด ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ ์ ๊ณ ๋ฏผํ๋ ๊ฐ๋ฐ์๋ก ์ฑ์ฅํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ ํจํด์ ๋น์ ์ ์ด๋ ฅ์์ ํฌํธํด๋ฆฌ์ค์๋ ์ข์ ์ฌ๋ก๊ฐ ๋ ๊ฒ์ ๋๋ค.
๊ฒฐ๊ตญ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ก์ ์ฌ๋ฌ๋ถ์ "์ฑ๋ฅ ์ต์ ํ" ๊ฒฝํ์ ๋น๋นํ ์ดํํ ์ ์๊ฒ ๋ฉ๋๋ค. ๋ฐฑ์๋์ ์ค์ ์ฑ๋ฅ๊ณผ ๋ฌด๊ดํ๊ฒ, ์ฌ์ฉ์๋ ์ฌ๋ฌ๋ถ์ "ํธ๋ฆญ" ๋๋ถ์ ๋น ๋ฅธ ๋์์ ๊ฒฝํํ ํ ๋๊น์.
ยฉ๏ธ์์ฆIT์ ๋ชจ๋ ์ฝํ ์ธ ๋ ์ ์๊ถ๋ฒ์ ๋ณดํธ๋ฅผ ๋ฐ๋ ๋ฐ, ๋ฌด๋จ ์ ์ฌ์ ๋ณต์ฌ, ๋ฐฐํฌ ๋ฑ์ ๊ธํฉ๋๋ค.