개발자는 새로운걸 잘 받아들일 줄 알아야 한다지...난 정말 그런거 못해..
그래도 성장하려면 이런 머리 깨지는 것도 해야지!(머리아팡...)
리덕스 뽀개자!
#리덕스란?
크로스 컴포넌트 또는 앱 와이드 상태를 위한 상태 관리 시스템
데이터를 관리하도록 도와준다.
// 로컬 상태 아닌 상태에서 데이터 관리를 도와준다는 말.
상태는 3가지로 구분할 수 있다.
1.Local State
로컬 상태
: 데이터가 변경되어서 하나의 컴포넌트에 속하는 UI에 영향을 미치는 상태
: 토글 버튼 등 useState()/useReducer()
2.Cross-Component State
크로스 컴포넌트 상태
: 다수의 컴포넌트에 영향을 미치는 상태
: 모달 컴포넌트가 다수의 컴포넌트에 영향을 미치거나 하는 등 그럴때는 prop chains/ prop drilling을 사용
3.App-Wide State
앱 와이드 상태
: 애플리케이션의 모든 컴포넌트에 영향을 미치는 상태
: 사용자 인증 등 prop chains/ prop drilling.
리덕스는 애플리케이션에 있는 하나의 중앙 데이터 저장소
이다.
여기서 데이터는 상태를 가리킨다.
우리는 저장소를 딱 한개만 가지고 전체 애플리케이션에 모든 상태를 다 저장한다.
이러면 복잡할 것 같지만 그렇지 않다.
우리는 이 중앙 데이터 저장소에 데이터를 저장하고 컴포넌트 안에서 사용할 수 있다.
state가 변경되면 리덕스가 알아채고 업데이트하길 기대한다.
간단한 리덕스 동작 흐름
중앙 데이터 저장소는 컴포넌트에 Subscription(구독)을 설정한다. -> 데이터가 변경될 때마다 저장소가 컴포넌트에 알려준다. -> 컴포넌트는 필요한 데이터(인증, 인가, 등)를 받게 된다. -> 그 데이터를 사용한다.
아래 더 자세히 알아보자.
시작하기
- npm init -y : 기본 질문에 모두 yes라고 대답한다.
- npm install redux
- import redux = require('redux')
리덕스의 중심 개념은 저장소
이다.
저장소 만들기
- redux.createStore()를 호출한다.
리덕스 라이브러리에서 온 이 메소드는 저장소를 생성한다.
저장소의 역할은 데이터를 관리하는 것이다.
리듀서 함수 만들기
- const counterReducer = (state, action) => {return {} }
- 리듀서 함수는 항상 2개의 입력(파라미터)를 받는다.
- 항상 새로운 상태 객체를 리턴해야만 한다.
- 리듀서 함수에 의해 데이터가 결정된다.
- 리듀서 함수는 순수한 함수로 같은 값을 넣으면 어떠한 부수적인 효과 없이 동일한 리턴을 주어야 한다.
- 리듀서 함수는 액션이 도착할 때마다 새로운 상태 스냅샷을 뱉어내야 한다.여기서 스냅샷이 뭘까 ?
마치 사진 찍듯이 특정 시점에 스토리지의 파일 시스템을 포착해 보관하는 기술을 말한다.
리듀서 함수에서는 액션이 도착하면 해당하는 리턴값을 뱉는다.
기본적인 카운터 리듀서 함수는 아래와 같다.
const redux = require("redux")
const counterReducer = (state, action) => {
return {
counter: state.counter + 1 // 1을 더한 새로운 객체를 리턴한다.
}
}
const store = redux.createStore(counterReducer); // 저장소를 변경하는 리듀서 함수를 넣어준다.
이제 이 저장소를 구독할 함수와 발송할 액션도 필요하다.
구독부터 함수를 만들자. 파라미터를 받지 않지만 저장소에 getState()
메서드를 호출할 수 있고, 이는 createStore()
에서 사용할 수 있다.
getState()
는 업데이트 된 후 최신 상태 스냅샷을 제공한다.
구독함수는 상태가 변경될 때마다 트리거 된다.
이를 실행할 메서드도 필요하다. 이때는 store에 subscribe 메서드를 호출한다.
const counterSubscriber = () => { // 구독함수 만들고 아래 최신상태 스냅샷을 제공하는 getState() 를 넣고 콘솔로 출력한다.
const latestState = store.getState();
console.log(latestState);
}
store.subscribe(counterSubscriber); // 구독함수를 실행한다.
리덕스는 데이터와 저장소가 변경될 때마다 이를 실행하게 된다.
즉 리덕스가 실행시켜 줄테니 우리는 함수를 호출하지 말고 그냥 넣어주자.
아무튼 액션을 넣지 않고 이대로 실행을(터미널에 node 파일이름
) 한 번 해보면 state가 없어서 오류가 난다.
state에는 초기값이 없어 설정해주어야 한다.
const counterReducer = (state = {counter: 0}, action) => { // state에 초기값 넣어주기
이렇게 하면 실행되지만, 액션이 없어 출력값이 없다.
이제 액션을 만들고 디스패치 해보자.
저장소에 액션을 발송하는 dispatch()
메서드를 호출하면 된다.
store.dispatch({ type : "increment" })
그러면 이제 따로 콘솔을 쓰지 않고도 counterSubscriber 구독 함수 안에 있는 콘솔이 실행되어 {counter: 2}
을 출력한다.(아까 액션 만들기 전에 한 번 실행되어 +1인 상태로 한 번 더 불러왔기 때문에 2 이다.)
여기까지가 리덕스의 핵심적인 작동 방식이다.
정리하자면.
1. redux.createStore()로 저장소를 만들고 리듀서 함수를 만든다음 연결한다.
2. 구독 함수도 만들어서 getState() 해준다.
3. 액션 dispatch() 해준다.
현재는 액션이 아무런 효과가 없는데 이 부분을 더 자세히 알아보자.
액션은 리듀서 함수 안에서 다른 동작을 위해 사용한다.
예시로 만약 액션 type이 'increment'라면 현재 상태에 1을 더한 값을 리턴하고, 아니라면 전에 상태를 리턴한다.
const counterReducer = (state, action) => {
if(action.type === 'increment'){ // 만약 이 액션이 디스패치 되었다면 리턴값을 출력해줘.
return {
counter: state.counter + 1
}
}
return state; // 아니라면 초기 스테이트를 출력해줘
}
액션을 추가해보자.
store.dispatch({ type : "decrement" })
그리고 리듀서 함수에도 적절한 식을 넣으면 된다.
const counterReducer = (state, action) => {
if(action.type === 'increment'){
return {
counter: state.counter + 1
}
}
if(action.type === "decrement"){
return {
counter: state.counter - 1
}
}
return state;
}
이렇게 하고 실행시키면 counterSubscriber 구독 함수에 있는 콘솔로 { counter: 1 }{ counter: 0 } 이 나온다.
dispatch 된 순서대로 나온 것이다.
여기까지는 바닐라 js 에서 사용한 리덕스 사용법이었다. 리액트에서는 사용법이 좀 다른가보다. 다음 글에서 알아보자.