Choosing the State Structure

date
2026-01-30
order
2
link
  • Choosing the State Structure โ€“ React

You will learn

  • ์–ธ์ œ ํ•˜๋‚˜์˜ ํ˜น์€ ์—ฌ๋Ÿฌ๊ฐœ์˜ state ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”์ง€
  • state๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ํ”ผํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ๋“ค
  • ํ”ํ•œ state ๊ตฌ์กฐ์˜ ๋ฌธ์ œ๋“ค์€ ๋ฌด์—‡์ด๊ณ  ์–ด๋–ป๊ฒŒ ๊ณ ์น˜๋Š”์ง€

state ๊ตฌ์กฐํ™”์˜ ์›์น™

state ๊ตฌ์กฐํ™”์˜ ์™„์„ฑ๋„๋Š” ์ˆ˜์ •ํ•˜๊ณ  ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์ฆ๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ์™€ ๊ดด๋กœ์šด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

  1. ๊ด€๋ จ์žˆ๋Š” state ๊ทธ๋ฃนํ™” - ๋‘ ๊ฐœ ์ด์ƒ์˜ state ๋ณ€์ˆ˜๊ฐ€ ์–ธ์ œ๋‚˜ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค๋ฉด ์ด ๋ณ€์ˆ˜๋ฅผ ๋‹จ์ผ state ๊ฐ์ฒด๋กœ ํ•ฉ์นœ๋‹ค.
  2. ๋ชจ์ˆœ๋œ state ํ”ผํ•˜๊ธฐ - ๋‘ ๊ฐœ๊ฐ€ ๋™์‹œ์— true์ผ ์ˆ˜ ์—†๋Š” state๋“ค์ฒ˜๋Ÿผ ์„œ๋กœ ๋ชจ์ˆœ์„ ์ผ์œผํ‚ค๋Š” state๊ฐ€ ์žˆ์œผ๋ฉด ์ˆ˜์ •ํ•œ๋‹ค.
  3. ๋ถˆํ•„์š”ํ•œ state ๋งŒ๋“ค์ง€ ์•Š๊ธฐ - ์—ฌ๊ธฐ์„œ "๋ถˆํ•„์š”"ํ•˜๋‹ค๋Š” ๋ง์€ ๋‹ค๋ฅธ state๋‚˜ props์—์„œ ๊ณ„์‚ฐ๋  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋ผ๋ฉด state๋กœ ํ•ด๋‹น ์ •๋ณด๋ฅผ ๊ด€๋ฆฌ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๋œป์ด๋‹ค.
  4. state ์ค‘๋ณต ํ”ผํ•˜๊ธฐ - ๊ฐ™์€ ์ •๋ณด๊ฐ€ ์—ฌ๋Ÿฌ state์— ์ค‘๋ณต๋˜์–ด ์กด์žฌํ•  ๊ฒฝ์šฐ ๋™๊ธฐํ™”ํ•˜๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ํ”ผํ•ด์•ผ ํ•œ๋‹ค.
  5. ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ(nested) state ํ”ผํ•˜๊ธฐ - ๊นŠ์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด ํ˜•ํƒœ์˜ state๋Š” ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์‰ฝ์ง€ ์•Š๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ€๋Šฅํ•œ ํ•œ ํ‰ํƒ„ํ•œ(flat) ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๋„๋ก ์„ค๊ณ„ํ•œ๋‹ค. (ํ•˜์ด๋ผ์ดํŠธ ํ•˜์ง€ ์•Š์€ ์›์น™์€ ์ด์ „ ๋ฌธ์„œ์—์„œ ๋‹ค๋ฃฌ ์ค‘๋ณต๋œ ๋‚ด์šฉ์ž„)

1. state ๊ทธ๋ฃนํ™”

์–ธ์ œ ์—ฌ๋Ÿฌ ์ •๋ณด๋ฅผ ํ•˜๋‚˜์˜ state ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•ด์•ผํ•˜๊ณ  ์–ธ์ œ๋Š” ๊ฐ๊ฐ ๋”ฐ๋กœ ์—ฌ๋Ÿฌ๊ฐœ์˜ state ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ? ๋งˆ์šฐ์Šค ์ขŒํ‘œ์ฒ˜๋Ÿผ ๊ฑฐ์˜ ํ•ญ์ƒ ๋™์‹œ์— ์—…๋ฐ์ดํŠธ๋˜๋Š” ์ •๋ณด๋“ค์ธ ๊ฒฝ์šฐ ๋ณ„๋„์˜ state๋กœ ๋‘๋Š”๊ฒƒ๋ณด๋‹ค ํ•˜๋‚˜๋กœ ๋ณ‘ํ•ฉ์‹œํ‚ค๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

// X
const [x, setX] = useState(0);
const [y, setY] = useState(0);

// O
const [position, setPosition] = useState({ x: 0, y: 0 });

return (
    <div
      onPointerMove={e => {
        setPosition({
          x: e.clientX,
          y: e.clientY
        });
      }}
    //...

state ๊ฐ์ฒด์˜ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์—†๋‹ค.

setPosition({x:100});

์ด๋ ‡๊ฒŒ position ์˜ xํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์—†๋‹ค. ํ•ญ์ƒ ๊ธฐ์กด position์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

setPosition({...position, x:100});

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด x ํ•„๋“œ๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ๊ฐ’๋“ค์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ x์˜ ๊ฐ’๋งŒ ๋ฐ”๊ฟ”์„œ ์ƒˆ๋กœ์šด state๋กœ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

2. ๋ชจ์ˆœ๋˜๋Š” state

export default function FeedbackForm() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  const [isSent, setIsSent] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    setIsSending(true);
    await sendMessage(text);
    setIsSending(false);
    setIsSent(true);
  }
  
  //...

์œ„์˜ ์ฝ”๋“œ๋Š” ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜์ง€๋งŒ isSending๊ณผ isSent๋Š” ๋ชจ์ˆœ๋˜๋Š” ์ƒํ™ฉ์„ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด setIsSending(false)๋ฅผ ํ–ˆ์ง€๋งŒ setIsSent(true) ํ•ด์ฃผ๋Š”๊ฒƒ์„ ๊นœ๋นกํ•  ์ˆ˜๋„ ์žˆ๊ณ , isSending๊ณผ isSent๊ฐ€ ๋™์‹œ์— ์ฐธ์ด๋˜๋Š” ์—ญ์„ค์„ ๋งŒ๋“ค์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ž˜์„œ status๋กœ state๋ฅผ ํ†ตํ•ฉํ•˜๊ณ  sending, sent, typing(์ดˆ๊ธฐ๊ฐ’) ์ค‘์— ํ•˜๋‚˜์˜ ๊ฐ’์„ ๊ฐ€์ง€๋„๋ก ํ•˜๋ฉด ๋œ๋‹ค.

์ƒ์ˆ˜ ์„ ์–ธ

๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด์„œ(isSendingํ˜น์€ isSent๊ฐ€ ํ™•์‹คํžˆ ์ฝ๊ธฐ ํŽธํ•˜๋‹ค) ์ด๋ ‡๊ฒŒ ์ƒ์ˆ˜๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค.

const isSending = status === 'sending';
const isSent = status === 'sent';

3. ๋ถˆํ•„์š”ํ•œ state

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + ' ' + lastName);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
    setFullName(firstName + ' ' + e.target.value);
  }

์—ฌ๊ธฐ์„œ fullName ๋Š” ๊ตณ์ด state ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. firstName๊ณผ lastName์„ ํ†ตํ•ด ๊ณ„์‚ฐ๋  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

const fullName = firstName + ' ' + lastName;

์ด๋ ‡๊ฒŒ ์ผ๋ฐ˜ ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋ฉด, ๋ Œ๋”๋ง๋งˆ๋‹ค ์ตœ์‹  ๊ฐ’์„ ์ฝ์„ ์ˆ˜ ์žˆ์œผ๋ฉด์„œ๋„ ๊ตณ์ด setFullName์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ํ•„์š”๋„ ์—†์–ด์ง„๋‹ค.

4. ์ค‘๋ณต๋œ state

export default function Menu() {
  const [items, setItems] = useState(initialItems);
  const [selectedItem, setSelectedItem] = useState(
    items[0]
  );

์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด selectedItem state์— item ๊ฐ์ฒด๋กœ ์ €์žฅํ•˜๊ณ  ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๊ฑด ์ข‹์€ ํŒจํ„ด์ด ์•„๋‹ˆ๋‹ค. items์— ์ด๋ฏธ ์žˆ๋Š” ์ •๋ณด๋ฅผ ๊ทธ๋Œ€๋กœ seletedItem๋„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด ๋•Œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๋Š” ๋‘ ์ •๋ณด ์‚ฌ์ด์˜ ๋™๊ธฐํ™”๊ฐ€ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ์— ์ด๋ ‡๊ฒŒ ๊ฐ item์˜ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž :

function handleItemChange(id, e) {
    setItems(items.map(item => {
      if (item.id === id) {
        return {
          ...item,
          title: e.target.value,
        };
      } else {
        return item;
      }
    }));
  }
  
  return(
  //...
	<ul>
        {items.map((item, index) => (
          <li key={item.id}>
            <input
              value={item.title}
              onChange={e => {
                handleItemChange(item.id, e)
              }}
            />
            {' '}
            <button onClick={() => {
              setSelectedItem(item);
            }}>Choose</button>
          </li>
        ))}
      </ul>

์—ฌ๊ธฐ์„œ ์•„์ดํ…œ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•œ๋‹ค๊ณ  ํ•˜๋ฉด items์— ์žˆ๋Š” ํ•ด๋‹น ์•„์ดํ…œ๋งŒ ๋ณ€๊ฒฝ๋˜๊ณ  selectedItem์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค. "pretzels" ๋ผ๋Š” ์•„์ดํ…œ์„ ์„ ํƒํ–ˆ๋‹ค๋ฉดselectedItem === {id:0, name:"pretzels"} ์ด๋ ‡๊ฒŒ ๋œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๋•Œ "pretzels"์˜ ์ด๋ฆ„์„ "Pretzels ํ”„๋ ˆ์ฒผ"๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค๋ฉด

items = {
  {
    id : 0,
    name : "Pretzels ํ”„๋ ˆ์ฒผ"
  }
  //...
}

์ด๋ ‡๊ฒŒ ๋˜์ง€๋งŒ selectedItem์€ ๊ทธ๋Œ€๋กœ {id:0, name:"pretzels"} ๋กœ ๋‚จ์•„์žˆ๊ฒŒ ๋œ๋‹ค. (selectedItem๋„ ํ•จ๊ป˜ ๋ฐ”๊ฟ”์ฃผ๋ฉด ๋˜๋Š” ๊ฐ„๋‹จํ•œ ๋ฌธ์ œ์ง€๋งŒ ํ•ญ์ƒ ๊นœ๋นกํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์€ ์‰ฝ์ง€ ์•Š์€ ์ผ์ด๊ณ , ์• ์ดˆ์— ๋ฌธ์ œ์˜ ๊ฐ€๋Šฅ์„ฑ์„ ์ฐจ๋‹จํ•˜๋Š”๊ฒŒ ์ตœ์„ ์ด๋‹ค.)

๊ทธ๋ž˜์„œ ์ด๋ ‡๊ฒŒ selectedId ๋ฅผ state๋กœ ๊ฐ€์ ธ๊ฐ€๊ณ  selectedItem์€ ์ผ๋ฐ˜ ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค.

  const [items, setItems] = useState(initialItems);
  const [selectedId, setSelectedId] = useState(0);

  const selectedItem = items.find(item =>
    item.id === selectedId
  );

5. ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ state

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ state๋ฅผ ๊ฐ€์ •ํ•ด๋ณด์ž

export const initialTravelPlan = {
  id: 0,
  title: '(Root)',
  childPlaces: [{
    id: 1,
    title: 'Earth',
    childPlaces: [{
      id: 2,
      title: 'Africa',
      childPlaces: [{
        id: 3,
        title: 'Botswana',
        childPlaces: []
      }, 
      //....
      ]
    }, {
      id: 10,
      title: 'Americas',
      childPlaces: [{
        id: 11,
        title: 'Argentina',
        childPlaces: []
      },  
      //...
      ]
    }, {
      id: 19,
      title: 'Asia',
      childPlaces: [{
        id: 20,
        title: 'China',
        childPlaces: []
      },
      //...
      ]
    }, {
      id: 26,
      title: 'Europe',
      childPlaces: [{
        id: 27,
        title: 'Croatia',
        childPlaces: [],
      }, 
      //...
      ]
    }, {
      id: 34,
      title: 'Oceania',
      childPlaces: [{
        id: 35,
        title: 'Australia',
        childPlaces: [],
      },
      //...
      ]
    }]
  }, {
    id: 42,
    title: 'Moon',
    childPlaces: [{
      id: 43,
      title: 'Rheita',
      childPlaces: []
    }, {
      id: 44,
      title: 'Piccolomini',
      childPlaces: []
    }, {
      id: 45,
      title: 'Tycho',
      childPlaces: []
    }]
  },
  //...
};

์ด ๊ตฌ์กฐ์—์„œ ์žฅ์†Œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋งค์šฐ ๋ณต์žกํ•˜๊ณ  ๊ธธ์–ด์งˆ ๊ฒƒ์ด๋‹ค. (state์—…๋ฐ์ดํŠธ๋Š” ์ผ๋ถ€ ๊ฐ’๋งŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๊ณ  ์ „์ฒด ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•ด์•ผํ•œ๋‹ค. ์˜ˆ์‹œ - setState({...state, newState}))

ํ•˜์ง€๋งŒ ๋‹น์—ฐํžˆ๋„ ์ด๋ ‡๊ฒŒ ํฐ state๊ฐ€ ํ•„์š”ํ•  ์ˆ˜๊ฐ€ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํฐ state๋ฅผ ๋งŒ๋“ค๋ฉด ์•ˆ๋˜๋Š”๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ์ด๋•Œ ์ ์šฉ ๊ฐ€๋Šฅํ•œ ํŒ์ด "ํ‰ํƒ„ํ™”"๋‹ค.

export const initialTravelPlan = {
  0: {
    id: 0,
    title: '(Root)',
    childIds: [1, 42, 46],
  },
  1: {
    id: 1,
    title: 'Earth',
    childIds: [2, 10, 19, 26, 34]
  },
  2: {
    id: 2,
    title: 'Africa',
    childIds: [3, 4, 5, 6 , 7, 8, 9]
  },
  //...
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด initialTravelPlan ์€ ๊นŠ์€ ๊ณ„์ธต์„ ๊ฐ€์ง€๋Š” ๋ณต์žกํ•˜๊ฒŒ ๊ผฌ์—ฌ์žˆ๋Š” ๊ฐ์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ํ•˜๋‚˜์˜ ๊ณ„์ธต์œผ๋กœ ์ด๋ฃจ์–ด์ง„ ๋‹จ์ˆœํ•œ ๋งต ํ˜•ํƒœ๊ฐ€ ๋œ๋‹ค. ๋งˆ์น˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ์ฒ˜๋Ÿผ.

์ด์ œ ํ‰ํƒ„ํ•˜๊ฒŒ ๋งŒ๋“ (์ •๊ทœํ™” normalize ๋ผ๊ณ ๋„ ๋ถ€๋ฅธ๋‹ค.) state๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๋Š”๊ฒƒ์€ ์‰ฌ์›Œ์ง„๋‹ค.

// ์ง€์šฐ๋Š” ๋กœ์ง
function handleComplete(parentId, childId) {
    const parent = plan[parentId];
    // Create a new version of the parent place
    // that doesn't include this child ID.
    const nextParent = {
      ...parent,
      childIds: parent.childIds
        .filter(id => id !== childId)
    };
    // Update the root state object...
    setPlan({
      ...plan,
      // ...so that it has the updated parent.
      [parentId]: nextParent
    });
  }

์žฌ๊ท€ ๋ Œ๋”๋ง

์ด๋ ‡๊ฒŒ ํŠธ๋ฆฌ ๊ตฌ์กฐ(๋‚˜์œ ์˜ˆ์‹œ์—์„œ๋Š” ์ง„์งœ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋กœ ๋˜์–ด์žˆ์—ˆ๊ณ , ์ •๊ทœํ™” ๋œ ํ›„์—๋Š” ๊ฐœ๋…์ ์œผ๋กœ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค)์ปดํฌ๋„ŒํŠธ๋ฅผ ์žฌ๊ท€ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹(DFS)์œผ๋กœ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค.

function PlaceTree({ id, placesById }) {
  const place = placesById[id];
  const childIds = place.childIds;
  return (
    <li>
      {place.title}
      {childIds.length > 0 && (
        <ol>
          {childIds.map(childId => (
            <PlaceTree
              key={childId}
              id={childId}
              placesById={placesById}
            />
          ))}
        </ol>
      )}
    </li>
  );
}

์˜ˆ์‹œ ๋ฌธ์ œ

1. ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š” ์ปดํฌ๋„ŒํŠธ

export default function Clock(props) {
  const [color, setColor] = useState(props.color);
  return (
    <h1 style={{ color: color }}>
      {props.time}
    </h1>
  );
}

props๋กœ color์™€ time์„ ๋ฐ›๊ณ ์žˆ๋Š”๋ฐ ๋˜ ๋‹ค์‹œ color state๋ฅผ ์„ ์–ธํ•˜์—ฌ ๊ฐ€์ง„๋‹ค. ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ƒ‰์„ ๋ณ€๊ฒฝํ•ด๋„ state์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ฒŒ๋œ๋‹ค(์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋  ๋•Œ ๊ฐ€์กŒ๋˜ ์ดˆ๊ธฐ๊ฐ’์„ state๋กœ ๊ฐ„์งํ•˜์ง€๋งŒ, ๊ทธ๊ฑธ ๋ณ€๊ฒฝํ•˜๋Š” ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์—). state๋ฅผ ๋ฌด๋ถ„๋ณ„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ. ์ง์ ‘ props.color๋ฅผ ๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋ฉด ๋ถ€๋ชจ์˜ state ๋ณ€๊ฒฝ์— ๋งž์ถฐ ์ƒ‰์ด ๋ณ€ํ•˜๊ฒŒ ๋œ๋‹ค.

2. ๊ณ ์žฅ๋‚œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๋ฌผํ’ˆ์„ ์ฒดํฌํ•œ ํ›„์— ์‚ญ์ œ๋ฅผ ํ•ด๋„ ์นด์šดํ„ฐ์— ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š” ๋“ฑ์˜ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

let nextId = 3;
const initialItems = [
  { id: 0, title: 'Warm socks', packed: true },
  { id: 1, title: 'Travel journal', packed: false },
  { id: 2, title: 'Watercolors', packed: false },
];

export default function TravelPlan() {
  const [items, setItems] = useState(initialItems);
  const [total, setTotal] = useState(3);
  const [packed, setPacked] = useState(1);

  function handleAddItem(title) {
    setTotal(total + 1);
    setItems([
      ...items,
      {
        id: nextId++,
        title: title,
        packed: false
      }
    ]);
  }

  function handleChangeItem(nextItem) {
    if (nextItem.packed) {
      setPacked(packed + 1);
    } else {
      setPacked(packed - 1);
    }
    setItems(items.map(item => {
      if (item.id === nextItem.id) {
        return nextItem;
      } else {
        return item;
      }
    }));
  }

  function handleDeleteItem(itemId) {
    setTotal(total - 1);
    setItems(
      items.filter(item => item.id !== itemId)
    );
  }

  return (
    <>  
      <AddItem
        onAddItem={handleAddItem}
      />
      <PackingList
        items={items}
        onChangeItem={handleChangeItem}
        onDeleteItem={handleDeleteItem}
      />
      <hr />
      <b>{packed} out of {total} packed!</b>
    </>
  );
}

total, packed๊ฐ™์€ ๋ถˆํ•„์š”ํ•œ state๋“ค์„ ๊ฐ€์ง€๊ธฐ ๋–„๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ. ์ •ํ™•ํžˆ๋Š” ์ด state๋“ค์„ ์ œ๋Œ€๋กœ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ด์ง€๋งŒ.

  • handleDeleteItem์—์„œ packed state๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜์ง€ ์•Š๋Š”๋‹ค.

3. ์„ ํƒ ์‚ฌ๋ผ์ง

์ฝ”๋“œ๊ฐ€ '์ •์ƒ์ '์œผ๋กœ ์ž‘๋™์„ ํ•˜๊ธฐ๋Š” ํ•œ๋‹ค. ๋‹ค๋งŒ "star"์™€ "unstar"๋ฅผ ๋ˆŒ๋Ÿฌ ๋‘˜ ์‚ฌ์ด๋ฅผ ์˜ค๊ฐˆ ๋•Œ ์ž ๊น ํ˜ธ๋ฒ„ํ• ๋•Œ ๋ณด์ด๋Š” ํ•˜์ด๋ผ์ดํŠธ ํšจ๊ณผ๊ฐ€ ์ž ๊น ๊บผ์ง„๋‹ค. ์ •ํ™•ํžˆ๋Š” ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์ด์ง€ ์•Š์œผ๋ฉด ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ๋Œ์•„์˜ค์ง€ ์•Š๋Š”๋‹ค.

export default function MailClient() {
  const [letters, setLetters] = useState(initialLetters);
  const [highlightedLetter, setHighlightedLetter] = useState(null);

  function handleHover(letter) {
    setHighlightedLetter(letter);
  }

  function handleStar(starred) {
    setLetters(letters.map(letter => {
      if (letter.id === starred.id) {
        return {
          ...letter,
          isStarred: !letter.isStarred
        };
      } else {
        return letter;
      }
    }));
  }

  return (
    <>
      <h2>Inbox</h2>
      <ul>
        {letters.map(letter => (
          <Letter
            key={letter.id}
            letter={letter}
            isHighlighted={
              letter === highlightedLetter
            }
            onHover={handleHover}
            onToggleStar={handleStar}
          />
        ))}
      </ul>
    </>
  );
}

๋”ฐ์•… ๋ณด๋‹ˆ ์ฒซ๋ˆˆ์— ๋ฌธ์ œ์˜ ์›์ธ์ด ๋ฌด์—‡์ธ์ง€๋Š” ์ผ๋‹จ ์•Œ๊ฒ ๋‹ค. highlightedLetter๊ฐ€ letter์™€ ์ค‘๋ณต๋œ state์ด๊ธฐ ๋•Œ๋ฌธ์ธ ๊ฒƒ ๊ฐ™๋‹ค. ์ฆ‰ ๊ฐ์ฒด ์ „์ฒด๋ฅผ state๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋˜๊ณ  ์„ ํƒ๋œ letter์˜ id๋งŒ state๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ทธ๊ฒŒ ์™œ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ค๋Š”๊ฑฐ์ง€..? ์ด์œ ๋ฅผ ์•Œ๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€์„œ ๋ณธ๋ฌธ์„ ์ฝ์—ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‹ˆ๊นŒ "star" ํ˜น์€ "unstar" ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด letters state๊ฐ€ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค. ํ•˜์ง€๋งŒ highlightedLetter๋Š” ์•„์ง ์—…๋ฐ์ดํŠธ ์ „์— ๋จธ๋ฌผ๋Ÿฌ ์žˆ๊ณ , ๋น„๋กœ์†Œ ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์—ฌ์•ผ setHilightedLetter๊ฐ€ ํ˜ธ์ถœ๋˜์–ด ์—…๋ฐ์ดํŠธ ๋˜๋Š” ๋™๊ธฐํ™”๊ฐ€ ์ด๋ฃจ์–ด์ง€๊ธฐ ๋–„๋ฌธ์—.