#3. React + Redux로 ToDo List App 만들기

마리 Mari·2021년 7월 1일
0

Goal

  • 입력된 글자가 제출되면, 이를 저장하고 화면의 목록에 표시하는 App
  • 목록의 항목이 선택되면, 해당 항목의 상세페이지로 이동
  • 목록의 항목 옆의 삭제 버튼이 클릭되면, 해당 항목이 목록에서 삭제


App without Redux (setup)

1. ToDo App의 기본 골격 만들기

index.html

<body>
	<div id="root"></div>
</body>

/components/App.js

import React from "react";

function App() {
	return "APP"
}

export default App;

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

ReactDOM.render(<App />, document.getElementById("root");

2. Router 설정

/component/App.js

import React from 'react';
import { HashRouter as Router, Route } from "react-router-dom";
import Home from '../routes/Home';
import Detail from '../routes/Detail';

import '../scss/style.scss';

function App() {
    return ( 
        <Router>
            <Route path="/" exact component={Home}></Route>
            <Route path="/:id" exact component={Detail}></Route>
        </Router>
    );
}

export default App;

/routes/Home.js

export default () => "Home;

/routes/Detail.js

export default () => "Detail;

3. Home 페이지 구성하기

/routes/Home.js

  • 제목, 입력창, 입력 버튼, 입력된 to-do 목록
  • input의 value를 text라는 state로 관리
  • onChange: text를 변경된 값으로 수정
  • onSubmit: 입력된 text를 저장하고, text를 빈 string 값으로 변경
import React, { 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" value={text} onChange={onChange}/>
				<button>Add</button>
			</form>
			<ul></ul>
		</>
	);
}

export default Home;




App with Redux

1. React에 Redux 도입하기

store.js

  • reducer 만들기
  • string 변수로 등록
  • action creator 함수 만들기, 객체로 내보내기 (함수명 헷갈리는 것 방지용)
import { createStore } from "redux";

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

const addToDo = (text) => {
    return {
        type: ADD,
        text
    };
};
const deleteToDo = (id) => {
    return {
        type: DELETE,
        id: parseInt(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 !== parseInt(action.id));
        default:
            return state;    
    }
}

const store = createStore(reducer);

export const actionCreators = {
    addToDo,
    deleteToDo
};

export default store;

index.js

  • react-redux의 <Provider> import 하기

    : The component makes the Redux store available to any nested components that need to access the Redux store.

    → 컴포넌트는 Redux store에 접근이 필요한 내장된 component들이 Redux store을 사용할 수 있게 해줍니다.

  • <Provider>는 인자로 store를 받는다.

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

ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>, 
  document.getElementById("root")
);

2. store의 state를 Home component로 가져오기

connect( mapStateToProps, mapDispatchToProps )

  • react-redux의 함수로, component와 store의 state를 연결해주는 기능을 한다.
  • 인자로는 2개의 함수를 받는다.
export default connect(mapStateToProps, mapDispatchToProps) (Home);

mapStateToProps( state, ownProps )

  • connect의 1번째 인자. component에서 store의 state를 가져와서 읽을 수 있게 해준다.

  • 인자로 store의 현재 state인 state와 기존 props인 ownProps를 받는다.

    stateownProps를 이용하여 필요한 정보를 가공 후 return하면 됨.

    → return type은 객체

  • 읽어온 state를 component의 props로 넣어준다.

    connect 함수가 component로 보내지는 props에 끼어드는 형식

/component/Home.js

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

function Home({ toDos }) {
	//...
	return(
		...
		<ul>
			{ toDos.map(toDo => <li key={toDo.id}>{toDo.text}</li> )}
		</ul>
	)
}

function mapStateToProps(state) {
    return { toDos: state }
}

export default connect(mapStateToProps) (Home);

3. Home component에서 store의 state를 변경하기 (dispatch)

mapDispatchToProps

  • connect 함수의 2번째 인자(생략 가능)
  • component에서 store로 state에 관한 dispatch 요청을 보낼 수 있다.
  • 인자로 store의 method인 dispatch 와 기존 props인 ownProps(생략 가능)를 받는다.
  • dispatch를 활용하여 함수를 만든 후 객체로 return한다. return한 객체는 component의 props로 들어온다.
function mapStateToProps(state) {
    return { toDos: state }
}

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

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

이처럼 component에서는 state/dispatch만 하면 되고, store의 세부적인 구현(action creator, dispatch ~ action, subscription 등)은 store에서 처리하기 때문에 코드가 깔끔해진다.


4. ToDo component 만들기, 삭제 기능 구현

/components/ToDo.js

  • 기존 <li>...</li> 부분을 별도의 component로 구현
  • text 옆에 delete 버튼 구현 (store에 해당 객체의 삭제 요청을 보냄)
  • mapDispatchToState를 통해 dispatch로 삭제요청을 보내는 함수를 props로 받아온다. 이후 button의 onClick property의 값으로 해당 함수를 넣는다.
  • connect의 1번째 인자인 mapStateToProps를 생략할 경우, connect에 1번째 인자로 null을 넣어야 함.
import React from "react";

function ToDo({ text, onBtnClick }) {
	return (
		<li>
			{text}
			<button onClick={onBtnClick}>DEL</button>
		</li>
	);
}

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

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

5. Detail page 구현

/components/ToDo.js

  • Detail page로 가는 Link 걸기
...
import { Link } from "react-router-dom";

function ToDo({ text, onBtnClick, id }) {
	return (
		<li>
			<Link to={`/${id}`} >
				{text}
			</Link>
			<button onClick={onBtnClick}>DEL</button>
		</li>
	)
}

/routes/Detail.js

  • Detail page의 id 값을 가져오는 방법

    1. useParams hooks를 이용하여 params.id로 값을 받아온다.

    2. mapStateToProps를 이용하여 ownProps.match.params.id로 값을 받아온다.

      → Redux 연습을 위해 2의 방법 선택

import React from "react"
import { connect } from "react-redux";

function Detail({.toDo }) {
	return (
		<>
			<h1>Detail</h1>
			<h5>Created at: {toDo.id}</h5>
		</>
	)
}

function mapStateToProps(state, ownProps) {
	const {
		match: {
			params: { id }
		}
	} = ownProps;
	return { toDo: state.find(toDo => toDo.id == parseInt(id) ) };
}

새로 고침시 toDo list가 초기화 되면서 error가 발생. → toDo?.text 로 해결.

  • toDo가 null이 아닐때만 text에 접근.




✨ Challange: toDos를 local storage에 저장하기

  • store에 변화가 있을 때마다, local storage에 update
  • 새로고침 시, local에 저장된 toDos가 store의 default value로 설정됨

store.js

//...
const localStorageStore = JSON.parse(window.localStorage.getItem("toDoStore"))

const reducer = ( state = localStorageStore, action) => {
	//...
}

const store = createStore(reducer);

const saveDataOnLocalStorage = () => {
    const currentState = store.getState();
    window.localStorage.setItem("toDoStore", JSON.stringify(currentState));    
}
store.subscribe(saveDataOnLocalStorage);

//...

참고자료: Window.localStorage - Web API | MDN

profile
우리 블로그 정상영업합니다.

0개의 댓글