ReactJS - Immutable.js

Blackeichi·2023년 3월 23일
0

출처 : https://backback.tistory.com/313

Immutable.js 익히기

Immutable.js는 자바스크립트에서 불변성 데이터를 다룰 수 있도록 도와준다.

Immutable.js를 알기전에 객체 불변성을 알아야 한다.

객체 불변성

Immutable.js

객체 불변성을 이해하려면 간단한 자바스크립트 코드를 실행해 보아야 한다. 크롬 웹 브라우저에서 개발자 도구를 열고 다음 코드를 입력해보자

let a = 7;
let b = 7;
let object1 = {a: 1, b:2 };
let object2 = {a: 1, b:2 };

보다시피 a 값과 b값은 같다.

둘은 === 연산자로 비교해 보면 당연히 true를 반환할 것이다.

하지만 object1과 object2가 가진 값이 같더라도 서로 다른 객체이기 때문에 둘을 비교하면 false를 반환한다

object1 === object2;
// false

다음 코드로는 어떨까

let object3 = object1;
object1 === object3;
// true

object3에 object1을 넣고, 두 값을 비교하면 true를 반환한다. object1과 object3은 같은 객체를 가리키기 때문이다.

그렇다면 다음 코드를 실행하고 나서 비교하면 어떨까?

object3.c = 3 ;
object1 === object3
//true
 
object1
//object { a: 1, b:2, c:3 }

object1에도 c값이 생성되었다


그렇다면 다른 예제로 다음코드는 어떨까

let array1 = [0,1,2,3,4];
let array2 = array1;
array2.push(5);

이렇게 array2에 5를 상비하고, array1과 array2를 비교하면 무엇이 나올까

array1 == array2
//true

이번에도 true를 반환

리액트 컴포넌트는

state또는 상위 컴포넌트에서 전달받은 props 값이 변할 때 리렌더링되는데,

배열이나 객체를 직접 수정한다면 내부 값을 수정했을지라도 레퍼런스가 가리키는 곳은 같기 때문에

똑같은 값으로 인식 한다.

이런 이슈 때문에 지금까지 여러 층으로 구성된 객체 또는 배열을 업데이트해야 할 때,

전개 연산자(...)를 사용해서 기존 값을 가진 새 객체 또는 배열을 만들었던 것이다.

하지만 그렇게 작업하다 보면 간단한 변경을 구현하는 데도 코드가 복잡할 때가 있다.

예를 들어 수정해야 할 값이 객체의 깊은 곳에 위치한다면

다음 형식으로 해야된다.

기존 방식

let object1 = {
    a : 1, 
    b : 2,
    c : 3,
    d : {
        e : 4,
        f : {
            g : 5,
            h : 6
        }
    }
};

//h 값을 10으로 업데이트 한다.

let object2 ={
    ...object1,
    d : {
        ...objec1.d,
        f : {
            ...object1.d.f,
            h: 10
        }
    } 
 
}

객체를 불변성을 유지할 필요가 없다면 다음과 같이 간단하게 해도 된다.

object1.d.f.h = 10;

배열을 다룰때도 마찬가지인데

배열 안에 있는 값을 수정하려면 수정하려는 원소 위치를 전후를 slice로,

가져와야 하는데 꽤 귀찮은 작업이다.

이런 작업들을 간소화하려고 페이스북에서 만든 라이브러리 Immutable.js가 있다.

이 라이브러리 사용하면 이 코드는 다음 형식으로 작성 할 수 있다.

let object1 = Map({
    a: 1,
    b: 2,
    c: 3,
    d: Map({
        e: 4,
        f: Map({
            g: 5,
            h: 6
        })
    });
});
 
let object2 = object1.setIn([ 'd', 'f', 'h'], 10);
 
object1 === object2;
//false

Map

Immutable의 Map은 객체 대신 사용하는 데이터 구조이다.

자바스크립트에 내장된 Map과 다름

CDN을 이용하여 불러온다. JSBin(https://jsbin.com/)
Html (CDN)

<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.7.3/immutable.min.js"></script>

리액트에서는 라이브러리에서 불러온다.
import { Map, fromJS } from 'immutable';

const {Map} = Immutable;
 
const data = Map({
  a:1,
  b:2
});

Map을 사용할 때는 Map 함수 안에 객체를 넣어서 호출 한다.

이번에는 여러 층으로 구성된 객체를 Map으로 만들어 보자

const {Map} = Immutable;
 
const data = Map({
  a:1,
  b:2,
  c: Map({
    d:3,
    e:4,
    f:5
  })
});

이 처럼 객체 내부에 또는 다른 객체들이 있다면 내부 객체들도 Map으로 감싸 주어야 나중에 사용하기 편하다.

( 내부 객체들도 Map을 필수로 써야 하는 것은 아니지만,

내부에서 Map을 사용하지 않으면 추 후 setIn, getIn을 활용 할 수 없다.)

객체 내용을 네트워크에서 받아 오거나 전달받는 객체가 너무 복잡한 상태라면 일일이 그 내부까지 Map으로 만들기 힘들수도 있다.

이때는 fromJS를 사용할 수 있다.

const {Map, fromJS} = Immutable;
 
const data = fromJS({
  a:1,
  b:2,
  c: {
    d:3,
    e:4,
    f:5
  }
});

fromJS를 사용하면 이 코드처럼 내부에 있는 객체들은 Map을 쓰지 않아도 된다.

Immutable로 Map을 만들어 주었는데, 이를 콘솔에 프린트하면 어떻게 나오는지 확인해 보자.

const {Map, fromJS} = Immutable;
 
const data = Map({
  a:1,
  b:2,
  c: Map({
    d:3,
    e:4,
    f:5
  })
});
 
console.log(data);

console.log(data);를 해보면 객체 정보가 매우 길게 나타난다.

여기에서 나타나는 것들은 Immutable 데이터 가진 내부 변수 및 함수들이다.

해당 데이터를 실제로 활용하거나 업데이트를 해야 할 때는 내장 함수를 사용해야 한다.

예를 들어 data내부의 a값을 참조하고 싶다면

data.a로 작성하는 것이 아니라, data.get('a')를 해야 한다.

Immutable 객체에 내장된 함수들은 종류가 매우 많은데,

자주 사용하는 것 위주로 알아보겠다.

  • 자바스크립트 객체로 변환

Immutable 객체를 일반 객체 형태로 변형하는 방법은 다음과 같다.

const deserialized = data.toJS();
console.log(deserialized);
//{a: 1, b: 2, c: { d :3, e :4 }}

특정 키의 값 불러오기

특정 키의 값을 불러올 때는 get 함수를 사용합니다.

data.get('a'); //1

깊숙이 위치하는 값 불러오기

Map 내부에 또 Map이 존재하고, 그 Map 안에 있는 키 값을 불러올 때는 getIn 함수를 사용한다.

data.getIn(['c' , 'd' ]); //3
  • 값 설정

새 값을 설정할 때는 get 대신 set을 사용한다.

const newData = data.set('a', 4);

set을 한다고 해서 데이터가 실제로 변하는 것은 아닌다.

주어진 변화를 적용한 새 Map을 만드는 것이다.

console.log(newData === data);

서로 다른 Map이기 때문에 false를 프린트한다.

기존 data값은 그대로 남아 있고, 변화가 적용 된 데이터를 newData에 저장하는 것이다.

깊숙이 위치하는 값 수정 ( setIn )

깊숙이 위차하는 값을 수정할 때는 setIn을 사용한다.

이때 내부에 있는 객체들도 Map 형태여야만 사용할 수 있다는 점에 주의해야 한다.

const newData = data.setIn(['c','d'],10);

여러 값 동시에 설정 ( marge )

값 여러 개를 동시에 설정해야 할 때는 mergeIn를 사용한다.

예를 들어 c값과 d값, c값과 e값을 동시에 바꾸어야 할 때는 코드를 다음과 같이 입력한다.

[ 방법 1 ]

const newData = data.mergeIn(['c'], { d : 10, e : 10})

이렇게 mergeIn를 사용하면 c안에 들어 있는 f값은 그대로 유지하면서 d값과 e값만 변경한다.

또는 코드를 다음과 같이 입력 할 수도 있다.
[ 방법 2 ]

const newData = data.setIn(['c', 'd'], 10)
                    .setIn(['c', 'e'], 10);

그리고 최상위에서 merge를 해야 할 때는 코드를 다음과 같이 입력한다.

const newData = data.marge({a : 10 , b : 10});

즉, set을 여러번 할지, 아니면 merge를 할지는 그때그때 상황에 맞춰 주면 되지만,

성능상으로 set을 여러번 하는것이 빠르다

( 하지만 애초에 오래 걸리는 작업이 아니므로 실제 처리 시간의 차이는 매우 미미하다.)

List

Immutable 데이터 구조로 배열 대신 사용한다.

배열과 동일하게 map. filter, sort, push, pop 함수를 내장하고 있다.

이 내장 함수를 실행하면 List 자체를 변경하는 것이 아니라, 새로운 List를 반환하는 것을 꼭 기억하길

또 리액트 컨포넌트는 List 데이터 구조와 호환되기 때문에 map 함수를 사용하여 데이터가 들어있는 List를

컴포넌트 List로 변환하여 JSX에서 보여주어도 제대로 렌더링 된다.

생성

const { List } = Immutable;
const list = List( [0,1,2,3,4 ] );

객체들을 List를 만들어야 할때

객체들을 Map으로 만들어야 추후 get과 set을 사용 할 수 있다.

const { List, Map, fromJS } = Immutable;
const list = List([
    Map({value: 1}),
    Map({value: 2})
 ]);
 
// or 
 
const list2 = fromJS([
    {value:1},
    {value:2}
]);

fromJS를 사용하면 내부 배열은 List로 만들고, 내부 객체는 Map으로 만든다.

그리고 Map과 마찬가지로 List도 toJS를 사용하여 일반 배열로 반환할 수 있다.

이 과정에서 내부에 있는 Map들도 자바스크립트 객체로 변환된다.

console.log(list.toJS());
  • 값 읽어 오기

n번째 원소 값은 get(n)을 사용하여 읽어 온다.

list.get(0);

0번째 아이템의 value값은 다음과 같이 읽어 온다.

list.get([0, 'value']);
  • 아이템 수정
    n번째 아이템을 수정해야 할 때는 set과 setIn을 사용한다.
    원소를 통째로 바꾸고 싶을 때는 다음와 같이 set을 사용한다.
const newList = list.set(0, Map({vlaue:10}))

List의 Map 내부 값을 변경하고 싶을 때는 다음과 같이 setIn을 사용 한다.

const newList = list.setIn([0, 'vlaue'],10);

다음 방법으로는 update를 사용할 수도 있다.

const newList = list.update(0, ,item => item.set('value', item.get('value') * 5 ) )

값을 업데이트해야 하는데 기존 값을 참조해야 할 때는 이처럼 update를 사용하면 편하다.

첫번째 파라미터는 선택할 인덱스 값

두번째 파라미터는 선택한 원소를 업데이트하는 함수

update를 사용하지 않았다면 다음과 같이 작성해야된다.

const newList = list.setIn(0,'value'), list.getIn([0, 'value'] * 5 )
  • 아이템 추가

아이템을 추가할 때는 push를 사용한다.

이 함수를 사용한다고 해서 Array처럼 기존 List자체에 아이템을 추가하는 것은 아니다.

새 List를 만들어서 변환하므로 안심하고 사용할 수 있다.

const newList = list.push(Map({value: 3}))

리스트 맨 뒤가 아니라 맨 앞에 데이터를 추가하고 싶다면 push대신 unshift를 사용해야한다.

const newList = list.unshift(Map({value: 3}))
  • 아이템 제거
    아이템을 제거할 떄는 delete를 사용한다.
const newList = list.delete(1);

이렇게 작성하면 인덱스 1인 아이템을 제거한다.

Array가 가진 내장 함수를 List도 대부분 가지고 있다.
예를 들어 마지막 아이템을 제거하고 싶다면 pop을 사용해도 된다.

const new List = list.pop();
  • List크기 가져오기
    배열 크기를 가져올 때는 length를 참조하지만,

List에서는 length가 아니라 size를 참조해야 한다.

console.log(list.size);

비어있는지 확인하고 싶다면 .isEmpty()를 사용 할 수 있다.

console.log(list.isEmpty());
profile
프론트엔드 주니어 개발자 한정우입니다. 😁

0개의 댓글