[React] React 완벽 가이드 섹션 18 : Redux
https://www.udemy.com/course/best-react/
1. Redux
1) 개념 : 크로스 컴포넌트, 앱 와이드 상태를 위한 상태 관리 시스템
- 우리의 상태를 변경하고 화면에 표기하는 데이터를 관리하도록 도와주는 역할
2) State의 종류
ⓐ 로컬 state : 데이터가 변경되어서 하나의 컴포넌트에 속하는 UI에 영향을 미치는 상태
👉 useState를 이용하거나 더 복잡하다면 useReducer을 사용하여 관리할수 있음
ⓑ Cross-component state : 다수의 컴포넌트에게 영향을 미치는 상태
👉 prop-chain을 구축하여 상태를 관리해야한다
ⓒ App-Wide State : 전역으로 영향을 미치는 상태 ex) 로그인 상태
👉 프롭체인으로 관리 가능하지만, 복잡하기에 컨텍스트라 리덕스를 사용함
3) Context vs Redux
- 컨택스트는 앱 와이드(전역) 상태를 위한 상태관리 시스템
- 컨택스트는 잠재적인 단점이 있다.
- 상태관리가 굉장히 복잡해 질 수 있음, 설정이 복잡
- 데이터가 자주 바뀌는 상황에서는 성능에 문제가 있을 수 있음
4) 작동방식
- 하나의 저장소에 전체 애플리케이션의 모든 상태를 저장함
- 컴포넌트가 저장소에서 데이터를 가져와 사용할 수 있음(구독의 개념)
- 하지만 컴포넌트가 직접 저장된 데이터를 조작하지 않는다
- 리듀서 함수가 저장소 데이터의 업데이트를 담당함
- 컴포넌트는 리듀서 함수의 Action을 디스패치하여 리듀서 함수에게 전달함
5) 리듀서 함수
- 리덕스 라이브러리에 의해 호출되는 함수
- 항상 2개의 파라미터를 받음(기존의 상태, dispatched 액션)
- 항상 새로운 상태 객체를 리턴해야함, 함수는 pure한 함수여야함 (다른 사이드 이펙트가 실행x)
순수 자바스크립트에서 리덕스 사용하기
const redux = require('redux'); // 리덕스를 불러움(설치한 후)
// 리듀서 함수, 이전 상태와 action을 인자로 받음
// 항상 새로운 상태를 리턴함(보통 객체 형태)
const counterReducer = (state = {counter: 0}, action) => {
if(action.type === 'increment'){
return {
counter: state.counter+1
}
}
if(action.type === 'decrement'){
return {
counter: state.counter-1
}
}
return state;
};
// 저장소 생성, 매개변수로 리듀서 함수가 들어감
const store = redux.createStore(counterReducer);
// subscriber 정의
const counterSubscriber = () => {
// createStore로 생성된 저장소에서 사용할수 있는 메서드(상태를 받아옴)
const lastestState = store.getState();
console.log(lastestState);
};
store.subscribe(counterSubscriber);
// action 정의
store.dispatch({type:'increment'});
store.dispatch({type:'decrement'});
React에서 리듀서 사용하기
* 절대로 기존의 state를 변경하면 안됨. 객체형태로 새로운 상태를 반환해야한다
왜냐하면 자바스크립트에서 객체, 배열은 참조값이기 때문에 기존의 상태를 변경할시 재정의, 변경 가능성이 있다.
1) store을 생성할 파일 생성
- 보통은 src폴더/store 폴더에 생성한다.
2) 리듀서 함수를 생성한다.
- 리듀서 함수는 항상 state를 반환해야 하며, 매개변수로 초기 state와 action을 받는다.
const initialCounterState ={counter:0, showCounter: true};
const counterReducer = (state = initialCounterState, action) => {
if(action.type === 'increment'){
return {
counter: state.counter+1,
showCounter: state.showCounter
}
}
if(action.type === 'increase'){
return {
counter: state.counter+ action.amount,
showCounter: state.showCounter
}
}
if(action.type === 'decrement'){
return {
counter: state.counter-1,
showCounter: state.showCounter
}
}
if(action.type === 'toggle') {
return {
showCounter: !state.showCounter,
counter: state.counter
}
}
return state;
}
3) store을 생성한다.
- createStore() 를 이용하여 인자로 리듀서 함수를 받는다.
const store = createStore(counterReducer);
3) 리듀서 state가져오기
- store에 저장된 state를 가져오기 위해선 useSelector()를 사용해야한다.
- dispatch()를 사용하여 우리가 생성했던 action type에 따라 state 변경이 가능하다.
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter);
const show = useSelector((state) => state.showCounter);
const incrementHandler = () => {
dispatch({type: 'increment'});
};
const increseHandler = () => {
dispatch({type: 'increase', amount: 5 });
};
const decrementHandler = () => {
dispatch({type: 'decrement'});
};
const toggleCounterHandler = () => {
dispatch({type:'toggle'})
};
문제점 : action 이름을 가져오거나 생성하는 데 있어 실수가 생길 우려 큼, 중복 문제
👉 react-toolkit을 사용한다!
- 기존 함수 외 createSlice() 사용
import { createSlice } from "@reduxjs/toolkit";
const initialCounterState ={counter:0, showCounter: true};
const counterSlice = createSlice({
name: 'counter',
initialState: initialCounterState,
reducers: {
increment(state){
state.counter++;
},
decrement(state){
state.counter--;
},
increase(state, action) {
state.counter = state.counter + action.payload;
},
toggleCounter(state){
state.showCounter = !state.showCounter;
}
}
});
export const counterActions = counterSlice.actions;
export default counterSlice.reducer;
// store/index.js
// const store = configureStore({
// reducer: counterSlice.reducer
// }); //단일 reducer가져올때
const store = configureStore({
reducer: {counter: counterReducer, auth: authReducer}
}); //여러개 가져올때
const dispatch = useDispatch();
const counter = useSelector(state => state.counter.counter);
const show = useSelector(state => state.counter.showCounter);
const incrementHandler = () => {
dispatch(counterActions.increment());
};
const increseHandler = () => {
dispatch(counterActions.increase(10));
};
const decrementHandler = () => {
dispatch(counterActions.decrement());
};
const toggleCounterHandler = () => {
dispatch(counterActions.toggleCounter());
};
- 툴킷을 사용했을 때 중복 문제나 코드 유지보수성/가독성이 좋아진다.