본문 바로가기

프로젝트 회고/2) project

Todo List- TS

 

간단한 Todo List, 처음 TS를 접하는 사람들에게는 간단하지 않다...!

(React, Typescript, JsonSever를 이용했습니다.) 

 

 

 

1. TodoForm - Todo의 할 일을 추가하는 헤더부분

 

 

1) 헤더 입력창 텍스트

: useState를 통해 입력창이 비어있는 지 확인할 수 있습니다. 비어 있지 않다면 handleSubmit 함수를 통해 onAdd를 호출합니다.

  preventDefalut()는 꽤 자주 사용하는 함수로, 바로 제출되는 이벤트를 막습니다. 모달창에서 모달안의 영역을 클릭했을 경우에도
  모달이 닫히는 걸 방지할 때 유용히 사용됩니다.

//TodoFormProps 인터페이스정의 : 입력받을 값은 문자열임을 알려줌
export interface TodoFormProps {
  onAdd: (text: string) => void;
}

//TodoForm은 리액트 함수 컴포넌트,TodoFormProps 타입의 onAdd 를 인자로 받아서 사용할 것
// text의 디폴트는 빈문자이고, handleSubmit을 이용해 이벤트 객체를 받아
//preventDefalut()로 바로제출되는 이벤트를 막고
//입력된 text가 비어있지 않다면 onAdd 호출하고 text를 초기화시킨다

const TodoForm: React.FC<TodoFormProps> = ({ onAdd }) => {
  const [text, setText] = useState("");

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!text) {
      return;
    }
    onAdd(text);
    setText("");
  };

 

 

2) 렌더링 될 부분 (실제 화면에 보이는 부분)

  // input과button(헤더부분) 요소를 렌더링 onChange 이벤트 핸들러를사용하여 입력값이 변할 때 text 상태 업데이트
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
      <button className="button">Add</button>
    </form>
  );
};

// 다른곳에서 사용 가능하도록 내보내기
export default TodoForm;

 

 

 

 

2. TodoList - 항목 리스트 만들기

 

1) 항목 인터페이스

: 타입스크립트에서 인터페이스는 정말 자주 사용합니다. 
  타입을 지정해주기 위해 사용하는 데, 확장성이 좋고 즉 재사용성이 높아 많이 사용하는 방법입니다.
   한 번 지정해주면 어디서든 갖다쓸 수 있어 사용하기 편리합니다.

// Todo 항목 인터페이스 (id,text, done의 속성을 갖음)
export interface Todo {
  id: number;
  text: string;
  done: boolean;
}

//Props 인터페이스, 함수 속성을 포함
export interface TodoListProps {
  todos: Todo[];
  onDeleteTodo: (id: number) => void; // 항목 삭제
  onToggleDone: (id: number) => void; // 항목의 상태 토글
}

 

 

 

2) 첫 항목 세팅

: useEffect를 사용하여, 첫 렌더링 됐을 경우 이미 저장된 항목이 있다면 항목을 불러와서 세팅할 수 있도록 합니다.
  타입을 지정해 주어야 하기 때문에 제네릭을 사용하여 Todo 객체의 배열이라는 것을 명시했습니다.
  제네릭이란 useState<Todo[]>로 표시한 부분처럼 <> 안에 해당 타입을 명시하는 형태 입니다. 이를 사용하는 이유는 
  정확한 타입을 명시하므로서 타입의 안정성을 높이고, 한 번 지정해 준 타입이므로 후에 useState<T> 로 해당 타입을
  재사용할 수 있기 때문입니다. 

  → useEffect로 서버에서 정보를 받아와 이를 setTodo로 상태 변경 해주어 저장된 todo를 확인합니다.

const TodoList: React.FunctionComponent<TodoListProps> = ({
  onToggleDone,
  onDeleteTodo,
}) => {
  const [todos, setTodos] = useState<Todo[]>([]); // 배열 형태의 Todo 객체를 가진 배열을 사용

  // useEffect를 사용하여 컴포넌트가 처음 렌더링 될 때, 서버에서 todo를 받아와 상태로 설정
  useEffect(() => {
    fetch("http://localhost:4000/todos")
      .then((response) => response.json())
      .then((data) => setTodos(data));
  }, []);

 

 

3) 항목 추가

//TodoForm 컴포넌트에서 입력된 값을 서버에 POST 요청으로 보내고, 서버에서 응답받은 데이터를 상태에 추가
  const handleAddTodo = (text: string) => {
    const newTodo: Todo = {
      id: todos.length + 1,//id 는 고유한 값이어야하므로 길이에 +1 한 값을 주어 각각이 고유한 값을 갖도록 함
      text,
      done: false,
    };
    fetch("http://localhost:4000/todos", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(newTodo), // 새로운 항목을 JSON형식으로 변환하여 바디로 보냄
    })
      .then((response) => response.json()) //응답을 받았다면, JSON 데이터를 파싱
      .then((data) => setTodos([...todos, data])); //todo 항목 배열에 추가
  };

 

4) 항목의 완료, 미완료 상태 

// 항목의 완료,미완료 상태를 보여주는 함수, 인자로 id가 들어가고 이는 number타입임
  const handleTodoToggle = (id: number) => {
 
    const updatedTodos = todos.map((todo) => {
      if (todo.id === id) {     //todo 배열에서 해당 id의 todo를 찾기
        return { ...todo, done: !todo.done }; 
      }
      return todo; //done의 반대로 설정한 후
    });
    setTodos(updatedTodos); // 업데이트 된 todo배열을 setTodo 상태로 저장
    fetch(`http://localhost:4000/todos/${id}`, { //업데이트 할 항목의 url
      method: "PATCH",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ done: !todos.find((todo) => todo.id === id)?.done }), // 업데이트 할 값을 JSON 형식으로 보내기
      // 이전 상태에 따라서 값을 바꾸기 위해서 현재 todo의 done값이 아닌,이전에 저장된 todo의 done값을 반대로 설정하는 것
    });
  };

 

5) 항목삭제

 

  // 항목을 삭제하기 위한 함수 id: number 타입을 인자로 받음
  const handleDeleteTodo = (id: number) => {
  
    //filter를 통해 id에 해당하는 항복을 제거시킴
    const filteredTodos = todos.filter((todo) => todo.id !== id);
    
    setTodos(filteredTodos); //항목 업데이트
    
    //해당 url을 fetch하여 삭제시킴
    fetch(`http://localhost:4000/todos/${id}`, {
      method: "DELETE",
    });
  };

export default TodoList;

 

 

 

3. App.tsx - 합쳐서 렌더링!

 

import React, { useState } from 'react';
import TodoList, { Todo } from './components/TodoList';

//Todo 상태관리
export const App: React.FC = () => {
return (
    <div>
      <TodoList todos={todos} onToggleDone={handleTodoToggle} onDeleteTodo={handleDeleteTodo} />
    </div>
  );
};

 

 

4. Json Server  (가짜 API 이용하기!)

 

 

 

1)JSON Server 설치

npm install -g json-server

 

2) 서버에서 사용할 파일 생성

db.json 

객체 형태로 작성해 주어야 한다

{
  "todos": [
    {
      "id": 1,
      "text": "아침먹기",
      "done": false
    },
    {
      "id": 2,
      "text": "점심먹기",
      "done": true
    },
    {
      "id": 3,
      "text": "저녁먹기",
      "done": false
    }
  ]
}

 

3)JSON Server 시작

json-server --watch db.json --port 4000

 

 

 

5. CSS

#root {
    height: 100vh;
}

.construct{
    display:flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 80vh;
    position: relative;
}

.header{
    position: fixed; /*위치 상단 고정*/
    top : 0px;
}

.list{
    position: fixed;
    height: 50vh;
    overflow-y: scroll; /*영역 벗어나면 스크롤*/
    margin-top: 20vh;
    
}

button {
    background-color: #4CAF50; 
    border: none; 
    color: white; 
    padding: 4px 9px; 
    text-align: center; 
    text-decoration: none; 
    display: inline-block; 
    font-size: 13px; 
    margin: 4px 2px; 
    cursor: pointer; /* 커서 스타일 변경 */
    border-radius: 4px; 
  }
  
  button:hover {
    opacity: 0.8; /* 마우스 오버 효과 */
  }