why?
apollo, graphql 조합이 rest API data를 관리하기에는 redux보다 적절하다고 생각하지만, 그렇다고 frontend 전반의 전역 상태관리를 대체할 수는 없는 것이다.
이 외에도 테스트와 디버깅이 쉽고, 변경된 상태에 관해서만 rendering이 발생하여 최적화가 잘 되어 있다.
middleware 사용으로 sideEffect나 비동기 관련 로직 작성시에도 유용하다.
redux-toolkit
redux를 좀 더 효율적으로 사용할 방법이 없는지 검색중에 velopert님의 글을 참고하게 되었고 redux-toolkit으로 action, reducer를 간단하게 작성할 수 있는 방법을 알게 되었다.
tutorial
// modules/todos.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export type Todo = {
id: number;
text: string;
done: boolean;
};
type TodosState = Todo[];
const initialState: TodosState = [
{ id: 1, text: '타입스크립트 배우기', done: true },
{ id: 2, text: '타입스크립트와 리덕스 함께 사용해보기', done: true },
{ id: 3, text: '투두리스트 만들기', done: false },
];
export const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTodo(state, action: PayloadAction<string>) {
const nextId = Math.max(...state.map((todo) => todo.id)) + 1;
state.push({ id: nextId, text: action.payload, done: false });
},
toggleTodo(state, action: PayloadAction<number>) {
return state.map((todo) => {
return todo.id === action.payload
? { ...todo, done: !todo.done }
: todo;
});
},
removeTodo(state, action: PayloadAction<number>) {
return state.filter((todo) => todo.id !== action.payload);
},
},
});
export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;
export default todosSlice.reducer;
기존의 action, reducer, initState를 createSlice 인자 객체에 통합할 수 있게 되었다.
그리고 기존 recucer의 경우에 state 인자를 immutable하게 사용해야 했기에 action.type case에 따라 return하는 방법이 까다로웠다.
toolkit의 장점은 state 인자를 수정해도 문제가 되지 않는 다는 것이다.
// modules/index.ts
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import todosReducer from './todos';
const rootReducer = combineReducers({
todos: todosReducer,
});
const store = configureStore({
reducer: rootReducer,
// middleware, devtool 등 추가 가능
});
export type RootState = ReturnType<typeof store.getState>;
export default store;
// index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './modules/index';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
store에 reducer를 비롯한 middleware, devtool 등의 도구들을 통합할 수 있게 되었다.
// hook/useTypedSelector.ts
import { TypedUseSelectorHook, useSelector } from 'react-redux';
import { RootState } from '../modules';
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
// components/todoList.tsx
const TodoList = () => {
// const todos: Todo[] = useSelector((state: RootState) => state.todos);
const todos: Todo[] = useTypedSelector((state) => state.todos);
return (<div></div>)
}
hook으로 useSelector에 toolkit이 제공하는 type을 지정해두고 사용하면 state인자에 type을 매번 지정 해야하는 번거로움을 없앨 수 있다.
// components/todoItem.tsx
import { removeTodo, toggleTodo } from '../modules/todos';
import { useDispatch } from 'react-redux';
const TodoItem = () => {
const dispatch = useDispatch();
// ....
return (
<span className='text' onClick={() => dispatch(toggleTodo('text'))}>
)
}
dispatch는 todosSlice.action에서 반환된 method를 활용한다.
다음은 redux-saga를 공부해 봐야겠다.