리액트에서의 배열 수정

박상준·2023년 11월 8일

배열 수정

배열 출력을 다뤘다면 이젠 수정하는 법을 적용해보겠다. 아무리 배열을 출력한다해도 새로운 데이터가 들어왔을때 수정되지 않는다면 이 프로젝트의 의미는 없다. 그냥 배열만 출력하는 화면일뿐.

배열 수정을 하기 전에

배열 수정하기전에 꼭 알아야할 데이터 타입과 복사에 대해서 알아보겠다.
데이터 타입에는 크게 두가지가 있다.

  1. 원시 타입
  2. 참조 타입

원시 타입은 데이터가 메모리에 직접 저장되는 형태를 의미한다.

원시타입 데이터:
string, boolean, number, null, undefined, symbol

이런 원시타입 데이터는 메모리에 직접 저장되기 때문에 복사를 해도 같은 값이 저장이 된다.

const say1 = "hello";
const say2 = say1;
console.log(say1===say2); //true

그렇기 때문에 값을 비교해도 true가 출력된다.

반면에 원시 타입과 다르게 참조 타임은 데이터 자체가 변수에 저장되는 것이 아니라 참조값이 저장된다.

const user = {
	name: 'alex'
}

이러한 객체를 지정한다면 객체 데이터는 메모리 어딘가에 저장되고 user변수에는 메모리 위치인 참조값이 저장된다.

const user = {
	name: 'alex'
}
const user2 = {
	name:'alex'
}
console.log(user === user2); // false

두개의 객체의 데이터가 같지만 참조값이 다르기 때문에 false값이 출력된다.
그래서 변수를 복사하면 원본 데이터가 복사되는 것이 아니라 참조값이 복사된다.

const user = {
	name: 'alex'
}
const user2 = user;
user2.name = 'tom';
console.log(user)
console.log(user2) // user와 user2 모두 tom으로 변경된다.

이러한 것에 따라서 자바스크립트에서 복사는 두가지로 나뉜다.

  1. 얕은 복사: 참조 값을 복사하는 것, 원본 변경시 복사본도 바뀜
  2. 깊은 복사: 원본과 같은 데이터 복사하는 것, 원본 변경되도 변경 없음

리액트 불변성

리액트에서 이 개념은 중요하게 작용한다. 왜냐하면 리액트는 불변성을 지켜주어야 하기 때문이다. 이유는 크게 두가지이다.

  1. 리액트 리렌더링 조건이 참조의 변화가 있어야 리렌더링 진행
  2. 불변성을 통해 사이드 이팩트와 복잡한 코드 방지 가능

1번의 이유는 리액트는 기본적으로 참조값을 비교해 렌더링을 진행한다. 즉 얕은 비교를 사용한다. 그래서 원본값이 바뀌어도 참조값이 바뀌지 않으면 리렌더링이 이루어지지 않는다. 그리고 리액트에서 객체를 다루는 경우 원본의 데이터를 하나씩 다 비교하는것이 아니라 참조값만 비교하면 효율적인 렌더링이 이루어질수 있다.

2번 이유를 통해 원본의 불변성을 지킴으로 원본을 참조한 다른 곳에서의 오류와 복잡한 코드를 간소화할 수 있다.

리액트에서 state 값을 다룰때 원시타입의 경우 불변성을 지켜주기 때문에 바로 수정이 가능하다.

const [num,setNum] = useState(0);
setNum(2);
console.log(num); // 2

하지만 객체나 배열의 경우는 불변성을 지켜주면서 수정을 해야한다.

const [arr,setArr] = useState([1,2,3]);

간단한 배열을 만들었을때 배열에 숫자를 추가할 것이다.

arr.push(4);

배열에 직접 접근해 데이터를 수정하는 경우 원본데이터는 변경되지만 리렌더링은 일어나지 않는다. 그 이유는 위해서 말한대로 리액트는 참조값을 비교해서 렌더링이 진행된다. 하지만 원본이 수정되면 참조값에는 변화가 없기때문에 리렌더링이 일어나지 않는다.

배열 수정하는 법

원본의 불변성을 지키면서 수정하는 법은 spread를 이용한 원본을 복사한 값을 수정하는 것이다.

const [arr,setArr] = useState([1,2,3]);
const arr2 = [...arr,4]; //spread사용
setArr(arr2); // 1,2,3,4

spread를 사용하는것 외에도 다른 함수인 concat()을 사용해도 된다.

setArr(arr.concat(4));

이제 todolist에서 적용해보겠다.

    const addItem = ()=>{
      if(inputValue === ''){
        alert('한글자 이상 입력해 주세요.');
      } else{
      const todos = {
        id: nextId.current,
        inputValue
      };
      const addList = [...todoList,todos];
      setTodoList(addList);
    };

우선 input에서 받은 데이터가 없을땐 alert을 보여준다.
input에서 받은 데이터가 있을땐 데이터와 id값을 todos라는 객체에 담아 아이템이 담기는 todoList(원본)을 복사한 addList에 추가해 데이터를 변경해준다. 나는 concat이 아닌 spread를 사용했다.

이런 방식으로 id값과 inputValue가 담긴 배열이 생성되고 이걸 렌더링하면

이런 결과가 나온다. 물론 원본의 불변성은 지켜주었기 때문에 입력을 할때마다 리렌더링이 정상적으로 이루어진다.

삭제 및 수정

배열 삭제 및 수정 기능도 같은 방식으로 구현할수 있다.

  • 삭제: 현재 배열에서 선택한 아이템을 감지해 아이템이 없는 배열 출력
  • 수정: 현재 배열에서 선택한 아이템을 감지해 아이템의 inputValue를 수정한 배열 출력

여기에서도 어떤 변수를 만들고 수정된 데이터를 저장한 후에 변경해준다.

  const removeItem = (id) => {
  const removelist = todoList.filter((todo) => todo.id !== id);
    setTodoList(removelist);
  };

removelist변수를 만들고 filter 함수를 이용해 선택한 아이템의 id와 일치하지 않는 아이템을 배열로 만들어준다. 그리고 새로 만든 배열로 변경해준다.

첫번째 아이템 삭제를 누르면 삭제된 배열을 확인할 수 있다.

수정하는 법도 비슷하다.

  const editItem = (id)=>{
    const edit = prompt('수정할 내용을 입력하세요');
    const editList = todoList.map((todo) => todo.id === id ? {...todo, inputValue: edit} : todo);
    setTodoList(editList);
  };

우선 prompt를 띄워 수정할 값을 edit에 저장한다.
그리고 map을 이용해 수정된 배열을 만든다. 여기에서는 삼항 연산자를 이용해 아이템의 id과 배열의 id를 비교하고 id가 일치하는 경우(true일 경우) 같은 id를 가진 아이템의 inputValue를 수정하고 일치하지 않는 경우(false일 경우) 그대로 반환한다.

수정된 배열을 확인할 수 있다.

마무리

이제 todolist의 핵심적인 기능은 다 구현이 되었다. 하지만 페이지를 새로고침하면 데이터가 초기화되고 페이지가 초기상태로 돌아가는 문제가 있다. 이러면 다음날 할일, 장기적으로 할일 등은 적어둘수 없는 2%부족한 todolist이다. 어떻게 데이터를 저장하고 불러오는지 다음 포스트에서 작성해보겠다.

profile
개인 블로그 플렛폼도 운영중입니다(https://blog-park.vercel.app/)

0개의 댓글