[Day110] React - 불변성(Immutability) Part.2

Validator·2023년 11월 23일
post-thumbnail

1. 불변성 (Immutability)

개념

  • 불변성은 데이터가 한번 생성된 후에 변경될 수 없다는 개념을 말한다. JavaScript에서 원시 타입(primitive types)은 자연스럽게 불변성을 갖는다. 예를 들어, 문자열 또는 숫자 값을 변경하려고 할 때, 실제로 원본 값은 변경되지 않고 새로운 값이 생성된다.
  • 객체와 같은 참조 타입(reference types)의 경우, 불변성은 자동으로 보장되지 않는다. 객체의 속성을 변경하면, 객체 자체가 변한다.

중요성

  • 불변성은 예측 가능한 코드를 작성하고, 복잡한 데이터 구조의 변화를 추적하는 데 도움을 준다.
  • React 같은 프론트엔드 라이브러리/프레임워크에서는, 불변성이 상태 관리의 핵심이며, 성능 최적화를 위해 필수적이다.

2. 얕은 복사 (Shallow Copy)

개념

  • 얕은 복사는 객체의 최상위 레벨의 속성만을 복사하는 것을 의미한다. 객체 내부의 객체(중첩된 객체)나 배열 등은 참조값을 공유한다.
  • JavaScript에서는 Object.assign() 메서드나 스프레드 문법(...)을 사용하여 얕은 복사를 수행할 수 있다.

예시

let obj = { a: 1, b: { c: 2 } };
let shallowCopy = { ...obj };

shallowCopy.b.c = 3;
console.log(obj.b.c); // 3

위 예시에서 shallowCopyobj의 얕은 복사본이다. shallowCopy의 내부 객체 bobjb와 동일한 참조를 공유하므로, 하나를 변경하면 다른 하나도 영향을 받는다.

3. 깊은 복사 (Deep Copy)

개념

  • 깊은 복사는 객체의 모든 레벨에서 속성을 복사하는 것을 말한다. 이는 중첩된 객체나 배열 내부의 모든 요소까지 새롭게 복사된다는 것을 의미한다.
  • JavaScript에서 깊은 복사를 수행하는 간단한 방법 중 하나는 JSON.parse(JSON.stringify(object))를 사용하는 것이다. 하지만 이 방법은 함수, Date 객체, undefined, 순환 참조 등을 제대로 처리하지 못하는 단점이 있다.

예시

let obj = { a: 1, b: { c: 2 } };
let deepCopy = JSON.parse(JSON.stringify(obj));

deepCopy.b.c = 3;
console.log(obj.b.c); // 2

위 예시에서 deepCopyobj의 깊은 복사본이며, deepCopy의 변경이 obj에 영향을 끼치지 않는다.

불변성과 복사 방식의 연관성

불변성 유지를 위한 복사 방식 선택

  • 불변성을 유지하기 위해서는 적절한 복사 방식을 선택하는 것이 중요하다. 객체의 깊이나 구조에 따라 얕은 복사나 깊은 복사를 선택할 수 있다.
  • 단순한 객체나 중첩되지 않은 구조에서는 얕은 복사로 충분하지만, 복잡한 객체나 중첩된 구조에서는 깊은 복사를 고려해야 한다.

성능 고려

  • 깊은 복사는 모든 레벨의 속성을 복사하기 때문에 처리 시간이 더 걸리고 메모리도 더 많이 사용한다. 따라서 필요한 경우에만 사용하는 것이 좋다.
  • 얕은 복사는 상대적으로 빠르고 효율적이지만, 중첩된 객체의 불변성을 보장하지 못한다.

불변성과 복사 방식의 차이점

  1. 불변성(Immutability): 데이터의 상태가 생성 후 변경되지 않는 특성.
  2. 얕은 복사(Shallow Copy): 객체의 최상위 레벨의 속성만 복사하고, 중첩된 객체나 배열은 참조를 공유.
  3. 깊은 복사(Deep Copy): 객체의 모든 레벨에서 속성을 복사하며, 중첩된 구조까지 완전히 새로운 복사본을 생성.

JavaScript에서 데이터를 관리할 때, 불변성, 얕은 복사, 깊은 복사의 개념을 정확히 이해하고 적절하게 적용하는 것이 중요하다. 이는 코드의 예측 가능성, 유지보수성, 성능 최적화에 큰 영향을 미친다. 특히, React와 같은 라이브러리/프레임워크에서 불변성을 유지하는 것은 상태 관리의 핵심이며, 애플리케이션의 성능을 향상시키는 데 필수적이다.


복잡한 상태 관리와 useReducer

useReducer는 React에서 복잡한 상태 관리를 위해 사용되는 훅이다. 이 훅은 상태 업데이트 로직을 컴포넌트에서 분리시켜 더욱 선언적이고 관리하기 쉬운 코드를 작성할 수 있게 해준다. 특히 복잡한 상태 구조를 가진 경우에 유용하다.

복잡한 상태의 예

복잡한 상태란 여러 하위 값들을 포함하고, 그 값들이 서로 연관되어 있어서 상태의 일부분만 변경되더라도 다른 부분에 영향을 미칠 수 있는 상태를 말한다. 예를 들어, 사용자의 입력 양식, 여러 계층의 데이터를 포함하는 객체, 또는 배열 등이 이에 해당한다.

useReducer와 복잡한 상태 관리

useReducer를 사용하여 복잡한 상태를 관리할 때, 다음과 같은 접근 방식을 취할 수 있다:

  1. 초기 상태 정의: 애플리케이션에서 관리해야 할 모든 상태 값을 포함하는 객체를 정의한다.
  2. Reducer 함수 작성: 액션 타입에 따라 상태를 어떻게 변경할지 결정하는 로직을 포함한다. 이 함수는 현재 상태와 액션 객체를 인자로 받아 새로운 상태를 반환한다.
  3. 액션 정의: 상태를 어떻게 변경할 것인지를 나타내는 객체로, 대게 type 속성을 포함한다.
  4. Dispatch 함수 사용: 컴포넌트에서 특정 액션을 발생시켜 상태를 업데이트한다.

복잡한 상태 관리 예제

import React, { useReducer } from 'react';

const initialState = {
  userInfo: {
    name: '',
    age: 0,
    hobbies: []
  }
};

function reducer(state, action) {
  switch (action.type) {
    case 'setName':
      return { ...state, userInfo: { ...state.userInfo, name: action.name }};
    case 'setAge':
      return { ...state, userInfo: { ...state.userInfo, age: action.age }};
    case 'addHobby':
      return { ...state, userInfo: { ...state.userInfo, hobbies: [...state.userInfo.hobbies, action.hobby] }};
    default:
      throw new Error();
  }
}

function UserInfoForm() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const handleNameChange = event => {
    dispatch({ type: 'setName', name: event.target.value });
  };

  const handleAgeChange = event => {
    dispatch({ type: 'setAge', age: event.target.value });
  };

  const handleAddHobby = hobby => {
    dispatch({ type: 'addHobby', hobby });
  };

  return (
    <div>
      <input type="text" value={state.userInfo.name} onChange={handleNameChange} />
      <input type="number" value={state.userInfo.age} onChange={handleAgeChange} />
      <button onClick={() => handleAddHobby('Programming')}>Add Hobby</button>
      <div>Name: {state.userInfo.name}</div>
      <div>Age: {state.userInfo.age}</div>
      <div>Hobbies: {state.userInfo.hobbies.join(', ')}</div>
    </div>
  );
}

이 예제에서는 사용자 정보를 포함하는 복잡한 상태를 useReducer로 관리한다. 각 액션은 상태의 특정 부분을 업데이트하고, 전체 상태는 불변성을 유지하면서 업데이트된다.

불변성을 유지하는 더 많은 방법들

객체의 깊은 복사

복잡한 객체 구조에서 불변성을 유지하려면 객체의 깊은 복사가 필요할 수 있다. 이를

위해 lodash와 같은 라이브러리의 깊은 복사 함수를 사용하거나, 직접 깊은 복사 로직을 구현할 수 있다.

Immer 라이브러리 사용

Immer는 JavaScript의 불변성을 쉽게 관리할 수 있게 해주는 라이브러리이다. 이 라이브러리를 사용하면 복잡한 객체도 쉽게 불변성을 유지하면서 업데이트할 수 있다.

정리

useReducer를 사용하면 복잡한 상태를 더욱 체계적으로 관리할 수 있다. 불변성을 유지하는 것은 리액트의 성능 최적화와 데이터의 일관성을 보장하는 데 중요하다. 이를 위해 JavaScript의 객체와 배열에 대한 이해, 순수 함수의 사용, 그리고 필요에 따라 외부 라이브러리를 활용하는 방법들을 고려할 수 있다.

0개의 댓글