일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 개발
- hokeys
- 호키스
- jest
- queue
- 스위프트
- react
- data structure
- 자바스크립트 자료구조
- 힛잇
- 계명대
- 스벨트
- hokidoki
- 이종호
- SWIFT
- 비동기
- javascript
- TDD
- 리액트
- HTML
- 리액트 예제
- 자바스크립트
- 자스민
- 개발자
- IOS
- 자료구조
- Svelte
- 호키도키
- Hitit
- 계명대 이종호
- Today
- Total
Dog foot print
redux-toolkit은 무엇을 해결하려고 했는가 ? 본문
엄청난양의 보일러 플레이트 코드.
redux
는 flux-pattern
을 이용한 전역 상태관리 라이브러리이다. [React, 라이브러리] react-redux . redux
를 사용하기 위해서는 action
, reducer
, store
의 관리를 위해 반복적인 코드 작성이 필요하다. 새로운 액션을 추가할때 마다, 액션 타입을 정의하고, 액션 생성자 함수를 만들어야 하며, 리듀서에서 해당 액션을 처리해야한다.
아래는 간단한 카운터를 구현한 “리듀서”이다.
// Actions
const INCREASE = "INCREASE";
const DECREASE = "DECREASE";
const UPDATE = "UPDATE";
const genAction = <T1 = undefined, T2 = T1>(action: string, f?: (arg?: T1) => T2) => (arg?: T1) => ({
type: action,
payload: f && f(arg)
})
// Action Constructor
export const increase = genAction(INCREASE);
export const decrease = genAction(DECREASE);
export const update = genAction<number>(UPDATE, (n?: number) => n || 0);
type Action = {
type: typeof INCREASE | typeof DECREASE | typeof UPDATE
payload?: number
}
const initialState = 1;
const reducer = (state = initialState, action: Action): number => {
switch (action.type) {
case INCREASE: return state + 1;
case DECREASE: return state - 1;
case UPDATE: return action.payload!;
default: return state
}
}
export default reducer
여기서 문제는 “action” 증가 할때마다 액션타입에 대한 reducer
의 행동을 정의해야하며, 액션 생성자 함수를 만들어야 해야해서, 작성해야 하는 코드의 양이 매우 많아지는 것이다. 아래는 redux-toolkit
으로 작성한 예제이다.
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
const name = "COUNTER"
const initialState = 0
const counterSlice = createSlice({
name,
initialState,
reducers: {
increase: (s) => s + 1,
decrease: (s) => s - 1,
update: (_, action: PayloadAction<number>) => action.payload
}
})
export default counterSlice;
export const { increase, decrease, update } = counterSlice.actions;
불변성 유지 로직
react를 다루면서 항상 말을 듣는 것이 불변성(Immutable)일 것이다. 이는 컴포넌트를 다시 렌더링 하기위해서 최신의 상태와 프롭스를 기존 상태와 프롭스에 대해 ===
(Strict Equal) 를 이용해 비교하여, 값의 최신상태여부를 확인하는데 이용된다.
“redux”에서 객체나 배열의 구조가 변경될 때, Object.assign
과 [...]
전개 연산자를 사용하여, 아래와 같이 새로운 배열이나 객체를 반환해야 한다.
const ADD = "ADD";
const TOGGLE = "TOGGLE";
export type Todo = { id: string, title: string, todo: string, completed: boolean }
const genAction = <T1 = any, T2 = T1>(action: string, f?: (arg: T1) => T2) => (arg: T1) => ({
type: action,
payload: f && f(arg)
})
export const add = genAction(ADD, (t: Todo) => t)
export const toggle = genAction(TOGGLE, (id: string) => id);
type Action<T = any> = {
type: typeof ADD | typeof TOGGLE
payload?: T
}
const initialState: Todo[] = [];
const reducer = (state = initialState, action: Action<Todo>): Todo[] => {
switch (action.type) {
case ADD: return action.payload ? [...state, action.payload] : state;
case TOGGLE: return action.payload ? state.map((v) => {
const todo = state.find((todo) => todo.id === action.payload!.id);
if (todo) todo.completed = !todo.completed;
return v;
}) : state;
default: return state
}
}
export default reducer
“redux”에 비해, “toolkit”은 내부적으로 Immer
를 사용하기 때문에, 가변 스타일의 코드를 작성할 수 있으며, 불변성을 자동으로 유지하게 해준다.
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
export interface Todo {
title: string,
id: string,
completed: boolean,
}
const name = "TODOS"
const initialState: Todo[] = []
const counterSlice = createSlice({
name,
initialState,
reducers: {
add: (s, action: PayloadAction<Todo>) => {
s.push(action.payload);
},
toggle: (s, action: PayloadAction<string>) => {
const find = s.find(v => v.id === action.payload);
if (find) find.completed = !find.completed
}
}
})
export default counterSlice;
export const { add, toggle } = counterSlice.actions;
redux-toolkit
의 reducer 액션 함수들에서 가변 스타일 코드를 이용해, 중첩구조의 값을 직접 변경하는 것으로 상태를 새롭게 변경 할 수 있다. 만약 함수의 반환값이 존재한다면, 새로운 반환값으로 상태가 결정된다. 분명 값을 이렇게 변경하는 것은 매우 편리하지만, “react”를 사용하는 프로젝트에서 다른 스타일로 상태를 변경하는 것은 조금 위험해보인다.
초기 실행 설정
redux
를 이용한 프로젝트에서는 보통 react-devtools
를 사용하거나, redux-thunk
와 같은 미들웨어를 추가해야하나, redux-toolkit
에는 이를 위한 설정들이 미리되어 있다. 다음은 redux
만을 사용했을때 흔히들 사용하는 설정이다.
import { combineReducers } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { legacy_createStore as createStore, compose, applyMiddleware } from 'redux';
import todos from "./todos";
import counter from './counter';
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
}
}
const rootReducer = combineReducers({
counter,
todos
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunkMiddleware))
)
export default store;
export type RootState = ReturnType<typeof rootReducer>;
매번 프로젝트를 진행해야 할때마다, 이러한 행동을 하는 것은 매우 귀찮다. 이를 위해, redux-toolkit
에서는 별다른 설정없이도 위의 설정을 그대로 기본값으로 지정해두었다. 아래는 redux-toolkit
의 아주 기본적인 설정예시이다.
import { configureStore } from "@reduxjs/toolkit";
import counterSlice from "./counter";
import todosSlice from "./todos"
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
todos: todosSlice.reducer
}
})
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch
export default store
configureStore
의 옵션객체에는 devtools
와 middleware
옵션이 있는데, 이를 활용해서 기본값과 함께 조합 할 수 있다.
'REACT' 카테고리의 다른 글
Next.js StaticImageData With Storybook (0) | 2023.04.28 |
---|---|
useMemo 및 memo 조금 더 자세히 사용해보기 (0) | 2023.04.11 |
useContext 조금 더 자세히 사용해보기 (0) | 2023.03.30 |
useEffect 조금 더 자세히 사용해보기 (0) | 2023.03.27 |
useState 조금 더 자세히 사용해보기 (0) | 2023.03.23 |