Today's AntipatternAll Posts
ํ…Œ๋งˆ
GitHubToday's AntipatternAll Posts

์•ˆํ‹ฐํŒจํ„ด์„ ํ†ตํ•ด ๋” ๋‚˜์€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›Œ๋ณด์„ธ์š”. ๊ฐœ๋ฐœ์ž๋“ค์ด ์‹ค์ˆ˜ํ•˜๋Š” ํŒจํ„ด๋“ค์„ ๋ถ„์„ํ•˜๊ณ  ๊ฐœ์„ ๋ฐฉ์•ˆ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.

์—ฐ๊ฒฐํ•˜๊ธฐ

ยฉ 2025 Smelly.dev All rights reserved.

July 8, 2025

๐Ÿ› ๋ถˆ๋ณ€์„ฑ(Immutability) ๋ฌด์‹œํ•œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ: ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ UI์™€ ๋””๋ฒ„๊น…์˜ ์•…๋ชฝ

JavaScript
React
์ƒํƒœ๊ด€๋ฆฌ
์„ฑ๋Šฅ
์ปดํฌ๋„ŒํŠธ
์—๋Ÿฌ์ฒ˜๋ฆฌ

Summary

React์—์„œ ์ƒํƒœ(๊ฐ์ฒด/๋ฐฐ์—ด)๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋ฉด ์–•์€ ๋น„๊ต๋กœ ์ธํ•ด ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•ด UI๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๊ณ  ๋ฒ„๊ทธ๋ฅผ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์Šคํ”„๋ ˆ๋“œ ๋ฌธ๋ฒ• ๋“ฑ์œผ๋กœ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Why Wrong?

React๋ฅผ ๋น„๋กฏํ•œ ๋Œ€๋ถ€๋ถ„์˜ ํ˜„๋Œ€ ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•ด '๋ถˆ๋ณ€์„ฑ(Immutability)' ๊ฐœ๋…์„ ์ค‘์š”ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ React๋Š” ์ƒํƒœ ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ๋•Œ, ๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ '๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ'๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์–•์€ ๋น„๊ต(shallow comparison)๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ธฐ์กด ์ƒํƒœ ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด์„ ์ง์ ‘ ์ˆ˜์ •(๋ฎคํ…Œ์ด์…˜)ํ•˜๋ฉด, ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€๋˜๊ธฐ ๋•Œ๋ฌธ์— React๋Š” ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ stale(์˜ค๋ž˜๋œ) UI๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋˜๋ฉฐ, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์™€ ํ™”๋ฉด ๊ฐ„์˜ ๋ถˆ์ผ์น˜๋ฅผ ์ดˆ๋ž˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ , ์›์ธ์„ ์ถ”์ ํ•˜๊ธฐ ๋งค์šฐ ์–ด๋ ค์šด ๋””๋ฒ„๊น… ์ง€์˜ฅ์— ๋น ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, React.memo๋‚˜ useMemo์™€ ๊ฐ™์€ ์ตœ์ ํ™” ๊ธฐ๋ฒ•๋„ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์•„ ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง์„ ์œ ๋ฐœํ•˜๊ฑฐ๋‚˜, ๋ฐ˜๋Œ€๋กœ ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๋Š” ๋“ฑ ์„ฑ๋Šฅ์—๋„ ์•…์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

How to Fix?

์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ๋Š” ํ•ญ์ƒ ๊ธฐ์กด ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ '์ƒˆ๋กœ์šด' ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด์„ ์ƒ์„ฑํ•˜์—ฌ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. JavaScript์˜ ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น(Spread syntax: ...), map, filter, slice, concat ๋“ฑ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์›๋ณธ์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š” ๋ฐฉ์‹์œผ๋กœ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ณต์žกํ•˜๊ณ  ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด์˜ ๊ฒฝ์šฐ, Immer.js์™€ ๊ฐ™์€ ๋ถˆ๋ณ€์„ฑ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋งˆ์น˜ ๊ฐ€๋ณ€์ ์ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ํŽธ๋ฆฌํ•˜๊ฒŒ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๊ณ  ์‹ค์ˆ˜๋กœ ์ธํ•œ ๋ฎคํ…Œ์ด์…˜์„ ๋ฐฉ์ง€ํ•˜์—ฌ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

Before Code (Bad)

import React, { useState } from 'react';

function BuggyList() {
  const [items, setItems] = useState([ { id: 1, text: '์ฒซ ๋ฒˆ์งธ ์•„์ดํ…œ', completed: false } ]);

  const handleAddItem = () => {
    // โŒ ์ง์ ‘ ๋ฐฐ์—ด์„ ๋ณ€๊ฒฝ (๋ฎคํ…Œ์ด์…˜)
    items.push({ id: items.length + 1, text: `์•„์ดํ…œ ${items.length + 1}`, completed: false });
    setItems(items); // React๋Š” ์ด ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Œ (์ฐธ์กฐ๊ฐ€ ๋™์ผ)
  };

  const handleToggleComplete = (id) => {
    // โŒ ์ง์ ‘ ๊ฐ์ฒด ์†์„ฑ์„ ๋ณ€๊ฒฝ (๋ฎคํ…Œ์ด์…˜)
    const itemToUpdate = items.find(item => item.id === id);
    if (itemToUpdate) {
      itemToUpdate.completed = !itemToUpdate.completed;
      setItems(items); // React๋Š” ์ด ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Œ (์ฐธ์กฐ๊ฐ€ ๋™์ผ)
    }
  };

  return (
    <div>
      <h2>๋ฒ„๊ทธ๊ฐ€ ์žˆ๋Š” ToDo ๋ฆฌ์ŠคํŠธ</h2>
      <button onClick={handleAddItem}>์•„์ดํ…œ ์ถ”๊ฐ€</button>
      <ul>
        {items.map(item => (
          <li key={item.id} style={{ textDecoration: item.completed ? 'line-through' : 'none' }}>
            {item.text}
            <input
              type="checkbox"
              checked={item.completed}
              onChange={() => handleToggleComplete(item.id)}
            />
          </li>
        ))}
      </ul>
    </div>
  );
}

export default BuggyList;

After Code (Good)

import React, { useState } => {
  const [items, setItems] = useState([ { id: 1, text: '์ฒซ ๋ฒˆ์งธ ์•„์ดํ…œ', completed: false } ]);

  const handleAddItem = () => {
    // โœ… ์Šคํ”„๋ ˆ๋“œ ๋ฌธ๋ฒ•์œผ๋กœ ์ƒˆ๋กœ์šด ๋ฐฐ์—ด ์ƒ์„ฑ
    setItems(prevItems => [
      ...prevItems,
      { id: prevItems.length + 1, text: `์•„์ดํ…œ ${prevItems.length + 1}`, completed: false }
    ]);
  };

  const handleToggleComplete = (id) => {
    // โœ… map์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋ฐฐ์—ด๊ณผ ์ƒˆ๋กœ์šด ๊ฐ์ฒด ์ƒ์„ฑ
    setItems(prevItems =>
      prevItems.map(item =>
        item.id === id ? { ...item, completed: !item.completed } : item
      )
    );
  };

  return (
    <div>
      <h2>๋ถˆ๋ณ€์„ฑ์„ ์ง€ํ‚จ ToDo ๋ฆฌ์ŠคํŠธ</h2>
      <button onClick={handleAddItem}>์•„์ดํ…œ ์ถ”๊ฐ€</button>
      <ul>
        {items.map(item => (
          <li key={item.id} style={{ textDecoration: item.completed ? 'line-through' : 'none' }}>
            {item.text}
            <input
              type="checkbox"
              checked={item.completed}
              onChange={() => handleToggleComplete(item.id)}
            />
          </li>
        ))}
      </ul>
    </div>
  );
}

export default FixedList;

You Might Also Like

React์—์„œ ๊ฐ์ฒด ์ƒํƒœ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ณต์‹์ ์ธ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์Šคํ”„๋ ˆ๋“œ ๋ฌธ๋ฒ•์„ ํ™œ์šฉํ•œ ์ƒˆ๋กœ์šด ๊ฐ์ฒด ์ƒ์„ฑ์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•ฉ๋‹ˆ๋‹ค.
https://react.dev/learn/updating-objects-in-state
React์—์„œ ๋ฐฐ์—ด ์ƒํƒœ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ณต์‹์ ์ธ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. `map`, `filter`, ์Šคํ”„๋ ˆ๋“œ ๋ฌธ๋ฒ• ๋“ฑ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐฐ์—ด ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ๋ฒ•์„ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค.
https://react.dev/learn/updating-arrays-in-state
๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด์˜ ๋ณต์‚ฌ๋ณธ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ณ  ์ƒˆ๋กœ์šด ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๊ธฐ์กด ์†์„ฑ์„ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” JavaScript์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ธ ์Šคํ”„๋ ˆ๋“œ ๋ฌธ๋ฒ•์— ๋Œ€ํ•œ ์ƒ์„ธํ•œ ์„ค๋ช…์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax