본문 바로가기
React

2021.07.28 React To-do List

by 해맑은 코린이 2021. 7. 29.

 

2021.07.28 React To-do List_정리노트

 

 

오늘은 드디어 이론 공부만 줄줄하다가 프로젝트 한다니까 넘나 행복쓰 ㅎㅅㅎ

 

프로젝트 전체코드를 다 다루진 않고, 흐름 정리하며, 놓치기 쉬운 것들 위주로 블로그에 포스팅할 예정이다!

스타일적인 부분들은 다ㅏㅏ 빼고 기능적인 부분만 다룰 것임

 

 

 

먼저 프로젝트 생성

 

$ yarn create react-app todo-app

 

 

필요한 라이브러리도 설치해주었다.

 

// to-do app directory 이동

$ cd todo-app

// classnames >> 조건부 스타일링을 편하게 해줌.
// sass 사용 위해서 낮은 버전 ( 호환이 되는 ) 으로 지정해서 설치
// react-icons 는 리액트에서 다양하고 예쁜 아이콘을 제공하는 라이브러리
$ yarn add node-sass@4.14.1 classnames react-icons

 

 

react-icons 라이브러리 주소는 이거!!

 

https://react-icons.github.io/react-icons/icons?name=md 

 

React Icons

 

react-icons.github.io

 

쓰는 방법과 함께 여러가지 아이콘을 제공한다! 크으으

 

 

 

 

또, 프리티어 설정부터 해볼까나

Prettier

코드 포맷터. Format on Save 라는 VS code 설정을 한 사람이 있다면 , ( 저장과 동시에 코드 이쁘게 보여주는 기능 )

익숙한 느낌...? 이지 않을까 말그대로 코드의 형식을 정리하는 익스텐션이다. 

왜 필요한거지라고 물으신다면, 일단 첫번째로 혼자 프로젝트를 할 때도 자동으로 탭 간격이나, 세미콜론 등등을 자동으로 정리해주기 때문에 굉장히 편리하고 ( 자신만의 코드 작성 규칙이 없다면 ) 협업을 할 때에, 특히 서로의 OS 가 다른 경우 ( mac vs window ) 미리 서로 규칙을 맞춰놓은 다음 쓰게 되면 형태가 다름에서 오는 오류를 줄일 수 있음.

 

 

 

 

 

 

여기에 있는 익스텐션에서

 

 

 

 

 

이렇게 깔면 된다!

 

settings.json < .editorconfig < .prettierrc

파일 형태에서 적용되는 스타일의 강도는 이럼! 우리는 .prettierrc 이 파일을 최상위 디렉톨리에 만들어서 적용해봅씨단.

 

 

내가 쓴 옵션들은 이렇고, 다른 것들은 

https://uxgjs.tistory.com/150

 

VScode Code Formater 인 Prettier 완벽 적용하기

VSCode 익스텐션 중에 코드를 정렬해 주는 Formatter는 크게 Prettier과 Beautify가 있습니다. VScode에서는 2가지의 Formatter가 가장 많이 사용되는데 2가지는 약간 다른 특징이 있습니다. Prettier가 코드를 강

ux.stories.pe.kr

 

요분 블로그가면 친절하게 설명해주시고, 옵션값도 설명이 되어 있다!! 굿 ㅎㅁㅎ


 

 

컴포넌트는 총 4가지를 만들었고, 전부 src/components 디렉토리로 저장했다! src 에서 components 폴더 만들어서 안에다가 컴포넌트들 집어 넣으면 됨다 ㅎㅁㅎ 그리고 또한 각 컴포넌트 이름과 일치하게 scss 파일도 만들어 주었다. 

 


 

TodoTemplate

화면을 가운데 정렬시켜주고, 앱의 타이틀을 보여줌. children 으로 내부 JSX 를 props 로 가져와서 렌더링해줌.

다른 요소들을 감쌀 가장 큰 틀이라고 생각하면 될 듯.

 

src/components/TodoTemplate.js

 

 

실행화면

 

 



이렇게 우리가 만든 요소들을 감싸주는 역할!


 

TodoInsert

새로운 항목을 입력하고 추가할 수 있는 컴포넌트 .

state 를 통해 인풋 상태를 관리함.

 

 

src/components/TodoInsert.js

import React, { useCallback, useState } from 'react';
import { MdAdd } from 'react-icons/md';
import './TodoInsert.scss';

const TodoInsert = ({ onInsert }) => {
  const [value, setValue] = useState('');

  // 리렌더링 될 때 마다 함수를 새로 만들지 않고, 한번 함수를 만들고 재사용할 수 있도록 useCallback 사용
  const onChange = useCallback((e) => {
    setValue(e.target.value);
  }, []);
  // 이 함수가 호출되면, props 로 받아온 onInsert 함수에 현재 value 값을 파라미터로 넣어서 호출, value 의 값 초기화
  // 그렇다면 왜 onClick 말고 onSubmit 로 설정했을까? : 엔터를 눌러도 동작하기 때문. onClick 은 따로 이벤트 넣어주어야함.
  const onSubmit = useCallback(
    (e) => {
      onInsert(value);
      setValue(''); //submit 하고 나서는 input value 값 초기화
      // submit 이벤트는 브라우저에서 새로고침을 발생시켜서, 이를 방지하기 위해 해당 함수 호출.
      e.preventDefault();
    },
    // 함수 내부에서 state 값을 사용하고 있으므로, 어떤 값이 바뀔 때 함수를 새로 생성해야 한다. 지금 그 값들이 바로 onInsert,value
    
    [onInsert, value],
  );
  return (
    <form className="TodoInsert" onSubmit={onSubmit}>
      {/* 추가 인풋을 현재 value 를 받아와서 업데이트 해줌. */}
      <input
        placeholder="할 일을 입력하세요."
        value={value}
        onChange={onChange}
      />
      <button type="submit">
        <MdAdd />
      </button>
    </form>
  );
};

export default TodoInsert;

 

이 컴포넌트에서 정리할 점은, 

props 로 전달해야 할 함수를 만들 때는 useCallback Hooks 를 사용하여 함수를 감싸면, 성능을 아낄 수 있다.

onClick 을 사용하지 않고 , onSubmit 를 사용하는 이유는 엔터 또한 submit 에 포함 되기 때문이고, submit  할 때는 새로고침이 일어나기 때문에 preventDefault() 함수를 호출시켜주어야 한다.

 

 

 

여기서 또 인풋에 value 가 state 값으로 업데이트 되는 걸 잘 보고 싶다면? 콘솔로 찍어보는 방법도 있지만, 크롬 익스텐션을 이용해도 편리!

 

React Developer Tools

리액트 개발 팀이 만든, 리액트 개발자 툴.

 

 

해당 확장프로그램을 설치하면 React 관련 컴포넌트들을 볼 수 있고, 이렇게 해당 컴포넌트를 선택하고 인풋창에 ㅎㅇㅎㅇㅎㅇ 치면, state 값이 저렇게 ㅎㅇㅎㅇ 로 잘 업데이트 되는 것을 볼 수 있음.

 


 

 

 

TodoListItem

각 할 일 항목에 대한 정보를 보여주는 컴포넌트 .

todo 객체를 props 로 받아 와서 상태에 따라 다른 스타일의 UI 를 보여줌.

>> 해당 할일을 했으면 체크하고, 해당 할 일을 지우는 remove 버튼이 있고 각 버튼에 대한 UI 변동

 

src/components/TodoListItem.js

import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';

import cn from 'classnames';
import './TodoListItem.scss';

const TodoListItem = ({ todo, onRemove, onToggle }) => {
  const { id, text, checked } = todo;
  return (
    <div className="TodoListItem">
      {/* 조건부 스타일링 위해서 classnames 사용 */}
      {/* classnames 는 true 인 값만 classname 으로 적용시켜주므로, */}
      {/* checked 가 true면,  "checkbox checked" false면, "checkbox" 이렇게 적용됨. */}
      <div className={cn('checkbox', { checked })} onClick={()=> onToggle(id)}>
        {/* checked class 있으면 체크박스 엘리먼트 띄우고, 아니면 체크 안된 빈칸 띄우기 */}
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        {/* 데이터 전체를 객체비구조화 할당으로 가져왔으니까 text 를 쓰면 App 에 적은 객체에서 text 만 가져오기 가능 */}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => onRemove(id)}>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;

 

 

여기서 정리할 점은

style

react-icons 와 classnaems 로 조건에 맞는 icon을 띄울건데, classnames 로 true 인 class 만 적용을 간편하게 설정했고, checked 에 따른 아이콘을 삼항연산자로 불러와 주었다!

 

여기서 checked 의 부분 스타일은 scss에서 어떻게 설정할지나, 다른 scss 의 개념등이 쵸큼 나와서 해당 컴포넌트의 스타일 scss 파일만 첨부하게쑴.

 

 

src/components/TodoListItem.scss

 

.TodoListItem {
  padding: 1rem;
  display: flex;
  align-items: center;
  // 자식 요소 중에 짝수번째 자식요소 설정
  &:nth-child(even) {
    background: #f8f9fa;
  }

  .checkbox {
    cursor: pointer;
    flex: 1; // 차지할 수 있는 영역 모두 차지
    display: flex;
    align-items: center;
    svg {
      // 아이콘
      font-size: 1.5rem;
    }
    .text {
      margin-left: 0.5rem;
      flex: 1; // 차지할 수 있는 영역 모두 차지
    }
    // 체크되었을 때 스타일 설정
    &.checked {
      svg {
        color: #22b8cf;
      }
      .text {
        color: #adb5bd;
        // 글자 중간에 줄 그어짐
        text-decoration: line-through;
      }
    }
  }

  .remove {
    display: flex;
    align-items: center;
    font-size: 1.5rem;
    color: #ff6b6b;
    cursor: pointer;
    &:hover {
      color: #ff8787;
    }

    // 엘리먼트가 겹치는 ( 사이사이에 ) border-top 설정
    & + & {
      border-top: 1px solid #dee2e6;
    }
  }
}

 

크크 나머지는 css 속성이니까 다덜 화이팅 ㅎㅅㅎ ㅋㅋㅋㅋㅋㅋㅋ다음에 flex 에 대해서도 css 기회되면 포스팅해야지..! 나름 flex-froggy ( flex 속성 가르쳐주는 게임 ) 끝까지 깼다규 ~~~~ 물론 기초지만서도 ㅎ 쨋든 스타일 파일은 얘만 포스팅하규 설명은 주석으로!

 checked 클래스가 true가 되면 해당 스타일이 적용되게 했다!

 

 

function

객체 형태로 몽땅 가져온 데이터들을 비구조화 할당으로 가져와서 함수의 파라미터와 해당 값에 따른 버튼 UI, 화면에 보이는 텍스트 등등으로 요모조모 써주었음.

 


 

TodoList

배열을 props 로 받아오고, 이를 배열 내장 함수 map 을 사용해서 여러 개의 TodoListItem 컴포넌트로 변환해서 보여줌.

 

src/components/TodoList.js

import React from 'react';
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({ todos, onRemove, onToggle}) => {
  // App 에서 props 로 넘겨준 부분을 받아와서 TodoListItem 으로 변환하여 렌더링 해줄 것임
  return (
    <div className="TodoList">
      {/* map 을 사용하여 컴포넌트로 변환할 때는 key 값 필요 ( 고유의 id 값으로 설정해주었음. )*/}
      {/* todo 데이터는 통째로 props 로 TodoListItem 에 전달 => 객체 통째로 전달해주는게 나중에 여러 종류의 값을 전달해야 하는 경우에 최적화가 편함*/}
      {todos.map((todo) => (
        <TodoListItem todo={todo} key={todo.id} onRemove={onRemove} onToggle={onToggle}/>
      ))}
    </div>
  );
};

export default TodoList;

 

요 컴포넌트에서 다시 정리할 점들은,

map 을 사용하여 컴포넌트를 변환할 때는 key 값이 필요하며, key 값은 여기서 고유의 id 값으로 설정해서 넣어주었다.

여러 종류의 값을 전달해야하는 경우에는 객체를 통째로 전달해주는 것이 최적화에 편하다.

 

 

이렇게 TodoList 안에 TodoListItem 들이 잘 전달되고 있다!

 

 


 

App

추가할 일정 항목에 대한 상태들 관리

todos 라는 배열은 객체를 담고, useState 를 사용했다. 각 항목의 고유 id, 내용 (text), 완료 여부 ( checked ) 를 알려 주는 값이 포함 되어 있다. 

 

src/App.js

import React, { useCallback, useRef, useState } from 'react';
import TodoInsert from './components/TodoInsert';
import TodoTemplate from './components/TodoTemplate';
import TodoList from './components/TodoList';

// 나중에 추가할 일정 항목에 대한 상태를 관리
function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: '리액트 기초 알아보기',
      checked: true,
    },
    {
      id: 2,
      text: '컴포넌트 스타일링 해보기',
      checked: true,
    },
    {
      id: 3,
      text: 'Todo-List 만들어보기',
      checked: false,
    },
  ]);

  //  고윳값으로 사용될 id : useState 가 아니라 왜 useRef 로 만드냐?
  // id 값은 렌더링되는 정보가 아니기 때문에, 화면에 보이지도 않고, 이 값이 바뀐다고 해서 컴포넌트가 리렌더링될 필요가 없기 때문. 단순히 새로운 항목을 만들 때 참조되는 값일뿐임.
  const nextId = useRef(4);
  // props 로 전달해야 할 함수를 만들 때는 useCallback 사용하여 함수를 감싼다. ( 성능의 최적화를 위해 )
  const onInsert = useCallback(
    (text) => {
      const todo = {
        id: nextId.current,
        text,
        checked: false,
      };
      // 받아온 정보를 새로 todos 에 추가해서 갈아끼움.
      setTodos(todos.concat(todo));
      // 추가한 후, id 값을 +1 씩 증가시킴.
      nextId.current += 1;
    },
    [todos],
  );

  const onRemove = useCallback(
    (id) => {
      setTodos(todos.filter((todo) => todo.id !== id));
    },
    [todos],
  );

  const onToggle = useCallback(
    (id) => {
      // map 을 사용하여 특정 id 를 가지고 있는 객체의 checked 값을 !로 반전 시켜줌.
      // 근데 하나만 변환하는데 왜 map 씀 ? >> 이 코드는 todo.id 와 현재 파라미터의 id 값이 같을 때는 우리가 정해준 규칙대로 새로운 객체를 생성하지만, id 값이 다를 때는 변화를 주지 않고, 처음 받아왔던 상태 그대로 반환.
      // 그렇기 때문에 map 을 사용하여 만든 배열에서 변화가 필요한 원소만 업데이트 되고 나머지는 그대로 남아 있게 되는 것.
      setTodos(
        todos.map((todo) =>
          todo.id === id ? { ...todo, checked: !todo.checked } : todo,
        ),
      );
    },
    [todos],
  );
  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  );
}

export default App;

 

먼저 App 에 있는 기능들의 흐름을 쭈욱 정리하자면, todos 라는 데이터 객체가 담긴 배열을 만들고, 새로 추가될 항목의 id 를 useRef로 정해서 추가해서 새로 갈아끼우는 함수인 onInsert , 해당 id 값을 제외한 모든 값을 가진 새로운 배열을 반환해서 해당 id 값을 지우는 기능의 함수인 onRemove, ! 를 써서 해당 완료 여부인 checked 값을 반전시켜주는 ( true 이면 false로,  false 이면 true 로) onToggle 함수로 이루어져있다.

이 3개의 함수들은 각각 TodoInsert , TodoList 에 props 로 넘겨줄 함수들이기에, 성능의 최적화를 위해 useCallback 을 사용했다.

 

여기서 정리를 쭈욱 해봅씨단

 

 

먼저 주석에도 적혀있지만,

 

id 값은 렌더링 되는 정보가 아니라 단순히 새로운 항목을 만들 때 참조되는 값일뿐이기 때문에, 화면에 보이지도 않고 이 값이 바뀐다고 해서 컴포넌트가 리렌더링 될 필요가 없다. 이 경우  useState 대신 useRef 를 썼다.

 

props 로 함수를 전달해줄 때는, 렌더링을 최적화하기 위해 함수를 재사용할 수 있는 useCallback 의 사용을 습관화 한다.

 

!을 쓰면 해당 값을 반전 시킬 수 있다.

 

하나의 상태를 바꾸는데, map 을 쓰는 이유는 현재 파라미터로 받아오는 id 값 ( 클릭한 항목의 id ) 은 변화가 필요하기 때문에 업데이트하고, 나머지는 변화를 주지 않고 처음 받아왔던 상태를 그대로 반환한다.

 

 

 

filter 

기존의 배열은 그대로 둔 상태에서 특정 조건을 만족하는 원소들만 따로 추출하여 새로운 배열을 만드는 함수

 

const array = [1,2,3,4,5,6,7,8,9,10]
const biggerThanFive = array.filter(number => number >5 )

// 결과 : [6,7,8,9,10]

 

filter 함수에는 조건을 확인해주는 함수를 파라미터로 넣어야함. 그 함수는 true or false 값을 반환해야 하며, 여기서 true 를 반환하는 경우만 새로운 배열에 포함됨.

 

 

해당 filter 함수를 사용해서, 해당 조건에 만족하는 원소들, 즉 여기서는 해당 지우려는 id 값을 뺀 나머지의 객체들이 새 배열에 담겨서 반환하는 것! >> 결국 지우는 기능이 되는 것이다.

 


 

 

 

으으 지금까지 썼던 기능들을 거의 다 정리하면서 쓴 것 같다. 그러는 과정에서 props 로 함수를 넘겨주는 과정이 너무 헷갈렸는데 특히 이

 

 

 

 

 

 

onInsert 함수가 너무 헷갈렸다. 일단 기능들은 새로운 id 를 가진 객체를 업데이트 해주는 거는 이해했는데, props 를 넘겨주는 과정에서

너무 헷갈렸..... 역시 한국말은 아 다르고 어 다르다고 자식컴포넌트에서 props 를 파라미터로 해서 넘겨주면 부모에서 props 를 받아서 설정값을 정해주는 느낌으로 이해했는데 자식 컴포넌트에서 props 를 함수로 쓰고 다시 그걸 부모 컴포넌트에서 함수로 설정해주는 과정이 너무 헷갈..^^

 

 

출처 - 리액트 공식문서

 

props,state 관련 포스팅에도 추가했다만, 공식 문서에서 props 가 어떻게 렌더링 되는지 과정을 자세히 설명해놓은 곳을 잠시.. 다녀옴

 

ㅇㅎㅇㅎ 그러니까 부모 컴포넌트에서 설정값을 자식 컴포넌트에서 props 로 해서 반환하는거군.

책에서도 물론 같은 흐름과 같은 문맥상..? 이지만서도..^^ 나는 내가 잘못이해했나..? 싶을 정도로 그 미묘한 어감차이가 나를 엄청 헷갈리게 만들었던 것 같다..

 

그러면 이해가 된당.

 

 

그러니까

 

 

src/App

여기서 text 값을 파라미터로 넣어서, 새로 추가하는 항목인 todo에 text 값을 받아서 추가해주는 함수를 작성해주고,

 

해당 함수를 props 로 부모 컴포넌트인 App 에서 전달!

 

src/components/TodoInsert

 

해당 자식 컴포넌트에서 읏챠 하고 설정값 ( 함수 ) 을 받아서, 받아온 onInsert 함수에  value값을 파라미터로 넣어서 호출해줄텐데,

그 value 값은 해당 자식 컴포넌트의 onChange 이벤트에서 가져온 e.target.value 를 setValue 로 업데이트 해준 value 값이 되는 셈이다.....!!!!!!!!!!!!

 

이 함수는 언제 호출되느냐..?/

 

 

바로 form 을 제출하면 호출되는 것....!!!!

 

음음... 후 이제 조금 이해가 가는군 아이구..어렵다어려워

 

 

이게 하나 이해되고 나면,

 

src/components/TodoList

 

APP 에서 props 로 설정해준 함수들을 TodoList 에서 받아서 다시 props 로 넘겨주고, 

 

src/components/TodoListItem

 

 

다시 TodoList 의 자식 컴포넌트인 TodoListItem 에서 받아와서 클릭을 할 때 id 값을 파라미터로 받아와서 호출한 과정도 찬찬히 읽어보면 정리가 될 것..!

자식에서 다시 더 밑의 자식으로 또 들어가다니.. 후.. 굉장히 헷갈렸다.


오.. 오늘은 생각보다 혼자 두다다다 치면서 할만한데...? 포스팅할 내용 적겠다.. 라고 생각했지만 정말 말도 안되는 생각이었다.

역시 각 컴포넌트마다 기능 정리하니까 헷갈리는 점이 한둘도 아닌데 대체 무슨 자신감으로.....

역시 그냥 책을 보고 따라치는건 뇌가 제대로 생각하면서 치는게 아냐^^...

오늘도 블로그의 힘을 느끼며 정리끝 트트ㅡㄲ특!

'React' 카테고리의 다른 글

2021.08.03 리액트 라우터로 SPA 개발하기  (3) 2021.08.03
2021.07.30 React Todo-List 성능 최적화, immer  (0) 2021.07.30
2021.07.24 React styling  (2) 2021.07.24
2021.07.23 React Hooks  (0) 2021.07.23
2021.07.19 React Life Cycle  (2) 2021.07.21

댓글