Vanilla Redux-(2) TO DO LIST

Hong Un Taek·2021년 8월 14일
0

JavaScript

목록 보기
4/4
post-thumbnail
post-custom-banner

😲 시작하기에 앞서

노마드강의의 초보자를 위한 리덕스 101을 복습하며 정리하려고 포스팅한다.
react에 redux를 사용하기 전 바닐라로 조금 더 쉽게 개념을 잡아보려고한다. 생략된 개념이 있을 수 있으므로 이해가 가지않는다면
Vanilla Redux-(1) Counter를 참고하길바란다.

1. Vanilla ToDo

😁 index.html

심플하다. h1, input, button 그리고 ul로 구성되어있다.

😁 index.js

formsubmit되면 onSubmit함수가 실행이되고 input에서
값을 가져온다음 createToDo함수를 호출하고
input에서 얻은 텍스트를 인자로 보낸다.
그러면 list item을 만들어주고 listinnerText
인자로받은 toDo로 바꿔준다음 listappend시키는 구조이다.

하지만 지금 코드는
어플리케이션이 데이터를 가지고 있지 않고 HTML을 수정해주는 것 뿐이다.
이전 섹션에 배운 것을 연습삼아 Redux를 이용해보자.

😁 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 reducer = (state = [],  action) => {
  console.log(action);
  switch(action.type){
    case ADD_TODO:
      return [];
    case DELETE_TODO:
      return [];
    default:
      return state;
  }
};
const store = createStore(reducer);

const onSubmit = e => {
  e.preventDefault();
  const toDo = input.value;
  input.value = "";
  store.dispatch({ type: ADD_TODO, text: toDo });
};

form.addEventListener("submit", onSubmit);

먼저 reduximport해주고
import {createStore} from "redux";

이전에 배운 reducerstore함수를 만들어줬다.
const reducer = (state = [], action = []) => {}
const store = createStore(reducer);

그리고 각각의 action들을 만들고
const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";

reducer함수에 swtich문을 이용하여 (뭐가됐든 우선)return하게 만들었다.

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

마지막으로 사용자가 submit할때 리스트를 만들고 리스트 아이템들을
리스트에 넣어주는 createToDo를 호출하는대신 dispatch 시켜줬다.
현 케이스에서는 action말고도 text가 필요하다.
store.dispatch({ type: ADD_TODO, text: toDo });

이것을 콘솔로 확인해보자.

📕console

input에 text를 입력하고 add버튼을 눌렸을때 콘솔로그가 실행되며
dispatch로 인해store.dispatch({ type: ADD_TODO, text: toDo });
actiontypetext가 맞게 나오는걸 확인할 수 있다.

여기서 꼭 이해하고 넘어가야하는게 있다

1.1 State Mutation

📕Redux 공식문서 中

Redux 공식문서 Three Principles(세가지 원칙) 카테고리를보면
Changes are made with pure functions라는 소제목이 있다.
내용을 살펴보면
Remember to return new state objects,
instead of mutating the previous state.

이전 상태를 변경하는 대신 새 상태 개체를 반환해야 합니다.

즉, state를 mutate하는게 아니라 새로운 objects를 리턴해야한다.
상태를 수정하는 것이 아니라 새로운 것을 리턴해야된다는 의미이다.

😄index.js

앞서 말한 것처럼 state.push()같이 state를 변형하는것은 하지않을것이다.
return state.push(action.text)


Es6 spread 문법을 이용해 기존의 state를 똑같이 가져와
새로운 array로 return할 수 있다.
return [...state, { text: action.text }];

콘솔로 확인해보자

📕console

subscribe을 이용해 변화가 생길때 콘솔로그를 실행하게된다.
사진에서와같이 text를 입력할때마다 기존의 state값을 가져와 입력한text를 포함한 새로운 array를 출력하는것을 확인할 수 있다.
store.subscribe(() => console.log(store.getState()));

😁index.js

이제 delete하는 기능을 추가할건데 현재는 text값밖에 없어 새로운 array를 생성할때 Date.now()를 이용해 고유값을 추가해줄것이다.
case ADD_TODO:
return [...state, { text: action.text, id: Date.now() }];

1.2 Delete To Do

삭제 기능을 만들기전 onSubmit함수에서 dispatch를 하는것이 아닌
각각의 addToDo, deleteToDo함수를 만들겠다.

먼저 addToDo함수는 text(input의 value)를 파라미터로 받고
store.dispatch를 호출해준다.

const addToDo = text => {
  store.dispatch({ type: ADD_TODO, text });
}

그리고 todo들을 paint해줘야하는데 paintToDos함수를 만들고
이 함수를 subcribe해준다.
paintToDos함수가 실행되면 getState를 가져와 toDos변수에 담고
리스트를 생성해 text값과 id값을 지정해주고 append시켜준다.

const paintToDos = () =>{
  const toDos = store.getState();
  ul.innerText ="";
  toDos.forEach(toDo => { 
    const li = document.createElement('li');
    li.id = toDo.id;
    li.innerText = toDo.text;
    ul.appendChild(li);
  })
}
store.subscribe(paintToDos);

추가적으로 reducer에서 ADD_TODO일때 리턴하는 순서를 수정해주겠다. 이렇게 수정해준다면 todo가 새롭게 추가될때마다 array의 첫부분에 있게된다.
return [{ text: action.text, id: Date.now() }, ...state];

이제 삭제기능을 만들건데 list item이 생성될때마다 삭제버튼 또한 필요하다. 버튼을 생성하고 클릭이벤트를 등록하였다. deleteToDo함수가 실행되게끔

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

이제 deleteToDo 함수를 만들어보자.
자 생각해보자.
우리는 list item에 고유의 id값을 넣어주었다. 현재 버튼에는 eventListener가 등록되어있고
event객체의 target.parentNode를 이용하면
고유의 id를 가진 해당 list item에 접근할 수 있다.

const deleteToDo = e => {
  const id = e.target.parentNode.id;
  store.dispatch({ type: DELETE_TODO, id });
}

이렇게 addToDo, deleteToDo 함수 생성하였고 추가적으로
actionreturn하는 함수를 생성할것이다.
대부분의 사람들이 주로 reducer 위에 추가한다고한다.

const addToDo = text => {
  return {
     type: ADD_TODO,
     text
  }
}

const deleteToDo = id => {
  return {
    type: DELETE_TODO,
    id
  }
}

그리고 기존의 함수를 수정해주겠다.

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


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

자 조금 정리를 하자면 addToDo, deleteToDo함수는 오로지 action을 return
dispatchAddToDo, dispatchDeleteToDo함수는 addToDo, deleteDo함수의
return하는 object를 dispatch해준다.

1.3 Delete To Do part Two

이제 정말 삭제기능을 만들어볼 차례이다.

조건은
내가 삭제할,선택한 todo를 삭제하되
내가 삭제할 todoid에 해당하지 않는 todo들을 유지시키면된다.
Array.filter를 사용해 조건을 작성하고 그 조건이 충족되면 해당 요소는 새로운 array에 남게하면된다.
절~~대 Array.prototype.splice()같이 기존의 배열을 수정하는것이아닌
새로운 array값을 return해야한다.

case DELETE_TODO:
  return state.filter(toDo => toDo.id !== action.id );

자 이제 완성이다! 아래는 완성한 index.js이다.

😁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:
      //Never state mutation!! 
      //return state.push(action.text);

      // Return something new
      return [{ text: action.text, id: Date.now() }, ...state];

    case DELETE_TODO:
      return [];
    default:
      return state;
  }
};

const store = createStore(reducer);

store.subscribe(() => console.log(store.getState()));  


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


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


const paintToDos = () =>{
  const toDos = store.getState();
  ul.innerText ="";
  toDos.forEach(toDo => { 
    const li = document.createElement('li');
    const btn = document.createElement('button');
    btn.innerText = "DEL";
    btn.addEventListener('click', deleteToDo);
    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
cherrycock's Velog
post-custom-banner

0개의 댓글