TIL 22-06-04

thisisyjin·2022년 6월 4일
0

TIL 📝

목록 보기
59/113

React

Today I Learned ... React.js

🙋‍♂️ Reference Lecture


Redux Lecture

  • React Redux
  • Redux Toolkit

Redux

🔻 기존 Hooks를 이용한 Todo App은?

import { useState } from 'react';

const Home = () => {
 const [text, setText] = useState('');
 const [todos, setTodos] = useState([]);

 const onChangeInput = (e) => {
   setText(e.target.value);
 };

 const onSubmitForm = (e) => {
   e.preventDefault();
   setTodos([...todos, text]);
   console.log(todos);
   setText('');
 };

 return (
   <>
     <h1>To Do</h1>
     <form onSubmit={onSubmitForm}>
       <input type="text" value={text} onChange={onChangeInput} />
       <button>Add</button>
     </form>
     <ul>
       {todos.map((todo) => (
         <li>{todo}</li>
       ))}
     </ul>
   </>
 );
};

Store 생성

src/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.id !== action.id);

    default:
      return state;
  }
};

const store = createStore(reducer);

export default store;

index (Provider)

react-redux의 Provider 컴포넌트를 임포트한 후,
store을 props로 전달해줌.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import store from './store';
import { Provider } from 'react-redux';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

App (Routes)

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './routes/Home';
import Detail from './routes/Detail';

const App = () => {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/:id" element={<Detail />} />
      </Routes>
    </Router>
  );
};

export default App;

Add ToDo

connect 함수

이제는 useSelector과 useDispatch 훅을 사용하는 것을 권장한다.

mapStateToProps

function mapStateToProps(state, ownProps)

-> redux store로부터 state를 받아옴.
반드시 무언가를 return 해야함.

import { useState } from 'react';
import { connect } from 'react-redux';

const Home = (props) => {
  console.log(props);
  const [text, setText] = useState('');
  const [todos, setTodos] = useState([]);

  const onChangeInput = (e) => {
    setText(e.target.value);
  };

  const onSubmitForm = (e) => {
    e.preventDefault();
    setTodos([...todos, text]);
    setText('');
  };

  return (
    <>
      <h1>To Do</h1>
      <form onSubmit={onSubmitForm}>
        <input type="text" value={text} onChange={onChangeInput} />
        <button>Add</button>
      </form>
      <ul>
        {todos.map((todo) => (
          <li>{todo}</li>
        ))}
      </ul>
    </>
  );
};

// 추가한 부분
const getCurrentState = (state, ownProps) => {
  return { cute: true };
};

export default connect(getCurrentState)(Home);

🔻 수정

const mapStateToProps = (state) => {
  return { toDos: state };
  // toDos 를 현재 컴포넌트(Home)의 props로 지정함
};

export default connect(mapStateToProps)(Home);

-> store과 컴포넌트를 연결한 것. (props로 지정해주면서)


mapDispatchToProps

  • connect 함수는 두개의 인자를 갖는다.
  • 하나는 mapStateToProps 이고, 다른 하나는 mapDispatchToProps 이다.
export default connect(mapStateToProps, mapDispatchToProps)(Home);
...
const mapDispatchToProps = (dispatch) => {
  console.log(dispatch);
};

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

dispatch 함수 구조는 다음과 같다.

function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error( // 생략 )
    }
    if (typeof action.type === 'undefined') {
      throw new Error( // 생략 );
    }
    if (isDispatching) {
      throw new Error( // 생략);
    }
	 
    try { 
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    var listeners = currentListeners = nextListeners;

    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      listener();
    }

    return action;
    // 액션을 return하는 함수 = dispatch
  }

dispatch

import { useState } from 'react';
import { connect } from 'react-redux';
import { actionCreators } from '../store';

const Home = ({ toDos, addTodo }) => {
  const [text, setText] = useState('');

  const onChangeInput = (e) => {
    setText(e.target.value);
  };

  const onSubmitForm = (e) => {
    e.preventDefault();
    // 🔻 dispatch 함수 호출. (Payload인 text 전달)
    addTodo(text);
    setText('');
  };

  return (
    <>
      <h1>To Do</h1>
      <form onSubmit={onSubmitForm}>
        <input type="text" value={text} onChange={onChangeInput} />
        <button>Add</button>
      </form>
      <ul>{JSON.stringify(toDos)}</ul>
      // 🔺 배열을 문자열 그대로 보여줌
    </>
  );
};

const mapStateToProps = (state) => {
  return { toDos: state };
};

const mapDispatchToProps = (dispatch) => {
  // 🔻 addToDo (액션생성함수)를 디스패치함.
  return {
    addTodo: (text) => dispatch(actionCreators.addToDo(text)),
  };
};

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

-> id는 Date.now() 로 저장됨.

 case ADD:
      return [{ text: action.text, id: Date.now() }, ...state];

Todo 렌더링

  1. components/ToDo.jsx 생성
const Todo = ({ text, id }) => {
  return (
    <li>
      {text} <button>DEL</button>
    </li>
  );
};

export default Todo;
  1. Home 컴포넌트 수정
import { useState } from 'react';
import { connect } from 'react-redux';
import { actionCreators } from '../store';

import ToDo from '../components/ToDo';

const Home = ({ toDos, addTodo }) => {
  const [text, setText] = useState('');

  const onChangeInput = (e) => {
    setText(e.target.value);
  };

  const onSubmitForm = (e) => {
    e.preventDefault();
    addTodo(text);
    setText('');
  };

  return (
    <>
      <h1>To Do</h1>
      <form onSubmit={onSubmitForm}>
        <input type="text" value={text} onChange={onChangeInput} />
        <button>Add</button>
      </form>
      <ul>
        // 🔻 하위 컴포넌트로 props 전달 (text, id)
        {toDos.map((todo) => (
          <ToDo text={todo.text} id={todo.id} key={todo.id} />
        ))}
      </ul>
    </>
  );
};

const mapStateToProps = (state) => {
  return { toDos: state };
};

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

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

✅ 참고 - spread 이용 Props

{toDos.map((toDo) => (
          <ToDo text={toDo.text} id={toDo.id} key={toDo.id} />
        ))}

🔻

{toDos.map((toDo) => (
          <ToDo {...toDo} key={toDo.id} />
        ))}

Delete ToDo

ownProps 이용

import { connect } from 'react-redux';
import { actionCreators } from '../store';

const Todo = ({ text, deleteToDo }) => {
  const onClickButton = () => {
    deleteToDo();
  };
  return (
    <li>
      {text} <button onClick={onClickButton}>DEL</button>
    </li>
  );
};

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    deleteToDo: () => dispatch(actionCreators.deleteToDo(ownProps.id)),
  };
};

export default connect(null, mapDispatchToProps)(Todo);

delete 할때는 store의 state를 가져올 필요는 없고, dispatch만 하면 된다.

-> connect의 첫번째 인자로 null을 넣어준다.

mapDispatchToProps의 두번째 매개변수인 ownProps를 보면 ownProps.id가 id에 해당한다.
-> payload인 id로 넣어주면 된다.

다른 방법

import { connect } from 'react-redux';
import { actionCreators } from '../store';

const Todo = ({ text, id, deleteToDo }) => {
  const onClickButton = () => {
    deleteToDo(id);
  };
  return (
    <li>
      {text} <button onClick={onClickButton}>DEL</button>
    </li>
  );
};

const mapDispatchToProps = (dispatch) => {
  return {
    deleteToDo: (id) => dispatch(actionCreators.deleteToDo(id)),
  };
};

export default connect(null, mapDispatchToProps)(Todo);

ownProps가 아닌 직접 Todo에서 props를 구조분해로 받은 다음, deleteToDo의 인자로 전달해주는 방법도 된다.

❗️ 참고 - 아래와 같이 deleteToDo를 직접 넣어줘도 OK.

return (
    <li>
      {text} <button onClick={() => deleteToDo(id)}>DEL</button>
    </li>
  );

❔ 언제 ownProps를 쓰고 언제 직접 값을 대입하나?

  • 해당 컴포넌트의 props를 인자로 넣어줘야 할 때는
    ownProps를 쓰자. (ToDo)
  • 해당 컴포넌트의 props가 아닌 ref나 state를 넣어줘야 할 때는 직접 인자로 받아 대입하자. (Home)

Detail

ToDo.jsx 수정

import { connect } from 'react-redux';
import { actionCreators } from '../store';
import { Link } from 'react-router-dom';

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

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    deleteToDo: () => dispatch(actionCreators.deleteToDo(ownProps.id)),
  };
};

export default connect(null, mapDispatchToProps)(Todo);

/:id 로 이동하는 Link 컴포넌트 추가함

Detail.jsx

import { useParams } from 'react-router-dom';

const Detail = () => {
  const { id } = useParams();
  return <div>Detail {id}</div>;
};

export default Detail;

-> useParams() 사용함.

Detail.jsx 수정

import { useParams } from 'react-router-dom';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';

const Detail = ({ toDo }) => {
  const { id } = useParams();
  const toDoText = toDo.find((toDo) => toDo.id === parseInt(id));
  return (
    <>
      <h1>{toDoText.text}</h1>
      <h5>Created at : {id}</h5>
      <Link to="/">Home</Link>
    </>
  );
};

const mapStateToProps = (state) => {
  return {
    toDo: state,
  };
};

export default connect(mapStateToProps)(Detail);


profile
기억은 한계가 있지만, 기록은 한계가 없다.

0개의 댓글