typescript와 react 를 활용하여 todo list를 만들어보겠습니다.
create react-app 을 통해 typescript react app을 생성하고 필요한 라이브러리를 다운받습니다.
yarn create react-app --typescript
yarn add styled-components uuid
export interface TodoInterface {
id: string;
title: string;
checked: boolean;
}
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;
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;
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;