Immutable.js 확장시켜 활용하기

Immutable.js를 위한 라이브러리 소개

Immutable.js를 모르는 분들을 위한 소개

Immutable.js는 불변성을 가진 객체를 만들어주는 라이브러리로, Facebook에서 만들었다. 아래 예제를 보면 어떤 느낌인지 알 수 있다.

/* with Immutable.Map */
const map1 = Immutable.Map({a:1, b:2, c:3});
const map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
/* with native Object */
const obj1 = {a:1, b:2, c:3};
const obj2 = obj1
obj2["b"] = 50
map1.b; // 50
map2.b; // 50

객체에 변경을 가한다는 개념이 아닌, “변경된 객체는 새로운 객체”인 것이다.

하지만 이 글에서 소개하고싶은 내용은 이 Immutable.js를 더 잘 쓸 수 있도록 돕는 라이브러리에 관한 것이기 때문에, 소개는 최대한 간략하게 하겠다.

Destructing Assignment with Immutable.js

Babel과 함께 ES2016 이상의 환경에서 개발하는 사람이라면, Destructing Assignment 문법을 잘 활용하고 있을 것이다. 그렇지만 Immutable.js와 함께하는 환경에서는, 이 문법을 쓰기에 불편한 점이 많았다.
하지만 extensible-destructing을 살짝 끼워넣기만 한다면 그럴 일 없다!

// .babelrc
{
"plugins": [
"extensible-destructuring",
{"mode": "optout", "impl": "immutable"}
]
}

광활한 babel plugin에 해답이 있었다. 이 플러그인을 활용하면 다음과 같은 코드가 가능해진다.

const map1 = Immutable.Map({a:1, b:2, c:3});
const {a, b, c, d} = map1;
// a == 1, b == 2, c == 3, d == undefined

하지만 한 가지 주의할 점은 있다.

function findA(map) {
const {a} = map;
return a;
}

만약 위와 같은 잘 동작하는 어떤 함수가 있었는데, 요구사항이 바뀌어 a를 찾아 result에 담아야 한다고 가정해보자. 코드만 보아서는 map이 Object인지 Immutable 객체인지 알 수가 없다.

function setResultFromA(map) {
const {a} = map;
return map.set("result", a); // or
map.result = a; return map; // 둘 중 어떤 코드가 맞을까?
}

class Data extends Immutable.Map

가끔씩은 객체지향적인 코드가 필요해지면 class가 필요할 때도 있다. 이런 식으로 객체 자체가 어떠한 메소드를 가지고 있을 때 더 좋은 코드가 되는 경우가 있기 때문이다.

class Rect {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}

그런데 Immutable.js에 익숙해지다보면, 이런 방식들이 가끔 불편해질 때가 생긴다. 무의식적으로 아래와 같은 코드를 치게 되는 것이다.

const rect1 = new Rect(10, 20);
const rect2 = rect1.set("width", 20);

그렇다고 class RectImmutable.Map의 모든 메소드를 오버라이딩 할 수도 없는 노릇인데, Immutable.js의 모든 데이터 타입은 class 기반으로 작성된 것이 아니라 extends 가 불가능하다.

이 불편함을 해소하기 위해 확장 가능한 class로 감싸주는 라이브러리가 바로 extendable-immutable

import {Map} from 'extendable-immutable';
class Rect extends Map {
constructor(width, height) {
super({width, height})
}
getArea() {
return this.get("width") * this.get("height");
}
}
const rect1 = new Rect(10, 20);
const rect2 = rect1.set("width", 20); // Now it works!
console.log(rect1.getArea()) // 200
console.log(rect2.getArea()) // 400
console.log(rect2 instanceof Immutable.Map) // true

이 외에도 eslint-plugin-immutablejs, react-immutable-proptypes, immutable-diff 등, Immutable.js를 잘 이용할 수 있게 돕는 라이브러리들이 많다.

이 글을 읽는 사람들은 Immutable.js에 어떤 라이브러리를 더해서 사용중일까, 궁금해진다.

Show your support

Clapping shows how much you appreciated HyeonSeok Yang’s story.