12월 셋째 주 TWIL : React로 To-do List 만들기

윤슬기·2019년 12월 22일
1

TWIL

목록 보기
22/32
post-thumbnail

Immersive Course

Immersive Course(이하 IM)는 프로그래밍 교육기관 코드 스테이츠의 웹 개발 심화 코스이다. 아래 내용은 IM에서 배우고, 내가 찾아보고, 다른 수강생들이 전해 준 지식이다.


To-do List with React

이번 주에는 리액트를 이용해 To-do list를 만들었다. 이전에는 컴포넌트 뼈대가 미리 제공된 상태에서 시작했지만, 이번에는 create-react-app을 베이스로 처음부터 만들었다. 깃허브 레포지토리는 여기이다. 구현하려는 기능은 다음과 같았다.

  • 카테고리와 할 일 목록을 만들고 수정하고 지울 수 있다.
  • 할 일은 완료하거나 미완료 상태로 만들 수 있다.
  • 할 일을 검색할 수 있다.

이를 토대로 우선 데이터 흐름과 컴포넌트 레이아웃을 짰다. 이전에 간단한 리액트 코드를 작성해보면서 사전 설계가 중요하다고 느꼈기에, 나름대로 꼼꼼히 4시간 정도 공을 들였다.
00-스크린샷 2019-12-17 오후 4.27.53.png00-스크린샷 2019-12-17 오후 4.28.05.png

완성된 모습

그리고 완성한 투두 리스트는 다음과 같다. 코드 작성은 22시간 정도 걸렸다.
20191222202217.gif→ 할 일 목록을 생성하고 지우기, 완료 혹은 미완료 상태로 만들기
20191222164153.gif→ 할 일 목록과 카테고리 내용을 수정하기
20191222164214.gif→ 할 일 목록을 검색하기, 검색 결과에서 할 일 목록을 클릭하면 해당 카테고리로 이동하기

느낀 점

1. 더 단단히 설계하자

크게 보자면 구현하려던 기능은 모두 구현했다. 하지만 기능들이 동작하는 세부적인 데이터 흐름이 아름답지 않고, 일부 설계 단계에 고려하지 못한 부분들은 구현할 수 없었다. 코드를 실제로 작성해보니 설계 단계에서 훨씬 더 세부적인 사항들을 결정했어야 했음을 알았다. 예를 들자면,

  1. 카테고리와 할 일 목록을 만들고 수정하고 지울 수 있다.
  • 데이터는 localStorage에 저장하는가?
  • 각 기능은 어떤 이벤트가 있을 때 일어나는가?
  • 기능이 동작한 후에는 화면에 무엇이 랜더 되는가?
  • 카테고리를 만들었을 때, 바로 오늘의 할 일을 입력할 수 있도록 할 일 입력창에 포커스가 옮겨지도록 할 것인가?
  1. 할 일은 완료하거나 미완료 상태로 만들 수 있다.
  • 할 일을 '완료' 상태로 만들었을 때 완료된 목록은 어디에 어떻게 표시되는가?
  • 혹은 표시되지 않고 삭제되는가? 완료 되었을 때도 내용 수정이 가능한가?
  1. 할 일을 검색할 수 있다.
  • 검색된 목록에서 내용 삭제 및 수정이 가능한가?
  • 카테고리 명이 함께 표시되는가?
  • 클릭 시 해당 카테고리로 이동하는가?

이렇게 내가 처음에 고려했던 것보다 훨씬 섬세하고 면밀한 데이터의 흐름, 처리 방법, 그에 따른 컴포넌트 설계가 필요하다고 느꼈다. 하지만 이 역시 여러 번 설계하고 실제로 코드를 짜 보지 않으면 생각해내기 어렵다고 본다. 실습만이 살길이다!

2. 공식 문서를 제대로 보자

더불어 설계나 코드를 짜기 전에 리액트 공식문서를 집중해서 읽었음에도, 가장 기본적인 부분을 지키지 않고 있었다. 공식 문서에는 어떤 데이터를 state 혹은 props로 결정할지 도움을 주는 가이드가 있었다.

[ React로 사고하기 | React ]
...제품의 원본 목록은 props를 통해 전달되므로 state가 아닙니다. 검색어와 체크박스는 state로 볼 수 있는데 시간이 지남에 따라 변하기도 하면서 다른 것들로부터 계산될 수 없기 때문입니다. 그리고 마지막으로 필터링된 목록은 state가 아닙니다. 제품의 원본 목록과 검색어, 체크박스의 값을 조합해서 계산해낼 수 있기 때문입니다.

하지만 나는 완료 목록, 현재 카테고리 목록 등을 모두 따로 state로 만들어서 컴포넌트에 전달했다. 아래는 내가 App 컴포넌트에 설정한 state이다.

this.state = {
  todoList: [],
  currentCategoryTodoList: [],
  currentCategory: { id: 1, name: '미분류' },
  completedTodoList: [],
  searchResult: [],
  isSearching: false,
};

리액트는 공식 문서가 굉장히 친절하고, 정보를 찾아보기도 편하다. 설계 시 공식 문서를 체크하면서 중요한 부분들을 한 번 더 짚어야겠다고 생각했다.

3. 앞으로 나아가기

이번 프로젝트를 마치고 더 공부하려는 부분은 다음과 같다.

  1. 확장성을 염두에 둔 구조와 데이터 형식을 만드는 방법
  2. 생명주기 메서드를 활용하는 방법

아쉬운 부분은 많지만, 처음부터 끝까지 혼자 작업하고, 어느 정도 계획한 기능을 구현해서 기쁘다. 무엇이든 아는 만큼 눈에 보이고 궁금한 지점이 늘어나게 되니 꾸준히 프로젝트를 진행하고, 좋은 레퍼런스 코드를 찾아 공부해야겠다.

배운 점

1. Ref

[ How to focus element in React | Tuomo Kankaanpää ]
[ React.createRef | React ]
[ Ref와 DOM | React ]
...Ref의 바람직한 사용 사례는 다음과 같습니다.
포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때. 애니메이션을 직접적으로 실행시킬 때. 서드 파티 DOM 라이브러리를 React와 같이 사용할 때.

버튼을 누르면 특정 input 엘리먼트에 커서가 위치하도록 하려고 방법을 찾아보다가 알게 되었다. Ref는 직접적으로 인스턴스나 DOM 엘리먼트를 수정해야 하는 경우 사용된다.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.inputRef = React.createRef(); 
    // React.createRef() 이용하여 ref를 만든다
  }

  render() {
    return <input type="text" ref={this.inputRef} />;
  } // 원하는 엘리먼트에 ref가 담긴 변수 inputRef를 지정한다.

  componentDidMount() {
    this.inputRef.current.focus();
    // .current로 ref로 지정된 엘리먼트에 접근하여 원하는 동작을 실행시킨다.
  }
}

2. input value를 state로 다루기

[ 제어 컴포넌트 (Controlled Component) | React ]
...value 어트리뷰트는 폼 엘리먼트에 설정되므로 표시되는 값은 항상 this.state.value가 되고 React state는 신뢰 가능한 단일 출처 (single source of truth)가 됩니다.

input, select 등 사용자의 정보 선택이나 입력을 받는 경우, 사용자의 입력값을 반드시 그 컴포넌트의 state에 저장했다가 저장된 내용을 해당 엘리먼트의 value로 설정하여 사용자에게 보여주어야 한다. 사용자의 입력이 정말 정확하게 시스템에 입력되었는지를 확인해야 하기 때문이다. 더불어 사용자 입력을 수정하거나 유효성을 검사하는 것이 간단해진다.

이 과정은 매우 빠르게 처리되므로 사용자는 자신이 입력한 값과 state로 설정되었다가 다시 엘리먼트의 value로 설정되어 화면에 렌더되는 값 사이의 차이를 느낄 수 없다.

class Input extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '',
    };
    // 사용자가 입력한 값이 할당될 state
  }

  handleChange(e) {
    this.setState({
      value: e.target.value,
    });
    // input의 값이 바뀔 때마다 그 값을 state의 value에 할당한다.
  }

  render() {
    return (
      <input
      type="text"
      value={this.state.value}
      onChange={this.handleChange.bind(this)}
      />
    );
  }
}
profile
👩🏻‍💻

0개의 댓글