리액트 배열 내부의 객체 업데이트

렐루·2024년 5월 16일
0

리액트

목록 보기
14/20

배열 내부의 객체는 배열 안에 있지 않다.

let obj = {
  name: 'Niki de Saint Phalle',
  artwork: {
    title: 'Blue Nana',
    city: 'Hamburg',
    image: 'https://i.imgur.com/Sd1AgUOm.jpg',
  }
};

위와 같은 객체가 있습니다.
이러한 객체는 중첩되어 존재하는 것처럼 보입니다.
하지만 객체들은 그저 그렇게 보일 뿐, 독립적으로 존재합니다.

let obj1 = {
  title: 'Blue Nana',
  city: 'Hamburg',
  image: 'https://i.imgur.com/Sd1AgUOm.jpg',
};

let obj2 = {
  name: 'Niki de Saint Phalle',
  artwork: obj1
};

이렇게 별도로 존재합니다.
그저 해당 객체를 가리키고 있을 뿐입니다.

그렇기 때문에 해당 객체는 자신의 위치를 알려줌으로써 다른 객체들과 자유롭게 관계를 맺을 수 있고, 일방적인 수정은 관계를 맺고 있는 모든 객체들에게 영향을 주게 되는 겁니다!!!

전체 코드, 문제점

import { useState } from 'react';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [myList, setMyList] = useState(initialList);
  const [yourList, setYourList] = useState(
    initialList
  );

  function handleToggleMyList(artworkId, nextSeen) {
    const myNextList = [...myList];
    const artworkIndex = myNextList.findIndex(
      a => a.id === artworkId
    );
    myNextList[artworkIndex] = {
      ...myNextList[artworkIndex],
      seen: nextSeen
    }
    setMyList(myNextList)
  }

  function handleToggleYourList(artworkId, nextSeen) {
    const yourNextList = [...yourList];
    const artwork = yourNextList.find(
      a => a.id === artworkId
    );
    artwork.seen = nextSeen;
    setYourList(yourNextList);
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={myList}
        onToggle={handleToggleMyList} />
      <h2>Your list of art to see:</h2>
      <ItemList
        artworks={yourList}
        onToggle={handleToggleYourList} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

위의 사진을 보면 코드의 동작 모습을 알 수 있습니다.
처음 아래 체크박스를 클릭하면 위의 체크박스가 같이 체크가 되는 버그가 발생하고 있음을 알 수 있습니다.
그런데 또 위의 체크박스를 클릭하면 그 다음부터는 아래의 체크박스와 별도로 동작하게 됩니다.

이러한 상황은 다음의 코드 때문입니다.

const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];
const [myList, setMyList] = useState(initialList);
  const [yourList, setYourList] = useState(
    initialList
  );

function handleToggleMyList(artworkId, nextSeen) {
    const myNextList = [...myList];
    const artworkIndex = myNextList.findIndex(
      a => a.id === artworkId
    );
    myNextList[artworkIndex] = {
      ...myNextList[artworkIndex],
      seen: nextSeen
    }
    setMyList(myNextList)
  }

  function handleToggleYourList(artworkId, nextSeen) {
    const yourNextList = [...yourList];
    const artwork = yourNextList.find(
      a => a.id === artworkId
    );
    artwork.seen = nextSeen;
    setYourList(yourNextList);
  }

위의 코드에서 해당되는 코드만 일부 가져왔습니다.
먼저 함수는 위의 체크박스들을 관리하는 함수
아래는 아래 체크박스를 관리하는 함수입니다.

분명 둘다 처음에는 initialList이라는 객체들이 담긴 배열에서 시작했지만 useState 훅을 사용해서 별도의 리스트로 관리해주고 있습니다.

하지만 위의 함수와는 다르게 아래의 함수는 위의 체크박스까지 건드리고 있습니다.
이는 배열 내부의 객체는 배열 안에 있지 않기 때문입니다.

const yourNextList = [...yourList];
    const artwork = yourNextList.find(
      a => a.id === artworkId
    );
    artwork.seen = nextSeen;
    setYourList(yourNextList);

yourNextList 라는 이름으로 배열을 새로 만들어도 내부 객체를 새로 생성하지 않고 있기 때문에 내부 객체를 직접 수정하면 해당 객체를 참조하고 있는 다른 객체들이 모두 영향을 받게 되는 것입니다.

해결방안, 가리키는 객체를 새로 만든다.

해결방안으로는 모든 객체를 다시 만드는 것이 아니라 수정이 필요한 객체가 생길 경우 해당 객체만 새로 만들어서 바꿔주면 되는 것입니다.

const myNextList = [...myList];
    const artworkIndex = myNextList.findIndex(
      a => a.id === artworkId
    );
    myNextList[artworkIndex] = {
      ...myNextList[artworkIndex],
      seen: nextSeen
    }
    setMyList(myNextList)

위의 코드를 보면 findIndex로 해당 객체의 인덱스를 찾아서 객체를 새로 만들어 주고 seen이라는 요소만 바꿔주고 있습니다.
이렇게 되면 다른 객체들은 위와 아래의 배열에서 동시에 참조하고 있어도 제가 체크행위를 통해서 수정한 객체는 새로 만들어졌기 때문에 고유하게 참조하게 되는 것입니다.

그래서 사진에서 봤을 때 위에서 체크를 한 객체는 아래서 체크를 통해 접근하려고 해도 불가한 것입니다.

해결 코드로 두번쨰도 있습니다. 그냥 변형 코드로 참고만 하셔도 좋을 것 같습니다!!

setMyList(myList.map(artwork => {
  if (artwork.id === artworkId) {
    // 변경된 *새* 객체를 만들어 반환합니다.
    return { ...artwork, seen: nextSeen };
  } else {
    // 변경시키지 않고 반환합니다.
    return artwork;
  }
}));

이렇게 맵 메서드를 사용해서 복사한 다음 if 문으로 아이디가 같을 때 해당 객체를 새로 생성하는 방법도 있습니다.

느낀점

코딩을 배우다 보면 위와 같은 상황들을 일상생활에서 자주 마주치게 됨을 느낍니다. 제가 무지해서 실제 현상을 오해하고 잘못 알게 되는 그런 일들..

예를 들어 컴터나 티비같은 가전제품은 때려야 말을 듣는다든지...ㅎ
대표적으로 시간의 상대성도 그렇고요.
알고봤더니 사람들이 너무 느려서 잘못 알고 있었던 것이었죠.

이런 것들을 하나하나 배워가는 일은 정말 즐거운 것 같습니다.
열심히 하겠습니다!!!

profile
프론트 공부중입니다!

0개의 댓글