React + Typescript その3

前回のあらすじ

前回、登録ボタンを押すと、フォームに入力したタイトルと本文がアラートに表示されるようにしました。

前回

今回は、タイトルと本文をTodoコンポーネントの配列に追加して、さらにこの配列でTodoListコンポーネントを更新するところまでを行います。

前回は主にStateを使いましたが、今回はPropsの使い方の話になります。

というわけで、前回のソースコード を元に話を進めます。

準備

TODOリストのデータはTodoコンポーネントのStateで配列として保持することにします。

そのためにまず、1件のTODOデータを扱うオブジェクトのインタフェースTodoDataを定義します。

TodoDataインタフェース

この定義はTodoコンポーネントだけではなくTodoListコンポーネントとTodoItemコンポーネントでも利用するので、Todo.tsxに定義するのではなく別ファイルTodoData.tsxを用意してから、Todo.tsxTodoList.tsxにインポートして利用します。

TodoData.tsx

export interface TodoData {
  id: number;
  title: string;
  content: string;
  date: string;
  time: string;
}

タイトルと本文だけではなく、IDと更新日時もTodoDataで扱うようにします。

Todo.tsx および TodoList.tsxTodoItem.tsx では、以下の1文を加えてインポートしてください。

import { TodoData } from './TodoData';

TodoコンポーネントのPropsとState

次に前回同様、TodoコンポーネントにPropsとStateのインタフェースを定義して、コンストラクタで初期化します。

Todo.tsx

// Propsインタフェース
interface TodoPropsInterface {
}

// Stateインタフェース
interface TodoStateInterface {
  todoData: TodoData[];
  idCount: number;
}

class Todo extends React.Component<TodoPropsInterface, TodoStateInterface> {
  public constructor(props:TodoPropsInterface) {
    super(props);
    this.state = {
      todoData: [],
      idCount: 0,
    };
  }

StateのメンバtodoDataがTODOデータの格納配列、idCountはIDの配番に使うカウンタです。

クリックリスナの引き上げ

前回、登録ボタンを押すとTodoEditコンポーネントのonClick_Submit()メソッドが呼ばれるようにしました。しかし、このメソッドではTODOデータの格納先であるTodoコンポーネントのStateにアクセスできません。
そこで、onClick_Submit()メソッドをTodoコンポーネントに引き上げます。

ざっくりと処理の流れを説明するとこんな感じです。

  1. TodoコンポーネントにonClick_Submit()メソッドを用意する。
  2. TodoコンポーネントからTodoEditコンポーネントのPropsにonClick_Submit()メソッドをわたす。
  3. TodoEditコンポーネントは、登録ボタンが押されたら、PropsのonClick_Submit()メソッドを呼ぶ。
  4. TodoコンポーネントのonClick_SubmitメソッドでTODOデータを配列に追加する。

この手順に従って実装を進めてみましょう。

onClick_Submit()メソッドを用意する

まず、TodoコンポーネントonClick_Submit()メソッドを用意します。

内容は以下のとおりです。タイトルと本文を受け取って、それを元にTodoDataオブジェクトを作成して配列todoDataに追加する。ついでにidCountを進めています。

Stateを変更する場合は、Stateのメンバ変数を直接変更するのではなくて、必ずsetState()メソッドを使うことに注意してください。

Todo.tsx

  // クリック:登録
  private onClick_Submit(title:string, content:string) {
    if (title == '' || content == '') {
      alert("タイトルと本文を入力してください。");
      return;
    }

    // 日時文字列作成
    let date = new Date();
    let dateStr = date.getFullYear() + "/" + (date.getMonth() + 1) + "/" + date.getDate();
    let timeStr = date.getHours() + ":" + date.getMinutes() + ":" + date.getUTCSeconds();

    // Todoデータ追加
    let todoData = this.state.todoData.slice();
    todoData.push({
      id: this.state.idCount,
      title: title,
      content: content,
      date: dateStr,
      time: timeStr,
    });
    this.setState({
      todoData: todoData,
      idCount: this.state.idCount + 1,
    });
  }

PropsにonClick_Submit()メソッドを加える

TodoEditコンポーネントのPropsにonClick_Submit()メソッドを追加します。
最後のvoidonClick_Submit()メソッドの戻り値の型です。今回は何も返さないのでvoidです。

TodoEdit.tsx

// Propsインタフェース
interface TodoEditPropsInterface {
  onClick_Submit(title:string, content:string):void;
}

TodoEditコンポーネントのPropsにonClick_Submit()メソッドを追加すると、VisualStudioCodeでは以下のように、render()メソッドでTodoEditコンポーネントがエラーになります。
TodoEditコンポーネントはPropsにonClick_Submit()メソッドを必要としているのに、それが与えられていない」という警告です。

というわけで与えてやります。

Todo.tsx

  // 描画
  public render() {
    return (
      <div>
        <TodoEdit onClick_Submit={(t, c) => this.onClick_Submit(t, c)}/>
        <TodoList />
      </div>
    );
  }

PropsのonClick_Submit()メソッドを呼ぶ

TodoEditコンポーネントのonClick_Submit()メソッドは不要になったので削除します。
登録ボタンではPropsのonClick_Submit()メソッドを呼びます。引数のタイトルと本文はTodoEditコンポーネントのStateから渡します。

TodoEdit.tsx

  // クリック:登録
  // private onClick_Submit() {
  //   alert(this.state.title + " " + this.state.content);
  // }

…

  <div>
    <button>新規</button>
    <button onClick={() => this.props.onClick_Submit(this.state.title, this.state.content)} >登録</button>
  </div>

TODOデータを配列に追加する

これは前述のonClick_Submit()メソッドのコードの通りです。

TODOリストの表示

配列にTODOデータを追加できるようになりました。ただこれでは内部データに追加されただけなので確認ができません。
そこで次は、TODOデータ配列をTODOリストに表示できるようにします。

ここでもやはりPropsを利用してTodoTodoListTodoItemの各コンポーネントへとデータを渡します。

TodoListコンポーネントのProps(とState)

TodoListコンポーネントのPropsにはTodoDataの配列を追加します。

TodoList.tsx

import {TodoData} from './TodoData';

// Propsインタフェース
interface TodoListPropsInterface {
  todoData:TodoData[];
}

// Stateインタフェース
interface TodoListStateInterface {
}

class TodoList extends React.Component<TodoListPropsInterface, TodoListStateInterface> {

TodoItemコンポーネントのProps(とState)

TodoItemコンポーネントは1件のTODOデータを扱うだけなので、PropsにはTodoDataをひとつ追加します。

TodoItem.tsx

import {TodoData} from './TodoData';

// Propsインタフェース
interface TodoItemPropsInterface {
  todoData: TodoData;
}

// Stateインタフェース
interface TodoItemStateInterface {
}

class TodoItem extends React.Component<TodoItemPropsInterface, TodoItemStateInterface> {

TODOデータの表示

あとは上から順にデータを渡して、TodoItemコンポーネントで表示するだけです。

まずTodoコンポーネントからTodoListコンポーネントへTODOデータ配列を渡します。

Todo.tsx

  // 描画
  public render() {
    return (
      <div>
        <TodoEdit onClick_Submit={(t, c) => this.onClick_Submit(t, c)}/>
        <TodoList todoData={this.state.todoData} />
      </div>
    );
  }

次に、TodoListコンポーネントからTodoItemコンポーネントへTODOデータをひとつずつ渡します。
JSX内でループすることができないので、まずitemsを組み立ててからJSXに組み込んでいる点に注目してください。

TodoList.tsx

  // 描画
  render() {
    const items = this.props.todoData.map((d) => {
      return <TodoItem todoData={d} />;
    });

    return (
      <div>
        <table className='todo-table'>
          <tr>
            <th>タイトル</th>
            <th>本文</th>
            <th>日付</th>
            <th>時刻</th>
            <th></th>
            <th></th>
          </tr>
          {items}
        </table>
      </div>
    )
  }

最後にTodoItemコンポーネントでの表示部分の修正です。

TodoItem.tsx

  // 描画
  render() {
    return (
      <tr>
        <td>{this.props.todoData.title}</td>
        <td>{this.props.todoData.content}</td>
        <td>{this.props.todoData.date}</td>
        <td>{this.props.todoData.time}</td>
        <td><button>編集</button></td>
        <td><button>削除</button></td>
      </tr>
    );
  }

次回に続く

今回はここまでです。
TODOリストに行を追加できるようになりました。

ここまでの ソースコード です。

次回は編集ボタンと新規ボタンの動作を実装して、この一連の記事を締め括りたいと思います。