이 전 글에서 Redux
에 대해 전반적으로 알아보았으니 이번 글에서는 한 번 직접 코드를 작성해보면서 알아보겠습니다.
이 글의 코드는 노마드코더의 Redux 강의를 보고 학습하며 정리하였습니다.
React
환경에서 ToDo 기능을 만들면서 리덕스를 어떤식으로 활용할 수 있는지에 대해 코드로 배워보도록 하겠습니다. 간편하게 CRA를 활용하여 리액트 프로젝트를 시작하고, Redux
를 설치합니다.
yarn create react-app react-redux-learning
//or
npx create-react-app react-redux-learning
yarn add redux react-redux
//or
npm install redux react-redux
만약 react-redux 먼저 설치 후 redux를 설치하게 되면 간혹 문제가 생겼다는 글이 있어서 최대한 redux -> ract-redux 순서로 진행해주세요.
설치가 모두 완료되었으면 코드를 작성해보도록 하겠습니다.
import { legacy_createStore as createStore } from "redux";
import { addToDo, deleteToDo } from "./action";
const toDoReducer = (state = [], action) => {
switch (action.type) {
case addToDo.type:
return [{text: action.text, id: Date.now()}, ...state];
case deleteToDo.type:
return state.filter(toDo => toDo.id !== action.id);
default:
return state;
}
};
const store = createStore(toDoReducer);
export default store;
리덕스 내부의 상태를 업데이트 할 때 절대 state
를 직접적으로 변경하려하면 안됩니다. 위의 코드처럼 배열의 경우엔 새로운 배열로 반환하여 교체해야 합니다. state.push(...)
이런식으로 mutate
는 불가합니다.
하지만.. @reduxjs/toolkit
을 사용하면 이러한 문법을 허용해준다고 하는데요. 이는 다음 글에서 따로 다루도록 하겠습니다! 일단 직접 상태를 변경하면 안된다라고 알아두시면 좋을 것 같습니다!
export const addToDo = (text) => {
return {
type: "ADD",
text,
};
};
export const deleteToDo = (id) => {
return {
type: "DELETE",
id,
};
};
이렇게 reducer
, store
, action
을 작성하였으면 이제 리액트에서 리덕스를 사용할 수 있도록 index.js
에서 Provider
로 감싸줍니다.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./components/App";
import { Provider } from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
이제 컴포넌트에서 리덕스의 상태 값을 업데이트하거나, 읽어올 수 있습니다. 그럼 이제 ToDo 리스트가 나오는 메인 화면을 작성해보겠습니다.
import { useState } from "react";
function Home() {
const [text, setText] = useState("");
function onChange(e) {
setText(e.target.value);
}
function onSubmit(e) {
e.preventDefault();
setText("");
}
return (
<>
<h1>To Do</h1>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="What To Do?"
value={text}
onChange={onChange}
/>
<button>Add</button>
</form>
<ul></ul>
</>
);
}
export default Home;
Home 화면은 기본적으로 이렇게 코드를 작성하였습니다. 이제 리덕스를 이용하여 ToDo 내용을 저장하고, <ul>
태그 안에 <ToDo />
컴포넌트를 렌더링 할 수 있도록 코드를 추가하겠습니다. 그럼 추가로 아래 코드를 작성해주세요.
(위 내용에서 지우는 내용 없이 아래에 위치에 맞게 코드를 그냥 추가로작성만 해주세요!)
import { useSelector, useDispatch } from "react-redux";
// ToDo 컴포넌트 파일을 생성해주세요!
import { ToDo } from "./ToDo";
import { addToDo } from "./store"
function Home() {
...
// 리덕스 상태를 구독하는 Hook
const toDos = useSelector((state) => state);
// 액션을 발생시키기 위한 dispatch를 사용할 수 있게 해주는 Hook
const dispatch = useDispatch();
...
function onSubmit(e) {
e.preventDefault();
dispatch(addToDo(text));
setText("");
}
return (
<>
...
</form>
<ul>
{toDos.map((toDo) => (
<ToDo {...toDo} key={toDo.id} />
))}
</ul>
</>
)
}
리덕스의 상태 값을 조회하기 위해 useSelector
Hook을 사용하였습니다. 그리고 액션을 발생시키기 위해 useDispatch
Hook을 사용하여 submit
이벤트가 발생하면 dispatch(action)
함수를 호출하여 현재 text 값을 리덕스 상태 값에 업데이트 합니다.
이제 <ToDo />
컴포넌트를 작성하면서 ToDo 항목을 제거하는 기능도 추가해보겠습니다.
import { useDispatch } from "react-redux";
import { deleteToDo } from "../redux/action";
function ToDo({ text, id }) {
const dispatch = useDispatch();
return (
<li>
{text}
<button onClick={() => dispatch(deleteToDo(id))}>Delete</button>
</li>
);
}
export default ToDo;
생각보다 간단하지 않나요? Delete 버튼을 누르면 dispatch(deleteToDo(id))
함수가 호출되어 store
내부의 리듀서에서 action.type
에 해당하는 로직으로 상태 값을 새로 생성합니다. 만약에 Delete 버튼을 눌러도 삭제가 안되신다면 리듀서에서 toDo.id
와 action.id
의 데이터 타입이 동일한지 확인해보시길 바랍니다.
코드를 모두 작성 후 toDo 추가와 삭제 모두 잘 작동하시나요? 막상 직접 코드를 작성하면서 사용해보니 제가 생각했던 것보다 기본적인 사용법은 어렵진 않았습니다. 😀
이 다음 글에서는 지금 작성한 코드들을 @reduxjs/toolkit
을 사용하여 조금 더 다듬어보도록 하겠습니다. 그럼 다음 글에서 뵙겠습니다