핵심 개념: state, action, reducer
핵심 개념: state, action, reducer (Core Concepts)
Redux의 핵심 개념은 딱 세 가지예요. 앱의 상태를 평범한 객체로 표현하고, 상태를 바꾸는 모든 일을 액션으로 기술하며, 그 둘을 이어 주는 리듀서 함수를 작성하는 것. 이 흐름만 잡으면 Redux가 왜 그런 구조를 갖는지 이해할 수 있어요.
상태는 평범한 객체
앱의 상태를 평범한 객체로 표현한다고 상상해 봐요. 예를 들어 할 일(todo) 앱의 상태는 이렇게 생겼을 거예요.
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
이 객체는 "모델"과 비슷하지만 setter가 없어요. 그래서 코드의 다른 부분이 상태를 마음대로 바꿔서 재현하기 어려운 버그를 만들지 못하게 되죠.
액션으로 변경을 표현
상태를 바꾸려면 액션을 dispatch 해야 해요. 액션은 "무슨 일이 일어났는지"를 기술하는 평범한 자바스크립트 객체예요. 몇 가지 예시예요.
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
모든 변경이 액션으로 기술되도록 강제하면 앱에서 무슨 일이 벌어지는지 명확해져요. 무언가 바뀌면 그 이유를 알 수 있죠. 액션은 "무슨 일이 있었는지"의 흔적(빵 부스러기) 같은 역할을 해요.
리듀서로 상태와 액션을 연결
상태와 액션을 이어 주기 위해 리듀서(reducer)라는 함수를 작성해요. 마법 같은 건 없고, 상태와 액션을 인자로 받아 앱의 다음 상태를 반환하는 평범한 함수예요. 큰 앱을 위해 한 번에 만들기보다, 상태의 일부를 관리하는 작은 함수들로 나눠서 작성해요.
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter
} else {
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
그리고 앱의 전체 상태를 관리하는 또 하나의 리듀서를 작성해서, 이 두 리듀서를 호출해 조합하면 돼요. 이렇게 하면 상태가 어디서 어떻게 바뀌는지가 명확해져서, 버그를 찾고 새 기능을 추가하기가 훨씬 수월해져요.