-
설치방법
yarn add redux react-redux
기본세팅
1. src 폴더 아래에 redux폴더를 만든다.
2. redux폴더 아래에 각각 config, modules폴더를 만든다.
3. config폴더 아래에 configStore.js파일을 만든다.
import { createStore } from "redux"; import { combineReducers } from "redux"; /* 1. createStore() 리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수) 이다. 리덕스를 사용할 시 creatorStore를 호출할 일은 한 번밖에 없다. */ /* 2. combineReducers() 리덕스는 action —> dispatch —> reducer 순으로 동작한다. 이때 애플리케이션이 복잡해지게 되면 reducer 부분을 여러 개로 나눠야 하는 경우가 발생한다. combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어준다. */ const rootReducer = combineReducers({}); const store = createStore(rootReducer); export default store;
4. index.js에서 설정해준다.
// 원래부터 있던 코드 import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; // 우리가 추가할 코드 import store from "./redux/config/configStore"; import { Provider } from "react-redux"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( //App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어줍니다. <Provider store={store}> <App /> </Provider> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
5. 모듈에 들어갈 reducer함수를 만들어준다.
// src/modules/counter.js // 초기 상태값 const initialState = { data:{ } }; // 리듀서 const todo = (state = initialState, action) => { switch (action.type) { default: return state; } }; // 모듈파일에서는 리듀서를 export default 한다. export default todo;
리듀서 함수 작성하기
1. switch문에 case작성하기(type에 따라 리턴문을 어떻게 줄건지 결정한다.)
import { v4 as uuidv4 } from 'uuid'; export const DATA_SAVE = 'todoList/data_save' const initialState = { // const [data, setData] = useState([ // { id: 1, title: '리엑트 1주차강의듣기', content: '1주차 강의 1회독하기', done: true }, // { id: 2, title: '리엑트 2주차강의듣기', content: '2주차 강의 1회독하기', done: false }, // ]) data: [ { id: 1, title: '리엑트 1주차강의듣기', content: '1주차 강의 1회독하기', done: true } ] } export default todo
initialState에 기본값을 넣어주는데 "" 로 빈문자열을 넣어줄 수 있지만 초기값을 넣어줌으로써 위에 주석처럼 원래는 data,setData로 useState를 이용해서 데이터 값을 저장하고 관리했지만 reducer을 이용해서 관리 할 수 있다.
const todo = (state = initialState, action) => { switch (action.type) { case DATA_SAVE: return { //기존에 들어온 내용을 ...으로 복사 해서 배치한다. ...state, data: [ //inputData배열에 새로운 객체를 추가한다. ...state.data, { id: uuidv4(), title: action.payload.title, content: action.payload.content, done: false }] } default: return state } }
id값과 done값은 reducer자체에서 생성하고 title, content값만 유저가 입력한 값을 가지고 action에 payload를 통해 title,content로 받아올 수 있다.
2. dispatch를 사용할 컴포넌트에서 임포트 해서 변수에 넣어준다.
const dispatch = useDispatch();
dispatch가 액션을 스토어에 던진다!!!
action 객체에는 type, payload가 있다.
type에 따라 스위치문이 변경된다.
const onCreate = (title, content) => { dispatch({ type: DATA_SAVE, payload: { title, content } }) } // const onCreate = (title, content) => { // const newList = { // title, // content, // done, // id: data.length + 1 // } // setData([...data, newList]) // }
아래 코드와 동일한 코드이지만 아래코드는 useState를 이용해서 data에 리스트를 저장해서 사용하는 방식이였다면
dispatch를 이용해서 reducer에 타입에 맞게 payload에 추가로 보내줄 데이터를 담아서 보내준다.
redux를 이용해서 데이터 가져오기
1. useSelector를 import한다.
import { useSelector } from "react-redux"; const todoList = useSelector((state) => { return state.todo.data }) console.log('todoList:', todoList) //todoList: (4) [{…}, {…}, {…}, {…}]
todoList라는 변수에 데이터를 담아온다. console에 찍어봤을때 정상적으로 입력된 데이터를 전부 가져오는걸 볼 수 있다.
가져온 데이터를 가지고 map메소드를 돌려서 리스트를 ui에 보여질 수 있도록 할 수 있다.
redux를 이용해서 데이터 삭제하기
삭제를 원하는 아이템에 id값을 reducer에 보내야되기 때문에 useDispatch를 import해준다.
import { useDispatch, useSelector } from "react-redux"; const dispatch = useDispatch()
import { useDispatch, useSelector } from "react-redux"; import { DATA_DELETE, DONE_CHANGE } from "redux/modules/todo"; const TodoWorking = () => { const dispatch = useDispatch() const todoList = useSelector((state) => { return state.todo.data }) const onDeleteHandle = (id) => { dispatch({ type: DATA_DELETE, payload: id }) } <div className="TodoWorking"> <h1>Working.. </h1> <div className="list"> { todoList.filter((item) => { return item.done !== true }).map((item) => { return ( <div className="listBox" key={item.id}> <h2>{item.title}</h2> <p>{item.content}</p> <div className="btn"> <button onClick={() => onDeleteHandle(item.id)}>삭제하기</button> <button onClick={() => onDoneChangeHandle(item.id)}>완료하기</button> </div> </div> ) }) } </div>
case문에 삭제할 수 있도록 state.data에서 filter매서드를 이용해서 payload로 들어온 id값과 동일하지 않은걸로 다시 배열을 만들어서 다시 리스트에 보여지게 한다.
case DATA_DELETE: return { data: state.data.filter((item) => item.id !== action.payload) }
redux이용해서 수정하기
삭제하기랑 동일하게 id값을 받아서 일치하는 type으로 id값을 payload로 담아서 보낸다.
const onDoneChangeHandle = (id) => { dispatch({ type: DONE_CHANGE, payload: id }) }
working이랑 done이 동일한 작업을 하지만 done값이 true, false로 나뉘기 때문에 동일한 함수로 id값을 보내지만
리듀서에서는 삼항연산자를 이용해서 들어오는 값의 done값을 파악하고 각각 반대로 변경해주면 토글방식으로 함수 한개에 끝낼 수 있다.
case DONE_CHANGE: return { data: state.data.map((item) => { if (item.id === action.payload) { return item.done === false ? { ...item, done: true } : { ...item, done: false } } return item }) }
여기서 끝내도 정상적으로 코딩이 되었고 잘 돌아가긴 하나 만약 엄청 많은 컴포넌트에서 사용중이라면 하나하나 변경하기 어렵다.
하나하나 바꿔주는 하드코딩방식은 지양해야한다. dispatch도 사람이 적는거다 보니 실수가 날 수 있어서 변수로 지정해놓고 쓰는게 코드를 단축화 할 수 있다.
export const data_save = (payload) => { return { type: DATA_SAVE, payload } } export const data_delete = (payload) => { return { type: DATA_DELETE, payload } } export const done_change = (payload) => { return { type: DONE_CHANGE, payload } }
가져올 데이터를 함수에 담아서 가져올 수 있다.
//App.js const onCreate = (title, content) => { dispatch(data_save{ title, content }) }
//todoList const onDeleteHandle = (id) => { dispatch(data_delete(id)) } const onDoneChangeHandle = (id) => { dispatch(done_change(id)) }
더 간략하게 코드를 짤 수 있고 실수 없이 보낼 수 있다.
어느정도 이해했다 생각했는데 다시 혼자서 해보려니까 너무 어렵고 뭐가뭔지 전혀 기억하지 못했다.
위에 이미지를 보고 왜 저렇게 가는지만 먼저 이해하는게 먼저일 것 같다는 생각이 들었다.
다음번엔 이전에 프로젝트했던 항해캡슐을 리덕스를 이용해서 하는걸로 연습해봐야 할 것 같다.