노마드코더 리덕스 - React(1)

이동주·2022년 5월 26일
1

리덕스를 사용한 React To-Do-List

1. Setup

기본세팅- react-router-dom@6

  • components/app.js
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "../routes/Home";
import Detail from "../routes/Detail";
function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/:id" element={<Detail />} />
      </Routes>
    </Router>
  );
}
export default App;

변경사항 1) Router > Routes > Route
변경사항 2) component 대신 element, 안에는 컴포넌트 형식으로 작성

  • index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./components/App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

변경사항 1) 선언하여 따로 빼줌, react-dom/client에서 불러옴

  • routes/Home
import React from "react";
import { useState } from "react";
function Home() {
  const [text, setText] = useState("");
  function onChange(e) {
    setText(e.target.value);
  }
  function onSubmit(e) {
    e.preventDefault();
    setText("");
    console.log(text);
  }
  return (
    <>
      <h1>To Do</h1>
      <form onSubmit={onSubmit}>
        <input type="text" value={text} onChange={onChange} />
        <button>Add</button>
      </form>
      <ul></ul>
    </>
  );
}
export default Home;

2. Connecting the Store

기본세팅 - store.js

import { createStore } from "redux";

const ADD = "ADD";
const DELETE = "DELETE";

export const addToDo = (text) => {
  return {
    type: ADD,
    text,
  };
};

export const deleteToDo = (id) => {
  return {
    type: DELETE,
    id,
  };
};

const reducer = (state = [], action) => {
  switch (action.type) {
    case ADD:
      return [{ text: action.text, id: Date.now() }, state];
    case DELETE:
      return state.filter((toDo) => toDo !== action.id);
    default:
      return state;
  }
};

const store = createStore(reducer);

export default store;

(1) store를 App에 연결

index.js에 Provider 추가해서 store를 연결

...

import { Provider } from "react-redux";
import store from "./store";

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

=> Provider은 store가 필요함

3. mapStateToProps

Home에서 state를 가져와서 ul에 그려줘야 한다.
그러면 react-redux에서는 어떻게 state에 접근을 하는가?

(1) connect

  • componenet들을 store에 연결시켜 줌
  • connect는 두 개의 argument를 가짐
    => state
    => dispatch

(2) mapStateToProps

connect의 첫 번째 argument
state와 props를 가져오는 함수

...

import { connect } from "react-redux";

...

function mapStateToProps(state, ownProps) {
  return { toDos: state };
}

export default connect(mapStateToProps)(Home);

argument 1) store에 있는 state
argument 2) component의 props

=> 함수에 return {key: value} 형식으로 작성하면 props가 추가됨

4. mapDispatchToProps

dispatch는 어떻게 하는가?

(1) mapDispatchToProps

connect의 두 번째 argument
dispatch를 전달할 수 있는 함수
=> 이걸 사용한 후부터는 props를 바꿀 수 있게 됨

...

function mapDispatchToProps(dispatch, ownProps) {
  return { addToDo: (text) => dispatch(actionCreators.addToDo(text)) };
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

(2) useSelector와 useDispatch

react hook을 사용해서 connect(mapStateToProps, mapDispatchToProps)를 사용하지 않고 state를 가져오고 dispatch를 하는 것

현재는 connect를 사용하지 않고 useSelector와 useDispatch를 사용함

useSelector

getState랑 똑같은 기능(store에서 정보를 가져옴)이고 리액트에서는 mapStateToProps 대체

useDispatch

mapDispatchToProps 대체

(3) react hook으로 바꿔보기

  • 전체 코드
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addToDo } from "../store";

function Home() {
  const [text, setText] = useState("");
  const toDo = useSelector((state) => state);
  const dispatch = useDispatch();

  function onChange(e) {
    setText(e.target.value);
  }
  function onSubmit(e) {
    e.preventDefault();
    console.log(text);
    setText("");
    dispatch(addToDo(text));
  }
  return (
    <>
      <h1>To Do</h1>
      <form onSubmit={onSubmit}>
        <input type="text" value={text} onChange={onChange} />
        <button>Add</button>
      </form>
      <ul>{toDo.map((a) => JSON.stringify(a))}</ul>
    </>
  );
}

export default Home;

5. Deleting To Do

(1) ul에 todo 및 DEL 버튼 추가

  • component 추가
import React from "react";

function ToDo ({text}) {
  return (
    <li>
      {text}
	  <button>DEL</button>
   </li>
  )
}

export default ToDo;
  • 에 component 연결
...

import ToDo from "../components/ToDo";

...

return (
  ...
  <ul>
    {toDo.map((td) => (
  	  <ToDo {...td} key={td.id}/>
  	))}
  </ul>
)

=> key 값은 중복되지 않는 값으로 적기
=> 위와 같이 코드를 작성하면 todo 옆에 DEL 버튼이 함께 생성됨

(2) DEL 버튼으로 todo 삭제

  • mapDispatchToProps 대신 useDispatch 사용
import React from "react";
import { useDispatch } from "react-redux";
import { deleteToDo } from "../store";

function ToDo ({text, id}) {
  const dispatch = useDispatch();
  const onClick = () => {
    dispatch(deleteToDo(id))
  }
  return (
  	<li>
      {text} <button onClick={onClick}>DEL</button>
    </li>
  )
}

export default ToDo;

6. Detail Screen

(1) Detail 페이지 만들기

  • ToDo 페이지에 Link 추가
...

import { Link } from "react-router-dom";

...

return (
    <li>
      <Link to={`/${id}`}>
        {text} <button onClick={onClick}>DEL</button>
      </Link>
    </li>
  );

...
  
  • Detail 페이지 추가
import React from "react";
import { useParams } from "react-router-dom";

function Detail() {
  const id = useParams().id;
  return <h1>Detail</h1>;
}

export default Detail;

=> useParams로 가져온 id는 path parameter의 정보임

(2) Detail 페이지에 toDoText 추가

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

function Detail() {
  const id = useParams().id;
  const toDos = useSelector((state) => state);
  const toDoText = toDos.find((toDo) => toDo.id === parseInt(id));
  return (
    <>
      <h1>{toDoText?.text}</h1>
      <h5>Created at : {toDoText?.id}</h5>
    </>
  );
}

export default Detail;
profile
안녕하세요 이동주입니다

0개의 댓글