본문 바로가기
React

[React] 불변성을 유지하며 객체 변경하기

by 배잼 2022. 9. 28.

react 하면 불변성입니다. 코드를 짤 때 '불변성을 지켜야 한다' 라는 말을 많이 들어봤을 거에요. 불변성이 왜 중요할까요?

 

1. 원본 데이터의 보존

이전 자바스크립트의 원시 값과 객체에 대해 정리할 때, 객체를 할당한 변수는 주소를 저장한다고 노트했습니다. (참고 : https://pearjam.tistory.com/7?category=535826) 즉, 주소를 저장하기에 해당 객체의 수정이 일어나면 그 객체를 참조하던 변수들은 고스란히 변경된 값을 반영하고, 알 수 없는 에러가 발생할 수 있습니다.

예시

const mathClassStudents = ['Kim', 'Lee'] //수학 수업을 듣는 학생 명단
const englishClassStudents = mathClassStudents //영어 수업을 듣는 학생 명단이 수학 수업을 듣는 학생들과 같다.

englishClassStudents.push('Park') //영어 반에 Park 이라는 학생이 새로 들어왔다!

console.log(englishClassStudents) // 영어 수업의 학생 명단은 ['Kim', 'Lee', 'Park'] 로 정상 출력 된다.
console.log(mathClassStudents) // 하지만 수학 수업의 학생 명단도 ['Kim', 'Lee', 'Park']로 출력된다!!! 
//이럴 때는 할당할 때 스프레드 연산자를 써 줘야 한다 :)

 

2. react의 상태 업데이트를 위해

react는 상태 업데이트를 할때 참조값을 사용합니다. ob1이라는 변수에 임의의 객체 A를 저장했다고 합시다. (그렇다면 obj1은 임의의 객체 A의 주소를 가지고 있는 셈입니다). 만약 객체 A를 직접 변경했더라도, obj1이 저장하는 객체의 주소가 변경되지 않습니다. 이는 state가 동일하다고 처리되어 리렌더링이 발생하지 않습니다.

(*react가 상태 변화를 어떻게 처리하는지는 다음에 정리해 보겠습니다)


불변성을 지키는 방법 몇 가지

Redux-Saga를 공부하면서 ADD, DELETE, EDIT을 구현했던 것을 정리해보겠다. 발생한 action에 대해 reducer 가 switch문으로 처리해주는 코드다. case는 너무 신경쓰지 않아도 되고, 그 아래 로직을 보면 좋을것 같다.

 

1. ADD - concat() 사용

case ADD:
      return state.concat(action.question);

아주 간단하다. state가 기존 상태고, concat를 사용해서 action안에 들어있는 question이라는 값을 넣어준다. 위 예제에서 본 push 대신 concat를 써준 셈이다.

 

2. DELETE - filter() 사용

case DELETE:
      return state.filter((question) => question.id !== action.index);

state를 filter해서 걸러지지 않는 값만 배열로 리턴한다. state 안에 있는 question의 id와, 발생한 액션 안에 담긴 index의 값이 일치하는 값을 제외한다. 따라서 해당 index에 해당하는 id를 가진 question을 삭제하는 셈이다.

 

3. EDIT - map(), 스프레드 연산자 사용

case EDIT_TIME:
      return state.map((question) =>
        question.id === action.question.id // id 가 일치하면
          ? { ...question, time: action.question.time } // time 값을 바꿔준다. 불변성 유지를 위해 스프레드 연산자 사용
          : question
      );

map도 참 많이 쓰는 친구다. state 안에 있는 question을 뽑아내서 return 값에 해당하는 것으로 하나씩 mapping해주고, 배열을 리턴한다. 이 경우에는 삼항연산자로 핸들링을 해줬다. 변경하려고 targeting 한 id와 (action.question.id) question의 id가 일치하면 time을 바꾼 값을 리턴한다. 다른 경우에는 그냥 놔둔다. 이때, 스프레드 연산자(…)가 있는데, 객체를 복사하거나 property를 업데이트 할 수 있다. 이를 이용해 불변성을 유지하며 time 업데이트가 가능했다.

 

4. EDIT - 객체 안의 객체

case EDIT_ANSWER:
      return state.map((question) => {
        const ansArray = [...question.answer];

        ansArray[action.answer.answerId] = {
          ansId: action.answer.answerId,
          text: action.answer.text,
        };

        return question.id === action.answer.questionId // id 가 일치하면
          ? {
              ...question,
              answer: ansArray,
            }
          : question;
      });

question 객체 안에 answer 라는 property는 array를 값으로 가진다. 이때, 그 array를 수정하려는 경우는 어떻게 해야 할까?

  1. 먼저 question 안에 answer를 ansArray로 복사한다.
  2. 그 후, ansArray의 특정인덱스(action.answer.answerId)의 값을 변경한다. 배열 수정을 통째로 하는 게 아니라, 원래 answer의 특정 인덱스만 수정한다는 뜻이다.
  3. ansArray에 값을 옮길 때는 스프레드 연산자를 사용했기 때문에 ansArray의 값이 변경되어도 question 안에 있는 answer는 값이 변하지 않는다
  4. 그 다음에, 3.에서 했던 것처럼 수정하려던 질문의 id와 현재 question의 id가 일치한다면 answer를 ansArray로 바꿔주고, 아니라면 리턴해준다.

더 좋은 방법, 더 나은 코드가 있으면 연락 주세요. 감사합니다!

 

'React' 카테고리의 다른 글

[React] Mock Server 세팅 (MSW)  (0) 2022.10.14

댓글