👋

TypeScriptの機能早見表

2024/01/08に公開

これってどういう使い方やったっけ?となりがちなものをまとめておきたいと思って書いた記事です。
今後もこれどうやったっけとかあれば追加していく予定です。
これも追加しといてほしいなどあればコメントまでお願いします!

Generics

Generics(ジェネリックス)とは?

まず、GenericsTypeScriptに限った機能ではない。
C#Javaといった言語もGenericsを搭載されている。
Genericsは抽象的な型引数(よくライブラリで見かける<T>みたいなやつ)を使用して、実際に利用されるまで型が確定しないクラス・関数・インターフェイスを実現する為に使用される。
もっと砕けた言い方をするとすれば、関数を作る際に引数が必要だけど、Genericsは型まで引数として取ってしまえーって感じなやつ。

型の安全性とコードの共通化の両立は難しいものです。あらゆる型で同じコードを使おうとすると、型の安全性が犠牲になります。逆に、型の安全性を重視しようとすると、同じようなコードを量産する必要が出てコードの共通化が達成しづらくなります。こうした問題を解決するために導入された言語機能がジェネリクスです。ジェネリクスを用いると、型の安全性とコードの共通化を両立することができます。

https://typescriptbook.jp/reference/generics

具体例

関数ver

型引数が1つ

function hoge<T>(arg: T): T {
  return arg;
}

hoge<number>(1);
// > function hoge<number>(arg: number): number
hoge<string>("fuga");
// > function hoge<string>(arg: string): string

hoge("fuga");
// > function hoge<"fuga">(arg: "fuga"): "fuga"

型引数が複数

function hoge<T, U, P>(arg1:T, arg2: U, arg3: P): P {
  return arg3;
}
hoge("foo", true, 4);
// > function hoge<string, boolean, 4>(arg1: string, arg2: boolean, arg3: 4): 4

function fuga<T, U, P, V>(arg1:T, arg2: U, arg3: P): V {
  return arg;
}
fuga("foo", true, 4);
// > function fuga<string, boolean, number, unknown>(arg1: string, arg2: boolean, arg3: number): unknown

クラスver

class Klass<T> {
  item: T;

  constructor(item: T) {
    this.item = item;
  }

  getItem(): T {
    return this.item;
  }
}

let newKlass = new Klass<string>("hoge");
newKlass.getItem();
// > let newKlass: Klass<string>
// > (method) Klass<string>.getItem(): string

let newKlass = new Klass<number>(5);
newKlass.getItem();
// > let newKlass: Klass<number>
// > (method) Klass<number>.getItem(): number

インターフェイスver

interface Hoge<T, U> {
  foo: T;
  boo: U;
}

let hoge: Hoge<string, number> = {
  foo: "foo",
  boo: 2
}
// > let hoge: Hoge<string, number>

参考記事
https://qiita.com/k-penguin-sato/items/9baa959e8919157afcd4
https://www.typescriptlang.org/docs/handbook/generics.html
https://typescriptbook.jp/reference/generics

infer

inferとは?

Conditional Types(条件付き型*)で使用するextends内の推論される型を導入するための宣言。
※条件付き型
↓のこと

T extends U ? X : Y

inferの使われ方

ReturnType

Utility Typesの中のReturnTypeなどでinferが使われている。
https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype

type ReturnType<T> = T extends (...args: any) => infer R ? R : any;

type Hoge = ReturnType<number>;
// type Hoge = any
type Fuga = ReturnType<() => number>;
// type Hoge = number

Flatten

Tが配列なら、その中身の型をinferで推測し、型として定義する。
https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types

type Flatten<T> = T extends Array<infer U> ? U : T;

const users = [
  { name: 'a', age: 25 },
  { name: 'b', age: 30 }
]
type obj = Flatten<typeof users>
// type obj = {
//     name: string;
//     age: number;
// }

Unpack

条件付きタイプをネストして、次の順序で評価されるパターン一致のシーケンスを形成する。
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types

type Unpacked<T> = T extends (infer U)[] ? U
  : T extends (...args: any[]) => infer U ? U
  : T extends Promise<infer U> ? U
  : T;
  
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string

参考記事
https://qiita.com/iwata-goq/items/677251ce778afcacd33e

as const satisfies

そもそもsatisfiesってなに?

式 satisfies 型
TypeScript4.9で導入された書き方。
式が型にマッチするかをチェックする。

type Hoge = {
  foo: string
};

// 型注釈
const hoge: Hoge = {
  foo: 'foo'
};
// > OK

// satisfies
const hoge = {
  foo: 'foo'
} satisfies Hoge;
// > OK

const hoge = {
  fo: 'foo'
} satisfies Hoge;
// > NG
// > 'fo' は型 'Hoge' に存在しません

const hoge = {
  foo: 1
} satisfies Hoge;
// > NG
// > 型 'number' を型 'string' に割り当てることはできません。

型注釈とsatisfiesの違いは?

結果が型注釈と同じでsatisfiesってなんのために使うのってなりそうだけど、
「型注釈」は推論結果を維持できないのに対し「satisfies」は推論結果を保持できる

const hoge: Hoge = {
  foo: 'foo'
};
// > const hoge1: Hoge

const hoge = {
  foo: 'foo'
} satisfies Hoge;
// > const hoge2: {
// >     foo: string;
// > }

より恩恵がわかりやすいのは下記コードだと思います。

type ColorList = {
  [key in 'red' | 'green' | 'blue']: unknown
};

const colorList: ColorList = {
  red: 'red',
  green: [0, 255, 0],
  blue: 'blue',
};
colorList.green.map((val) => val * 2);
// NG
// > 'colorList1.green''は 'unknown' 型です

const colorList = {
  red: 'red',
  green: [0, 255, 0],
  blue: 'blue',
} satisfies ColorList;

colorList.green.map((val) => val * 2);
// OK

そもそもas constってなに?

式 as const
TypeScript3.4で導入された書き方。

  • リテラル型をwideningしない
  • オブジェクト内のプロパティがreadonlyになる
  • 配列リテラルの推論結果がタプル型になる
const myArray = [
  'hoge',
  'fuga'
]
// > const myArray: string[]

const myArray = [
  'hoge',
  'fuga'
] as const;
// > const myArray: readonly ["hoge", "fuga"]

まとめ

wideningを防止しつつ、型推論結果を維持できる。
定数をexportする際は、予期せぬバグを防ぐためにも、as const satisfiesを使用しておいたほうがいい。

コードでの活用例

const version = "1.0.0" as const satisfies `${number}.${number}.${number}`

const urlList = {
  apple: "https://www.apple.com/jp/",
  google: "https://www.google.com/",
  yahoo: "https://www.yahoo.co.jp/",
} as const satisfies {
  [key: string]: `https://${string}`
};

参考記事
https://speakerdeck.com/tonkotsuboy_com/typescript-4-dot-9noas-const-satisfiesgabian-li-160d825b-7765-4c2b-945d-bb72cc389557
https://zenn.dev/tonkotsuboy_com/articles/typescript-as-const-satisfies

namespace

型に.でアクセスできるもの

React.FCのように親.型名でアクセスができるのがnamespaceの機能。
型をまとめて定義しておきたいときに便利。

namespace Hoge {
  export type Fuga = string;
}

const test: Hoge.Fuga = 'hoge'; // OK
const test: Hoge.Fuga = 10; // NG  型 'number' を型 'string' に割り当てることはできません。

namespaceの書き換え

下記コードでも上記で書いたコードと同じ意味になる。

Hoge.ts
export type Fuga = string;
import type * as Hoge from 'Hoge.ts'

const test: Hoge.Fuga = 'hoge'; // OK
const test: Hoge.Fuga = 10; // NG  型 'number' を型 'string' に割り当てることはできません。

まとめ

namespaceを発生させるにはnamespaceimport *のどちらか。
好みの問題にはなるが、個人的にはimport *で事足りるかと。

参考記事
https://zenn.dev/uhyo/articles/ts-namespace-2023

Discussion