Redux는 우리가 잘 알다 시피 전역 상태 관리 라이브러리이다. 말 그대로 전역에서 상태를 관리 할 수 있게 도와주는 라이브러리이다.
그림을 통해 Redux의 진행을 확인할 수 있다. 단방향으로의 관리가 이뤄지는 것을 알 수 있다.
우리는 Redux를 통해 전역 상태 관리를 할 수 있지만 너무 어렵다. 왜? 쓸 코드가 너무나 많다.
사용하기 위해서는 rootReducer를 정의하면서 combineReducer를 사용해야하고
// reducers/index.js
import { combineReducers } from "redux";
import todos from "./todos";
const rootReducer = combineReducers({
todos,
});
export default rootReducer;
합칠 리듀서들을 위한 세부 Reducer들을 정의해야하고
// reducers/todos.js
export const ADD_TODO = "TODO/ADD_TODO";
export const REMOVE_TODO = "TODO/REMOVE_TODO";
export const TOGGLE_TODO = "TODO/TOGGLE_TODO";
export const addTodo = (text) => ({ type: ADD_TODO, text });
export const removeTodo = (id) => ({ type: REMOVE_TODO, id });
export const toggleTodo = (id) => ({ type: TOGGLE_TODO, id });
const initialState = {
todos: [],
};
const todos = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [
...state.todos,
{ id: Date.now(), text: action.text, completed: false },
],
};
case REMOVE_TODO:
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.id),
};
case TOGGLE_TODO:
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
),
};
default:
return state;
}
};
export default todos;
store도 만들고
// store/index.js
import { createStore } from "redux";
import rootReducer from "../reducers";
const store = createStore(
rootReducer,
);
export default store;
App에 store를 제공해주기까지 해야하는데
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import App from "./App";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
컴포넌트에서 또 redux를 사용해야한다. 너무많다.
// components/TodoList.js
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addTodo, removeTodo, toggleTodo } from "../reducers/todos";
const TodoList = () => {
const [input, setInput] = useState("");
const todos = useSelector((state) => state.todos.todos);
const dispatch = useDispatch();
const handleAddTodo = () => {
if (input.trim()) {
dispatch(addTodo(input));
setInput("");
}
};
const handleRemoveTodo = (id) => {
dispatch(removeTodo(id));
};
const handleToggleTodo = (id) => {
dispatch(toggleTodo(id));
};
return (
<div>
<h1>Todo List</h1>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add a new todo"
/>
<button onClick={handleAddTodo}>Add</button>
<ul>
{todos.map((todo) => (
<li key={todo.id} style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
<span onClick={() => handleToggleTodo(todo.id)}>{todo.text}</span>
<button onClick={() => handleRemoveTodo(todo.id)}>Remove</button>
</li>
))}
</ul>
</div>
);
};
export default TodoList;
이를 해결하기 위해 나온것이 Redux Toolkit(RTK)이다.
RTK란 현재 Redux로직을 작성할 때 권장하는 방법으로, Redux의 복잡성을 줄이고 개발 효율성을 높이기 위해서 만들어졌다. 기본적으로 Redux의 코어 라이브러리를 포함하고 있어 별도의 설정없이 사용할 수 있다.
이제 똑같이 RTK로 TodoList를 만드는 로직을 보면 더 간단한 코드를 볼 수 있다.
1. createSlice를 통해 reducer와 action을 한번에 정의한다.
// features/todos/todosSlice.js
import { createSlice } from "@reduxjs/toolkit";
const todosSlice = createSlice({
name: "todos",
initialState: {
todos: [],
},
reducers: {
addTodo: (state, action) => {
state.todos.push({
id: crypto.randomUUID(),
text: action.payload,
completed: false,
});
},
removeTodo: (state, action) => {
state.todos = state.todos.filter((todo) => todo.id !== action.payload);
},
toggleTodo: (state, action) => {
const todo = state.todos.find((todo) => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
},
});
export const { addTodo, removeTodo, toggleTodo } = todosSlice.actions;
export default todosSlice.reducer;
// store/index.js
import { configureStore } from "@reduxjs/toolkit";
import todosReducer from "../features/todos/todosSlice";
const store = configureStore({
reducer: {
todos: todosReducer,
},
});
export default store;
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import App from "./App";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addTodo, removeTodo, toggleTodo } from "../features/todos/todosSlice";
const TodoList = () => {
const [input, setInput] = useState("");
const todos = useSelector((state) => state.todos.todos);
const dispatch = useDispatch();
const handleAddTodo = () => {
dispatch(addTodo(input));
};
const handleRemoveTodo = (id) => {
dispatch(removeTodo(id));
};
const handleToggleTodo = (id) => {
dispatch(toggleTodo(id));
};
return (
<div>
<h1>Todo List</h1>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add a new todo"
/>
<button onClick={handleAddTodo}>Add</button>
<ul>
{todos.map((todo) => (
<li
key={todo.id}
style={{ textDecoration: todo.completed ? "line-through" : "none" }}
>
<span onClick={() => handleToggleTodo(todo.id)}>{todo.text}</span>
<button onClick={() => handleRemoveTodo(todo.id)}>Remove</button>
</li>
))}
</ul>
</div>
);
};
export default TodoList;
확실히 action과 state를 관리하는 로직에서 간단, 간편해진것이 눈에 보인다.
그렇다면 이런 간편함 이외에 우리가 RTK를 사용하는 이유가 뭘까?
spread
문법을 사용하지 않아도 상태를 업데이트할 수 있다.이렇기 때문에 우리는 Redux를 동작만 알고 있고 RTK를 통해서 Redux를 사용하면 되겠다~
처음엔 어렵지만 적응한다면 쉽게 전역상태관리를 할 수 있게 되지 않을까 생각하고 있다.