# 5.TodoList編

  • TodoListを作る

# ゴール

  • 繰り返し項目をリスト表示できるようになる
  • 入力内容を取得して扱えるようになる

# 完成形

todo5

# やること

  • TodoListを表示する
  • Todoの完了/未完了を切り替える
  • 新しいTodoを追加する

# TodoListを表示する

# TodoListコンポーネントを作成

  • src/components/TodoList.tsxを作成する
    • まずはTodoListの文字列を返すだけのものを作成
import React from 'react';

function TodoList() {
  return (
    <div>
      <h1>TodoList</h1>
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
  • TodoListコンポーネントを表示するようにApp.tsxを修正

 


 




import React from 'react';
import TodoList from './components/TodoList'; // importを追加

function App() {
  return <TodoList />; // TodoListコンポーネントを使用
}

export default App;
1
2
3
4
5
6
7
8
  • ここまででは画面にTodoListと表示されるだけ

# TodoListの定義

  • まずはTodoListのTodoの型定義を作成しておく
    • Todoはidtitledoneの3つのフィールドからなるとする
  • src/interfaces/Todo.tsを作成する
export type TodoType = {
  id: number;
  title: string;
  done: boolean;
};
1
2
3
4
5
  • 次にTodoListコンポーネントでStateとしてTodoのリストを保持するようにする
  • todoListというStateを定義し初期値としてdefaultTodoを設定する
  • console.logでStateに値を設定できていることを確認する

 

 
 
 
 
 
 


 
 









import React from 'react';
import { TodoType } from '../interfaces/Todo';

// TodoListのデフォルト値の定義を追加
const defaultTodo: TodoType[] = [
  { id: 1, title: 'HelloWorldを作る', done: true },
  { id: 2, title: 'Counterを作る', done: true },
  { id: 3, title: 'TodoListを作る', done: false },
];

function TodoList() {
  const [todoList, setTodoList] = React.useState<TodoType[]>(defaultTodo);
  console.log(todoList); // stateの値を確認する処理を追加
  return (
    <div>
      <h1>TodoList</h1>
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

todo_console

TIP

  • ブラウザ上でF12(もしくは右クリック->検証)を押すと開発者ツールを出すことができる
  • Consoleタブを選択するとconsole.log()の出力を確認することができる
  • 出力のされ方はブラウザによって多少の差異がある

TIP

  • defaultTodoTodoTypeの配列なので変数の宣言時にconst defaultTodo: TodoType[]といった形で型の情報を付与している

# TodoListを画面に表示する

  • Stateで保持しているTodoListを画面に表示する















 
 
 
 






import React from 'react';
import { TodoType } from '../interfaces/Todo';

const defaultTodo: TodoType[] = [
  { id: 1, title: 'HelloWorldを作る', done: true },
  { id: 2, title: 'Counterを作る', done: true },
  { id: 3, title: 'TodoListを作る', done: false },
];

function TodoList() {
  const [todoList, setTodoList] = React.useState<TodoType[]>(defaultTodo);
  console.log(todoList); // stateの値を確認する処理を追加
  return (
    <div>
      <h1>TodoList</h1>
      {/* TodoListの要素数分だけpタグを生成する処理を追加 */}
      {todoList.map(todo => (
        <p key={todo.id}>{todo.title}</p>
      ))}
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • .mapを使うことでTodoの数だけpタグを生成している
  • 今回のようにループ処理で同じ形式のタグを複数生成する場合はkey属性に一意な値を設定する必要がある
    • Reactの特徴である仮想DOMを用いたレンダリングの最適化を実現するために使われる
  • ここまででTodoの一覧が表示されるようになった

todo

# Todoの完了/未完了を切り替える

  • 次は表示されたTodoをクリックすることでTodoの完了/未完了を切り替えられるようにする

# 完了時の見た目を修正する

  • 今のままでは完了も未完了も同じ見た目になっているので、完了の場合に取り消し線がつくようにしておく
  • styleを定義し、doneがtrueの場合のみ適用されるようにする
  • TodoList用のstyleとしてsrc/styles/TodoList.module.cssを作成する
.done {
  color: #bbb;
  text-decoration: line-through;
}
1
2
3
4
  • src/components/TodoList.tsxにCSSを適用する


 













 
 
 









import React from 'react';
import { TodoType } from '../interfaces/Todo';
import styles from '../styles/TodoList.module.css';

const defaultTodo: TodoType[] = [
  { id: 1, title: 'HelloWorldを作る', done: true },
  { id: 2, title: 'Counterを作る', done: true },
  { id: 3, title: 'TodoListを作る', done: false },
];

function TodoList() {
  const [todoList, setTodoList] = React.useState<TodoType[]>(defaultTodo);
  return (
    <div>
      <h1>TodoList</h1>
      {todoList.map(todo => (
        // style属性を追加
        // doneがtrueならstyleを適用、falseなら何も適用しない
        <p key={todo.id} className={todo.done ? styles.done : undefined}>
          {todo.title}
        </p>
      ))}
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

TIP

  • styleの適用部分で三項演算子を使っている
  • (条件式) ? (trueの時の値) : (falseの時の値);
  • doneがtrueのTodoに取り消し線が表示されるようになった

todo2

# クリックされたことを検知する

  • Todoを表示するpタグにonClick属性を追加する
  • どのTodoがクリックされたか判別できるようにid属性も追加しておく
  • toggleComplete関数を追加しクリックした際に呼び出されるようにする













 
 
 
 
 





 



 
 










import React from 'react';
import { TodoType } from '../interfaces/Todo';
import styles from '../styles/TodoList.module.css';

const defaultTodo: TodoType[] = [
  { id: 1, title: 'HelloWorldを作る', done: true },
  { id: 2, title: 'Counterを作る', done: true },
  { id: 3, title: 'TodoListを作る', done: false },
];

function TodoList() {
  const [todoList, setTodoList] = React.useState<TodoType[]>(defaultTodo);

  // toggleComplete関数を追加
  const toggleComplete = (event: React.MouseEvent) => {
    const target = event.target as Element;
    alert(target.id); // event.target.idでクリックされた要素のid属性を取得できる
  };

  return (
    <div>
      <h1>TodoList</h1>
      {todoList.map(todo => (
        // id属性とonClick属性を追加
        <p
          key={todo.id}
          className={todo.done ? styles.done : undefined}
          id={String(todo.id)}
          onClick={toggleComplete}
        >
          {todo.title}
        </p>
      ))}
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  • Todoの文字列をクリックすると、そのTodoのidがalertで表示される

# クリックされたTodoのdoneを切り替える

  • クリックしたTodoのdoneの値を更新する
















 
 
 
 
 
 
 
 
 
 
 
 





















import React from 'react';
import { TodoType } from '../interfaces/Todo';
import styles from '../styles/TodoList.module.css';

const defaultTodo: TodoType[] = [
  { id: 1, title: 'HelloWorldを作る', done: true },
  { id: 2, title: 'Counterを作る', done: true },
  { id: 3, title: 'TodoListを作る', done: false },
];

function TodoList() {
  const [todoList, setTodoList] = React.useState<TodoType[]>(defaultTodo);

  const toggleComplete = (event: React.MouseEvent) => {
    const target = event.target as Element;

    // 属性の値は全て文字列で返却されるので数値型に変換する
    const id = Number(target.id);

    // Stateに保持しているtodoListに対してループ処理
    const newTodoList = todoList.map(todo => {
      // クリックされたtodoのidと一致したらdoneのtrue/falseを反転させる
      if (todo.id === id) todo.done = !todo.done;
      return todo;
    });

    // 新しいtodoListでStateを更新する
    setTodoList(newTodoList);
  };

  return (
    <div>
      <h1>TodoList</h1>
      {todoList.map(todo => (
        <p
          key={todo.id}
          className={todo.done ? styles.done : undefined}
          id={String(todo.id)}
          onClick={toggleComplete}
        >
          {todo.title}
        </p>
      ))}
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

TIP

  • JavaScriptでは等価演算子は===を使う
  • 否定の場合は!==
  • ここまでできるとTodoの完了/未完了をクリックすることで切り換えられるようになる

todo3

# 新しいTodoを追加する

  • ここまででTodoの完了/未完了を管理できるようになった
  • 次は新しくTodoを追加できるようにする

# 入力フォームの作成

  • 追加したいTodoの内容を入力するフォームと追加ボタンを配置する


























 
 
 
 
 
















import React from 'react';
import { TodoType } from '../interfaces/Todo';
import styles from '../styles/TodoList.module.css';

const defaultTodo: TodoType[] = [
  { id: 1, title: 'HelloWorldを作る', done: true },
  { id: 2, title: 'Counterを作る', done: true },
  { id: 3, title: 'TodoListを作る', done: false },
];

function TodoList() {
  const [todoList, setTodoList] = React.useState<TodoType[]>(defaultTodo);

  const toggleComplete = (event: React.MouseEvent) => {
    const target = event.target as Element;
    const id = Number(target.id);
    const newTodoList = todoList.map(todo => {
      if (todo.id === id) todo.done = !todo.done;
      return todo;
    });
    setTodoList(newTodoList);
  };

  return (
    <div>
      <h1>TodoList</h1>
      {/* 入力域とボタンを追加 */}
      <p>
        <input />
        <button>追加</button>
      </p>
      {todoList.map(todo => (
        <p
          key={todo.id}
          className={todo.done ? styles.done : undefined}
          id={String(todo.id)}
          onClick={toggleComplete}
        >
          {todo.title}
        </p>
      ))}
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

todo4

# 入力内容を取得する














 
 

 
 
 
 














 
 
 
 
















import React from 'react';
import { TodoType } from '../interfaces/Todo';
import styles from '../styles/TodoList.module.css';

const defaultTodo: TodoType[] = [
  { id: 1, title: 'HelloWorldを作る', done: true },
  { id: 2, title: 'Counterを作る', done: true },
  { id: 3, title: 'TodoListを作る', done: false },
];

function TodoList() {
  const [todoList, setTodoList] = React.useState<TodoType[]>(defaultTodo);

  // 入力域の参照を定義
  const inputRef = React.useRef<HTMLInputElement>(null);

  // addTodo関数を追加
  const addTodo = () => {
    alert(inputRef.current?.value); // 入力内容はinput.current?.valueで取得できる
  };

  const toggleComplete = (event: React.MouseEvent) => {
    const target = event.target as Element;
    const id = Number(target.id);
    const newTodoList = todoList.map(todo => {
      if (todo.id === id) todo.done = !todo.done;
      return todo;
    });
    setTodoList(newTodoList);
  };

  return (
    <div>
      <h1>TodoList</h1>
      {/* 入力域を参照できるようにref属性を追加 */}
      <input ref={inputRef} />
      {/* onClick属性を追加 */}
      <button onClick={addTodo}>追加</button>
      {todoList.map(todo => (
        <p
          key={todo.id}
          className={todo.done ? styles.done : undefined}
          id={String(todo.id)}
          onClick={toggleComplete}
        >
          {todo.title}
        </p>
      ))}
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  • 入力域の参照を取得
    • const inputRef = React.useRef<HTMLInputElement>(null);で入力域の参照を定義
    • <input ref={inputRef} />ref属性にセットすることで、変数inputRefにinputタグの情報が格納される
  • 追加ボタンクリック時の挙動
    • <button onClick={addTodo}>追加</button>でclick時にaddTodo関数を呼び出すようにしている
  • ここまでで追加ボタンを押すと入力した内容がalertに表示されるようになった

# 入力内容をTodoListに追加

















 
 
 
 
 
 
 
 
 
 
 
 

































import React from 'react';
import { TodoType } from '../interfaces/Todo';
import styles from '../styles/TodoList.module.css';

const defaultTodo: TodoType[] = [
  { id: 1, title: 'HelloWorldを作る', done: true },
  { id: 2, title: 'Counterを作る', done: true },
  { id: 3, title: 'TodoListを作る', done: false },
];

function TodoList() {
  const [todoList, setTodoList] = React.useState<TodoType[]>(defaultTodo);

  const inputRef = React.useRef<HTMLInputElement>(null);

  const addTodo = () => {
    // 新しいtodoを組み立てる
    const todo: TodoType = {
      id: todoList.length + 1,
      title: inputRef.current?.value!, // 入力内容をtitleにセット
      done: false,
    };

    // [...state.todoList, todo]とすることで現在のtodoListの配列の最後尾に新しいtodoを追加できる
    setTodoList([...todoList, todo]);

    // 入力域を空にする
    if (inputRef.current) inputRef.current.value = '';
  };

  const toggleComplete = (event: React.MouseEvent) => {
    const target = event.target as Element;
    const id = Number(target.id);
    const newTodoList = todoList.map(todo => {
      if (todo.id === id) todo.done = !todo.done;
      return todo;
    });
    setTodoList(newTodoList);
  };

  return (
    <div>
      <h1>TodoList</h1>
      <input ref={inputRef} />
      <button onClick={addTodo}>追加</button>
      {todoList.map(todo => (
        <p
          key={todo.id}
          className={todo.done ? styles.done : undefined}
          id={String(todo.id)}
          onClick={toggleComplete}
        >
          {todo.title}
        </p>
      ))}
    </div>
  );
}

export default TodoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
  • これで入力された内容がTodoListの末尾に追加されるようになった
  • 入力した内容が残り続けないように、空文字をセットすることでフォームをリセットしている

todo5

# まとめ

  • useStateはStateとして配列も管理することができる
  • .mapをうまく活用することで配列を繰り返し処理で画面に表示することができる
  • useRefを使って入力域とフィールドを紐付けることで入力内容を取得することができる
Last Updated: 2021/01/26