리액트는 컴포넌트 단위로 개발되며 각 컴포넌트가 유기적으로 동작할 수 있다.
이러한 동적인 변화는 상태(state)를 통해 이루어지는데, 서로 영향을 받는 컴포넌트 구조가 복잡해질 수록 state를 적절히 사용하는 것이 어려워진다.
그래서 필요한 것이 상태 관리이며, 상태 관리 대표 라이브러리인 Redux에 대해 알아보고자 한다.
(https://www.robinwieruch.de/react-redux-tutorial/#basics-in-redux-without-react의 BASICS IN REDUX WITHOUT REACT 파트를 번역하며 정리했습니다.)
기본적으로 reducer는 action의 정보와, store에 저장되어 있던 이전 state를 취합하여 새로운 state로 만든다. store의 state가 바뀌면, view(앱의 컴포넌트 트리)는 이 store를 subscribe한다.
View -> Action -> Reducer(s) -> Store -> View
리덕스에서 action은 자바스크립트 객체다.
type과 optional payload를 가지고 있다. type은 종종 action type으로 불린다. type이 string literal일 때, payload는 string에서 object까지, 무엇이든 될 수 있다.
다음의 action은 새로운 todo item을 생성할 때 사용될 수 있다.
{
type: 'TODO_ADD',
todo: { id: '0', name: 'learn redux', completed: false },
}
action을 실행하는 것은 리덕스에서 dispatching이라고 한다. 리덕스 store에서 상태를 바꾸기 위해 action을 dispatch할 수 있다. 오직 상태를 바꾸고 싶을 때만 action을 dispatch한다. view에서도 action을 dispatch하게끔 작동시킬 수 있다. 이것은 HTML 버튼을 클릭하는 것만큼이나 간단하다. 게다가 리덕스 action에서 payload는 필수가 아니다. action type만 가지고 있는 action을 정의할 수 있다. 마지막에 action이 dispatch되면, 리덕스의 모든 reducer를 거친다.
reducer는 무향 데이터 흐름 체인(chain of the unidirectional data flow)의 다음 파트이다. view는 action을 dispatch한다. action type과 optional payload를 가진 action object는, 모든 reducer를 거친다.
reducer는 순수한 함수이다. reducer는 input이 동일하다면 항상 동일한 output을 생성한다. 여기엔 side effects가 없으므로 이것은 단지 input/output의 작동일 뿐이다.
reducer는 두 개의 input으로 state와 action을 가진다. state는 항상 리덕스 store의 전역 state object이다. action은 type과 optional payload를 가진 dispatch된 action이다.
reducer는 이전 state와 입력되는 action으로 새로운 state를 만든다(reduce 한다)
(prevState, action) => newState
reducer가 순수 함수로서 side-effects를 가지지 않는다는 함수형 프로그래밍 원칙과 별개로, reducer는 immutable 데이터 구조를 가지고 있다. 받아오는 prevState object를 변화시키지 않고 newState object를 리턴한다. 그러므로, 다음의 reducer는 허용된 reducer 함수가 아니다.
function(state, action) {
state.push(action.todo);
return state;
}
배열의 push 메서드는 새로운 state object를 리턴하지 않고 이전 state를 변형시킨다.
그에 반해, 다음 예제는 허용된다. 이전 state를 온전히 유지하면서 새로운 state를 리턴하기 때문이다.
function reducer(state, action) {
return state.concat(action.todo);
}
지금까지 todo 앱은 state 업데이트를 유발하는 action, 그리고 previous state와 action을 취합하는 reducer를 가지고 있다. 이들을 하나로 묶기 위해 리덕스 store가 필요하다.
리덕스 store는 하나의 전역 state object를 가진다. 여기엔 여러 store나 다수의 state가 없다. store는 앱에서 오직 하나의 instance이다. 게다가 이것은 당신이 리덕스를 사용하며 마주하는 첫번째 library dependency이다. 그러므로 store object를 생성하기 위해 리덕스 library에서 import 선언문을 사용하자.
import { createStore } from 'redux';
createStore 함수는 단 하나의 필수 인자로 reducer를 받는다.
const store = createStore(reducer);
createStore는 선택 인자로 초기 state를 받는다.
todo앱의 예에서, reducer는 todo 리스트에 상태로 작용한다. todo의 목록 리스트 빈 배열이나, 할 일이 미리 채워진 초기화 상태여야 한다. 초기화되지 않았다면 reducer는 동작하지 않는다. undefined argument에서 동작하기 때문이다.
const store = createStore(reducer, []);
이제 reducer에 대해 알고있는 store 인스턴스를 가지고있다. 리덕스 셋업은 끝이 났다. 하지만 store와 상호작용하려면 어떻게 해야할까? state를 바꾸기 위해 action을 dispatch하고, store에서 state를 가져오고, store에서 state의 업데이트 여부를 관찰해야(listen) 한다.
첫번째: action은 어떻게 dispatch할까?
store.dispatch({
type: 'TODO_ADD',
todo: { id: '0', name: 'learn redux', completed: false },
});
두번째: 전역 state를 store에서 어떻게 가져올까?
store.getState();
세번째: state 업데이트를 관찰(listen)하려면 어떻게 store를 subscribe/unsubscribe 할까?
const unsubscribe = store.subscribe(() => {
console.log(store.getState());
});
unsubscribe();