액션은 무언가 일어난다는 사실을 기술하지만, 그 결과 애플리케이션의 상태가 어떻게 바뀌는지는 특정하지 않습니다. 이것은 리듀서가 할 일이죠.
상태 설계하기
Redux에서 애플리케이션의 모든 상태는 하나의 객체에 저장됩니다. 어떤 코드건 작성하기 전에 형태를 생각해보는건 좋은 일이죠. 여러분의 앱의 상태를 객체로 만든다면 어떤 표현이 가장 단순할까요?
우리의 할일 앱을 위해 두 가지를 저장하고 싶습니다:
현재 선택된 필터;
할일의 실제 목록.
여러분은 종종 데이터 뿐만 아니라 UI 상태도 상태 트리에 저장해야 한다는걸 발견하실겁니다. 그래도 좋지만, 데이터는 UI 상태와 분리하도록 하세요.
{ visibilityFilter:'SHOW_ALL', todos: [{ text:'Consider using Redux', completed:true, }, { text:'Keep all state in a single tree', completed:false }]}
관계에 대한 한마디
더 복잡한 앱에서는 각기 다른 개체들이 서로를 참조하게 만들고 싶으실겁니다. 우리는 여러분이 앱의 상태를 가능한한 중첩되지 않도록 정규화할것을 권합니다. 모든 개체가 ID를 키로 가지고, ID를 통해 다른 개체나 목록을 참조하도록 하세요. 앱의 상태를 데이터베이스라고 생각하시면 됩니다. 이런 접근은 normalizr's의 문서에 자세히 나와있습니다. 예를 들어 상태 안에 todosById: { id -> todo }와 todos: array<id>처럼 구현하는 것이 실제 앱에서는 더 적절합니다. 하지만 예시에서는 단순하게 하겠습니다.
액션 다루기
상태 객체가 어떻게 생겼는지 정했으니 리듀서를 작성해봅시다. 리듀서는 이전 상태와 액션을 받아서 다음 상태를 반환하는 순수 함수입니다.
사이드이펙트를 어떻게 일으키는지는 심화과정에서 확인하게 될겁니다. 지금은 리듀서가 반드시 순수해야 한다는 점만 기억해두세요. 인수가 주어지면, 다음 상태를 계산해서 반환하면 됩니다. 예기치 못한 일은 없어야 합니다. 사이드 이펙트도 없어야 합니다. API 호출도 안됩니다. 변경도 안됩니다. 계산만 가능합니다.
이 정도로 해두고, 우리가 전에 정의했던 actions을 이해하도록 천천히 리듀서를 작성해봅시다.
초기 상태를 정하는데서 시작하겠습니다. Redux는 처음에 리듀서를 undefined 상태로 호출합니다. 그때가 초기 상태를 반환할 기회입니다:
import { VisibilityFilters } from'./actions'constinitialState= { visibilityFilter:VisibilityFilters.SHOW_ALL, todos: []}functiontodoApp(state, action) {if (typeof state ==='undefined') {return initialState }// 지금은 아무 액션도 다루지 않고// 주어진 상태를 그대로 반환합니다.return state}
우리는 state를 변경하지 않았습니다.Object.assign()을 통해 복사본을 만들었죠. Object.assign(state, { visibilityFilter: action.filter })이라고 써도 여전히 틀립니다: 첫번째 인수를 변경하게 되니까요. 여러분은 반드시 첫번째 인수로 빈 객체를 전달해야 합니다. ES7로 제안된 object spread syntax을 써서 { ...state, ...newState }로 작성할수도 있습니다.
default 케이스에 대해 이전의 state를 반환했습니다. 알 수 없는 액션에 대해서는 이전의 state를 반환하는것이 중요합니다.
switch문은 진짜 보일러플레이트가 아닙니다. Flux의 진짜 보일러플레이트는 개념적인 부분입니다: 변경사항을 보내야 하고, 디스패쳐에 스토어를 등록해야 하고, 스토어가 객체가 되어야 합니다(그리고 유니버셜 앱을 만들때 그 복잡성이 드러나죠). Redux는 이러한 문제들을 이벤트 이미터 대신 순수 리듀서를 사용함으로써 해결했습니다.
많은 이들이 아직도 문서에 switch문을 사용하는가를 보고 프레임워크를 선택한다는건 불행한 일입니다. 만약 여러분이 switch를 싫어한다면 “보일러플레이트 줄이기”에 나온 것처럼 핸들러 맵을 받도록 직접 만든 createReducer 함수를 사용할 수 있습니다.
더 많은 액션 다루기
다룰 액션이 2개 더 있습니다! 우리의 리듀서가 ADD_TODO를 다룰 수 있도록 확장합시다.
변경 없이 배열의 특정 할일만 수정하고 싶기 때문에, 그 할일의 앞과 뒤를 잘라냈습니다. 만약 이런 코드를 자주 작성해야 한다면 React.addons.update, updeep같은 헬퍼나 Immutable 같이 깊은 수정을 지원하는 라이브러리를 사용하는것이 좋습니다. state를 복사하기 전엔 그 안의 무엇에도 할당하지 말아야 한다는걸 기억하세요.
좀 더 이해하기 쉽게 만들 방법이 있을까요? todos와 visibilityFilter는 서로 완전히 독립적으로 수정되는것 같습니다. 간혹 상태의 필드들이 서로에게 의존하고 있어 더 고려할 사항이 있는 경우도 있지만, 이번엔 쉽게 todos의 수정을 별도의 함수로 분리할 수 있습니다: