[Redux] 사용법부터 이론까지 살펴보는 Redux: 4. Redux-toolkit
리덕스는 전역 상태관리 라이브러리이자 중앙집중식 스토어로서 유용하다. 하지만, 리덕스에는 세 가지 불편함이 존재했다.
- store, reducer, actionCreater 등의 초기 세팅을 위해 반복적으로 많은 코드를 작성해야 한다.
- reducer는 불변성의 법칙에 따라 store를 변형하면 안된다. 따라서, 복잡한 state를 가공하기 위해 spread 연산자 등의 기법을 사용해서 긴 코드를 작성해야 한다.
- reducer을 잘 사용하기 위해 여러 라이브러리를 직접 설치해줘야 한다.(immer, reselect ...)
- 타입스크립트가 제대로 지원되지 않는다.
이런 불편함에 대한 대안으로서 등장한 redux-toolkit은 다음 이점을 가지고 있다.
- 반복적으로 작성하던 세팅을 간소하게 작성 가능
- 스토어 변경 과정에서 immer 라이브러리를 이용한 비불변성 업데이트 가능
- 다양한 미들웨어와 라이브러리를 기본으로 세팅
- 타입스크립트로 store의 타입 명시, action, reducer 등을 잘 지원함.
- dev-tool이라는 강력한 디버깅 툴 지원
이 글에서는 redux-toolkit을 이용해서 기존 리덕스 코드를 줄이는 방법을 정리한다.
세팅
npm install react-redux
npm install @reduxjs/toolkit
createAction
목적
기존의 리덕스에서는 일정한 형식의 action 객체를 생성하기 위해서 action creator를 직접 정의해야 했다. createAction은 이 과정을 간소화한다.
기존의 action
const addTodo = (text) => ({ type: ADD, payload: {text} });
const deleteTodo = (id) => ({ type: DELETE, payload: {id }});
export const actionCreators = {
addTodo,
deleteTodo,
};
//@ dispatch로 액션을 보내는 경우
dispatch(actionCreators.addTodo("hello"))
createAction을 통한 축약
// 액션의 타입 정의
const addTodo = createAction("add");
const deleteTodo = createAction("delete");
// "add"
console.log(addTodo.type)
//객체를 반환함
//{type:"add",payload:{text:"hello"} }
addTodo({text:"hello"})
- 주의: 타입 이름 겹침 금지 | 서로 다른 객체 간에 타입명이 겹치면 안됨. 겹칠 시, 하나의 액션이 중복되는 다른 액션에도 적용되는 오류
callback 설정
위 방식은 액션 객체의 type 프로퍼티만 자동 설정해준다. 그 외의 프로퍼티까지 설정하고 싶으면 콜백 함수를 설정해주자.
// 액션의 타입 정의
const addTodo = createAction("add", (payload) => ({ payload: {text:payload } }));
const deleteTodo = createAction("delete", (payload) => ({ payload: {id:payload } }));
//객체를 반환함
//{type:"add",payload:{text:"hello"} }
addTodo("hello")
createReducer
목적
리듀서의 swith 분기처리 로직을 간소화하고 immer 라이브러리를 이용하여 기존 상태값의 mutate를 허용한다.
기존의 리듀서
const countReducer = function (state = [], action) {
switch (action.type) {
case addTodo.type:
{
return [action.value, ...state];
}
case deleteTodo.type:
{
return state.filter((element) => element.id != action.value);
}
default:
return state;
}
};
// 기존 리듀서는 스위칭문이 필요하고, 기존 state를 mutate해서는 안됨
creatReducer
const addTodo = createAction("add", (payload) => ({ payload: {text:payload } }));
const deleteTodo = createAction("delete", (payload) => ({ payload: {id:payload } }));
const RootReducer = createReducer(state,
(builder) => {
builder.addCase(actionCreator.addTodo, (state, action) => {
state.todo.unshift({ id: Date.now(), text: action.payload });
});
//기존 state의 불변성을 무시하고 업데이트 가능
builder.addCase(actionCreator.deleteTodo, (state, action) => (
{...state, todo: state.todo.filter((e) => e.id !== action.payload)})
builder.addDefaultCase((state, action) => (state))
});
}
);
- 선택적 mutate 가능 | 새 state를 반환하거나 기존 state를 mutate 하거나 선택 가능하다. 이는 리덕스 툴킷이 immer 같은 라이브러리를 이용해서 실제로는 새 스테이트를 반환해줌
configureStore
목적
createStore의 대체 도구로, 다양한 옵션 설정과 편리한 개발자 툴 사용이 가능하다.
const countReducer = createReducer(///)
const countStore = configureStore({ reducer: countReducer });
개발자 툴 | Redux DevTools (구글 확장 프로그램)
구글의 확장프로그램으로 설치가능한 툴이다. 현재의 리덕스 스테이트, 액션, dispatch가 작동하는 과정을 보여주기 때문에 디버깅에 용이하다. 이 툴은 리덕스 configurestore을 쓰는 최신 사이트라면 사용가능하다. 개발자 도구 탭에서 사용가능하다.
확장프로그램 링크
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko
createSlice
목적
reducer과 action creator을 동시에 생성하고 관리할 수 있게 해준다. 내부적으로는 createAction 및 createReducer를 사용하므로 Immer를 사용하여 "mutating" 불변 업데이트도 가능하다.
const countReducer = createSlice({
name: "todo",
initialState: [],
reducers: {
add:
// 액션 타입 설정: add
(state, action) => {
state.push({ id:action.payload.id });
},
},
delete:
// 액션 타입
(state, action) => state.filter((element) => element.id != action.payload),
});
const countStore = configureStore({ reducer: todo });
https://redux-toolkit.js.org/api/createSlice
참고로 이런 훅을 사용할 시, 기존에 컴포넌트로 전달되던 state의 형태에 변화가 있을 수 있다. 그러니 주의해야 한다.