본문 바로가기
매일 해내는 개발/Develog

[Develog] 타입스크립트로 TodoList만들어보기

by 해야지 2023. 1. 19.
반응형

과제는 기존 프로젝트의 리팩토링이었지만

따로 수업을 듣고 있었던 터라 새로운 프로젝트로 개발하였다.

 

깃허브 링크

 

GitHub - jeLee94/type_prac

Contribute to jeLee94/type_prac development by creating an account on GitHub.

github.com

 

0. 현재까지 완성된 모습

2. 파일 구조

src
ㄴcomponents
    ㄴAddTodo.tsx
    ㄴTodoItems.tsx
    ㄴTodos.tsx
ㄴmodels
    ㄴtodo.ts
ㄴstyles
    ㄴGlobalStyle.ts
App.tsx
indes.tsx

 

3. 코드 해설

App.tsx

function App() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [doneTodos, setDoneTodos] = useState<Todo[]>([]);

//Todo 추가 기능
  const addTodoHandler = (todoText: string) => {
    const newTodo = new Todo(todoText);
    setTodos((prevTodos) => {
      return prevTodos.concat(newTodo);
    });
  };

//Todo 삭제 기능
  const onDeleteHandler = (todoId: string) => {
    if (window.confirm('정말 삭제하시겠습니까?')) {
      setTodos((prevTodos) => {
        return prevTodos.filter((todo) => todo.id !== todoId);
      });
      setDoneTodos((prevTodos) => {
        return prevTodos.filter((todo) => todo.id !== todoId);
      });
    }
  };

//Todo 완료 기능
  const onDoneHandler = (todoId: string) => {
    setTodos((prevTodos) => {
      return prevTodos.filter((todo) => todo.id !== todoId);
    });
    setDoneTodos((prevTodos) => {
      return prevTodos.concat(todos.filter((todo) => todo.id === todoId));
    });
  };
  console.log(doneTodos);



  return (
    <Div>
      <AddTodo onAddTodo={addTodoHandler} />
      <Todos
        todoItems={todos}
        doneItems={doneTodos}
        onDeleteHandler={onDeleteHandler}
        onDoneHandler={onDoneHandler}
      />
    </Div>
  );

이 부분에서 기존 자바스크립트 코드와 달라진 건 사실 함수 매개변수에 타입을 지정하는 것 밖에 없다.

 

AddTodo.tsx

const AddTodo: React.FC<{ onAddTodo: (text: string) => void }> = (props) => {
  const todoTextInputRef = useRef<HTMLInputElement>(null);

  const submitHandler = (event: React.FormEvent) => {
    event.preventDefault();

    const enteredText = todoTextInputRef.current!.value;
    if (enteredText?.trim().length === 0) {
      //throw an error
      alert('내용을 입력하세요');
      return;
    } else {
      props.onAddTodo(enteredText); //todo추가
      todoTextInputRef.current!.value = ''; //input창 초기화
      todoTextInputRef.current?.focus();
    }
  };
  return (
    <Form onSubmit={submitHandler}>
      <Label htmlFor='text'>Todo text</Label>
      <Input type='text' id='text' ref={todoTextInputRef} autoFocus />
      <Button>추가하기</Button>
    </Form>
  );
};

1) React.FC : React에서 타입스크립트를 사용하여 함수형 컴포넌트를 만들고 싶을 때 사용해야 한다. 하지만 요즘은 사용을 지양하는 추세라고 한다.

2) useRef<HTMLInputElement> : 어떤 타입의 ref가 올지 정해주지 않으면 타입스크립트가 어떤 타입이 올지 몰라서 never 타입이라고 정해버린다. 그러면 필요한 옵션들을 못쓰기 때문에 형식을 미리 지정해줘야 한다.
HTMLInputElement는 자바스크립트에서의 input 타입이다.

3) onAddTodo : (text: string) => void : AddTodo 함수를 사용하는 App.tsx에서 사용하는 매개 함수이다.
함수 형식임을 표현하기 위해 화살표 함수를 사용하였고 반환값이 없기 때문에 void, 매개변수는 string타입을 받아올 것이라고 알려준다.

4) React.FormEvent : 폼에서 일어나는 이벤트를 받기 위해서는 이와 같이 지정한다. 여기에 React.MouseEvent로 지정하면 타입이 맞지 않기 때문에 오류가 발생한다.

 

Todos.tsx

const Todos: React.FC<{
  todoItems: Todo[];
  doneItems: Todo[];
  onDeleteHandler: (todoId: string) => void;
  onDoneHandler: (todoId: string) => void;
}> = (props) => {
  return (
    <Div>
      <TodoDiv>
        <p>To Do</p>
        {props.todoItems.map((item) => (
          <TodoItem
            key={item.id}
            text={item.text}
            onRemoveItem={props.onDeleteHandler.bind(null, item.id)}
            onDoneItem={props.onDoneHandler.bind(null, item.id)}
          />
        ))}
      </TodoDiv>
      <hr />
      <DoneDiv>
        <p>Done</p>

        {props.doneItems.map((item) => (
          <TodoItem
            key={item.id}
            text={item.text}
            onRemoveItem={props.onDeleteHandler.bind(null, item.id)}
            onDoneItem={props.onDoneHandler.bind(null, item.id)}
          />
        ))}
      </DoneDiv>
    </Div>
  );
};

1) 매개변수: Todos를 사용하는 App.tsx에서 전달해주는 변수 및 함수들

2) Todo와 Done을 분리하여 구성

3) onRemoveItem : bind함수 사용 -> 아래 해설 자세히..

 

TodoItems.tsx

const TodoItem: React.FC<{
  text: string;
  onRemoveItem: (event: React.MouseEvent) => void;
  onDoneItem: (event: React.MouseEvent) => void;
}> = (props) => {
  return (
    <Div>
      <p>{props.text}</p>
      <p>
        <button onClick={props.onDoneItem}>완료</button>
        <button onClick={props.onRemoveItem}>삭제</button>
      </p>
    </Div>
  );
};
export default TodoItem;

 

 

삭제 기능, 완료 기능에서 bind함수를 사용한 이유

0. bind 함수란

1. TodoItem에서 삭제, 완료 기능을 사용하기 위해서는 Todos.tsx를 거쳐 App.tsx까지 가야한다.

2. TodoItem에서는 Todos.tsx에서 MouseEvent 타입의 매개변수를 받는 함수를 사용한다고 했는데 
Todos.tsx에서는 App.tsx에서 string타입의 매개변수를 받는 함수를 사용한다고 되어있다.

TodoItem.tsx
Todos.tsx

따라서 bind함수를 제거하게 되면 다음과 같은 오류가 발생하게 된다.

bind함수에 대한 개념은 아직 이해중..

지금은 이 매개변수가 다른 두 함수를 연결 하기 위해 사용했다고 이해했다. 

Todos.tsx bind함수를 뺄 경우

 

 

반응형

댓글