Redux로 TodoList 작성

개발자지망생·2023년 12월 5일
0

React

목록 보기
22/24

오늘의 학습목표
1. 상태관리를 Redux로 전역상태관리 해보기
2. React-Router-Dom 사용하기
3. Detail 페이지 만들기
4. Custom hook 만들기

1.Redux 구성

전역 관리를 위한 메소드(함수)

// src>redux>config>configstore
import { createStore } from "redux";
import { combineReducers } from "redux";

import todos from "../modules/todos";

const rootReducer = combineReducers({
  todos: todos,
});
const store = createStore(rootReducer);

export default store;
  • 구성요소
  1. createStore()
    리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수) 입니다.
    리덕스는 단일 스토어로 모든 상태 트리를 관리한다

  2. combineReducers()
    리덕스는 action —> dispatch —> reducer 순으로 동작
    이때 애플리케이션이 복잡해지게 되면 reducer 부분을 여러 개로 나눠야 하는 경우가 발생
    combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어줍니다.

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(
  <React.StrictMode>
  //App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어줍니다.
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);


reportWebVitals();

modules 만들기

// src>redux>modules>todos.js
import uuid from "react-uuid";

// 초기 상태값
const initialState = {
  todos: [
    {
      id: uuid(),
      title: "할일1",
      contents: "기본",
      isDone: false,
    },
  ],
};

// 리듀서
const todos = (state = initialState, action) => {
  switch (action.type) {
    case "ADD_TODO":
      return { todos: [...state.todos, action.payload] };
 // payload를 더해줄때 [ 배열을 열고 state를 풀어준뒤
 // action.payload 를 추가하고 ] 닫아준다 
 // 이렇게 써져있다고 보면된다 .
// ... 스프레드 문법

    case "FILLTER_TODO":
      return {
        todos: state.todos.filter((todo) => {
          return todo.id !== action.payload;
        }),
      };
    case "IS_DONE":
      return {
        todos: state.todos.map((todo) => {
          if (todo.id === action.payload) {
            return { ...todo, isDone: !todo.isDone };
          } else {
            return todo;
          }
        }),
      };
    default:
      return state;
  }
};
// 리듀서를 내보낸다
export default todos;
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import Input from "../components/Input";
import { Link } from "react-router-dom";

const Home = () => {
  const dispatch = useDispatch();
  const todos = useSelector((state) => state.todos.todos);

  return (
    <>
      <div>
        <Input />

        <body>
          <div>
            <h3>할일</h3>
            {todos
              .filter((check) => {
                return check.isDone === false;
              })
              .map((item) => {
                return (
                  <>
                    <div key={item.id}>
                      <Link to={`/${item.id}`} key={item.id}>
                        <div>상세보기</div>
                      </Link>
                      <div>{item.title}</div>
                      <div>{item.contents}</div>
                      <button
                        onClick={() => {
                          dispatch({ type: "IS_DONE", payload: item.id });
                        }}
                      >
                        완료
                      </button>
                      <button
                        onClick={() => {
                          dispatch({ type: "FILLTER_TODO", payload: item.id });
                        }}
                      >
                        삭제
                      </button>
                    </div>
                  </>
                );
              })}
          </div>
          <div>
            <h3>한일</h3>
            {todos
              .filter((check) => {
                return check.isDone === true;
              })
              .map((item) => {
                return (
                  <>
                    <div key={item.id}>
                      <div>{item.title}</div>
                      <div>{item.contents}</div>
                      <button onClick={() => {}}>완료</button>
                      <button onClick={() => {}}>삭제</button>
                    </div>
                  </>
                );
              })}
          </div>
        </body>
        <footer></footer>
      </div>
    </>
  );
};

export default Home;

mapfilter를 이용해서 원하는 내용 출력

2. Custom Hook

중복되는 로직이나 기능을 리액트 훅을 이용해서 별도로 분리한 훅

src > hooks > useInput.js
input과 useState 가 늘어날수록 로직이 늘어나기에 같은 작업을
반복하는 로직을 여기로 빼두고 .

import React, { useState } from "react";

const useInput = () => {
  const [value, setValue] = useState("");

  // 3. 핸들러 로직도 구현합니다.
  const handler = (e) => {
    setValue(e.target.value);
  };

  const clearValue = () => {
    setValue("");
  };

  // 1. 이 훅은 [ ] 을 반환하는데, 첫번째는 value, 두번째는 핸들러를 반환합니다.
  return [value, handler, clearValue];
};

export default useInput;

아래와 같이 사용할수있다 .

useState 대신에 만든 custom hook인 useInput을 불러와서 사용한다.

import React from "react";
import uuid from "react-uuid";
import { useDispatch } from "react-redux";
import useInput from "../hooks/useInput";

const Input = () => {
  const dispatch = useDispatch();

  const [title, onChangeTitleHandler, clearTitle] = useInput("");
  const [contents, onChangeBodyHandler, clearContents] = useInput("");
  return (
    <div>
      {" "}
      <header>
        <h1> 하루하루 해야할일</h1>
        <input type="text" value={title} onChange={onChangeTitleHandler} />
        <input type="text" value={contents} onChange={onChangeBodyHandler} />
        <button
          onClick={() => {
            const newTodo = {
              id: uuid(),
              title: title,
              contents: contents,
              isDone: false,
            };

            dispatch({ type: "ADD_TODO", payload: newTodo });
            clearTitle();
            clearContents();
          }}
        >
          추가하기
        </button>
      </header>
    </div>
  );
};

export default Input;

3.React-Router-Dom

여러 페이지를 구현할수있게 해주는 패키지

src > shard > router.js

<Route path="/:id" element={} />에서 path="/:id"
부분은 URL의 매개변수(파라미터)로 사용하겠다는겁니다.
파라미터로 들어가게된 부분은 Detail 부분 참조

import React from "react";
// 1. react-router-dom을 사용하기 위해서 아래 API들을 import 합니다.
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../page/Home";
import Detail from "../page/Detail";

// 2. Router 라는 함수를 만들고 아래와 같이 작성합니다.
//BrowserRouter를 Router로 감싸는 이유는,
//SPA의 장점인 브라우저가 깜빡이지 않고 다른 페이지로 이동할 수 있게 만들어줍니다!
const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
		// "/:id 를 사용한 이유는 useParams를 이용하기위함
        <Route path="/:id" element={<Detail />} />
      </Routes>
    </BrowserRouter>
  );
};

export default Router;

App.js

import React from "react";
import Router from "./shard/router";

const App = () => {
  return <Router />;
};

export default App;

4 Detail(상세보기) 페이지 만들기

  • useParams 를 이용해서 uuid로 만들어진 고유한 아이디로
    useSelector로 가져온 todos중에서 id와 같은것만 find함수를
    통해서 구분해서 보여주는 로직
  1. useParams 는 React-Router-Dom 을 설치하면 사용할수있다.
  2. 파라미터 정보를 가져와 활용할수있다.

useNavigate()를 활용해서 버튼을 눌렀을시 원하는 페이지로 이동하게 구성할수있다.

import React from "react";
import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";

const Detail = () => {
  const { id } = useParams();
  const todos = useSelector((state) => state.todos.todos);
  const findTodos = todos.find((todo) => todo.id === id);
  const navaigte = useNavigate();
  return (
    <div>
      <h2>title : {findTodos.title}</h2>
      <h2>title : {findTodos.contents}</h2>
      <button
        onClick={() => {
          navaigte("/");
        }}
      >
        이전으로
      </button>
    </div>
  );
};

export default Detail;

URL의 매개변수(파라미터)로 각 Todo의 id를 사용하는 방법
Link to={/${item.id}}이부분이 / :id가 된다는 부분이다.

<Link to={`/${item.id}`} key={item.id}>
         <div>상세보기</div>
</Link>

5 정리

1.항상 data의 타입을 신경써줘야한다.
2.중복되는 부분을 어떻게 정리할수있을지 계속 생각해봐야한다 (리팩토링)
3.코드를 고치고 제대로 저장했는지 확인하자...(찾는대 1시간걸림)
4.console.log로 data가 어떻게 들어왔는지 잘확인하고 진행하는 버럿을 들이지
5.주석을 달고 어떤걸할지 글로 적어놓고 정확히 인지하고 작업을 진행하자

profile
프론트엔드개발자를 목표로 공부중입니다.

0개의 댓글