typescript 를 이용한 Todo List

Hyor·2022년 12월 2일
0
post-custom-banner

typescript와 react 를 활용하여 todo list를 만들어보겠습니다.

프로젝트 구조

CRA

create react-app 을 통해 typescript react app을 생성하고 필요한 라이브러리를 다운받습니다.

yarn create react-app --typescript
yarn add styled-components uuid

types/index.ts

export interface TodoInterface {
  id: string;
  title: string;
  checked: boolean;
}

App.tsx

import React, { useState } from 'react';
import styled from 'styled-components';
import CreateTodo from './components/CreateTodo';
import Todo from './components/Todo';
import { TodoInterface } from './types';

const dumydata = [
  { id: '1', title: 'test1', checked: false },
  { id: '2', title: 'test2', checked: true },
  { id: '3', title: 'test3', checked: false },
];

function App() {
  const [todos, setTodos] = useState(dumydata);

  const create = (newTodo: TodoInterface) => {
    const newTodoList: Array<TodoInterface> = [...todos, newTodo];
    setTodos(newTodoList);
  };
  const handleClickDelete = (id: string) => {
    const newTodoList: Array<TodoInterface> = todos.filter((todo) => todo.id !== id);
    setTodos(newTodoList);
  };
  const handleChangeChecked = (id: string, checked: boolean) => {
    const newTodoList: Array<TodoInterface> = todos.map((todo) =>
      todo.id === id ? { ...todo, checked } : todo,
    );
    setTodos(newTodoList);
  };

  const sortedList = todos.sort((a: TodoInterface, b: TodoInterface) => (a.checked > b.checked ? 1 : -1));

  return (
    <StyledApp>
      <CreateTodo create={create} />
      <div className='todo-area'>
        <h1>Todo List</h1>
        <ul>
          {sortedList.map((todo: TodoInterface) => (
            <Todo
              key={todo.id}
              todo={todo}
              onChangedChecked={handleChangeChecked}
              onClickDelete={handleClickDelete}
            />
          ))}
        </ul>
      </div>
    </StyledApp>
  );
}

const StyledApp = styled.div`
  width: 100%;
  height: 100%;
  margin: 0 auto;
  padding: 0;
  background-color: #f5f5f5;
  font-size: 14px;
  color: #194e84;
  h3 {
    font-size: 14px;
    margin-top: 0;
  }
  ul {
    list-style: none;
    padding-inline-start: 0;
  }
  .todo-area {
    height: 100%;
    margin: 0 auto;
    padding: 20px;
  }
`;

export default App;

CreateTodo.tsx

import React, { useState } from 'react';
import styled from 'styled-components';
import { v4 as uuidv4 } from 'uuid';
import { TodoInterface } from '../types';
interface CreateTodoInterface {
  create: (todo: TodoInterface) => void;
}

const CreateTodo: React.FC<CreateTodoInterface> = ({ create }) => {
  const defaultTodo = {
    id: uuidv4(),
    title: '',
    checked: false,
  };

  const [newTodo, setNewTodo] = useState(defaultTodo);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setNewTodo({ ...newTodo, [e.target.name]: e.target.value });
  };

  const handleSubmit = (e: { preventDefault: () => void }) => {
    e.preventDefault();
    create(newTodo);
    setNewTodo(defaultTodo);
  };

  return (
    <StyledForm onSubmit={handleSubmit} className='todo-area'>
      <h1>할일 등록</h1>
      <div className='todo-add-box'>
        <label htmlFor='title'>Title</label>
        <input
          value={newTodo.title}
          onChange={handleChange}
          id='title'
          type='text'
          name='title'
          placeholder='제목을 입력해주세요.'
          required
        />
      </div>
      <button>등록</button>
    </StyledForm>
  );
};

const StyledForm = styled.form`
  .todo-add-box {
    margin-bottom: 15px;
    label {
      display: block;
      margin-bottom: 4px;
    }
  }
  input {
    width: 100%;
    text-indent: 8px;
    height: 40px;
    border-radius: 5px;
    border: 1px solid #ddd;
    padding: 0;
  }
  button {
    width: 100%;
    height: 40px;
    background-color: #6aa1d9;
    border: 1px solid #314d68;
    border-radius: 10px;
    color: #fff;
    font-size: 15px;
  }
`;

export default CreateTodo;

Todo.tsx

import React from 'react';
import styled from 'styled-components';
import { TodoInterface } from '../types';
interface TodoObjInterface {
  key: string;
  todo: TodoInterface;
  onChangedChecked: (key: string, checked: boolean) => void;
  onClickDelete: (key: string) => void;
}

const Todo: React.FC<TodoObjInterface> = ({ todo, onChangedChecked, onClickDelete }) => {
  return (
    <StyledTodo checked={todo.checked}>
      <input
        type='checkbox'
        name='checked'
        checked={todo.checked}
        onChange={(e) => onChangedChecked(todo.id, e.target.checked)}
      />
      <span>{todo.title}</span>
      <button onClick={() => onClickDelete(todo.id)}>삭제</button>
    </StyledTodo>
  );
};

const StyledTodo = styled.li<{ checked: boolean }>`
  display: flex;
  padding: 10px;
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  border-radius: 10px;
  background-color: #fff;
  border: 1px solid #ddd;
  span {
    (props) => ;
    ${(props) => props.checked && 'text-decoration: line-through'};
  }
  button {
    height: 20px;
    background-color: #d96a6a;
    border: 1px solid #683135;
    border-radius: 10px;
    color: #fff;
    font-size: 15px;
  }
  .score {
    display: flex;
  }
  .circle {
    width: 15px;
    height: 15px;
    border-radius: 12px;
    background-color: #fcf67b;
    border: 1px solid #fcf67b;
    margin-right: 4px;
  }
  .circle:last-child {
    margin-right: 0;
  }
`;

export default Todo;

github

https://github.com/hyoseong1994/typescript-todo

profile
개발 노트
post-custom-banner

0개의 댓글