🐉

TypeScript 学習記録 #8(Reactに関わる型定義)

2021/05/16に公開

TypeScript 学習記録 #7 の続編。
TypeScript 学習記録シリーズの恐らくラスト。
今回のメインテーマ、「Reactに関わる型定義について」

今回主に参考にした教材:

見返す用リスト(主に自分用)

TypeScript基礎学習を完走したので、いつでも見返すことの出来るようにリスト化しておく(5/17)

Reactに関わる型定義

以下のことについて学んだ。

  • 関数コンポーネントの型定義
  • propsの型定義
  • useStateの型定義

以下では、Reactの関数コンポーネントの型定義やpropsの型定義、React hooksの型定義といった、Reactで開発していく上で最低限知っておいたほうが良いだろうというものをまとめた。

関数コンポーネントとpropsの型定義

関数コンポーネントの型定義にはReact.VFCを使う。似た型でReact.FCがあるが、React.FCReact.VFCの違いについては以下の記事が参考になる。現状ではReact.VFC推奨とのこと。

propsの型定義は、React.VFCの型にジェネリクス<T>を指定する形で定義する。
ジェネリクスとして指定したProps型以外の属性を受け取ったり、受け取ったpropsの属性がProps型で定義された属性の全てを満たしていなかったりするとエラーになる。

// 親コンポーネント
const Parent: React.VFC = () => {
  return (
    <div>
      <Child text="Reactに関わる型練習中:D" />
    </div>
  );
};


/* ------------------------------ */
// 子コンポーネントが受け取る`props`の型定義
type Props = {
  text: string;
};

// 子コンポーネント(ジェネリクス<T>を用いて、受け取る`props`の型を指定)
const Child: React.VFC<Props> = (props) => {
  // ここで、以下のように分割代入で`props`を展開するのはよく使いそうなので一応メモ
  // const { text } = props;

  return (
    <div>
      <h1>{props.text}</h1> {/* "Reactに関わる型練習中:D" と表示される */}
      <GrandChild text="孫ちゃんへ">
        孫コンポーネントに`children`としてpropsを渡す練習中:D
      </GrandChild>
    </div>
  );
};


/* ------------------------------ */
// 孫コンポーネントが受け取る`props`の型定義(childrenを含む場合の型定義)
type Props = {
  text: string;
  children: React.ReactNode;
};

// 孫コンポーネント(ジェネリクス<T>を用いて、受け取る`props`の型を指定)
const GrandChild: React.VFC<Props> = (props) => {
  return (
    <div>
      <p>{props.text}</p> {/* "孫ちゃんへ" と表示される */}
      <p>{props.children}</p> {/* "孫コンポーネントに`children`としてpropsを渡す練習中:D" と表示される */}
    </div>
  );
};

追記
propsの型の指定方法について、propsの要素数が1つや2つで、わざわざProps型として型エイリアスを作るまでもない場合は以下のように書くこともできる。
また、React.VFCReact.ReactNodeなどの型は、インポート時に分割代入して受け取ることでよりシンプルに書くことができる。

// 型をインポート(分割代入でより簡略化して型情報を受け取ることができる)
import type { ReactNode, VFC } from "react";

// ジェネリクスの部分に直接`props`が受け取る型を指定することができる(2つ以上の型をワンラインで書く場合は以下のように属性の区切りをカンマで区切る)
// また、型のインポート時に、型を分割代入で受け取っているので余分な`React.`の記述をなくすことができる
const GrandChild: VFC<{ text: string, children: ReactNode }> = (props) => {
  return (
    <div>
      <p>{props.text}</p>
      <p>{props.children}</p>
    </div>
  );
};

useStateの型定義

useStateの型定義は、useStateを宣言する時に型を指定する。
useStateに初期値を設定すると、型推論が効いて自動的に型を付けてくれるため、初期値から型が明確に分かる場合などは無理に型注釈をする必要はない。

// useStateにオブジェクトのステートを持たせるとき用の型定義
type UserData = {
  id: number;
  name: string;
};

const StateSample: React.VFC = () => {
  // 例1:初期値から型が明確な場合は型推論に任せる
  const [count, setCount] = useState(0);
  // 例2:ステートを`null`などでリセットする必要がある場合は、`null`も取ることが出来るようにジェネリクス<T>を用いて型を指定する
  const [count, setCount] = useState<number | null>(0);
  // 例3:ステートにオブジェクトを持たせる場合は、予め型エイリアスなどで型を定義しておき、その型をジェネリクス<T>で指定する(初期値はその型の構造を満たしている必要がある)
  const [user, setUser] = useState<UserData>({ id: 1122, name: "aiko" });

  return (
    <div>
      <h1>{count}</h1>
      <h1>{user.name}</h1>
    </div>
  );
};

eventオブジェクトに対する型定義(イベントハンドラなど)

引数として受け取ることの多いeventオブジェクトの型定義は少し厄介そうだが、onChangeonClickなどをhoverすると、onChangeonClickが引数として受け取るeventオブジェクトの型が書いてあるので、そこを参照するようにする。

例えば、onChangeイベントをhoverすると、(JSX attribute) onChange: (e: React.ChangeEvent<HTMLInputElement>) => voidと書かれているので、そこのe: React.ChangeEvent<HTMLInputElement>の部分を引数eventの型として定義する。onClickならばe: React.MouseEvent<HTMLButtonElement>というように、各イベントによって指定する型が異なるので都度確認が必要。

各イベントの型はこの記事がわかりやすい(参考:any型で諦めない React.EventCallback

以下の例は、インプットフォームに何か文字を入力する度に、onChangeイベントが呼ばれてフォームのステートを更新するという処理をTypeScriptで書いたもの。

const HandleEventSample: React.VFC = () => {
  // ユーザーが入力したテキストを保持するステート(初期値が空文字なので、型推論によって`string`型が定義される)
  const [inputText, setInputText] = useState("");

  // ユーザーが入力したテキストでステートを更新する関数
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setInputText(e.target.value);
  };

  return (
    <div>
      <input type="text" value={inputText} onChange={handleInputChange} />
      <h1>{inputText}</h1>
    </div>
  );
};

JSONの型推論(おまけ)

以下のことについて学んだ。

  • typeofを応用したJSONの型推論
    • typeofを使うことで宣言済みの変数の型を取得することができる。それを応用して、JSONで取得したデータの型をそのまま継承することが出来る。(参考
    • 上記の通り、typeofを上手く使うことで複雑な構造をしたデータの型を手打ちで打つ必要がなくなるらしい。だが、参考にした動画を見た感じ、一度JSONファイルとしてプロジェクトのディレクトリ内に置いた上で、それをもとにそのデータ構造から型を取得するといった流れだったので、データ構造そのものに変化がある時は結局はそこをいじらないといけないのかなと感じた。まあそれでも手打ちでデータ構造を一つ一つ型定義していくよりは遥かに手間は少ないと思う。

ここまでの感想

Reactに関わるTypeScriptの型定義のやり方を学んだ。これで基礎理解としてはほぼほぼ問題ないレベルに来たと思う。正直この1週間で相当強くなった気がする。

あとは実践あるのみ💪

今後の予定

  • 【済】非同期処理について学ぶ
  • 【済】ReactでのTypeScriptの型定義を学ぶ
  • 【済】Next.js×TypeScript×Tailwind×ESLint×Prettierの環境構築方法を学ぶ(入れられたらhuskyとlint-stagedも入れたい)
    • しまぶーさん(@shimabu_it)の素晴らしすぎるテンプレートがあったため一旦割愛(→ こちら
  • 【未】じゃけぇさんのReact入門講座をTypeScriptで作り直す
GitHubで編集を提案

Discussion