Today's AntipatternAll Posts
ν…Œλ§ˆ
GitHubToday's AntipatternAll Posts

μ•ˆν‹°νŒ¨ν„΄μ„ 톡해 더 λ‚˜μ€ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 방법을 λ°°μ›Œλ³΄μ„Έμš”. κ°œλ°œμžλ“€μ΄ μ‹€μˆ˜ν•˜λŠ” νŒ¨ν„΄λ“€μ„ λΆ„μ„ν•˜κ³  κ°œμ„ λ°©μ•ˆμ„ μ œμ‹œν•©λ‹ˆλ‹€.

μ—°κ²°ν•˜κΈ°

Β© 2025 Smelly.dev All rights reserved.

August 5, 2025

🎣 μ§€λ‚˜μΉœ 낙관적 μ—…λ°μ΄νŠΈ (Optimistic Updates) λ§Ήμ‹ : UI λΆˆμΌμΉ˜μ™€ 데이터 무결성 훼손

React
JavaScript
UX
μ—λŸ¬μ²˜λ¦¬
λΉ„λ™κΈ°μ²˜λ¦¬
μƒνƒœκ΄€λ¦¬

Summary

낙관적 μ—…λ°μ΄νŠΈλŠ” μ‚¬μš©μž κ²½ν—˜μ„ ν–₯μƒμ‹œν‚€μ§€λ§Œ, λ„€νŠΈμ›Œν¬ μ—λŸ¬ 처리 미흑 μ‹œ UI λΆˆμΌμΉ˜μ™€ 데이터 손싀을 μ΄ˆλž˜ν•©λ‹ˆλ‹€. μ—λŸ¬ 핸듀링, λ‘€λ°± κΈ°λŠ₯, μ„œλ²„ μƒνƒœ 동기화λ₯Ό 톡해 데이터 무결성을 보μž₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Why Wrong?

낙관적 μ—…λ°μ΄νŠΈλŠ” μ‚¬μš©μž κ²½ν—˜μ„ κ°œμ„ ν•˜κΈ° μœ„ν•΄ μ„œλ²„ 응닡을 기닀리지 μ•Šκ³  UIλ₯Ό μ¦‰μ‹œ μ—…λ°μ΄νŠΈν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. ν•˜μ§€λ§Œ λ„€νŠΈμ›Œν¬ λΆˆμ•ˆμ •μ΄λ‚˜ μ„œλ²„ μ—λŸ¬λ‘œ 인해 μ‹€μ œ μ—…λ°μ΄νŠΈκ°€ μ‹€νŒ¨ν•  경우, UIλŠ” 잘λͺ»λœ μƒνƒœλ₯Ό λ³΄μ—¬μ£Όκ²Œ λ©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 상황을 μ œλŒ€λ‘œ μ²˜λ¦¬ν•˜μ§€ λͺ»ν•˜λ©΄ 데이터 뢈일치, μ‚¬μš©μž ν˜Όλž€, 심지어 데이터 μ†μ‹€κΉŒμ§€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. 낙관적 μ—…λ°μ΄νŠΈλŠ” λ„€νŠΈμ›Œν¬ μš”μ²­μ΄ μ‹€νŒ¨ν•  κ°€λŠ₯성을 κ°„κ³Όν•˜κ³ , μ—λŸ¬ 핸듀링 둜직 없이 λ¬΄λΆ„λ³„ν•˜κ²Œ μ‚¬μš©λ  λ•Œ 큰 문제λ₯Ό μ•ΌκΈ°ν•©λ‹ˆλ‹€.

How to Fix?

낙관적 μ—…λ°μ΄νŠΈλ₯Ό μ μš©ν•  λ•ŒλŠ” λ°˜λ“œμ‹œ μ—λŸ¬ 핸듀링 λ‘œμ§μ„ 포함해야 ν•©λ‹ˆλ‹€. λ„€νŠΈμ›Œν¬ μš”μ²­ μ‹€νŒ¨ μ‹œ UIλ₯Ό 이전 μƒνƒœλ‘œ 되돌리고, μ‚¬μš©μžμ—κ²Œ μ μ ˆν•œ ν”Όλ“œλ°±(예: μ—λŸ¬ λ©”μ‹œμ§€)을 μ œκ³΅ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ˜ν•œ, λ°μ΄ν„°μ˜ μ΅œμ’… μƒνƒœλŠ” μ„œλ²„μ—μ„œ κ²€μ¦ν•˜κ³  ν΄λΌμ΄μ–ΈνŠΈμ— λ°˜μ˜ν•΄μ•Ό 데이터 무결성을 μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ‘€λ°± κΈ°λŠ₯, μž¬μ‹œλ„ 둜직, 그리고 μ„œλ²„μ™€μ˜ μƒνƒœ 동기화 λ©”μ»€λ‹ˆμ¦˜μ„ κ΅¬ν˜„ν•˜μ—¬ 낙관적 μ—…λ°μ΄νŠΈμ˜ μœ„ν—˜μ„ μ΅œμ†Œν™”ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Before Code (Bad)

// 낙관적 μ—…λ°μ΄νŠΈ 적용 (μ—λŸ¬ 처리 μ—†μŒ)
import React, { useState } from 'react';

function LikeButton() {
  const [likes, setLikes] = useState(0);

  const handleLike = async () => {
    setLikes(likes + 1); // μ¦‰μ‹œ UI μ—…λ°μ΄νŠΈ (낙관적 μ—…λ°μ΄νŠΈ)
    try {
      await fetch('/api/like', { method: 'POST' });
      // μ„œλ²„ μš”μ²­ 성곡 μ‹œ μ•„λ¬΄λŸ° λ™μž‘ μ—†μŒ
    } catch (error) {
      // μ—λŸ¬ λ°œμƒ μ‹œ μ•„λ¬΄λŸ° λ™μž‘ μ—†μŒ (문제!)
      console.error('Like request failed:', error);
    }
  };

  return (
    <button onClick={handleLike}>
      Like ({likes})
    </button>
  );
}

export default LikeButton;

After Code (Good)

// 낙관적 μ—…λ°μ΄νŠΈ 적용 (μ—λŸ¬ 처리 포함)
import React, { useState } from 'react';

function LikeButton() {
  const [likes, setLikes] = useState(0);
  const [isLiking, setIsLiking] = useState(false);

  const handleLike = async () => {
    if (isLiking) return;

    setIsLiking(true);
    const previousLikes = likes;
    setLikes(likes + 1); // μ¦‰μ‹œ UI μ—…λ°μ΄νŠΈ (낙관적 μ—…λ°μ΄νŠΈ)

    try {
      await fetch('/api/like', { method: 'POST' });
      setIsLiking(false);
    } catch (error) {
      console.error('Like request failed:', error);
      setLikes(previousLikes); // UI λ‘€λ°±
      setIsLiking(false);
      alert('Like failed. Please try again.'); // μ‚¬μš©μžμ—κ²Œ μ•Œλ¦Ό
    }
  };

  return (
    <button onClick={handleLike} disabled={isLiking}>
      Like ({likes})
    </button>
  );
}

export default LikeButton;