2022.10.04[Tue]

TypeScript チートシート

  • React
  • Next.js
  • TypeScript

目次

TypeScriptには、型定義や型チェックの方法がたくさんありますが、全てを覚えることは難しいです。
本記事では、その中でも実務ベースで知っておくと役立つ内容に絞ってご紹介します。
ReactやNext.jsでTypeScriptを使用する場合についても記載します。

Utility Types

TypeScriptが提供している型変換を助けてくれるユーティリティ型です。
グローバルにどこからでも使用することができます。
https://www.typescriptlang.org/docs/handbook/utility-types.html

Utility Typesは、一通り目を通すことをおすすめしますが、
その中でも特に使用率が高いものを独断と偏見でご紹介します。

Pick<T, K>

オブジェクト型から特定のプロパティを抜き出して型を作りたい場合に使用します。
オブジェクト型TからK(文字列または文字列のユニオン型)で指定したプロパティのみを含む型を返します。

type User = {
  firstName: string;
  lastName: string;
  createdAt: string;
  updatedAt: string;
};
type Person = Pick<User, "firstName" | "lastName">;

// Person
//   firstName: string;
//   lastName: string;

Omit<T, K>

オブジェクト型から特定のプロパティを除いて型を作りたい場合に使用します。
オブジェクト型TからK(文字列または文字列のユニオン型)で指定したプロパティを除いた型を返します。

type User = {
  firstName: string;
  lastName: string;
  createdAt: string;
  updatedAt: string;
};
type Person = Omit<User, "createdAt" | "updatedAt">;

// Person
//   firstName: string;
//   lastName: string;

ReturnType<T>

関数の戻り値から型を作りたい場合に使用します。Tには関数の型を指定します。

type T0 = ReturnType<() => string>;
// T0 = string;

const f = () => ({ a: 0, b: 'b' });
type T1 = ReturnType<typeof f>;  // typeof で型に変換して指定する
// T1 = { a: number, b: string };

型チェック

プリミティブ型の判定

プリミティブ型の判定にはtypeofを利用します。
typeofを使うと変数の型を取得できるので、そちらを元に判定します。

const foo = getFoo();
// foo = string | number

if (typeof foo === 'string') {
  // foo = string
}

オブジェクト型の判定

オブジェクト型の判定にはinstanceofを利用します。

const foo = getFoo();
// foo = string | string[]

if (foo instanceof Array) {
  // foo = string[]
}

オブジェクトが特定のプロパティを持つか判定

特定のプロパティを持っているかの判定にはinを利用します。

const foo = getFoo();
// foo = string | { error: ... }

if ('error' in foo) {
  // foo = { error: ... }
}

nullとundefinedの判定

== null or != nullで判定することで、nullundefinedの両方をチェックすることができます。
(==自体はjavascriptの機能ですが、TypeScriptできちんと型推論されるので合わせて記載しています。)

const f = (a?: number | null) => {
  if (a == null) return;

  // a = number
};

型の構築

オブジェクトから型を作る

オブジェクトから型を作ることで、enumの代わりに使うことができます。
enumには問題点があるので、こちらの方法を活用すると良いです。
enumの問題点:https://www.kabuku.co.jp/developers/good-bye-typescript-enum

// Bad
enum Mode {
  Eazy = 'eazy',
  Normal = 'normal',
  Hard = 'hard',
}

// Good
const Mode = {
  Eazy: 'eazy',
  Normal: 'normal',
  Hard: 'hard',
} as const;
type Mode = typeof Mode[keyof typeof Mode];

// MODE = 'eazy' | 'normal' | 'hard';

// 反復処理も可能
Object.values(Mode).forEach(mode => { ... });

enumの代わりに単純なユニオン型を使用することもあると思います。
こちらを利用するメリットは、「MODE.EAZYのように書ける」「反服処理が可能」なので、よりenumと同じ感覚で使えます。用途に合わせて使い分けるといいと思います。

React関連

コンポーネントから型を作る

React.ComponentProps<T>を使うと、指定したコンポーネントTのPropsから型を作ることができます。
コンポーネントから型を作ることで、型を再定義しなくていいので保守性が高まります。

type ChildProps = {
  className: string;
  value: string;
};
const Child = (props: ChildProps) => (...);

type Props = React.ComponentProps<typeof Child>;

// Props
//   className: string;
//   value: string;

HTML標準タグ(div, span, aなど)から作ることもできます。
HTML標準タグをラップしたコンポーネントを作る際は、こちらを使うと便利です。

type Props = React.ComponentProps<'button'>; // 文字列リテラルで指定する

const Button = (props: Props) => {
  return <button {...props}>ボタン</button>;
};
Refを明示的にする

ComponentPropsに似た定義で、ComponentPropsWithRef, ComponentPropsWithoutRefがあります。使い方はComponentPropsと同じですが、「refが付いている・付いていない」が明示的に分かるようになっています。

こちらにも書かれていますが、ComponentPropsよりComponentPropsWithRef, ComponentPropsWithoutRefの方が明示的で分かりやすいので推奨されています。
Useful Patterns by Use Case | React TypeScript Cheatsheets

Next.js関連

getStaticPropsからPropsの型を定義する

getStaticPropsからPropsが返されるので、そちらを元に型定義する方法です。
nextが用意しているInferGetStaticPropsTypeを使います。

import { InferGetStaticPropsType } from 'next';

type Data = { ... };

export const getStaticProps = async () => {
  const res = await fetch('https://.../data');
  const data: Data = await res.json();

  return {
    props: {
      data,
    },
  };
};

type Props = InferGetStaticPropsType<typeof getStaticProps>;

const Page = ({ data }: Props) => {
  // ...
};

export default Page;

注意点としては、getStaticPropsGetStaticProps<Props, Query>Propsを省略している形で指定していると、デフォルトの{ [key: string]: any; }で型推論されてしまいます。

import { GetStaticProps, InferGetStaticPropsType } from 'next'

export const getStaticProps: GetStaticProps = async () => {
  // ...

  return {
    props: {
      data,
    },
  }
};

type Props = InferGetStaticPropsType<typeof getStaticProps>;

// Props = { [key: string]: any; }
// `data`が渡されていることを型推論できない

GetStaticPropsPropsを正しく指定することで型推論させることは可能ですが、InferGetStaticPropsTypeに比べて修正範囲が多くなります。なので、InferGetStaticPropsTypeを使った方法が良いです。

GetStaticPropsの場合の修正範囲
・Propsの型定義の修正
・getStaticPropsの処理の修正

InferGetStaticPropsTypeの場合の修正範囲
・getStaticPropsの処理の修正

余談ですが、getStaticPropsの引数でcontextを使う場合、GetStaticPropsの代わりにGetStaticPropsContext<Q>を使うことができるのでGetStaticPropsは使わなくて問題ありません。

import { GetStaticPropsContext } from 'next';

export const getStaticProps = async (context: GetStaticPropsContext) => {
  // ...
};

getServerSidePropsからPropsの型を定義する

getServerSidePropsではInferGetServerSidePropsTypeを使用します。使い方はSSGの時と同じです。

import { InferGetServerSidePropsType } from 'next';

type Data = { ... };

export const getServerSideProps = async () => {
  const res = await fetch('https://.../data');
  const data: Data = await res.json();

  return {
    props: {
      data,
    },
  };
};

type Props = InferGetServerSidePropsType<typeof getServerSideProps>;

const Page = ({ data }: Props) => {
  // ...
};

export default Page;

参考

  • TypeScript: The starting point for learning TypeScript (typescriptlang.org)
  • Data Fetching: Overview | Next.js (nextjs.org)

Share

Strapi v4でカスタムAPIエンドポイントを作成するReactでマルチカラム対応なドラッグアンドドロップUIを作る。dnd-kitの使い方