리덕스 사용법에 대해서 확인해보겠습니다.
리덕스에는 리덕스 툴킷과 기본적인 리덕스 사용법으로 나뉘어져 있습니다.
이번에는 기본적인 리덕스 사용법에 대해서 확인해보겠습니다.
redux에서 사용되는 용어는 스토어, 액션, 디스패치, 리듀서가 있습니다.
스토어는 상태를 가지고 있으며 컴퍼넌트는 디스패치 함수를 통해 액션을 리듀서로 전달한다. 그리고 상태는 리듀서에 의해서만 변경이 가능하고 컴퍼넌트는 이러한 상태를 구독하게 되는 것이다. 좀 더 나아가면 미들웨어라는 내용이 추가되는데 미들웨어는 리듀서와 액션사이에서 어떤 역할을 수행하게 되고 비동기 작업등을 처리하게 된다.
스토어 : 리액트 프로젝트 내 상태 값들을 내장하고 있는 객체.
액션 : 상태 변화를 일으킬 때 참조하는 객체.
디스패치 : 액션을 스토어에 전달하는 것을 의미함.
리듀서 : 상태를 변화시키는 로직이 있는 함수.
편의를 위해 paskage.json
위치에 jsconfig.json
을 생성하여 절대경로에서 접근하도록 하겠습니다.
{
"compilerOptions": { "baseUrl": "src" },
"include": ["src"]
}
// 리덕스 라이브러리 다운로드
// 리덕스 개발툴 적용
$ npm install redux react-redux
$ npm install redux-devtools-extension
리액트에서 리덕스를 사용하기 위해서 redux 와 react-redux 를 설치합니다. 이때 크롬 익스텐션을 이용해 디버깅을 하고싶다면 redux-devtools-extension을 설치합니다.
// src/index.js
import { Provider } from "react-redux";
import store from "store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
리덕스에서는 하나의 store를 통해 전역상태를 관리하며 리액트에 전역상태를 관리하기 위해서는 react-redux에서 제공하는 Provider을 이용하여 관리할 store를 할당해줍니다.
App에 할당할 store를 설정하겠습니다.
store/reducers/index.js
에 설정한 값들을 가져와 createStore
를 통해 store
값으로 변경해줍니다.
필요에 따라 devtools를 추가할 수 있습니다.
// src/store/index.js
import { createStore } from "redux";
// 익스텐션을 통해 redux값을 디버깅하길 원한다면 추가
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "store/reducers";
// const store = createStore(rootReducer);
const store = createStore(rootReducer, composeWithDevTools()); // redux devtool 적용
export default store;
store/index.js
에서 사용하는 rootReducer
값을 만들어줍니다.
현재는 todo에서 사용하는 Reducer만 있지만 더 추가하여 combineReducers
를 통해 하나의 reducer로 만들어줄 수 있습니다.
이때 combineReducers
의 인자는 객체형태로 넘겨서 생성합니다.
// src/store/reducers/index.js
import { combineReducers } from "redux";
import todoReducer from "./todo";
const rootReducer = combineReducers({ todo: todoReducer });
export default rootReducer;
디스패치를 통해 액션을 호출하고 호출된 액션은 리듀서를 통해 스토어에 반영된다고 했습니다.
각각 Todo리스트에 전달할 객체들을 설정해줍니다. 이때 액션은 type과 payload로 구성하여 리듀서에서 값의 변경에 구분값인 type과 변경할 상태 payload를 넘겨줍니다.
import {
TODO_INSERT,
TODO_REMOVE,
TODO_UPDATE,
TODO_TOGGLE,
} from "store/reducers/todo";
// 액션 함수
export const todoInsert = (text) => {
return {
type: TODO_INSERT,
payload: {
text: text,
done: false,
},
};
};
export const todoRemove = (id) => {
return {
type: TODO_REMOVE,
payload: { id: id },
};
};
export const todoUpdate = (id, text) => {
return {
type: TODO_UPDATE,
payload: { id: id, text: text },
};
};
export const todoToggle = (id) => {
return {
type: TODO_TOGGLE,
payload: { id: id },
};
};
INITIAL_STATE를 통해 초기데이터를 설정할 수 있으며 리듀서에 전달되는 액션의 경우 type, payload로 나뉘기 때문에 구조분해 할당을 통해 {type, payload} 로 인자값을 명시해주어도 됩니다.
액션정의 부분은 액션과 리듀서 두 곳 모두에서 사용되는 부분으로 리듀서에 액션타입을 정의하였습니다.
// 초기 데이터
const INITIAL_STATE = {
todos: [
{
id: 1,
text: "첫번째",
done: false,
},
],
};
// 리듀서 생성
// export default function todoReducer(state = INITIAL_STATE, action) {
export default function todoReducer(state = INITIAL_STATE, { type, payload }) {
switch (type) {
case TODO_INSERT:
return {
...state,
todos: [
...state.todos,
{
id: Math.max(0, ...state.todos.map((todo) => Number(todo.id))) + 1,
text: payload.text,
done: false,
},
],
};
case TODO_REMOVE:
return {
...state,
todos: state.todos.filter((todo) => todo.id !== payload.id),
};
case TODO_UPDATE:
return {
...state,
todos: state.todos.map((todo) =>
todo.id === payload.id ? { ...todo, text: payload.text } : todo
),
};
case TODO_TOGGLE:
return {
...state,
todos: state.todos.map((todo) =>
todo.id === payload.id ? { ...todo, done: !todo.done } : todo
),
};
default:
return state;
}
}
// 액션정의
export const TODO_INSERT = "TODO/INSERT";
export const TODO_REMOVE = "TODO/REMOVE";
export const TODO_UPDATE = "TODO/UPDATE";
export const TODO_TOGGLE = "TODO/TOGGLE";
마지막으로 컴포넌트에서는 react-redux
에서 제공하는 useDispatch
를 통해 액션을 호출하여 스토어의 값을 변경할 수 있으며 useSelector
를 통해 값을 스토어에 선언한 값을 가져올 수 있습니다.
import { useSelector, useDispatch } from "react-redux";
import { todoInsert } from "store/actions/todo";
export default function Components() {
const { todos } = useSelector((state) => state.todo);
const dispatch = useDispatch();
const handleDispatch = () => {
dispatch(todoInsert({ payload }));
};
}
👉 예제는 깃허브에서 확인할 수 있습니다.
https://github.com/kim-gunwoo/redux-exam
리덕스 공식문서
https://ko.redux.js.org/introduction/getting-started