이전 포스팅 참고
이전 포스팅에서 잠깐 본 것 처럼 Immutable.js 는 좋은 라이브러리입니다.
Immutable.js 를 결국 사용하는 이유는 중첩 구조의 값을 변경한 새로운 객체를 만들어 낼 수 있다는 것 때문이고, 이렇게 불변적으로 값을 다룸으로써 부수 효과를 줄일 수 있습니다. 근래엔 여러 단계 중첩 된 데이터를 많이 사용하므로 이러한 깊은 값의 변화는 꽤 중요한 포인트긴 합니다.
Immutable.js 의 단점이라 한다면 그 내부가 바로 JSON 이 될 수 없는 커스텀 객체라는 부분입니다.
비단 Immutable.js 만의 단점이라기보단, Sequelize 나 TypeORM 등의 Node.js 에서 사용되는 ORM 의 모델들에도 해당됩니다.
- 커스텀 객체
JSON 은 데이터를 주고 받는데 거의 빠질 수 없는 포맷입니다. JSON 으로 굉장히 풍성하게 데이터를 표현할 수 있으며 모든 환경에서 이를 파싱하고 있습니다. 위에서 말한 커스텀 객체로 데이터를 다루면 먼저 .toJSON() 을 해야 합니다. (Immutable.js 에선 toJS() 혹은 toJSON() 이 되겠네요) 커스텀 객체가 중첩되어 있다면 내부를 순회하면서 전부 .toJSON() 을 해야 하는데, 기본적으로 JSON 관련된 처리가 cpu 를 타는걸 생각하면 꽤 자원을 잡아먹는 일입니다. 커스텀 객체가 크면 클수록 cpu 도 많이 쓰고 처리 시간도 오래 걸리겠죠.
Immutable.js 를 사용한다면 결국 위의 단점들은 어느정도 안고 가야 하는데, 그러기보다는 바로 JSON 이 될 수 있는 자바스크립트 기본 객체를 쓰면서도 Immutable.js 의 기능을 대신 할 수 있는 함수들을 아래에서 알아보려고 합니다.
- _.sel
Partial.js 의 _.sel 함수를 보기 전에, 비슷한 기능을 하는 문자열로 된 path 를 통해 값을 조회하는 Lodash 예시를 보도록 하겠습니다.
const _ = require('lodash');
const users = [
{
id: 1,
name: 'A',
items: [
{ id: 1, name: 'Sword', price: 300 },
{ id: 2, name: 'Shield', price: 400 },
],
},
{
id: 2,
name: 'B',
items: [
{ id: 2, name: 'Shield', price: 400 },
{ id: 3, name: 'Ax', price: 250 },
],
}
];
console.info(_.get(users, '[0].name')); // 'A'
console.info(_.get(users, '[1].items[0].name')); // 'Shield'
위와 동일한 기능을 하는 함수를 Partial.js 에서 별도의 문법을 만들었는데, JSON selector 라고 하는 _.sel 함수입니다.
문자열로 작성이 기본이며, 함수적으로도 동작시킬 수 있습니다. 간단한 예시를 보겠습니다.
const _ = require('partial-js');
const users = [
{
id: 1,
name: 'A',
items: [
{ id: 1, name: 'Sword', price: 300 },
{ id: 2, name: 'Shield', price: 400 },
],
},
{
id: 2,
name: 'B',
items: [
{ id: 2, name: 'Shield', price: 400 },
{ id: 3, name: 'Ax', price: 250 },
],
}
];
console.info(_.sel(users, '0 -> name')); // 'A'
console.info(_.sel(users, '1 -> items -> 0 -> name')); // 'Shield'
사용법은 Lodash 와 거의 동일한데 어떻게 보면 좀 더 직관적인 것 같기도 하고, 쓸만해보입니다.
여기에 추가로 함수를 통한 복잡한 쿼리를 해보도록 하겠습니다.
const _ = require('partial-js');
const users = [
{
id: 1,
name: 'A',
items: [
{ id: 1, name: 'Sword', price: 300 },
{ id: 2, name: 'Shield', price: 400 },
],
},
{
id: 2,
name: 'B',
items: [
{ id: 2, name: 'Shield', price: 400 },
{ id: 3, name: 'Ax', price: 250 },
],
}
];
console.info(_.sel(users, '(u=>u.id===1) -> name')); // 'A'
console.info(_.sel(users, '(u=>u.id===2) -> items -> (i=>i.id===3) -> name')); // 'Ax'
이런 표현식도 나름 유용하게 쓸 수 있겠네요. _.sel 이 내부적으로 처리되는 로직을 표현하면 아래와 같습니다.
const _ = require('partial-js');
const users = [
{
id: 1,
name: 'A',
items: [
{ id: 1, name: 'Sword', price: 300 },
{ id: 2, name: 'Shield', price: 400 },
],
},
{
id: 2,
name: 'B',
items: [
{ id: 2, name: 'Shield', price: 400 },
{ id: 3, name: 'Ax', price: 250 },
],
}
];
_.go(users,
_.find(_.l('u=>u.id===1')),
user => user.name,
console.info); // 'A'
위 예시에서 _.l 은 Partial.js 의 _.lambda 의 alias 입니다. _.sel 의 () 부분에 들어가는 부분은 _.find 의 predicate 와 동일해서 (val, idx, list) 인자를 모두 사용할 수 있습니다. 아래에선 위와 동일한 코드를 활용해 약간 TMI 인 문법 예시들을 보도록 하겠습니다.
_.go(users,
_.find(_.l('$.id===1')),
user => user.name,
console.info); // 'A'
_.go(users,
_.find(_.l('#1')),
user => user.name,
console.info); // 'A'
첫번째는 _.l 에선 인자를 하나만 사용할 때 기본 인자 이름을 $ 로 제공하는데, 이를 활용해 인자 부분 정의를 생략한 예시입니다.
두번째는 id 라는 키는 자주 사용되기 때문에 $.id 를 # 으로 대체할 수 있는 문법입니다.
Partial.js 에선 이런 부분도 제공을 하고 있어서 예시로 가져와봤지만, 제 생각엔 그리 좋은 것 같진 않습니다.
과도한 문법 생략은 오히려 코드 가독성을 해친다고 생각하며, 생략하지 않고 명시하는게 위의 예시들에선 더 좋지 않을까 싶습니다.
물론 이건 사견이므로 익숙해지면 더 편할 수 도 있으니, 개발자 취향에 따라가면 될 것 같습니다.
- 값 변경
Immutable.js 를 대신하려면 _.sel 에선 깊은 값 변경 또한 동일하게 지원해야 합니다.
const _ = require('partial-js');
const users = [
{
id: 1,
name: 'A',
items: [
{ id: 1, name: 'Sword', price: 300 },
{ id: 2, name: 'Shield', price: 400 },
],
},
{
id: 2,
name: 'B',
items: [
{ id: 2, name: 'Shield', price: 400 },
{ id: 3, name: 'Ax', price: 250 },
],
}
];
console.info(users[0].items[1]); // { id: 2, name: 'Shield', price: 400 }
_.set(users, '0 -> items -> (i=>i.id===2) -> price', 600);
console.info(users[0].items[1]); // { id: 2, name: 'Shield', price: 600 }
const test = _.im.set(users, '0 -> items -> (i=>i.id===2) -> price', 800);
console.info(users[0].items[1]); // { id: 2, name: 'Shield', price: 600 }
console.info(test[0].items[1]); // { id: 2, name: 'Shield', price: 800 }
_.set 은 객체 내부의 값을 직접 변경하는 함수고, _.im.set 은 _.set 의 immutable 버전입니다.
코드만 봐도 함수들에 의해 객체가 어떻게 처리되었는지 충분히 이해가 될 것입니다.
_.set 외에도 _.unset, _.pop, _.shift, _.push, _.unshift 와 같은 함수들로 깊은 값을 변경할 수 있습니다.
또한 _.im.set 처럼 각 함수 앞에 _.im 을 붙이면 immutable 버전으로 동작해 객체의 복사본을 리턴합니다.
함수형 프로그래밍에 대해 얘기하면서 Partial.js 를 중점적으로 봐왔기 때문에 Partial.js 의 함수들을 이용한 객체 컨트롤에 대해 좀 더 얘기하게 되었고, 꼭 이 라이브러리를 써야한다 는 주제는 아닙니다. Partial.js 의 함수들을 이용하면 기본 객체와 함수만으로 불변 값을 다룰 수 있기에 Immutable.js 와 비교해서 라이브러리를 선택 사용하면 되겠습니다.
- _.deep_pluck
간단한 함수 하나만 더 소개하려 합니다.
const _ = require('partial-js');
const users = [
{
id: 1,
name: 'A',
items: [
{ id: 1, name: 'Sword', price: 300 },
{ id: 2, name: 'Shield', price: 400 },
],
},
{
id: 2,
name: 'B',
items: [
{ id: 2, name: 'Shield', price: 400 },
{ id: 3, name: 'Ax', price: 250 },
],
}
];
console.info(_.deep_pluck(users, 'items.name')); // ['Sword', 'Shield', 'Shield', 'Ax']
깊은 곳에 있는 특정 property 의 내용을 한번에 조회하고 싶다면 _.deep_pluck 이라는 함수가 유용합니다.
Partial.js 의 함수들을 사용해 자바스크립트 기본 객체를 다뤄봤습니다.
만약 커스텀 객체였다면 메서드를 통해 값에 접근하거나 다뤄야 하므로 재사용성이 높은 함수를 만들기 어려울 수 있습니다.
또한 대체로 래핑되어있다보니 진짜 값은 숨어 있어서 다루기가 더 어려운 점도 있고요.
커스텀 객체만의 장점도 있겠지만 위에서 소개한 방법들을 사용해 중복 코드를 줄이고 기본 객체만을 사용해 cpu, 메모리, JSON 관련 추가 작업등의 수고로움을 줄일 수 있지 않을까 싶습니다.
'Backend > 함수형 자바스크립트' 카테고리의 다른 글
더 많은 함수형 자바스크립트 사용.. (2) (0) | 2021.12.04 |
---|---|
더 많은 함수형 자바스크립트 사용.. (1) (0) | 2021.11.28 |
값 다루기 (0) | 2021.11.06 |
지연 평가 (3) (0) | 2021.10.30 |
지연 평가 (2) (0) | 2021.10.23 |