[ immutable.js ] nested object에서의 $set 직접 구현해보기

한영재
한영재
Sep 3, 2018 · 5 min read

작성배경

리액트를 사용할 때, shouldComponentUpdate 라는 내부 LifeCycle Method 사용이 빈번하게 발생한다. state가 변화할때 나라는 컴포넌트와 전혀 상관 없다면 굳이 다시 render 함수가 호출 되는 것은 프로젝트 볼륨이 커진다면 분명 부담으로 작용하기 때문이다. 그런 연유로 우리는 보통 state의 불변성을 유지하기 위해 immutable-helper 들을 많이 사용한다. 그중 이번 글에서는 nested ojbect “$set”을 직접 구현한 것을 정리해보려 한다.

코드

1. 먼저 하드코딩을 통해 법칙을 찾기

// f: "x" -> f: "f" 로 바꿔 보자!
state = { a: "a", c: { d: { e: "e", f: "x" } } };
먼저 immutable.js 에서 바꾼다면 다음과 같은 명령어로 바꿀 것이다.
update(state, {c: { d: {f: { $set: "타겟" } } } })
c -> d -> f 총 3 depth 이다.이 depth(c,d,f 순) 순서대로 log 라는 배열에 담는다.
(주요 주제가 아니라 구현 부분은 생략한다. log 배열은 아래와 같다.)
log = ["c", "d", "f"]
하드코딩 된 코드를 먼저 살펴보면 아래와 같다.// 첫번째 depth 얕은 복사
// 결과 : newState = { a: "a", c: { d: { e: "e", f: "x" } } }
let newState = Object.assign({}, state);
// 두번째 depth 얕은 복사
// 결과 : newState[log[0]] = { d: { e: "e", f: "x" } }
newState[log[0]] = Object.assign({}, newState[log[0]]);
// 세번째 depth 얕은 복사
// 결과 : newState[log[0]][log[1]] = { e: "e", f: "x" }
newState[log[0]][log[1]] =
Object.assign({}, newState[log[0]][log[1]]);
// 최종 목적지 원하는 값 "f" 입력!
// 결과 : newState[log[0]][log[1]][log[2]] = f: ??
newState[log[0]][log[1]][log[2]] = "타겟";
return newState; // 결과 { a: "a", c: { d: { e: "e", f: "타겟" } } }

2. nested 정도에 관계없이 잘 작동하는 코드 구현하기

자 위는 하드코딩으로 이번 문제만 해결할 수 있는 코드이다. 이렇게 먼저 하드코딩으로 작성을 해보니 다음과 같은 법칙을 발견할 수 있었다.

  • depth (log 배열의 길이) 만큼 “[log[index]]” 가 뒤에 붙는다.

이를 바탕으로 nested 정도에 관계없이 잘 작동하는 코드를 구현해보도록 하자!!

::: 예시 사용법 :::
/*
state = { a: "a", c: { d: { e: "e", f: "x" } } };
command = {c: { d: {f: { $set: "타겟" } } } }
update(state, command)
*/
::: command 에서 뽑아낸다. 따로 함수를 만들어 구했다.(command 따라 달라짐) :::
let log = [c, d, f]
let changedValue = "타겟"
::: 변경할 state shallow-copy :::
let newState = Object.assign({}, state);
::: depth(log의 초기 인덱스) :::
let i = 0;
::: 재귀함수 정의 :::
function traverse(obj, index) {

:: 목적지(depth 끝)에 도달하면 바꾸고자 하는 값(changedValue) 대입 ::

// 아직 index + 1 : 1, nestedDepth - 1 : 2 이므로 통과
// index + 1 : 2, nestedDepth - 1 : 2 이므로 조건문 입성
if (index + 1 === nestedDepth - 1) {
// obj[f] = "타겟" 후 return 과 함께 재귀 종료
return (obj[log[index + 1]] = changedValue);
}
// obj = d: { e: "e", f: "x" }
obj = Object.assign({}, obj);
index = index + 1;
// obj[d] : { e: "e", f: "x" }, index : 1
traverse(obj[log[index]], index);
}:::: log[0] => 즉, newState["c"] 대입 ::::
newState[log[i]] = Object.assign({}, newState[log[i]]);
:::: 재귀함수 트리거 ::::
/* 다음과 같이 들어간다. traverse(d: { e: "e", f: "x" }, 0) */
traverse(newState[log[i]], i);
:::: 모든 재귀 종료후 해당 값 반환 ::::
return newState; // 결과 { a: "a", c: { d: { e: "e", f: "타겟" } } }

마치며

직접 구현해보니, 얼마나 라이브러리가 소중한 것인지를 다시금 깨달을 수 있었다. 프로젝트에만 전념 하느라 알고리즘이나 재귀에 소홀해서 였을까? 초반에 다소 헤멨다. 그래도 구현하는게 재미는 있었다. 그리고 아직 프로젝트 볼륨 상 immutable-helper 말고 spread operator로 불변성을 유지 시켰었다. 이제 더 큰 볼륨의 프로젝트를 맡는다고 하더라도 이번기회에 불변성 유지에 대한 두려움을 조금 떨칠 수 있었다. 만만세!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade