리덕스 툴킷(Redux toolkit) 이란?
리덕스를 있는 그대로 활용하려면, 매우 많은 보일러 플레이트 코드를 작성해야 한다. 또한, 너무 자유도가 높아서 다양한 형식으로 사용할 수 있다.
리덕스 툴킷은 리덕스의 보일러 플레이트 코드를 줄이고 표준을 강제해 Best Practice 의 형태로 사용하기 위해 탄생한 라이브러리이다.
툴킷 없는 리덕스 사용의 복잡함과 툴킷의 필요성
리덕스 작성의 일반적인 사이클은 이렇다.
- 액션 정의 -> 액션 함수 정의 -> 액션의 구체적 행위인 리듀서 정의
위 세가지의 코드를 작성해야 1개의 액션이 생성되는 것이다.
또한 다양한 이유로 부가 라이브러리가 사용된다.
- 불변성을 지키기 위한
immer
라이브러리 - store 값을 효율적으로 핸들링하고, 불필요한 렌더링을 막기 위해 사용하는
reselect
라이브러리 - 비동기를 수월하게 진행하기 위한
redux-thunk
,redux-saga
위에서 언급한 라이브러리만 해도 4개이다.
redux-toolkit
은 위의 문제를 해결하기 위해 나온 라이브러리로 redux-saga
를 제외한 위의 모든 기능을 지원한다.
또, Typescript 사용자를 위해 action type
, state type
등 Typescript 를 사용하며 필요한 Type Definition 까지 공식 지원한다.
특징 기능 (Features)
- redux-devtools 를 공식적으로 지원하여, 디버깅하기 편하다.
- redux-actions 를 포용
createSlice
함수 제공
- reselect 를 포용
- immer 의
produce
API 를 포용 - 비동기 액션을 처리하기 위한
redux-thunk
를 기본값으로 지원 - flux standard action 을 포용하여 액션 형식을 강제화
action type
,state type
등 타입스크립트에 사용되는 Type Definition을 공식 지원
configureStore
(store
쉽게 생성하기)
Redux toolkit 에서 제공하는 configureStore
API 를 이용해 좀 더 직관적이고 짧게 코드를 작성할 수 있다.
createStore()
API 이용한다면?
import { createStore, applyMiddleware } from "redux";
import todos from "./reducers";
function logger({ getState }) {
return (next) => (action) => {
console.log("will dispatch", action);
// Call the next dispatch method in the middleware chain.
const returnValue = next(action);
console.log("state after dispatch", getState());
return returnValue;
};
}
const store = createStore(todos, ["Use Redux"], applyMiddleware(logger));
store.dispatch({
type: "ADD_TODO",
text: "Understand the middleware",
});
configureStore()
API 를 이용한다면?
const store = configureStore({
reducer: rootReducer,
middleware: [logger],
});
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch; // Export a hook that can be reused to resolve types
createAction
(redux-actions
)
기존의 액션 함수 보일러 플레이트 코드 제거를 도와준다.
기존 Redux 보일러 플레이트 코드
const INCREMENT = "counter/increment";
function increment(amount: number) {
return {
type: INCREMENT,
payload: amount,
};
}
const action = increment(3);
// { type: 'counter/increment', payload: 3 }
Redux Toolkit 을 이용한 코드
import { createAction } from "@reduxjs/toolkit";
const increment = createAction<number | undefined>("counter/increment");
let action = increment(3);
// { type: 'counter/increment', payload: 3 }
한 줄이라 읽기 편하며, 또 다른 상수 변수를 선언할 필요도 없어진다. 아래와 같이 로그를 찍어보면 값이 나온다.
console.log(increment.toString());
// 'counter/increment'
console.log(`The action type is: ${increment}`);
// 'The action type is: counter/increment'
createSlice
(Ducks Patterns 공식 지원)
createSlice
는 덕스 패턴 (Ducks Pattern)을 이용해 초기 상태, 액션 타입, 액션 생성 함수, 리듀서를 함께 정의한다. 타입 안정성을 보장해주어 타입에 대해 걱정할 필요도 없다.
덕스 패턴이란? 액션 타입, 액션 생성 함수, 리듀서를 각각 다른 파일이 아닌 한 자바스크립트 파일 내에서 작성하는 것을 덕스 패턴이라고 한다.
const todosSlice = createSlice({
name: "todos",
initialState: [],
reducers: {
addTodo(state, action) {
const { id, text } = action.payload;
state.push({ id, text, completed: false });
},
},
});
위와 같이 작성하면, 아래와 같이 dispatch()
가능하다.
dispatch(todos.addTodo({ id: 1, text: "contents" }));
덕스 패턴 타입스크립트 예제 코드 살펴보기
const slice = createSlice({
name: "test",
initialState: 0,
reducers: {
increment: (state, action: PayloadAction<number>) => state + action.payload,
},
});
// now available:
slice.actions.increment(2);
// also available:
slice.caseReducers.increment(0, { type: "increment", payload: 5 });
혹은, 리듀서가 많을 때는 아래와 같이 깔끔하게 정리할 수도 있다.
type State = number;
const increment: CaseReducer<State, PayloadAction<number>> = (state, action) =>
state + action.payload;
createSlice({
name: "test",
initialState: 0,
reducers: {
increment,
},
});
immer 의 produce()
API 포용
사실 위의 예제에서 상태를 변경할 때 push()
함수를 사용하였는데, push()
를 이용한 배열 데이터 변경은 원본 배열을 수정하기 때문에 불변성을 해쳐서 리덕스 상태 관리에 부적합한 함수이다.
그러나, 리덕스 툴킷이 제공하는 createReducer
, createSlice
의 API 는 내부적으로 immer 의 produce()
API 를 포용했기 때문에, mutable 한 함수를 사용하더라도 불변성을 지킬 수 있다.
immutable 을 지키기 위해 생성되는 추가적인 코드들이 더이상 필요 없다.
FSA (Flux Standard Action) 포용
createAction
을 통해 생성되는 action 은 FSA (Flux Standard Action) 라는 human-friendly flux action 표준 형식으로 다룬다. 아래와 같은 인터페이스를 따르게 된다.
export interface Action<Payload> extends AnyAction {
type: string;
payload: Payload;
error?: boolean;
meta?: Meta;
}
Reselect 포용
createSelector 를 통해 selector
를 생성할 수 있다. 원래는 reselect 라는 별도의 라이브러리에서 제공하던 것이다. store 값에 접근할 때 memoization 이 가능하도록 도와주는 helper 함수 라이브러리이다.
import { createSelector } from "redux-toolkit";
const selectVisibleTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos;
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter((t) => t.completed);
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter((t) => !t.completed);
default:
throw new Error("Unknown filter: " + filter);
}
}
);
Type Definition
- RootState, Action payload 등 타입 등의 설정 문제를 해결하기 위해 내장 타입을 지원한다.
레퍼런스
'자바스크립트 > 라이브러리' 카테고리의 다른 글
아이언 세션 (Iron session) 이란 무엇인가? (쉬운 설명과 보안 관련 생각해볼점) (0) | 2024.04.03 |
---|---|
자바스크립트의 Socket.io 라이브러리란? (웹소켓 통신 라이브러리) (0) | 2024.02.10 |
자바스크립트 라이브러리 Axios 란? (0) | 2022.10.19 |
Socket.io 소켓 IO 란? (0) | 2022.10.15 |
리덕스 핵심 개념 10분만에 배우기 (0) | 2022.10.10 |