[React 기초] Todo list 만들기 :: 3)InputForm,TODOS

도깨비젤리

·

2021. 6. 12. 23:55

InputForm.js


InputForm 컴포넌트

 

 

import React, {useState} from 'react'
import {v4 as uuidv4} from 'uuid'

function InputForm(props){

  const [textInput, setInput] = useState('')
  
  //Submit 이벤트 발생시 실행 /현재 상태 저장 , localStorage에 상태 전달
  function onSubmit(e){
    e.preventDefault()
    if(textInput.length <=0){return}
    const id = uuidv4()
    props.addTodo(textInput,id,false)

    const data = {id:id,TODO: textInput,completed:false}

    handleLocalStorage(data)
    resetInput()
  }
  
  //localStorage에 저장
  function handleLocalStorage(data){
    const TODOS = localStorage.getItem('TODOS')
    const parseTODOS = JSON.parse(TODOS)
    const updatedTODOS = parseTODOS.concat(data)

    localStorage.setItem('TODOS',JSON.stringify(updatedTODOS))
  }
 // 사용자 입력과 state를 동기화
  function listenInput(e){
    setInput(e.target.value)
  }
// 사용자 입력 후 값 비워준다
  function resetInput(){
    const Input = document.querySelector('input')
    Input.value = ''
    setInput('')
  }

  return(
    <div className="InputForm">
      <form onSubmit={onSubmit}>
        <input type='text' onChange={listenInput} placeholder='오늘의 할 일은?' autoFocus></input>
        <button type="submit" className="create-button"> 입력 </button>
      </form>
    </div>
  )
}

export default InputForm

 

InputForm 컴포넌트의 역할


  1.  사용자의 입력을 감지하고 state에 반영한다.
  2.  submit 이벤트가 발행되었을 때, state를 저장한다 (localStorage 포함)
  3.  제출 이후, input.value와 state를 초기화 해서 다시 입력 받을 준비를 한다.

InputForm에서 사용자가 입력을 할 때, 컴포넌트에서 다음과 같은 사건이 순서대로 일어난다.

 

1. input.value이 변경될때마다, input 태그에 달린 onChange 이벤트 핸들러로 인해 listenInput이 호출된다. 

2. 사용자가 입력 버튼을 누르든가, 엔터키를 누르면 submit 이벤트가 발생되고, id,TODO,completed 정보를 addTodo 함수와 handleLocalStorage 함수에 parameter로 전달한다.

3. addTodo 함수는 Container의 state를 갱신하고, handleLocalStorage 함수는 브라우저의 LocalStorage에 저장한다.

4. 이후 resetInput() 함수가 실행되어 input.value와 state를 초기화 한다.

 

 

InputForm 컴포넌트는 create 기능만 담당하고 있다. 이 부분을 구현할때 가장 크게 배운 부분은,  props.addTodo()가 실행될 때, 분명 InputForm에는 addTodo에서 사용되는 변수인 todos (전체 state를 저장하는 배열) 에 대한 정보가 없음에도 불구하고, 문제없이 의도한바 작동한다는 점이였다.

 

검색을 해본 결과, addTodo 함수가 props로 InputForm으로 내려올 때, addTodo는 자신의 상태를 캡처해서 내려온다는 사실을 알게 되었다. 그래서 InputForm에서는 addTodo에 있는 todos라는 변수가 뭔지는 모르지만, 조회해서 사용할 수 있는 것이다. 말하자면, 컴포넌트 간 props 전달이 있을때 추상화 되어 전달된다는 것이다.

 

 

 

 

 

TODOS.js


 

TODO 컴포넌트

 

 

import React from 'react'

function TODOS (props){
  const TODOS = props.getTodos()

// localStorage를 갱신하는 함수
  function fetchlocalTODO(updatedItem){
    const localTODOS = localStorage.getItem('TODOS')
    const localparsedTODO = JSON.parse(localTODOS)
    const result = localparsedTODO.map((Item)=>{
      if(Item.id === updatedItem.id){
        Item = {...updatedItem}
      }
      return Item
    })
    localStorage.setItem('TODOS',JSON.stringify(result))

  }
// item의 completed 상태를 바꾸는 함수
  function checkTODO(e,Item){
    const target = e.target
    target.classList.toggle('completed')
    Item.completed = !Item.completed
    fetchlocalTODO(Item)
  }

//버튼을 클릭했을때 실행되는 함수
  const clickHandler = (id) =>{
    props.delTodos(id)
  }

  const listItems = TODOS.length >0 ? 
      // todo item이 존재할 때 실행
    TODOS.map(Item => 
      <li 
        key={Item.id}
        onClick={(e)=> checkTODO(e,Item)}
        >
        <span className= {Item.completed === true ? 'completed': ''}>{Item.TODO}</span>
        <button 
          className="delTODO"
          onClick={() => clickHandler(Item.id)}
          >X
        </button>
      </li>) : 
      // todo item이 없을 때 실행
      <div className="noTODO">
        <p>오늘의 할 일이 없습니다.</p>
      </div>



  return (
    <div className="TODO__container">
      <ol>
        {listItems}
      </ol>
    </div>
  )
}
export default TODOS

 

TODO 컴포넌트의 역할


  1. Todo item의 존재한다면 todo Items를 화면에 출력하고, 그렇지 않다면 대체 내용을 화면에 출력한다.
  2. X 버튼을 클릭하면 state와 localStorage에서 해당 아이템을 삭제한다.
  3. Item 내용을 클릭하면 Item.completed 상태를 바꾸고, completed의 true/false 여부에 따라 삭선을 긋는다.

 

TODO 컴포넌트는 Read 기능, Delete 기능, Update 기능을 구현한다  TODO 컴포넌트에서 감지하는 동작은 

  1.  삭제 버튼을 클릭
  2.  Item의 내용을 클릭

이 2가지이다. 그러면 각 동작이 시행되었을때, TODO 컴포넌트가 Read,Update,Delete 기능을 어떻게 구현하는지 순서에 따라 확인해보겠다.

 

 

1. 삭제 버튼을 클릭


0. props.getTodos()를 이용해 현재 관리하고 있는 상태를 TODOS라는 변수에 저장한다. 이후 , map 메서드를 통해 저장된 각 요소를 li 태그로 전환하고, key와 onClick 이벤트 핸들러를 부착한다.

 

1. 버튼을 클릭하면 onClick 이벤트 핸들러에 달린 익명 함수가 실행된다. 이 익명함수는 clickHandler 라는 함수를 호출하고, parameter로 Item.id를 전달한다

 

2. clickHandler는 props.delTodos를 실행하고, delTodos는 Container에서 정의된대로 filter 메서드를 활용해 해당 Item을 삭제하고 이를 state와 localStorage에 반영한다.

 

 

사실 이 기능은 잘못 구현 되었다. 왜냐하면, 굳이 clickHandler라는 함수가 중간에 존재할 필요가 없기 때문이다. 최초에 clickHandler를 작성할때는, onClick 이벤트에서 event와 Item.id 이 두 가지를 parameter로 받고 싶었는데 , onClick 핸들러는 parameter로 한가지 밖에 전달 할 수 없었기에, 람다를 사용해서 두개의 parameter를 전달한 것이다. 그런데, 디버깅을 하면서 event 객체가 필요없게 되었다. 하지만 나는 변경된 설계를 까맣게 잊고 계속 코딩을 했으며, 그 결과 이런 미생의 함수가 남아있게 된 것이다. 이런 실수를 블로그 글을 쓰면서 발견하다니.. ㅋㅋㅋ 진짜 별거 아닌 앱이라서 다행이다.

 

 

2. Item의 내용을 클릭


0. 랜더링 된 Todo Item 전체의 completed 속성을 확인하고, true 면 completed 클래스를 추가한다. compledted 클래스는 삭선을 추가하는 css가 적용되어있다.

 

1. 내용을 클릭하면 li 태그의 달린 onClick 핸들러에 딸린 checkTODO 함수를 호출한다. checkTODO는 parameter로 event와 Item을 받는데 이 arguments를 사용하여 completed 클래스를 토글하고, completed의 상태를 현재와 반대되는 값으로 바꾼다.

 

2. 이후 fetchlocalTODO 함수를 호출한다.  인자로는 클릭된 Item을 받는데, 이 인자를 활용하여  localStorage에 저장되어 있던 정보들을 꺼내 저장되어있던 Item의 completed 상태를 바꿔준 다음, 다시 localStorage에 저장한다.

 

 

 

 

Create나 Delete 기능은 localStorage에 때려넣거나 지우는 부분만 신경쓰면 되었는데, Update 기능은 현재 state와 싱크로를 맞춰야 했기에 fetchlocalTODO 라는 함수를 만들어 state의 속성이 바뀌었을때 같은 부분이 갱신되도록 하였다.

 

 

 

 

마치며


간단히 Todo list를 직접 만들어보며 React를 학습했다. 프로젝트를 마치며 느낀점은, React가 Vue 보다 개발자에게 더 많은 자유를 부여하는 것 같다.

 

Vue 같은 경우는 js를 통해 직접 기능을 구현하기 보다는 내부적으로 구현된 기능을 조합해서 사용하길 권장한다. 하지만 이게 개발자의 코딩스타일과 다를수도 있고, 이미 로직을 알고 있는 기능을 위해 Vue만의 방식을 새로 배워야하기에, 분명 누군가는 피곤한 단점이라고 생각 할 것이다 ( js에 익숙하지 않은 사람에게는 단점이 아니라 오히려 장점일 수도 있겠지만)

 

또, 가지고 놀기에도 React가 Vue보다 편하다. 이번 프로젝트를 진행하면서 데이터 흐름이 어떻게 되나 확인해보기 위해서 컴포넌트를 완전 떡 주무르듯 마음대로 다뤄봤는데, React는 단 하나의 불평도 없이 내 요청에 따라줬다. 뿐만 아니라 뭔가 문제가 있어 보이는 부분을 스스로 감지하여 콘솔 창에 관련된 공식문서 링크까지 띄워주는 친절함까지 보여줬다. 탬플릿에서 사용하지 않는 변수가 단 하나만 있어도 에러를 뿜으며 쓰러지는 Vue가 야속해보이기까지 했다.

 

하지만 html/css/js를 하나의 파일에서 관리할 수 있는 Vue는 정말 아찔할 정도로 매력적이다. Vue의 컴포넌트는 전통적인 방식으로 구현이 되기 떄문에, markup에 크게 신경쓸 필요가 없습니다. 일단 Markup만 제대로 되면 이후의 구현은 일사천리로 진행되는 경험에 비추어보았을때, 이건 엄청난 장점이라고 생각이 듭니다.

 

React의 참맛을 알기 위해서는 프로젝트를 몇개 더 진행해봐야 할 거 같다. 다음에도 간단한 앱을 한번 더 만들어보려고 하는데, 그때는 js 가 아니라 ts를 써보든가, react-redux 를 사용하든가 해서 좀 더 난이도 있는 목표를 달성해봐야겠다.