0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Typescriptの型 構文まとめ

0
Posted at

一覧

型の書き方

型を組み合わせる

型を絞り込む・上書きする

  • 型ガード:条件分岐で型を絞り込む
    • typeof x === "型名"
    • x instanceof クラス名
    • "プロパティ名" in x
    • function 関数名(x: unknown): x is 型(型述語)
  • never:(): never
    • 何も返さない・到達しないという型
  • アサーション:値 as 型
    • 型を「こうだ」と上書きする
  • 非 null アサーション:値!
    • null / undefined ではないと上書きする

型を後から指定する

値から型を取り出す

  • typeof / keyof:typeof 値 / keyof 型
    • 値から型を、型からキーのユニオンを取り出す
  • as const:値 as const
    • 読み取り専用にして、型を最も狭くする

型を作る

  • インデックス型:{ [key: string]: V }
    • 任意のキーに対する型を決める
  • マップ型:{ [K in keyof T]: T[K] }
    • キーを順に回して型を作る
  • Record<K, V>
    • K が string ならインデックス型、ユニオン型ならマップ型
  • 条件型:T extends A ? X : Y
    • 条件で型が変わる
  • infer:T extends F<infer U> ? U : never
    • 条件型の中で、型の一部を取り出す
  • ユーティリティ型:Partial<T> Pick<T, K> Omit<T, K> など
    • よく使う変換の組み込み版

値の型を確かめる

  • satisfies:値 satisfies 型
    • 型を満たすことだけ確認し、実際の型は初期値のまま残す

アノテーション

const 変数: 型 = 値
function 関数名(引数: 型): 戻り値の型

変数や引数、戻り値に型を宣言する。

const age: number = 20;

function greet(name: string): string {
  return `Hello, ${name}`;
}

any と unknown

let x: any
let x: unknown

any は、型チェックを無効にする型。何をしてもエラーにならない。

let a: any = "text";
a.foo(); // エラーにならない(実行時に落ちる)

unknown は、何でも入るが、使う前に型を確認させる型。
any の代わりに、まず unknown を使う。

let u: unknown = "text";

u.toUpperCase(); // エラー(型が分からない)

if (typeof u === "string") {
  u.toUpperCase(); // OK(string に絞り込まれた)
}

ユニオン型

型A | 型B

「AまたはB」を表す型。| でつなぐ。

type Id = number | string;

const a: Id = 1;
const b: Id = "abc";

ユニオン型の値は、両方に共通する操作しかできない。
片方だけの操作をするには、型を絞り込む(後述の型ガード)。

function show(id: Id) {
  id.toFixed(); // エラー(string には無い)
}

リテラル型

"値a" | "値b"

string ではなく、「この値だけ」という型。
ユニオン型と組み合わせると、選択肢を決められる。

type Mode = "light" | "dark";

const m: Mode = "light"; // OK
const n: Mode = "blue"; // エラー

数値や真偽値でも使える。

type Dice = 1 | 2 | 3 | 4 | 5 | 6;

インターセクション型

型A & 型B

「AかつB」を表す型。& でつなぐ。
両方のプロパティをすべて持つ型になる。

type Name = { name: string };
type Age = { age: number };

type Person = Name & Age;
// { name: string; age: number }

const p: Person = { name: "Taro", age: 20 }; // 両方必要

型ガード

typeof x === "型名"
x instanceof クラス名
"プロパティ名" in x
function 関数名(x: unknown): x is 型

条件分岐の中で、型を絞り込む。
ユニオン型や unknown を、安全に扱うために使う。

function show(id: number | string) {
  if (typeof id === "string") {
    id.toUpperCase(); // string
  } else {
    id.toFixed(); // number
  }
}

絞り込みの書き方は、主に3つ。

書き方 絞り込めるもの
typeof x === "string" プリミティブ型
x instanceof Date クラスのインスタンス
"name" in x プロパティがあるオブジェクト

独自の判定関数には、戻り値に x is 型 と書く(型述語)。
true を返したとき、呼び出し側で型が絞り込まれる。

function isString(x: unknown): x is string {
  return typeof x === "string";
}

const v: unknown = "abc";

if (isString(v)) {
  v.toUpperCase(); // string
}

never 型

function 関数名(): never
const x: never = 値

「値が存在しない」ことを表す型。
never には、どんな値も代入できない。

1. 必ず例外で止まる関数

呼び出し元に戻ってこない関数の戻り値になる。
void は「戻ってくるが値なし」、never は「戻ってこない」。

function fail(msg: string): never {
  throw new Error(msg);
}

function f(x: string | null): string {
  if (x === null) fail("null"); // ここで必ず止まる
  return x; // string に絞り込まれている
}

2. 分岐の漏れをコンパイル時に見つける

条件分岐で型が絞り込まれ、全部処理し終わると、残りの型は never になる。
never に代入して、「残りがない」ことを確認する。

type Shape = "circle" | "square";

function area(s: Shape) {
  if (s === "circle") return 1; // 残り: "square"
  if (s === "square") return 2; // 残り: never

  const _check: never = s; // OK(コンパイル時の検出)
  throw new Error("未対応");
}

Shape に "triangle" を足すと、残りが "triangle" になり、エラーになる。

type Shape = "circle" | "square" | "triangle";

// const _check: never = s;
// エラー("triangle" は never に入らない)
  • throw だけだと、実行して初めて気づく
  • never への代入なら、型を足した瞬間に気づく
  • 両方書くと、コンパイル時と実行時の両方で守れる

※ never は、条件型でも「この要素を消す」ために使う(条件型 で後述)。


アサーション

値 as 型

型を「こうだ」と上書きする。
まず型ガードで絞り込めないか考え、無理なときの最後の手段にする。

例:<input> に入力された文字を取り出す。

document.addEventListener("input", (e) => {
  console.log(e.target.value); // エラー(親の EventTarget に value は無い)
  console.log((e.target as HTMLInputElement).value); // OK(入力された文字)
});
  • e.target の型は EventTarget(親の型)で、value が無い
  • HTMLInputElement(<input> の型)は、その子で、value がある
  • as で、親から子の型に絞っている

非 null アサーション

値!

null / undefined ではないと上書きする。

例:id="app" の要素に、文字を入れる。

document.getElementById("app").textContent = "Hello"; // エラー(null かもしれない)
document.getElementById("app")!.textContent = "Hello"; // OK
  • getElementById("app") の型は HTMLElement | null(要素、または null)
  • null かもしれない値には .textContent を使えないので、エラーになる
  • 要素があると分かっているので、! で「null ではない」と教えて、エラーを消す

※ !true(=false) のように前に!がつくものは、論理否定


ジェネリクス

function 関数名<T>(引数: T): T
type 型名<T> = { プロパティ: T }

型を後から指定できるようにする。
定義側で <T> を宣言し、呼び出し側で T に入れる型を渡す。

function first<T>(arr: T[]): T {
  return arr[0];
}

first<number>([1, 2, 3]); // number
first<string>(["a", "b"]); // string

引数から決まるなら、呼び出し側の <number> は省略できる。

first([1, 2, 3]); // number(推論される)

extends を付けると、T に入れられる型を絞れる。

例:id を持つ値だけ受け取る関数。

function getId<T extends { id: number }>(item: T) {
  return item.id; // extends が無いと、T に id があるか分からずエラー
}

getId({ id: 1, name: "Taro" }); // OK(id がある)
getId({ name: "Taro" }); // エラー(id が無い)
  • T extends { id: number }:T は「id: number を持つ型」だけ
  • 絞ることで、関数の中で item.id が使える
  • id 以外のプロパティ(name など)は、あってもよい

型にも付けられる。

type Box<T> = { value: T };

const a: Box<number> = { value: 1 };
const b: Box<string> = { value: "a" };

型引数にはデフォルト値も指定できる。

type Box2<T = string> = { value: T };

const c: Box2 = { value: "a" }; // T は string

typeof と keyof

typeof 値
keyof 型

typeof は、値から型を取り出す。
型を別に書かなくても、値の定義が元になる。

const user = { id: 1, name: "Taro" };

type User = typeof user;
// { id: number; name: string }

関数にも使える。ReturnType と組み合わせることが多い。

function create() {
  return { id: 1 };
}

type Created = ReturnType<typeof create>;
// { id: number }

keyof は、型のキーをユニオン型にする。

type User = { id: number; name: string };

type Key = keyof User;
// "id" | "name"

typeof と組み合わせると、オブジェクトのキーの一覧が型になる。

const labels = { name: "名前", email: "メール" } as const; // as const は次の節

type LabelKey = keyof typeof labels;
// "name" | "email"

ジェネリクスと組み合わせると、存在するキーだけを受け取る関数を書ける。

function get<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

get({ id: 1, name: "Taro" }, "name"); // string
get({ id: 1, name: "Taro" }, "age"); // エラー

T[K] は、T の K というキーの値の型を取り出す書き方(インデックスアクセス型)。


as const

値 as const

値の後ろに付けて、読み取り専用にする。
型も、string ではなくリテラルの値そのままに狭まる。

const config = { mode: "dark" } as const;
// { readonly mode: "dark" }

config.mode = "light"; // エラー

配列に付けると、タプル型(readonly [...])になる。

const roles = ["admin", "user"] as const;
// readonly ["admin", "user"]

typeof と [number] を組み合わせると、要素をユニオン型に展開できる。

type Role = (typeof roles)[number];
// "admin" | "user"

インデックス型

{ [key: キーの型]: 値の型 }

キーの名前は決まっていないが、値の型は揃っているオブジェクトの型。

例:科目名が決まっていない、点数の一覧。

type Scores = { [key: string]: number };

const scores: Scores = { math: 80, english: 70 };

scores.science = 90; // OK(キーは自由に足せる)
scores.art = "A"; // エラー(値は number だけ)

マップ型

{ [K in keyof T]: 型 }

T のキーを1つずつ K に入れて、新しい型を作る。

例:全プロパティを boolean にした型。

type User = { id: number; name: string };

type Flags<T> = { [K in keyof T]: boolean };

type UserFlags = Flags<User>;
// { id: boolean; name: boolean }

T[K] と書くと、元の値の型を引き継げる。

type Copy<T> = { [K in keyof T]: T[K] };

Record

Record<K, V>

インデックス型とマップ型を、同じ書き方で作れるようにした略記。

  • K が string:インデックス型
  • K がユニオン型:マップ型
type A = Record<string, number>;
// { [x: string]: number }  → インデックス型

type B = Record<"a" | "b", number>;
// { a: number; b: number }  → マップ型

ユニオン型なら、キーの書き忘れや書き間違いをエラーにできる。

const label: Record<"name" | "email", string> = {
  name: "名前",
  email: "メール",
};

条件型

T extends A ? X : Y

条件によって決まる型。
T が A に当てはまれば X、そうでなければ Y になる。

extends の使い方は2つ。

  • アノテーション:T extends "admin" ? Admin : User
    • 条件で、型を Admin か User に決める
  • ジェネリクス:<T extends "admin" | "user">
    • T に渡せる値を絞る
type Admin = { role: "admin"; permissions: string[] };
type User = { role: "user" };

type Account<T extends "admin" | "user"> =
  T extends "admin" ? Admin : User;

type A = Account<"admin">; // Admin
type B = Account<"user">; // User

関数の戻り値に使うと、引数に応じて戻り値の型を変えられる。

declare function createAccount<T extends "admin" | "user">(
  role: T
): T extends "admin" ? Admin : User;

createAccount("admin"); // Admin
createAccount("user"); // User

ユニオン型を渡すと、メンバーごとに条件が評価されて、結果がユニオンになる(分配条件型)。

type ToArray<T> = T extends unknown ? T[] : never;

type C = ToArray<number | string>;
// number[] | string[]

never で「消す」

"a" | never は "a"。

これを使うと、条件型で「この要素を消す」ことができる。
Exclude の中身がこれ。

type MyExclude<T, U> = T extends U ? never : T;

type R = MyExclude<"a" | "b", "a">;
// "a" → never(消える)
// "b" → "b"
// 結果: "b"

infer

T extends 型<infer U> ? U : never

条件型の中で、型の一部を取り出して変数にする。
extends の右側に infer 変数名 と書くと、その位置にある型が変数に入る。

例:配列の、要素の型を取り出す。

type ElementOf<T> = T extends (infer U)[] ? U : never;

type A = ElementOf<number[]>; // number
type B = ElementOf<string[]>; // string
  • (infer U)[]:「何かの配列」の形。U は、その「何か」を入れる変数
  • number[] を渡すと、U は number
  • 配列ではない型を渡すと、never

例:関数の、戻り値の型を取り出す。

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type C = MyReturnType<() => number>; // number
  • (...args: any[]) => infer R:「何かの関数」の形。R は、その戻り値の型
  • () => number を渡すと、R は number
  • 組み込みの ReturnType は、これと同じ作り

ユーティリティ型

Partial<T>
Required<T>
Readonly<T>
Pick<T, K>
Omit<T, K>

よく使う型の変換が、組み込みで用意されている。
中身は、ここまでのマップ型や条件型でできている。

以下 User 型を使って説明

type User = { id: number; name: string; email: string };
ユーティリティ型 意味 結果
Partial<User> 全プロパティを省略可能に { id?: number; name?: string; email?: string }
Required<User> 全プロパティを必須に すべて必須
Readonly<User> 全プロパティを読み取り専用に すべて readonly
Pick<User, "id" | "name"> 指定したキーだけ残す { id: number; name: string }
Omit<User, "email"> 指定したキーを除く { id: number; name: string }
Exclude<"a" | "b", "a"> ユニオンから除く "b"
Extract<"a" | "b", "a"> ユニオンから抜き出す "a"
NonNullable<string | null> null と undefined を除く string
ReturnType<typeof f> 関数の戻り値の型 戻り値の型
Parameters<typeof f> 関数の引数の型 タプル型
Awaited<Promise<string>> Promise の中身 string

組み合わせて使うことも多い。

// id 以外を、更新用に省略可能にする
type UpdateUser = Partial<Omit<User, "id">>;

satisfies

値 satisfies 型

型チェックだけし、実際の型は値から推論する

type Palette = Record<"red" | "green", string | number[]>;

アノテーションだと、red も green も string | number[] 扱いになる。

const a: Palette = {
  red: [255, 0, 0],
  green: "#00ff00",
};

a.red.map((n) => n * 2); // エラー(number[]ではなくstring かもしれない)

satisfies なら、red は number[]、green は string のまま使える。

const b = {
  red: [255, 0, 0],
  green: "#00ff00",
} satisfies Palette;  // bがPaletteの型に合うかは検査(型定義はしない)

b.red.map((n) => n * 2); // OK(number[])
b.green.toUpperCase(); // OK(string)

もちろん、型に合わないとエラーになる(キーの書き間違いなど)。

const c = {
  red: [255, 0, 0],
  gren: "#00ff00", // エラー(Palette にないキー)
} satisfies Palette;

as const と組み合わせると、チェックしつつ最も狭い型になる。

const routes = {
  home: "/",
  about: "/about",
} as const satisfies Record<string, `/${string}`>;

// routes.home の型は "/"

使い分け:

書き方 チェック 型
const x: 型 = 値 する 指定した型
const x = 値 satisfies 型 する 値から決まる型
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?