LoginSignup
6
4

More than 3 years have passed since last update.

React + TypeScript でTodoリストを作りながら入門

Posted at

昔Vueを勉強する前にチラっとReactをやったものの挫折して、それ以来触って無かった者です。

最近Reactは分かりやすくなってるとか、TypeScriptとの相性が良いとか聞いたので少しやってみることにしました。

今回は色々な資料を当たりながら最終的にシンプルなTodoリストを作成しました。

React用の型が追加される「@types/react」も使ってみます。
何か間違ってたら指摘お願いします。

:bee: 作るもの

new - CodeSandbox.png
Codesandbox

追加と削除だけのとてもシンプルなTODOリスト。

:tools: コードを見てみる

Todoの型を決める

import * as React from "react";
import * as ReactDOM from "react-dom";

// ***************************************
// Todoの型定義
// ***************************************
interface Todo {
  id: number;
  name: string;
}

Todoは、idとnameというプロパティを持つ型だと決めている。
idはnumber型であり、nameはstring型である。

Todoコンポーネントを作る

Todoリスト1つづつの部分である。
HTMLタグで言うと ”li” の部分に当たる。

// ***************************************
// Todoコンポーネント
// ***************************************
// TodoListItemPropsの型定義
interface TodoListItemProps {
  todo: Todo;
  onDelete: (todo: Todo) => void;
}

const TodoListItem: React.FunctionComponent<TodoListItemProps> = ({
  todo,
  onDelete
}) => {
  const onClick = () => {
    onDelete(todo);
  };

  return (
    <li>
      {todo.name} <button onClick={onClick}>Delete</button>
    </li>
  );
};

TodoListItemコンポーネントは、「React.FunctionComponent」という型に従いそのPropsは「TodoListItemProps」という型に従うと決める。

「FunctionComponent」は自分では定義していない。
「@types/react」が提供してくれる型。

interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement | null;
    propTypes?: WeakValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

Macなら「Option + クリック」、winなら「ctrl + クリック」で定義されている型のところへ飛べるので、見てみるとこんな感じらしい。

うん、よく分からんけど関数のコンポーネントなんやろ(適当)

Todoリストコンポーネントを作る

フォームを含まないTodoリスト全体の部分。
HTMLタグで言うと ”ul” の部分に当たる。

上記の「TodoListItem」の親になる。

// ***************************************
// Todoリストの一覧表示部分
// ***************************************
// Propsの型定義
interface TodosListProps {
  todos: Todo[];
  onDelete: (todo: Todo) => void;
}

const TodosList: React.FunctionComponent<TodosListProps> = ({
  todos,
  onDelete
}) => (
  <ul>
    {todos.map(todo => (
      <TodoListItem todo={todo} key={todo.id} onDelete={onDelete} />
    ))}
  </ul>
);

フォーム部分のコンポーネントを作る

ユーザーが入力するフォームや、追加ボタンの部分。

// ***************************************
// Todo追加用フォーム
// ***************************************
// Propsの型定義
interface NewTodoFormProps {
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  onAdd: (event: React.FormEvent<HTMLFormElement>) => void;
  todo: Todo;
}

const NewTodoForm: React.FunctionComponent<NewTodoFormProps> = ({
  onChange,
  onAdd,
  todo
}) => (
  <form onSubmit={onAdd}>
    <input onChange={onChange} value={todo.name} />
    <button type="submit">Add a todo</button>
  </form>
);

特筆するところはないけど、「React.ChangeEvent」「React.FormEvent」なども上記のように「Option + クリック」「ctrl + クリック」でどういう型なので見れるので興味があれば見てみると良いかもしれない。

Todoアプリ全体のコンポーネント

一番親の部分

// ***************************************
// Todoリスト本体部分
// ***************************************
// 状態(State)の型定義
interface State {
  newTodo: Todo;
  todos: Todo[];
}

class App extends React.Component<{}, State> {
  state = {
    newTodo: {
      id: 1,
      name: ""
    },
    todos: []
  };

  render() {
    return (
      <div>
        <h2>React + TypeScript Todoリスト</h2>
        <NewTodoForm
          todo={this.state.newTodo}
          onAdd={this.addTodo}
          onChange={this.handleTodoChange}
        />
        <TodosList todos={this.state.todos} onDelete={this.deleteTodo} />
      </div>
    );
  }

  // Todoの追加
  private addTodo = (event: React.FormEvent<HTMLFormElement>) => {
    // デフォルトの送信機能を使わない
    event.preventDefault();

    // 空なら処理を止める
    if (!this.state.newTodo.name.length) {
      return;
    }

    // TodoリストStateの更新、引数には現在(更新前)のStateが入ってくる
    this.setState(previousState => ({
      newTodo: {
        id: previousState.newTodo.id + 1,
        name: ""
      },
      // 現在のTODOの配列と、新しいTODOを結合する
      todos: [...previousState.todos, previousState.newTodo]
    }));
  };

  // フォームの内容が変わったらnewTodoの内容も変える
  private handleTodoChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const inputedValue = event.target.value;

    this.setState({
      newTodo: {
        ...this.state.newTodo,
        name: inputedValue
      }
    });
  };

  // Todoの削除
  private deleteTodo = (todoToDelete: Todo) => {
    this.setState(previousState => ({
      todos: [
        ...previousState.todos.filter(todo => todo.id !== todoToDelete.id)
      ]
    }));
  };
}

ReactDOM.render(<App />, document.getElementById("root"));

:star: まとめ

何だかReactは凄く難しいイメージがあったのですが、意外とシンプルで基本普通のJavaScriptに近い形で書けるのが良いと思いました。

またTypeScriptも趣味でチョロっとしか触ったこと無かったのですが、補完やコードジャンプやエラー通知が便利だなと実感出来ました!

ここ数ヶ月Swiftを勉強していたのですが、結構TypeScriptと似ている印象です。

以上

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4