Back Ground

ReactJS - Immutable.js 본문

Javascript/React.js

ReactJS - Immutable.js

Back 2018. 12. 2. 16:39



Immutable.js 익히기 

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


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





객체 불변성

Immutable.js


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


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




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

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

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


1
2
object1 === object2;
// false
cs


다음 코드로는 어떨까


1
2
3
let object3 = object1;
object1 === object3;
// true
cs


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


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



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

object1에도 c값이 생성되었다





--------------------------------------------

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


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

cs


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



1
2
array1 == array2
//true
cs

이번에도 true를 반환




리액트 컴포넌트는 

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

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

똑같은 값으로 인식 한다.


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

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

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

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

다음 형식으로 해야된다.



기존 방식 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
        }
    } 
 
}
cs



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

1
object1.d.f.h = 10;
cs



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

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

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



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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
cs





Map


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

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



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

Html (CDN)

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


javascript

1
2
3
4
5
6
7
const {Map} = Immutable;
 
const data = Map({
  a:1,
  b:2
});
 
cs

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

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



javascript

1
2
3
4
5
6
7
8
9
10
11
12
const {Map} = Immutable;
 
const data = Map({
  a:1,
  b:2,
  c: Map({
    d:3,
    e:4,
    f:5
  })
});
 
cs


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

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

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



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

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


1
2
3
4
5
6
7
8
9
10
11
12
const {Map, fromJS= Immutable;
 
const data = fromJS({
  a:1,
  b:2,
  c: {
    d:3,
    e:4,
    f:5
  }
});
 
cs


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



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

1
2
3
4
5
6
7
8
9
10
11
12
13
const {Map, fromJS} = Immutable;
 
const data = Map({
  a:1,
  b:2,
  c: Map({
    d:3,
    e:4,
    f:5
  })
});
 
console.log(data);
cs


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

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


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


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

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


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

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





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

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


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


특정 키의 값 불러오기

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

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



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

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

1
data.getIn(['c' , 'd' ]); //3
cs





- 값 설정

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

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


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

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

1
console.log(newData === data);
cs


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

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





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

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

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

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




여러 값 동시에 설정 ( marge )

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

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


[ 방법 1 ]

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

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

또는 코드를 다음과 같이 입력 할 수도 있다.
[ 방법 2 ]
1
2
const newData = data.setIn(['c''d'], 10)
                    .setIn(['c''e'], 10);
cs



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

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



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

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

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















List 


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

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

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


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

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



생성 


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


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

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


1
2
3
4
5
6
7
8
9
10
11
12
const { List, Map, fromJS } = Immutable;
const list = List([
    Map({value: 1}),
    Map({value: 2})
 ]);
 
// or 
 
const list2 = fromJS([
    {value:1},
    {value:2}
]);
cs


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

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

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

1
console.log(list.toJS());
cs



- 값 읽어 오기 

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

1
list.get(0);
cs


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

1
list.get([0'value']);
cs




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

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

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


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

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


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


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

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



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

1
const newList = list.setIn(0,'value'), list.getIn([0'value'* 5 )
cs





- 아이템 추가

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

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

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

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


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

1
const newList = list.unshift(Map({value: 3}))
cs




- 아이템 제거
아이템을 제거할 떄는 delete를 사용한다.
1
const newList = list.delete(1);

cs

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


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


- List크기 가져오기 

배열 크기를 가져올 때는 length를 참조하지만,

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

1
console.log(list.size);
cs


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

1
console.log(list.isEmpty());
cs


더 알아 보고 싶다면 https:facebook.github.io/immutable-js/ 여기서 읽어 보면 된다.


Comments