#2. Vanilla JS + Redux로 ToDo List App 만들기

마리 Mari·2021년 6월 29일
0

Goal

  • 입력된 글자가 제출되면, 이를 저장하고 화면에 표시하는 App


App without Redux

index.html

<body>
    <h1>To Dos</h1>
    <form>
        <input type="text" placeholder="Write to do" />
        <button>Add</button>
    </form>
    <ul></ul>
  </body>

index.js

const form = document.querySelector("form");
const input = document.querySelector("input");
const ul = document.querySelector("ul");

const createToDo = toDo => {
    const li = document.createElement('li');
    li.innerText = toDo;
    ul.appendChild(li);
};

const onSubmit = e => {
    e.preventDefault();
    const toDo = input.value;
    input.value = "";
    createToDo(toDo);
};

form.addEventListener("submit", onSubmit);
  • UI만 변경할 뿐, Data가 없다는 문제가 있음.
  • Data를 array로 관리한다고 해도 안정성의 문제가 있음(데이터의 CRUD를 직접 관리해야함)



App with Redux

1. createStore, reducer의 기본적인 구성 구현

import { createStore } from "redux";

const reducer = ( state = [], action ) => {
	switch (action.type) {
		case "ADD_TODO":
			return [];
		case "DELETE_TODO":
			return [];
		case default:
			return state;
	};
};
const store = createStore(reducer);

2. createToDo 함수를 dispatch로 대체하기

  • ADD_TODO action과 ADD될 text Data를 함께 보내는 dispatch 실행
const addToDo = (toDo) => {
	store.dispatch({ type: "ADD_TODO", text: toDo });
}

const onSubmit = () => {
	e.preventDefault();
  const toDo = input.value;
  input.value = "";
  addToDo(toDo);
}

3. action creator 함수 만들기

const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";

//functions : action creators
const addToDo = text =>  return { type: ADD_TODO, text };
const deleteToDo = id => return { type: DELETE_TODO, id };

//...

//functions for only dispatch
const dispatchAddToDo = (text) => {
    store.dispatch(addToDo(text));
};

const dispatchDeleteToDo = (e) => {
    const id = parseInt(e.target.parentNode.id);
    store.dispatch(deleteToDo(id));
};
  • dispatch의 object를 반환하는 함수 만들기
    • 함수를 작은 단위로 세분화
      → 최적화 및 에러 가능성 낮추기 위함

4. dispatch 함수 완성하기

중요) state를 변형하지 않고, 새로운 객체를 생성하여 return 하기

  • ADD_TODO : 입력된 text를 state에 추가하는 기능

    → 입력된 텍스트를 갖는 객체와 state의 모든 객체를 포함하는 배열을 반환

case ADD_TODO:
  // **NOT** : return state.push(action.text);
  const newTodoObj = { text: action.text, id: Date.now() };
  return [newTodoObj, ...state];
  • DELETE_TODO: 입력된 ID의 객체를 state에서 삭제하는 기능

    → state의 객체 중 입력된 ID와 동일한 ID를 갖는 객체를 제외한 배열을 반환

    • splice : 객체를 변경시킴
    • filter: 새로운 객체를 반환
case DELETE_TODO:
	// **NOT** : return state.splice(...)
	const filterdObj = state.filter(toDo => toDo.id !== action.id;
  return filteredObj;

!! NEVER MUTATE STATE !!

  • state는 읽기 전용(read-only) property로, 직접 값을 변경할 수 없다.
  • state를 변경하는 유일한 방법은 store로 action을 보내는 것 뿐이다.
  • 새로운 state가 될 object를 return하면 됨.

5. state의 변화 감지 시 화면 새로 그리기

toDo li를 새로 그리는 함수 paintToDo

const paintToDos = () => {
    const toDos = store.getState();
    ul.innerHTML = "";
    toDos.forEach(toDo => {
        const li = document.createElement('li');
        const btn = document.createElement('button');
        btn.innerText = "DEL"
        btn.addEventListener('click', dispatchDeleteToDo);
        
        li.id = toDo.id;
        li.innerText = toDo.text;
        li.appendChild(btn);

        ul.appendChild(li);
    })
}; 

subscribe

store.subscribe(paintToDos);


전체 코드

index.js

import { createStore } from "redux";

const form = document.querySelector("form");
const input = document.querySelector("input");
const ul = document.querySelector("ul");

const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";

const addToDo = text => {
    return {
        type: ADD_TODO,
        text
    };
};
const deleteToDo = id => {
    return {
        type: DELETE_TODO,
        id
    };
};

const reducer = ( state = [], action ) => {
    switch (action.type) {
        case ADD_TODO:
            // **NOT** : return state.push(action.text);
            const newTodoObj = { text: action.text, id: Date.now() };
            return [newTodoObj, ...state];
        case DELETE_TODO:
            return state.filter(toDo => toDo.id !== action.id);
        default:
            return state;
    }
};

const store = createStore(reducer);

const dispatchAddToDo = (text) => {
    store.dispatch(addToDo(text));
};

const dispatchDeleteToDo = (e) => {
    const id = parseInt(e.target.parentNode.id);
    store.dispatch(deleteToDo(id));
};

const paintToDos = () => {
    const toDos = store.getState();
    ul.innerHTML = "";
    toDos.forEach(toDo => {
        const li = document.createElement('li');
        const btn = document.createElement('button');
        btn.innerText = "DEL"
        btn.addEventListener('click', dispatchDeleteToDo);
        
        li.id = toDo.id;
        li.innerText = toDo.text;
        li.appendChild(btn);

        ul.appendChild(li);
    })
};

store.subscribe(paintToDos);

const onSubmit = e => {
    e.preventDefault();
    const toDo = input.value;
    input.value = "";
    dispatchAddToDo(toDo);
};

form.addEventListener("submit", onSubmit);
profile
우리 블로그 정상영업합니다.

0개의 댓글