본문 바로가기

Computer Programming/[리액트를 다루는 기술]

[리액트를 다루는 기술] 10. Todo List 만들기

 리액트를 다루는 기술 - 김민준

 

책을 따라만 하는것에 그치고 싶지 않아서 최대한 내가 먼저 생각해서 만들어 본 후, 책의 코드와 비교했다. 그리고 새로 알게된 점은 주석도 달고 여기에 메모해서 기억해두려고 한다. 

 

 

디렉토리는 components와 styles 크게 두가지로 나눴고 css는 scss를 사용했다. npx create-react-app 으로 리액트 앱을 만든 후 App.js에 TodoTemplate.js 를 렌더링 한 후, 나머지 컴포넌트들은 이 컴포넌트의 props로 전달함으로서 렌더링했다.

 

 

1. classnames

classnames 라이브러리

- 동적으로 css 클래스를 조작해야할 때 간결하게 작성할 수 있음. 조건부로 클래스 이름을 동적으로 생성해서 복잡한 조건에 따라서도 해결할 수 있음. 

- styled-components 와 함께 많이 사용함  

 

const TodoListItem = ({todo}) => {
    const { text, checked } = todo

    return (
        <div className="TodoListItem">
            <div className= {cn('checkbox', {checked})} > 
            {/* //checkbox 클래스인데 checked 일 경우 동적으로 추가 */}
                {!checked? <MdCheckBoxOutlineBlank /> : <MdCheckBox />}
                <div className='text'>{text}</div>
            </div>
            <div className='remove'>
                <MdRemoveCircleOutline />
            </div>
        </div>
    )
}

export default TodoListItem;

 

.scss

 

  .checkbox {
    display: flex;
    align-items: center;
    flex: 1;
    cursor: pointer;
    svg {
      //아이콘
      font-size: 1.5rem;
    }
    .text {
      margin-left: 0.5rem;
      flex: 1;
    }
    //checked 가 true 일 때 보여줄 스타일
    &.checked {
      svg {
        color: hsl(188, 56%, 61%);
      }
      .text {
        color: #adb5bd;
        text-decoration: line-through;
      }
    }

 

 

 

2. useRef

TodoInsert에서 생성된 객체를 가지고 TodoList를 새로 업데이트해줘야한다. 이 때, input text는 useState으로 상태관리를 하지만 id 같은 경우는 화면에 렌더링 될 필요가 없으며 값이 바뀐다고 해서 새로 렌더링이 필요한것도 아니다. 따라서 useRef 를 사용하여 단순히 참조만 해도 된다.

 

App.js

  const nextId = useRef(5)

  const onInsert = useCallback((text) => {
    const nextTodo = {
      id: nextId.current,
      text,
      checked: false,
    }
    setTodos(todos.concat(nextTodo))
    nextId.current += 1
    console.log(todos)
  },[todos])

파라미터를 통해 입력이 들어오면 id는 ref의 current로 설정, 입력이 끝나면 +1을 해준다.

text: text는 줄여서 text,로 표기하고 checked 는 default로 false로 해준다. 불변성 유지를 위해 concat 함수를 사용해주고 state를 변경해준다. todos가 변경될 때 (새로운 투두가 들어오거나 기존 것이 삭제될 때)에만 함수를 다시 생성해주기 위해 useCallback을 사용했다.

 

 

 

3. React developer tools

프로젝트를 시작할 때 컴포넌트를 어떻게 나누어야할지 고민이 많이 되는데 이 개발 툴을 쓰면 개발자도구에서 마치 html 태그들 처럼 리액트 컴포넌트들이 어떻게 나뉘어서 어디에 중첩되어있는지 등을 볼 수 있다!!

 

이 책을 읽으면서 오늘의집 클론코딩도 해보고 싶었는데 컴포넌트 나누는 연습할 때 사용하면 좋을 것 같다 

 

https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi/related

 

React Developer Tools

Adds React debugging tools to the Chrome Developer Tools. Created from revision 2468a8735 on 5/19/2023.

chrome.google.com

 

 

 

오른쪽 패널에 state 도 볼 수 있다. 만들어져있는 개발 환경은 매우매우 좋은데.. 잘 활용할 수 있도록 공부를 열심히 해야겠다 

 

 

4. 불변성 유지 제거 filter()

배열의 불변성을 지키면서 원소를 제거하는 경우 내장 함수인 filter을 이용하면 좋다. 

 

App.js에서 만든 onRemove 함수를 TodoList 를 거쳐 props로 전달해주고 TodoListItem에서 건네받아 id를 넘겨준다.

 

 

const TodoListItem = ({todo, onRemove}) => {
    const { id, text, checked } = todo

    return (
        <div className="TodoListItem">
            <div className= {cn('checkbox', {checked})} >
            {/* //checkbox 클래스인데 checked 일 경우 동적으로 추가 */}
                {!checked? <MdCheckBoxOutlineBlank /> : <MdCheckBox />}
                <div className='text'>{text}</div>
            </div>
            <div className='remove' onClick={() => onRemove(id)}>
                <MdRemoveCircleOutline />
            </div>
        </div>
    )
}

 

 

5. 불변성 유지 수정 map()

 

  const onToggle = useCallback((id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? {...todo, checked: !todo.checked } : todo
    ))
  }, [todos])

수정하기 위해 가져온 id 값을 map 함수를 통해 같은 id를 가진 해당 객체를 찾아낸다. 아이디가 같다면 ...todo로 복사해주고 checked를 toggle 시킨다. 만약 다르다면 수정할 필요가 없으므로 todo를 그대로 반환한다. 이렇게 map 함수로 만들어진 새로운 todo list를 setTodos에 전달해 todos 데이터에 변화를 준다.

 

 

 

전달한 remove 와 toggle 역할 함수를 각각 onClick 에 콜백함수로 전달해서 구현했다.

 

const TodoListItem = ({todo, onRemove, onToggle}) => {
    const { id, text, checked } = todo

    return (
        <div className="TodoListItem">
            <div className= {cn('checkbox', {checked})} onClick={() => onToggle(id)}>
            {/* //checkbox 클래스인데 checked 일 경우 동적으로 추가 */}
                {!checked? <MdCheckBoxOutlineBlank /> : <MdCheckBox />}
                <div className='text'>{text}</div>
            </div>
            <div className='remove' onClick={() => onRemove(id)}>
                <MdRemoveCircleOutline />
            </div>
        </div>
    )
}