(React) 리액트의 불변성 immutable.js

호두파파·2021년 5월 4일
0

React

목록 보기
18/38

리액트를 사용한다면, immutability라는 말을 들어봤을 것이다. 리액트 컴포넌트의 state를 변경해야할 땐, 무조건, setState를 통해서 업데이트 해주어야 하며, 업데이트 하는 과정에서 기존의 객체의 값을 직접적으로 수정하면 절대 안된다.

setState를 통해 state를 변경하지 않으면 리렌더링이 되지 않는다.

불병성을 유지해야 하는 이유 : 컴포넌트 최적화

리액트는 부모 컴포넌트가 리렌더링 되면, 자식 컴포넌트들 또한 리렌더링된다. 이 과정은 가상 DOM에만 이뤄지는 렌더링이며, 렌더링을 마치고 리액트의 diffing 알고리즘을 통해 변화가 일어나는 부분만 실제로 업데이트 해준다.
아무리 실제 DOM에는 반영되지는 않겠지만, 그래도 CPU 쪽에 미세한 낭비가 발생하게 된다.
작은 규모의 프로젝트에서는 이런 부분이 전혀 문제되지 않지만, 규모가 큰 프로젝트를 작업하게 되면, 미세한 메모리 낭비가 서비스에 큰 구멍을 만든다.

그렇기 떄문에 코드상에서 불변함을 유지하면서 코드를 작성하는게 좋다.

import React, { Component } from 'react';
import User from './User';

class UserList extends Component {

  shouldComponentUpdate(nextProps, nextState) {
    // 불변성을 유지해주는 shouldComponentUpdate
    return nextProps.users !== this.props.users;
  }


  renderUsers = () => {
    const { users } = this.props;
    return users.map((user) => (
      <User key={user.id} user={user} />
    ))
  }

  render() {
    console.log('UserList 가 렌더링되고 있어요!')
    const { renderUsers } = this;
    return (
      <div>
        {renderUsers()}
      </div>
    );
  }
}

export default UserList;

그러나 기존의 객체를 건들이지 않고 새 객체를 생성해 불변함을 유지하며 값을 업데이트하는 것은 엄청나게 번거로운 일이다. 실수의 염려도 있다. 이러한 작업을 쉽게 해줄 수 있는 것이 immutable.js이다.

Immutable.js 시작하기

프로젝트에서 immutable 을 사용할 땐, 다음과 같이 패키지를 설치해서 사용한다.

yarn add immutable

immutable을 사용 할 때는 다음 규칙들을 기억해야 한다.
1. 객체는 Map
2. 배열은 List
3. 설정할 땐 set
4. 읽을땐 get
5. 읽은 다음에 설정할땐 update
6. 내부에 있는걸 ~ 할땐 뒤에 In을 붙인다 : setIn, getIn, updateIn
7. 일반 자바스크립트 객체로 변환할 땐 toJS
8. List엔 배열 내장함수와 비슷한 함수들이 있다. push, slice, filter, sort, concat... 전부 불변함을 유지한다.
9. 특정 key를 지울때(혹은 List에서 원소를 지울때) delete를 사용한다.

리액트 컴포넌트에서 Immutable 사용하기

state = {
    data: Map({
      input: '',
      users: List([
        Map({
          id: 1,
          username: 'velopert'
        }),
        Map({
          id: 2,
          username: 'mjkim'
        })
      ])
    })
  }

data라는 Map을 만들었고, 그 내부에는 userList가 있고, 그 안에 또 Map 두개가 안에 들어있다.

onChange = (e) => {
  const { value } = e.target;
  const { data } = this.state;
  
  this.setState({
    data: data.set('input', value)
  });
}

onButtonClick을 수정하면 다음과 같다.

onButtonClick = () => {
  const { data } = this.state;
  
  this.setState({
    data: data.set('input', '')
    .updata('users', users => users.push(Map({
      id: this.id++,
      username: data.get('input')
    })))
  })
}

이 함수에서는 input 값을 공백으로 만들어야 하고, users에 새 Map을 추가해주어야 한다. 이렇게 여러가지를 하는 경우에는 함수들을 중첩해 사용하면 된다.

상태 업데이트 로직이 완성되고 나면, render 함수도 변경해주어야 한다. Map 혹은 List의 값을 읽을 땐 data.get('users')로 읽어야 한다.

render() {
    const { onChange, onButtonClick } = this;
    const { data } = this.state;
    const input = data.get('input');
    const users = data.get('users');

    return (
      <div>
        <div>
          <input onChange={onChange} value={input} />
          <button onClick={onButtonClick}>추가</button>
        </div>
        <h1>사용자 목록</h1>
        <div>
          <UserList users={users} />
        </div>
      </div>
    );
  }

UserList와 User에서도 마찬가지로 값을 읽어올때 get을 사용해 주어야 한다.


출처 및 이어서 확인하기

이어서 확인하기

profile
안녕하세요 주니어 프론트엔드 개발자 양윤성입니다.

0개의 댓글