React-todolist 리팩토링으로 이벤트 위임 배워보기

2023. 7. 30. 19:23Trip to React

리액트 파일에 작성하여 리액트 카테고리에 넣었지만 실상 js에 대한 내용이다.

import { useState } from "react";

function ToDoList2() {
  const [todoList, setTodoList] = useState<string[]>([]);
  const [todo, setTodo] = useState("");

  const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTodo(e.target.value);
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // 기본적인 것을 잊고 있었다. 새로고침을 없애고 form을 이용해서 엔터고칠수 있다. 
    setTodoList((prev) => [...prev, todo]);
    setTodo("");
  };

  const handleDelete = (index: number) => {
    setTodoList((prevState) => prevState.filter((todo, i) => i !== index));
  };

  return (
    <div>
      <h1>To do App</h1>
      <form action="" onClick={handleSubmit}>
        <input type="text" onChange={handleInput} value={todo} />
        <button>Add</button>

        <ul>
          {todoList.map((todo, index) => (
            <>
              <li key={index}>{todo}</li>
              <button onClick={() => handleDelete(index)}>❌Delete</button>
            </>
          ))}
        </ul>
      </form>
    </div>
  );
}

export default ToDoList2;

코드를 읽어보면 누구나 react로 투두리스트를  만들어보라고 했을 시 만들만한 포맷이다. 여기서 고치려는 문제는

todoList를 map하는 과정에서 모든 버튼에 이벤트가 달리는 부분이다. 이렇게 되면 브라우저에서 계속해서 이벤트를 관리해야하기 때문에 메모리 문제가 생긴다. 이런 부분은 이벤트 위임을 통해서 해결할 수 있다.

 

이벤트 위임은 이벤트가 발생할 요소의 상위 요소에 이벤트를 등록해 놓고 이벤트에 조건을 걸어 해당 부분에 동작하도록 설정한다. 

 

import { useState } from "react";

function ToDoList2() {
  const [todoList, setTodoList] = useState<string[]>([]);
  const [todo, setTodo] = useState("");

  const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTodo(e.target.value);
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // 기본적인 것을 잊고 있었다. 새로고침을 없애고 form을 이용해서 엔터고칠수 있다.
    setTodoList((prev) => [...prev, todo]);
    setTodo("");
  };

  const handleDelete = (e: any) => {
    e.preventDefault();
    console.log({
      target: e.target,
      currentTarget: e.currentTarget,
    });
    console.log(e.target);
    const $li = e.target.closest("li");
    const id = parseInt($li.dataset.id, 10);
    console.dir({
      id,
    });

    setTodoList((prevState) => prevState.filter((todo, i) => i !== id));
  };

  return (
    <div>
      <h1>To do App</h1>
      <form onSubmit={handleSubmit}>
        <input type="text" onChange={handleInput} value={todo} />
        <button>Add</button>

        <ul onClick={handleDelete}>
          {todoList.map((todo, index) => (
            <li key={index} data-id={index}>
              <p>{todo}</p>
              <button>❌Delete</button>
            </li>
          ))}
        </ul>
      </form>
    </div>
  );
}

export default ToDoList2;

위의 예시에는 기존 버튼에 있던 이벤트를 상위 요소인 ul로 옮겼고 이벤트 함수 내에서는 li의 data-id 값으로 조건을 주어 해당 조건을 만족하는 경우에 제거가 되도록 바꾸었다. 웹에서 몇몇 예시를 찾아보니 event.target을 활용하여 하는 경우들이 많았다.

 

그리고 추가적으로 여기서 몇가지 중요한 개념들이 나온다. 

- 먼저 event.target과 event.currentTarget에 대한 구분이다.

 

1. event.target

 

이벤트 타겟의 경우 이벤트가 일어나는 타겟을 리턴한다. 위의 경우 실질적으로 이벤트가 동작하는 것은 버튼이므로 로그를 찍을 경우 버튼을 내뱉게 된다.

 

* 여기서 버튼에 어떤 연결점도 없어 어떻게 이벤트를 동작시키는지 궁금했는데.이벤트 버블링 현상에 의해서 상위 노드를 찾아가다가 handledelete를 만나 동작시키게 된다. 실제로 이벤트 버블링을 멈추는 event.stopPropagation()를 사용하니 동작이 엉망이 되었다.

 

2. event.currentTarget

 

이벤트 커런트 타겟의 경우 실제 이벤트가 등록되어 있는 요소를 가리킨다. 위 경우에는 ul에 등록시켰기에 로그를 찍으면 ul를 내뱉는다.

 

- element.closest()의 활용이다.

 

closest()의 경우 해당 요소에서 상위로 타고 올라가면서 가장 가까운 요소를 내뱉는다. 여기서 중요한 포인트는 자기 자신을 포함한다는 점이다. 위의 경우에서는 버튼에서 가장 가까운 li는 버튼의 상위 요소 li가 되기에 그 데이터 아이디 값을 찾아 삭제 해주는 로직이다.