# React + TypeScript TodoList
npx create-react-app todo-typescript --template typescript
### App.css書き換え
```
*, *:active, *:hover, *:before, *:after {
box-sizing: border-box;
}
.inner {
max-width: 700px;
margin: 0 auto;
}
.task-list {
list-style: none;
margin: 0;
padding: 0;
border-top: 1px solid #d7d2cd;
}
.task-list li {
padding: 20px 10px;
border-bottom: 1px solid #d7d2cd;
display: flex;
align-items: center;
position: relative;
}
.task-list li.done span {
text-decoration: line-through;
}
.task-list li label {
width: calc(100% - 100px);
display: block;
cursor: pointer;
}
.task-list li span {
display: block;
}
.task-list li .btn {
display: none;
width: 80px;
position: absolute;
right: 10px;
padding: 0.4em 1em;
font-size: 13px;
z-index: 10;
}
.task-list li:hover .btn {
display: block;
}
.inputForm {
margin-bottom: 40px;
background: #f9f3ee;
padding: 40px 0;
border-bottom: 1px solid #d7d2cd;
}
.inputForm .inner {
display: flex;
}
.inputForm .input {
width: 80%;
font-size: 15px;
outline: none;
border: solid 3px #d7d2cd;
padding: 10px;
border-radius: 7px;
margin-right: 10px;
}
.inputForm .input:focus {
background: #f9f9f0;
}
.inputForm .btn {
width: 20%;
}
.btn {
font-size: 15px;
cursor: pointer ;
font-weight: bold;
display: inline-block;
padding: 10px 15px;
text-decoration: none;
background: #668ad8;
color: #FFF;
border-bottom: solid 4px #627295;
border-radius: 7px;
outline: none;
}
.btn:active {
transform: translateY(4px);
border-bottom: none;
margin-bottom: 4px;
}
.btn.is-delete {
background-color: #d86681;
border-bottom-color: #956270;
}
.checkbox-input {
display: none;
}
.checkbox-label {
padding-left: 30px;
position: relative;
margin-right: 20px;
}
.checkbox-label::before {
content: "";
display: block;
position: absolute;
top: 2px;
left: 0;
width: 18px;
height: 18px;
border: 1px solid #d7d2cd;
border-radius: 4px;
}
.checkbox-input:checked + .checkbox-label {
color: #999;
}
.checkbox-input:checked + .checkbox-label::after {
content: "";
display: block;
position: absolute;
top: 0;
left: 5px;
width: 7px;
height: 14px;
transform: rotate(40deg);
border-bottom: 3px solid #d01137;
border-right: 3px solid #d01137;
}
```
mkdir src/components
### 型の設定
src/components/Types.ts
```
export type Task = {
id: number;
title: string;
done: boolean;
};
```
### TaskItem作成
src/components/TaskItem.tsx
```
import React from 'react'
import { Task } from './Types'
type Props = {
task: Task
handleDone: (task: Task) => void
handleDelete: (task: Task) => void
}
const TaskItem: React.FC<Props> = ({ task, handleDone, handleDelete }) => {
return (
<li className={task.done ? 'done' : ''}>
<label>
<input
type="checkbox"
className="checkbox-input"
onClick={() => handleDone(task)}
defaultChecked={task.done}
/>
<span className="checkbox-label">{ task.title }</span>
</label>
<button
onClick={() => handleDelete(task)}
className="btn is-delete"
>削除</button>
</li>
)
}
export default TaskItem
```
### TaskList.tsx
src/components/TaskList.tsx
```
import React from 'react'
import TaskItem from './TaskItem'
import { Task } from './Types'
type Props = {
tasks: Task[]
setTasks: React.Dispatch<React.SetStateAction<Task[]>>
}
const TaskList: React.FC<Props> = ({ tasks, setTasks }) => {
const handleDone = (task: Task) => {
setTasks(prev => prev.map(t =>
t.id === task.id
? { ...task, done: !task.done }
: t
))
}
const handleDelete = (task: Task) => {
setTasks(prev => prev.filter(t =>
t.id !== task.id
))
}
return (
<div className="inner">
{
tasks.length <= 0 ? '登録されたTODOはありません。' :
<ul className="task-list">
{ tasks.map( task => (
<TaskItem
key={task.id}
task={task}
handleDelete={handleDelete}
handleDone={handleDone}
/>
)) }
</ul>
}
</div>
)
}
export default TaskList
```
### TaskInput.tsx
src/components/TaskInput.tsx
```
import React, { useState } from 'react';
import { Task } from './Types';
type Props = {
setTasks: React.Dispatch<React.SetStateAction<Task[]>>;
tasks: Task[];
};
const TaskInput: React.FC<Props> = ({ setTasks, tasks }) => {
const [inputTitle, setInputTitle] = useState<string>('');
const [count, setCount] = useState<number>(tasks.length + 1);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputTitle(e.target.value);
};
const handleSubmit = () => {
setCount(count + 1);
const newTask: Task = {
id: count,
title: inputTitle,
done: false,
};
setTasks([newTask, ...tasks]);
setInputTitle('');
};
return (
<div>
<div className='inputForm'>
<div className='inner'>
<input
type='text'
className='input'
value={inputTitle}
onChange={handleInputChange}
/>
<button onClick={handleSubmit} className='btn is-primary'>
追加
</button>
</div>
</div>
</div>
);
};
export default TaskInput;
```
### App.tsx書き換え
```
import React, { useState } from 'react';
import TaskList from './components/TaskList';
import TaskInput from './components/TaskInput';
import { Task } from './components/Types';
import './App.css';
const initialState: Task[] = [
{
id: 2,
title: '次にやるやつ',
done: false,
},
{
id: 1,
title: 'はじめにやるやつ',
done: true,
},
];
const App: React.FC = () => {
const [tasks, setTasks] = useState(initialState);
return (
<div>
<TaskInput setTasks={setTasks} tasks={tasks} />
<TaskList setTasks={setTasks} tasks={tasks} />
</div>
);
};
export default App;
```
<!-- ## styled-componentsに一意のclassを張れるようにする。
yarn add styled-components @types/styled-components -->
<!-- あとは授業内でやればいいかな。 -->