Skip to content

React state 불변성

eGovFrameSupport edited this page Apr 6, 2023 · 1 revision

React UI 업데이트 단계

  1. 데이터 변경이 일어나면(state, props)
  2. Virtual DOM에 렌더링을 한다.
  3. 업데이트 이전의 Virtual DOM과 현재의 Virtual DOM을 비교하여, 변경된 부분을 반영한다. (재조정)
  4. 변경 사항을 real DOM에 반영한다.
  • React에서의 상태 변화 감지는 컴포넌트가 가지고 있는 데이터(state, props)의 비교로 이루어지는데 이 과정은 얕은 비교를 통해 이루어진다.

State 불변성

const [state, setState] = useState([1,2,3]);

const stateChange = () => {
  state.push(4);
  console.log(arr);
  setState(arr);
  }
};
  • setState() 실행 시, 데이터가 변경되므로 일반적으로는 렌더링 작업이 재수행되지만, 데이터가 참조 타입(객체, 배열)이면 주의가 필요.
  • 위 코드는 변경 전과 후의 데이터 내용은 다르지만 참조값이 동일하므로 동일한 것으로 판정, 렌더링이 되지 않는다.

불변성 이슈

  • 불변성을 지켜야 한다는 원칙은 비단 react에서만 존재하는 새로운 컨셉이 아님.
  • 불변성을 지키지 않을 경우, 데이터의 흐름을 추적하기 어렵고 이는 곧 예기치 못한 side effect나 버그로 이어진다.
  • 또한 원본 데이터가 변경될 경우 이 원본 데이터를 참고하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있으며 복잡도도 증가한다.

불변성 위반 사례

let x = {
  name: 'gildong'
};

let y = x;

x.name = 'kim';

console.log(y.name); // kim
console.log(x === y) // true
let x = ['foo'];
let y = x;

x.push('bar');

console.log(y); // ['foo', 'bar']
console.log(x === y); // true
  • 배열 및 객체와 같은 참조 타입의 데이터 변경시에는 원본을 직접 변경하지 않도록 한다.
  • push(), splice(), sort() 와 같은 함수 또한 원본을 변경하기 때문에 사용하면 안 된다.
  • 꼭 사용이 필요할 경우 기존의 데이터를 복사하고 그 복사본에 적용해 줘야 함

불변성 유지 방법

const user = { name: 'Choi', age: 25, friends: ['Park', 'Kim']};
const otherUser = { ...user, friends: [user.friends]};
user.name = 'Lee';
user.friends.push('Kang');

/*
user = { name: 'Lee', age: 25, friends: ['Park', 'Kim', 'Kang']};
otherUser = { name: 'Choi', age: 25, friends: ['Park', 'Kim']};
*/

user === otherUser  // false
user.friends === otherUser.friends // false
  • concat(), map(), filter(), slice(), spread operator를 이용.
  • 단, spread operator는 얕은 복사를 실행하므로 다차원(배열 속 배열 or 객체 속 객체)의 비 원시 자료형값 복사를 위해서는 내부 값까지 전부 복사할 필요가 있음.

다차원 객체 관련 – 깊은 복사 구현

const compare = (a, b) => {
  if(sortType === 'latest') {
   return parseInt(b.date) - parseInt(a.date);
 } else {
  return parseInt(a.date) - parseInt(b.date);
 }
}

const copyList = JSON.parse(JSON.stringify(diaryList));

const sortedList = copyList.sort(compare);

return sortedList;
  • 객체를 한 번 JSON 형태의 문자열로 만들었다가 다시 객체화 시켜서 값만 새 변수에 할당
  • sort()는 원본 배열을 변경하지만 이 경우는 별개의 복사본을 변경하므로 원본에는 영향을 주지 않음

다차원 객체 관련 – 깊은 복사 구현 2

const objects = [{ 'a' : 1 },{ 'b' : 2 }];
const deep = _.cloneDeep(objects);

console.log(deep[0] === objects[0]);
// => false
  • lodash 라이브러리의 cloneDeep()을 이용
  • 외부 라이브러리이므로 추가 인스톨 필요

다차원 객체 관련 – 깊은 복사 구현 3

const state = {
 number: 1,
 dontChangeMe: 2
};

const nextState = produce(state, draft => {
 draft.number += 1;
});

console.log(nextState);
// {number: 2, dontChangeMe: 2};
  • immer 라이브러리의 produce()를 이용
  • 첫 파라미터는 수정하고 싶은 state, 두 번째는 업데이트 정의 함수(불변성 고려 필요 없음)
  • 외부 라이브러리이므로 추가 인스톨 필요