이머 (Immer)를 소개해: 불변성 쉽게 하자

Wonkun Kim
6 min readJan 16, 2018

--

이 포스트는 Michael WeststrateIntroducing Immer: Immutability the easy way를 번역한 것입니다.

불변하는, 그리고 구조적으로 공유되는 데이터 구조는 상태를 저장하는 좋은 패러다임이야. 특히 이벤트 소싱 아키텍쳐와 함께 할 때 더욱 그렇지. 그러나 여기에서는 지불해야 하는 것이 있어. 불변성이 언어상에서 지원되지 않는 자바스크립트와 같은 프로그래밍 언어에서는 이전 상태에서 새로운 상태를 만들어 내는 과정은 지루한 법이지. 리덕스 생태계 링크 페이지에만 리덕스에서 불변 데이터 구조를 다루는데 도움을 줄 수 있는 67(!)개의 패키지가 등록되어 있다는 사실이 이를 뒷받침해.

그리고 여전히 대부분의 패키지는 근원적인 문제인 ‘언어적 지원의 부재'를 해결하지 않고 있어. 예를 들어, update-in이 클로져스크립트와 같은 언어에서는 우아한 개념인데 반해 자바스크립트에서는 기본적으로 너저분한 문자열로 된 경로에 의존해야 해. 이것은 오류에 취약하고 타입 체크가 어려우며 능숙하게 쓰기 위해 또 다른 하나의 API 함수 세트를 배우는 것을 요구해.

만약 언어와 싸우는 걸 멈추고 대신에 포용하면 어떨까? 지속성 (persistent)데이터 구조에 의해 제공되는 우아함을 포기하지 말고 말이야. 이게 바로 이머 (immer)가 하고자 하는 거야.

생산자 (Producers)

이머는 생산자를 작성하는 것으로 동작하고 가장 간단한 생산자는 다음과 같이 생겼어.

원 상태를 반환하는 가장 단순한 (빈) 생산자

생산 (produce) 함수는 두 개의 인자를 받아. 현재 상태 (currentState) 생산자 (producer) 함수 말이야. 현재 상태는 우리의 시작점을 결정하고 생산자는 무슨 일이 발생해야 하는지를 표현하지. 생산자 함수는 현재 상태의 프락시(proxy)인 드래프트 (draft)를 인자로 받아. 드래프트에 가해지는 어떠한 수정이라도 기록되고 다음 상태 (nextState)를 만들어 내는데 사용될 거야. 현재 상태는 이 과정 중에서 본래 상태를 유지하게 될거야.

이머는 구조 공유 (structural sharing)를 사용하고 위 예제의 생산자는 아무 것도 수정하지 않았기 때문에 다음 사태는 우리가 시작했던 상태와 정확히 같아.

그러면 생산자 함수에서 드래프트를 수정하기 시작했을 때 어떤 일이 벌어지는지 알아보자. 다만 생산자 함수는 아무것도 반환하지 않아. 단 하나 중요한 것은 우리가 만들게 될 변화뿐이야.

실제 생산자. 드래프트에 가해진 모든 변화는 다음 상태에 반영되는데 이전 상태에서 변하지 않은 아이템들은 구조적으로 공유된다

여기에선 생산 함수가 어떻게 동작하는지 볼 수 있어. 우리는 새로 더해진 할 일 아이템을 담고 있는 새로운 상태 트리를 만들었어. 그리고 두 번째 할 일 아이템의 상태를 변경했지. 우리가 드래프트에 가한 변경 점들이 멋지게 다음 상태에 반영된 것을 확인할 수 있어.

하지만 이게 다가 아니야. 위 코드의 마지막 줄은 드래프트 상에서 변경된 상태가 새로운 객체에 멋지게 반영된 걸 보여줘. 하지만 변경되지 않은 부분은 이전 상태와 구조적으로 공유되고 있어. 이 경우에는 첫 번째 할 일 아이템 (역자주: 코드 줄 20)

생산자와 함께한 리듀서

이제 새로운 상태를 만드는 것에 대한 기본적인 것을 배웠어. 배운 것을 리덕스의 리듀서에 활용해보자. 다음 코드는 많은 새 상품을 상태로 가져오는 공식 쇼핑 카트 예제에 기반을 둔 거야. 상품들은 배열로 받아진 후 reduce로 변환되어 상품 아이디를 키로 가지는 맵의 형태로 저장 돼.

전형적인 리덕스의 리듀서

표준 코드 같은 부분은 다음과 같아.

  1. 우린 새로운 상태 객체를 생성해야 만 해. 이 객체는 기존 상태를 유지한 채로 새 상품 맵을 섞어 놓은 것과 같아. 이렇게 간단한 경우에는 그렇게 나쁘진 않지만 이 과정은 모든 액션에 대해 그리고 우리가 무언가를 바꾸고 싶을 때마다 반복되어야 만 해.
  2. 리듀서가 무언가를 바꾸지 않았다면 반드시 기존 상태를 그대로 반환해야 해.

이머와 함께라면 현재 상태에 상대적으로 가할 변경 점에 대해서만 고려하면 돼. 따라서 다음 상태를 생성하는 노력이 필요 없게 돼. 생산자를 이용하면 같은 리듀서가 다음과 같은 간단한 코드가 되지.

이머를 이용해 리듀서를 간단히 작성하기

RECEIVE_PRODUCTS가 실제로 무엇을 하는지 얼마나 쉽게 이해 가는지 보이지 않아? 많은 코드가 제거되었고 default 케이스에 대한 처리도 필요가 없어졌어. 드래프트에 변경을 가하지 않으면 기본 상태가 그대로 반환되니까 말이야. 위의 두 리듀서 코드는 정확하게 똑같이 동작하게 될 거야.

제약 없음 (No strings attached)

드래프트를 일시적으로 변경해서 다음의 불변 상태를 만들어 내는 아이디어는 전혀 새로운 게 아냐. 예를 들어 ImmutableJS도 withMutations라고 비슷한 개념을 가지고 있어. 하지만 이머의 큰 장점은 전혀 새로운 라이브러리를 배울 필요가 없다는 점이야. 이머는 일반 자바스크립트 객체와 배열상에서 동작하거든.

장점은 더 있어. 표준화된 코드를 줄이기 위해 ImmutableJS와 다른 라이브러리들은 깊은 갱신(그리고 그 외 많은 작업)을 표현할 수 있도록 전용 메쏘드들을 제공해. 하지만 경로는 단순 문자열이고 타입 검사기 (type-checkers)에 의해 검증될 수 없어. 따라서 상당히 오류에 취약해져. 다음 예제에서는 ImmutableJS에서 list의 타입을 추론할 수 없다는 걸 보여줄 거야. 다른 라이브러리들은 이러한 접근을 좀 더 심화해서 경로 질의에 관해 자신들만의 언어를 만들어 내기도 했어.

이머는 깊은 갱신을 하는 동안에도 형을 유지해

이머는 이미 존재하는 자바스크립트의 구조상에서 동작하기 때문에 이런 접근에서 자유로워. 어떤 형 검사기에 의해서도 완벽하게 이해될 수 있지. 데이터를 변경하는 것도 네가 이미 익숙한 언어에서 이미 지원하는 API들을 이용할 수 있어서 편리하지.

오토 프리즈

마이클 틸러: 이머를 한 프로젝트에 사용하기 시작했다. 불변성을 처리하는 우아한 방식을 제공해. 보너스로 ,객체 프리징은 바로 내 테스트에서 버그를 발견해냈어.

커링

마지막 기능이야. 지금껏 우리는 두 개의 인자, 기본상태생산자 함수를 가지는 생산하기를 호출했어. 하지만, 어떤 경우에는 부분 구현을 사용하는 것이 더 편리할 수 있지. 생산자 함수만 가지고 생산하기를 호출하는 것도 가능해. 이렇게 하면 상태가 주어질 때 생산자 함수를 호출하는 새로운 함수를 만들게 돼. 이 새로운 함수는 임의의 추가 인자들을 받아서 생산자 함수에 넘기는 것이 가능해.

마지막 문장을 이해하지 못했다고 해도 걱정하지 마. 결국엔 네가 리듀서의 표준 코드를 커링을 활용해서 더 줄일 수 있다는 점이 중요해.

커리된 생산자 (이전 코드와 비교해 봐)

좋았어. 이게 기본적으로 이머가 가진 모든 기능이야. 바로 사용해 보길 바라. 하지만, 지금쯤 이머가 어떻게 동작하는 지 궁금할 지도 모르지. 그렇다면 2부로 가서 계속 읽어 보도록 해. (역자주: 분량이 많이 2부로 나누었습니다.)

--

--