Tips

初心者向け!ReactでTypeScriptを使ったpropsや関数の書き方

  • このエントリーをはてなブックマークに追加

「TypeScriptを勉強し始めたけどReactでどのように書くのかまではイメージできていない」
「ReactプロジェクトでTypeScriptを導入する方法を知りたい」
「フォーム機能の引数eについての型指定がよくわからない」

今回はReactプロジェクトでTypeScriptを使ったコーディングをする方法について初心者向けに解説します。

この記事では、ReactとTypeScriptの環境構築から、propsや関数の書き方を詳しく説明していきます。

propsの型付けや関数の引数・戻り値の型指定、またフォーム機能で使うe(イベント)に対する型指定まで紹介しますので、最低限ReactでTypeScriptを使う手順がイメージできるようになるはずです。

基本的にはReactとJavaScriptの基本がわかっていれば、理解できるものばかりで「あくまでReactの応用編」くらいなので安心して学んでいってください。

また動画でも解説しているので必要に応じて活用してください。

TypeScriptを使ったReactでのpropsの書き方

まずはpropsの書き方になります。

例題としてTODOリストのアプリを作ることを想定してコンポーネントを書いていきましょう。

import "./App.css";
import TaskList from "./screens/ts-props/TaskList";

function App() {
  return (
    <>
      <TaskList />
    </>
  );
}

export default App;
import React from "react";
import Task from "./Task";

const TaskList = () => {

  return (
    <div>
       <Task date="20230601" title="aaa" />
       <Task date="20230602" title="bbb" />
       <Task date="20230603" title="ccc" />
    </div>
  );
};

export default TaskList;
import React from "react";

const Task = (props) => {
  return (
    <div>
      <p>{props.date}</p>
      <p>{props.title}</p>
    </div>
  );
};

export default Task;

TODOリストを画面に表示するための親コンポーネントでTaskList.tsxがあり、1個のTODOを子コンポーネントのTask.tsxで用意しています。

propsを親→子に渡した場合、子コンポーネントの引数にpropsを書くのですが厳密にはpropsはオブジェクトになっていてプロパティごとに値が入ることになります。

上記の例で言うとdateとtitleという2つのプロパティに値が代入されています。

TypeScriptではそれぞれのプロパティに対して型を指定します。

import React from "react";
// ここを修正
const Task = (props: { date: string; title: string }) => {
  return (
    <div>
      <p>{props.date}</p>
      <p>{props.title}</p>
    </div>
  );
};

export default Task;

さらに現状だとpropsに値を代入するのを固定の値を入力していますが、繰り返し処理など固定の値を書けない場合は親コンポーネントの側でも型の指定が必要になります。

例えばAPIからTODOのデータを取得して画面に表示する方法に変えてみます。

// ここを追加
import React, { useEffect, useState } from "react";
import Task from "./Task";

const TaskList = () => {
  // ここを追加
  const [todos, setTodos] = useState([]);
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/todos/"
        );

        const json = await response.json();
        setTodos(json);
      } catch (error) {
        console.error(error);
      }
    };
    fetchData();
  }, []);

  return (
    <div>
       {/* ここを変更 */}
      {todos.map((todo: { id: number; title: string }) => (
        <Task key={todo.id} {...todo} />
      ))}
    </div>
  );
};

export default TaskList;

あとAPIから使用するプロパティはtitleのみを子コンポーネントにpropsとして渡すように変更しますので、子コンポーネントのTask.tsx側でdateは削除しておいてください。

import React from "react";
// dateを削除した
const Task = (props: { title: string }) => {
  return (
    <div>
      {/* dateを削除した */}
      <p>{props.title}</p>
    </div>
  );
};

export default Task;

APIはjsonplaceholderを使用しています。

https://jsonplaceholder.typicode.com/

APIで取得したデータはmapメソッドを使ってreturn文のなかに書いていきます。

mapメソッドの引数にはAPIから取得したデータが格納されたステート変数を書くわけですが、このmapメソッドの引数について型の指定を書いておくことになります。

そうすることで固定の値じゃない場合も正しくpropsを渡すことが可能になります。

「親でも子でも型を書いて2度手間だな」と思われた方がいるかもしれません。

逆にいうと型の見間違いや値の渡し忘れなどのヒューマンエラーに気づける、というメリットになっているわけです。

通常であればユーザーが問題の処理にぶつかって発見されることが多いエラーが、開発時に気づけるのはリリース後のトラブルの件数を緩和することにつながります。

とはいえ親コンポーネントと子コンポーネントの両方で同じようなことを書くのは手間です。

そこで実務ではReactプロジェクトのなかで何回も使う型のパターンは専用のファイルに分けて使い回すことが多いです。

srcフォルダ直下に新しくtypesフォルダを作り、そのなかにtypes.tsというファイルを新規で作成します。

- node_modules
- public
- src
  - types
    - types.ts
  - App.tsx
  - App.css

  ...そのほかコンポーネントのフォルダなど

先ほどのpropsの型をtypes.tsに改めて書きます。

export type TodoProps = { id: number; title: string };

types.tsにpropsの型ルールをtypeで書いておきエクスポートすることで各コンポーネントで必要に応じて使い回す流れです。

propsの型を直接書くのではなくtypes.tsからインポートする書き方に変えてみましょう。

import React, { useEffect, useState } from "react";
import Task from "./Task";
// ここを追加
import { TodoProps } from "../../types/types";

const TaskList = () => {
  
  const [todos, setTodos] = useState([]);
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/todos/"
        );

        const json = await response.json();
        setTodos(json);
      } catch (error) {
        console.error(error);
      }
    };
    fetchData();
  }, []);

  return (
    <div>
       {/* ここを変更 */}
      {todos.map((todo: TodoProps) => (
        <Task key={todo.id} {...todo} />
      ))}
    </div>
  );
};

export default TaskList;
import React from "react";
// ここを追加
import { TodoProps } from "../../types/types";
// ここを変更
const Task = (props: TodoProps) => {
  return (
    <div>
      <p>{props.title}</p>
    </div>
  );
};

export default Task;

ちなみに親コンポーネントのTaskList.tsx側でmapメソッドの引数に型を指定していますが、今回の場合はuseStateを使っていて初期値に型を書いてもOKです。

import React, { useEffect, useState } from "react";
import Task from "./Task";
import { TodoProps } from "../../types/types";

const TaskList = () => {
  // ここに型指定を書いた
  const [todos, setTodos] = useState<TodoProps[]>([]);
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/todos/"
        );

        const json = await response.json();
        setTodos(json);
      } catch (error) {
        console.error(error);
      }
    };
    fetchData();
  }, []);

  return (
    <div>
       {/* ここから型指定を削除した */}
      {todos.map(todo) => (
        <Task key={todo.id} {...todo} />
      ))}
    </div>
  );
};

export default TaskList;

さらに子コンポーネントのTask.tsx側でpropsを受け取る際に、propsと書かずに分割代入でプロパティ名だけ取り出す書き方もOKです。

import React from "react";
// ここを追加
import { TodoProps } from "../../types/types";
// 分割代入に変更
const Task = ({ title }: TodoProps) => {
  return (
    <div>
      {/* propsを削除できる */}
      <p>{title}</p>
    </div>
  );
};

export default Task;

画面の表示内容は変わらないのですが、少しでも効率的にコードを書くためにいろんな方法を知っておくと良いでしょう。

今回は詳しく解説しませんでしたがuseStateや分割代入については過去に別の記事で解説しているので必要な方はご覧ください。

propsで値を渡すときにオブジェクト形式だった場合もあります。

オブジェクト形式についてはプロパティの値に型指定を行います。

先ほどのtypes.tsを使って書いてみます。

export type TodoProps = { id: number; title: string };

// オブジェクトを渡すとき
export type UserProps = {
  username: {
    firstName: string;
    lastName: string;
  };
  followers: {
    firstName: string;
    lastName: string;
  }[];
};

オブジェクトのような入れ子形式のデータであったとしても基本的には値に何が入るかを考えてプロパティごとに型指定するだけです。

ちなみに同じような型の組み合わせであるときはtypes.tsの中で参照することも可能です。

上記のコードだとfirstNameとlastNameというプロパティ名、型の種類ともに同じですが、usernameとfollowersという2つに分けて管理しています。

このようなケースは以下のようにしても同じ意味として使用できます。

export type TodoProps = { id: number; title: string };

// ここを追加
export type NameProps = { firstName: string; lastName: string; };

// ここを修正
export type UserProps = {
  username: NameProps;
  followers: NameProps[];
};

同じ記載を何回もするのではなく再利用できるものを探せるようになると良いでしょう。

TypeScriptを使ったReactでのイベントと関数の書き方

続いてイベントと関数の書き方を紹介します。

基本的には引数と戻り値に型を指定するだけなので書き方さえ覚えればOKなことが多いです。

例題は少し変えてTODOリストを入力する画面を作っていきます。

import "./App.css";
// ここを変更
import Form from "./screens/ts-event/Form";

function App() {
  return (
    <>
      {/* ここを変更 */}
      <Form />
    </>
  );
}

export default App;
import React from "react";

const Form = () => {
  
  return (
    <div>
      <form>
        <input type="text" onChange={handleChange} />
        <button onClick={handleClick}>検索</button>
      </form>
      <div>
        <div>
          <span>掃除</span>
          <button onClick={() => deleteTodo(1)}>完了</button>
        </div>
        <div>
          <span>料理</span>
          <button onClick={() => deleteTodo(2)}>完了</button>
        </div>
        <div>
          <span>買い物</span>
          <button onClick={() => deleteTodo(3)}>完了</button>
        </div>
      </div>
    </div>
  );
};

export default Form;

このようなフォームにおいてinputタグとbuttonタグにそれぞれ関数を作っていきます。

・入力欄に入力したときの関数:handleChange

・検索ボタンをクリックしたときの関数:handleClick

・完了ボタンをクリックしたときの関数:deleteTodo

import React from "react";

const Form = () => {
  // ここを追加
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    console.log("クリック");
  };
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };
  const deleteTodo = (id: number) => {
    console.log(`${id}を削除`);
  };
  return (
    <div>
      <form>
        <input type="text" onChange={handleChange} />
        <button onClick={handleClick}>検索</button>
      </form>
      <div>
        <div>
          <span>掃除</span>
          <button onClick={() => deleteTodo(1)}>完了</button>
        </div>
        <div>
          <span>料理</span>
          <button onClick={() => deleteTodo(2)}>完了</button>
        </div>
        <div>
          <span>買い物</span>
          <button onClick={() => deleteTodo(3)}>完了</button>
        </div>
      </div>
    </div>
  );
};

export default Form;

handleDeleteの引数は数字にしていますが、こちらは実際の仕様によりますので適した型を指定します。

初学者の方が慣れないのはeの型だと思います。

何やら難しい単語が書かれていますが「イベントの型<イベントが発生する要素の型>」という感じです。

イベントというのは「クリック」「入力」などのことで、これらのイベントの種類にはそれぞれ事前に型名が決められています。

さらにイベントが発生する場所、つまりHTML要素もinputタグやbuttonタグなどがあり事前に型名が決められています。

それぞれ膨大な種類があり事前に暗記することは現実的ではないので、学習や開発を通じて1個ずつ覚えるか調べるような形になるでしょう。

とはいえ頻出のものは何回も使っているうちに覚えられるので安心してください。

初学者の方は「eにも型を書かないといけない」とだけ覚えておきましょう。

前章でやったpropsに関数を渡すこともありますが考え方は同じです。

import React from 'react';
import Button from './components/Button';

const App = () => {
  return (
    <div>
      <Button click={(e, text) => alert(text)} />
    </div>
  );
};

export default App;

buttonタグを持つButton.tsxにeを引数にとる関数を渡しました。

propsなのでtypeを使って受け取る関数の引数と戻り値の型を指定します。

引数はbuttonタグに向けたクリックイベントのeなのでReact.MouseEvent<HTMLButtonElement>という書き方になります。

また今回のように戻り値に型指定がないときvoidにします。

import React from 'react';

type BtnProps = {
  click: (e: React.MouseEvent<HTMLButtonElement>, text: string) => void;
};

const Button = (props: BtnProps) => {
  return (
    <div>
      {/* <button onClick={props.click}>Button</button> */}
      <button onClick={(e) => props.click(e, 'おはよう')}>Button</button>
    </div>
  );
};

export default Button;

続いてinputタグを持つInput.tsxにもeを持つ関数をpropsで渡してみます。

import React from 'react';
// ここを変更
import Input from './components/Input';

const App = () => {
  return (
    <div>
      {/* ここを変更 */}
      <Input change={(e) => console.log(e.target.value)} />
    </div>
  );
};

export default App;

inputタグにチェンジイベントのeを渡すので引数はReact.ChangeEvent<HTMLInputElement>という指定になります。

import React from 'react';

type InputProps = {
  change: (e: React.ChangeEvent<HTMLInputElement>) => void;
};

const Input = (props: InputProps) => {
  return (
    <div>
      <input type="text" onChange={props.change} />
    </div>
  );
};

export default Input;

TypeScriptでReactのCSSスタイルをpropsで渡す方法

propsはCSSを渡すことでも使われます。

そもそもReactにおけるCSSはHTMLタグにstyle属性で指定するものなので以下のような書き方になります。

import React from 'react';

import Title from './components/Title';

const StyleProps = () => {
  return (
    <div>
      StyleProps
      <Title styles={{ fontSize: '40px', color: 'red'}} />
    </div>
  );
};

export default Input;
import React from 'react';

type TitleStyleProps = {
  styles: string;
};

const Title = (props: TitleStyleProps) => {
  return (
    <div>
       <h1 style={props.styles}>タイトル</h1>
    </div>
  );
};

export default Title;

JavaScriptではCSSを文字列で扱うため型名をstringにして、あとは複数あるプロパティをオブジェクトで囲んで渡しています。

しかし実務ではこのような使い方はしません。

文字列であればCSSとは関係ない文字を書いてもコンパイルエラーで防げないからです。

そのため以下のようにすることが一般的です。

import React from 'react';

type TitleStyleProps = {
  // ここを修正
  styles: React.CSSProperties;
};

const Title = (props: TitleStyleProps) => {
  return (
    <div>
       <h1 style={props.styles}>タイトル</h1>
    </div>
  );
};

export default Title;

React.CSSPropertiesという特別な型が用意されています。

こちらであれば以下のようにCSSに関係ない文字列を渡そうとしてもコンパイルエラーで防ぐことができます。

import React from 'react';

import Title from './components/Title';

const StyleProps = () => {
  return (
    <div>
      StyleProps
      {/* CSSとは関係ないtestという文字列を含めてみる */}
      <Title styles={{ fontSize: '40px', color: 'red', test: 'test'}} />
    </div>
  );
};

export default Input;

また今回参考にした本はこちらになります。

良ければこちらからどうぞ。

  • このエントリーをはてなブックマークに追加