냅다 todo-list 만들어보기

nasagong·2023년 2월 15일
0

React

목록 보기
10/15
post-thumbnail

📚 들어가며

교재 10장에서 간단한 프로젝트로 일정관리 앱을 만든다. 책으로 보기 전에 엉성하더라도 지금까지 배운 내용들을 동원해서 프로토타입을 만들어 보고 싶었다. 아래는 그 과정이다..


🤦‍♂️ 실수들

1. 중첩된 <li> 태그

import "./Layout.css";
import { useState } from "react";
const Layout = () => {
  const [text, setText] = useState("");
  const [list, setList] = useState([]);
  const addList = () => {
    const tmp = list.concat(text).map((val,idx)=><li key={idx}>{val}</li>);
    setList(tmp);
  };
  const onChange = (e) => {
    setText(e.target.value);
  };
  const onClick = () => {
    addList(text);
    setText("");
  };
  return (
    <div className="wrapper">
      <div className="header">
        <input placeholder="Enter your task" value={text} onChange={onChange} />
        <button onClick={onClick}>submit</button>
      </div>
      <div className="main">
        <hr />
        <ul>{list}</ul>
      </div>
    </div>
  );
};
export default Layout;

일정 입력-출력 기능을 구현하려고 한 시도다.
교재를 따로 보지 않고 일단 우악스럽게 만들어봤다.
으악스러운 결과물이 나왔다. 리스트가 잘 나오나 싶더니 세 번째 값을 입력한 순간 뜬금 빈 리스트가 출력됐다.

뭐지..? 3번째에만 뭔가 버그가 걸리고 있나..? 왜 3번째에서 갑자기 이런 일이..?

지금에서야 바로 눈에 보이지만 처음엔 진짜 답답했다.
concant으로 새 배열을 만든 후
->그 배열에 map을 돌리고..

바로 map을 걸어버린 게 문제였다. 당연히 <li>태그가 중첩됐고.. 사실상 아래와 같은 코드를 입력한 셈이 됐다



2. 이벤트 처리

const foo = () =>{
      alert(100);
      return 100;
}

//1번
<button onClick={foo}>TEST</button>

//2번
<button onClick={foo()}>TEST</button>

두 버튼의 차이점은 뭘까?

1번 버튼을 사용한 경우 눌렀을 때 정상적으로 alert창이 나오지만 2번 버튼을 사용하면 버튼을 누를 때는 아무 반응이 없고, 뜬금없이 컴포넌트가 렌더링될 때 alert창이 나온다.

이것도 지금 생각하면 당연한거긴 한데.. 막상 아무것도 안 보고 작성하려니 헷갈렸다

1번 버튼의 작동 방식은 이렇다

버튼 클릭 -> foo함수 호출 -> alert창 출력
(리턴값은 어디에도 사용되지 않음)

반면 2번 버튼은..

JSX 렌더링 과정에서 버튼의 foo함수가 호출된 후 onClick이벤트에 값을 반환 (이 과정에서 alert창이 나타난다)
-> 버튼을 눌러도 함수가 호출되지 않는다.

따라서 파라미터가 필요한 이벤트 함수를 이벤트 발생 시에만 호출하고 싶다면 아래처럼 적어주는 게 맞다.

<button onClick={()=>foo(something)}>

🍩 초안

import "./Todo.css";
import { useState } from "react";
const Todo = () => {
  const [text, setText] = useState("");
  const [list, setList] = useState([]);
  const [id, setId] = useState(1);
  const addList = () => {
    const nextList = list.concat({ index: id, content: text });
    setList(nextList);
    setId(id + 1);
    setText("");
  };
  const onChange = (e) => {
    setText(e.target.value);
  };
  const clearAll = () => {
    setList([]);
  };
  const delList = (id) => {
    const nextList = list.filter((val) => val.index !== id);
    setList(nextList);
  };
  const onKeyPress = (e) => {
    if (e.key === "Enter") {
      addList();
    }
  };
  const newList = list.map((val, idx) => (
    <li className="list" key={idx}>
      {val.content}
      <button onClick={() => delList(val.index)} className="del">
        X
      </button>
    </li>
  ));
  return (
    <div className="wrapper">
      <div className="header">
        <input
          placeholder="Enter your task"
          value={text}
          onChange={onChange}
          onKeyPress={onKeyPress}
        />
        <button className="submit" onClick={addList}>
          Submit
        </button>
        <button onClick={clearAll}>Clear</button>
      </div>
      <div className="main">
        <hr />
        <ul>{newList}</ul>
      </div>
    </div>
  );
};
export default Todo;

class이름 등등 뭔가 잘못된 것 같은 느낌이 드는 요소들이 많지만 일단은 나름 동작은 잘 되는 결과물이 나왔다. 여기까지 만들면서 느낀 건 비교적 최근에 배운 지식도 막상 적용하려니 잘 생각이 나지 않는다는 것.. 개념이랑 사용법은 기억 나도 프로젝트에 이걸 접목시키겠다는 생각이 바로바로 들지 않는다.

일례로 hooks 챕터에서 배운 최적화를 위한 다양한 hook들이 내 코드에서 전혀 사용되지 않았다. useMemo..? useCallback..? 뭔가 좀 써봐야 하나..? 하는 생각은 들었지만 막상 쓰진 못했다..

그래도 뭐 이번 경험을 통해 나중엔 쓰게 되지 않을까? 그런 의미에서 이번엔 나름의 최적화를 시도해보고자 한다. 아래는 그 과정이다.

🚦 최적화

리렌더링될 때마다 불필요한 업데이트가 너무 자주 이루어진다는 생각이 들었다. newList 변수가 딱 그렇다. Hook을 사용해 횟수를 좀 줄여보자.

최종 코드

import "./Todo.css";
import { useState, useMemo, useCallback } from "react";
const Todo = () => {
  const [text, setText] = useState("");
  const [list, setList] = useState([]);
  const [id, setId] = useState(1);
  const addList = () => {
    const nextList = list.concat({ index: id, content: text });
    setList(nextList);
    setId(id + 1);
    setText("");
  };
  const onChange = (e) => {
    setText(e.target.value);
  };
  const clearAll = () => {
    setList([]);
  };
  const delList = useCallback((id) => {
    const nextList = list.filter((val) => val.index !== id);
    setList(nextList);
  },[list])
  const onKeyPress = (e) => {
    if (e.key === "Enter") {
      addList();
    }
  };
  const updatedList = useMemo(() => {
    const newList = list.map((val, idx) => (
      <li className="list" key={idx}>
        {val.content}
        <button onClick={() => delList(val.index)} className="del">
          X
        </button>
      </li>
    ));
    return newList;
  }, [list,delList]);
  return (
    <div className="wrapper">
      <div className="header">
        <input
          placeholder="Enter your task"
          value={text}
          onChange={onChange}
          onKeyPress={onKeyPress}
        />
        <button className="submit" onClick={addList}>
          Submit
        </button>
        <button onClick={clearAll}>Clear</button>
      </div>
      <div className="main">
        <hr />
        <ul>{updatedList}</ul>
      </div>
    </div>
  );
};
export default Todo;

useMemo, useCallback 이 사용되었다. 원래 계획은 list의 상태가 변경되었을 때만 useMemo가 반응해 newList를 업데이트하도록 만들 계획이었는데, useCallback까지 사용하게 되었다. 아래는 그 과정이다...

const updatedList = useMemo(() => {
    const newList = list.map((val, idx) => (
      <li className="list" key={idx}>
        {val.content}
        <button onClick={() => delList(val.index)} className="del">
          X
        </button>
      </li>
    ));
    return newList;
  }, [list]);

처음엔 useMemo를 위 코드처럼 사용했다. 컴파일 결과 아래와 같은 경고창이 떴다.

delList는 useMemo 내부에 포함돼있다. 따라서 delList 가 변할 때마다 ( 다시 생성될 때 ) useMemo도 이를 반영해야 한다. 즉 useMemo는 delList의 상태도 감시해야 한다. 따라서 dependency array에 delList를 추가해주자.

아직 끝나지 않았다. 이번엔 뭐가 문제일까? delList는 따로 조치를 취해두지 않았기 때문에 컴포넌트가 리렌더링될 때마다 다시 생성된다. useMemo도 이에 반응해 상태를 업데이트한다. 이러한 과정은 불필요하다. 따라서 delList를 useCallback으로 감싸고 의존성 배열에 list를 추가해줘야 하는 게 바람직하다.

이러한 과정을 통해

list의 상태 변화 - > delList 업데이트 -> useMemo 반응

이런 흐름이 완성된다. list의 상태 변화에만 주목하면 되는 환경이 완성된 거다.

profile
잘쫌해

0개의 댓글