Redux

Redux의 소개 글을 보면 왜 Redux를 써야 하는지 어떤 생각으로 설계를 하였는지를 설명하면서 코드 외에도 어떻게 짜라는 방법을 설명해주고 있다. Redux 글과 강의를 보다 내 나름대로의 정리가 필요해서 글로 정리하였다. 이 글은 Redux에 대한 소개를 다루지 않으며 Redux todo 프로그램을 기준으로 왜 이렇게 짜여지는 어떤 부분에 장점이 있는지를 다룬다.  todo  소스코드는 egghead.io에 소개된 redux 강의를 기준으로 하였으며(소개 글의 코드와 다르다) redux기본은 redux 사이트를 보는것을 추천 한글로 된 소개글은 태곤님의 소개글을 추천한다.

기능

Screen Shot 2015-12-18 at 10.57.17 PM일단, 예제 프로그램으로 소개되고 있는 todo 프로그램의 기능은 다음과 같다.

  1. 상단의 input을 통해서 todo를 추가 할 수 있다.
  2. 추가된 todo는 중간의 todo 목록으로 보여지며 todo를 클릭하면 완료 여부를 표시할 수 있다.
  3. 하단의 필터는 입력한 todo 목록을 상태별로 나눠서 볼 수 있게 해준다.

위 기능들이 동작을 할 때 현재 프로그램이 어떤지를 저장한게 상태(State)라고 한다. todo 프로그램에서는 해야  할 일을 정의한 todo와 이들의 목록인 todos 그리고 현재 화면에서 어떤 타입의 todo를 보여주고 있는지 여부인 visibilityFilter가 todo 프로그램의 상태로 저장되는 값이다.

State

redux_model

프로그램의 모든 상태들이 이 상태 트리 하나에 저장된다. MVC의 모델이 지녀야 할 값 외에도 프로그램의 현재 화면 상태, View의 상태도 이 트리에 모두 저장한다. (Single source of truth는 redux의 삼원칙중 처음에 나온다.) 상태 트리를 하나에 모두 저장하게 되면 현재 프로그램이 어떤 상태인지, 어떻게 변했는지를 알기가 쉬우며 디버깅도 쉬워진다. 프로그램 구현시 어려운 부분인 do/undo도 상태의 전/후를 저장하면 쉽게 처리할 수 있다.

Redux_동기화케이스

복수 개의 모델을 가지는 경우 단위별 개발이 빠르고 쉽게 만들 수도 있지만, 모델 상호간에 의존된 값을 사용하게 되는 경우 동기화를 해줘야 하는 이슈가 발생할 수도 있고 동기화를 하게 되는 경우는 각 모델간의 생성 순서나 동기화 순서가 이슈가 될 수 있다. Flux를 사용할 때 복수 개의 class로 만들어진 store를 사용할 때, 사용자의 요청에 따라서 응답하는 store들이 다른경우 서버 렌더링하는 경우에 응답에 따른 store 생성순서를 처리/관리하는 빙용이 생기게 되는데 한 개의 트리에 모든 상태를 저장하게 되면 이러한 이슈들을 해결할 수 있게 된다.

상태 트리를 만들 때는 트리의 구성은 다음의 기준을 제안하고 있다.

  • 어떠한 중첩된 구조 없이 가능한한 정규화된 상태를 유지한다.
    (샘플 코드에서는 nomalizr를 사용하여 중첩 데이타를 펼쳐서 사용)
  • 모든 엔터티들이ID를 키로 가지게 하고 엔터티 또는 리스트에서 특정 엔터티를 참조하고 싶을 때는 ID를 사용한다.
  • 더 자세한 것은 normalizr의 기준을 참고한다.

reference

Action

Ajax 응답을 받거나 사용자 인터랙션이 발생하였을 때 변경사항을 상태 트리에 알려주는 것은 액션(action)이다. 액션은 순수 객체로 만들어진다.  액션은 무엇이 변경되었는지를 표현하기 위한 type을 가진다. 액션은 최소한의 데이타로 표현되어야 하며 이 액션이 적용되었을 때 상태가 어떻게 바뀌었는지 유추할 수 있어야 한다.

todo 프로그램에서 액션은 다음의 3가지를 정의하고 있다.Redux_Actions

액션을 정의할  때는 몇 가지 컨벤션을 권장한다.

  • 액션의 타입은 문자열 상수 (const string)로 정의한다.
    • 심볼(Symbol)을 사용할 수도 있지만 문자열과 달리 기록하고 다시 실행하기 어려운 문제점을 가지고 있기 때문에 권장하지 않는다.
  • 상수를 사용하는 것을 권장한다.
    • 액션종류들을 한 곳에 모음으로써 어떠한 상수가 있는지 확인하기 쉽다.
    • 액션들의 이름을 짓는경우에 대한 일관성을 가질 수 있다.
    • PR에 올라오는 액션종류(추가,삭제,수정)에 따라 기능이 얼만큼 작업이 되었는지 같은 팀원이 인지하기 쉬워진다.
    • 상수를 가져다 사용하기 때문에 오타가 발생할 여지를 줄인다.
  • 액션을 생성할 때는 문자열 리터럴을 그대로 사용하기 보다는 액션을 생성하는 메소드를 사용하는 것을 권장한다.
    • 액션을 생성하는 곳이 여러 곳인 경우 함수만 변경 함으로서 한 번에 수정이 가능하다.

Screen Shot 2015-12-31 at 9.31.16 AM

Reducer

어떠한 액션에 대해서 상태를 어떻게 변경할지를 결정하는 것은 Reducer다. Reducer는 함수로 구성이 되며 이전 상태와 액션을 받아서 변경된 상태를 리턴한다. Reducer는 순수함수로 부수 효과가 없는 함수여야 한다. 즉, 자신이 처리할 액션인 경우 인자로 받은 상태에 변경 후의 새로운 상태를 만들어 리턴함으로서 참조 객체 변경으로 인한 오류를 막을 수 있게 된다. Reducer는 체인 형태로 동작해야 하기 때문에 자신이 처리하지 않는 액션에 대해서는 항상 인자로 받은 상태를 리턴해줘야 한다.

Redux_reducer

위의 그림을 예로 리듀서를 설명해보면 ADD_TODO 액션과 상태 트리를 받아서 새로운 상태 트리를 만들었지만 액션과 관련된 트리만 새롭게 변하고 필터와 관련된 좌측 부분은 그대로 유지된다. 상태 트리가 무한정 커질 수도 있지만 필요한 트리의 가지만 변경된다는 점과 변경안된 부분의 레퍼런스가 유지되어서 해당 값을 참조하는 곳에서 빠르게 동작할 수 있다는 장점이 있다.

Redux에서는 생성한 Reducer들을 함께 묶어주는 combinedReducers()를 제공해주는데 todo 프로그램에서는 할일 목록들을 관리해주는 todos와 필터 설정과 관련된 visibilityFilter를 사용하고 있으며 각각의 함수들은 자신의 관심사에만 집중한 형태로 구성된다. todos 함수는 할일 전체 목록에 대해서만 처리하며 할일 자체에 대해서는  todo 함수를 통해 다시 할당하게 된다.

Redux_separate_concern.png

아래는 todos와 todo를 처리하는 리듀서로 변경된 상태는 항상 새로운 객체를 생성해서 리턴하고 변경되지 않은 경우에는 기존 상태를 리턴하는 것을 확인할 수 있다.

Redux_reducer

Store

Screen Shot 2015-12-31 at 9.32.39 AM

스토어의 기본 골격은 위와 같다. 관찰자들을 등록하고 액션을 받았을 때 이를 전파시켜주는게 기본 골격이다. 즉, subscribe()를 통해 상태 트리가 변경된 경우 이를 청취할 함수를 등록하고 dispatch()를 통해 발생한 액션을 받으며 현재 상태를 얻어갈 수 있도록 getState()를 제공한다.

todo 프로그램에서는 사용자가 클릭과 같은 동작을 실행했을 때 액션을 생성하고 이를 store.dispatch(action)에 전달한 다음 store에 저장되어 있는 reducer들을 실행하면서 변경사항을 생성한 다음 새로운 상태 트리를 render()에 전달하게 된다. render()가 호출될 때 상태 트리의 속성들은 react의 속성(props)로 전달되어 리액트 컴포넌트가 새로 그려질 때 사용되게 된다.

Redux_flow

화면이나 네트워크에서 액션이 발생하여 스토어로 전달되고 스토어에서 생성된 새로운 상태가 다시 화면으로 전달되어 화면이 갱신되게 된다. 흐름을 보게 되면 단방향으로 구성되며 여기에는 동기 흐름만이 존재하며 비동기 처리는 middleware를 통해 이 흐름 밖에서 처리하게 된다.

Container component

액션으로 변경된 상태트리를 받는 리액트 컴포넌트를 Container component라 부른다. Todo  프로그램에서는 App(강의 코드에서는 TodoApp)이 여기에 해당된다. Container component의 아래에는 Presentational component들로 구성되며 르는 redux를 알지 못하면서 화면 그리는데 집중하는 요소들이 있다.

Redux_component

TodoList에서는 상태트리에서 todos 목록만 인자로 받아서 화면에 그리고 todo 항목을 클릭했을 때 이벤트를 배포하는 함수를 인자로 받아서 이벤트 핸들러에 설정하는 형태로 층을 분리해서 구성하고 있다.

Middleware

Middleware는 액션이 발생되어서 전파되는 시점부터 reducer에 도착하는 지점 사이에 서드파티를 추가할 수 있는 기능을 제공한다.(the moment it reaches the reducer로 사용되었는데 이 의미가 리듀서의 시작점이 아니라 리듀서 종료 지점으로 봐야할 것 같다. Implement middleware도 참조)  액션이 발생하여 상태 트리가 변경되었을 때 전/후의 흐름을 체크하거나 충돌 리포터를 사용하여 실행 시 예외를 체크 보고 할 수 있는 기능을 체인 형태로 제공할 수 있다. (Redux 의 Middleware 항목을 보면 Middleware를 어떻게 만들었는지, 개념에 대해서 잘 소개 되어 있다.)

Redux_concept

Middleware 개요 부분의 코드만 추려보면 다음과 같다. (실제 코드와는 다름에 주의!) store와 dispatch를 인자로 받아서 middleware의 코드를 적용한 dispatch를 리턴해서 액션이 발생하면 적용된 middleware들을 모두 실행하게 된다. Redux에서는 Middleware를 제외하고는 동기화 흐름만 지원하기 때문에 비동기 동작들은 Middleware로 구현되며 Ajax 호출과 같은 경우는 Thunk를 사용해 구현된다.

Redux_middleware_code

 Thunk

Redux에서 비동기 동작은 Redux Thunk Middleware를 통해서 실행된다. 인자로 받는 액션이 함수인 경우 이를 실행시켜 주는 간단한 함수이지만 비동기 동작을 통한 액션 발생을 함수 안으로 캡슐화 시켜주는 기능을 하게 된다.

thunkMiddleware

참고 자료

※ redux 문서를 보다보면 지속적으로 용어나 설명이 추가/변경이 되고 있어서 가장 최신 내용은 영문의 공식 문서를 보시기를 추천합니다. “왜?”라는 질문과 함께 ‘이렇게 풀었다’라는 설명을 보다보면 개발문서의 정석으로 느껴지네요.

덧글 2개

  1. 기술 블로그에 댓글 잘 안 쓰는데
    정말 훌륭한 포스트라 저도 모르게 댓글을 적고 있네요.
    감사합니다!

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중