TypeScriptの機能早見表
これってどういう使い方やったっけ?となりがちなものをまとめておきたいと思って書いた記事です。
今後もこれどうやったっけとかあれば追加していく予定です。
これも追加しといてほしいなどあればコメントまでお願いします!
Generics
Generics(ジェネリックス)とは?
まず、Generics
はTypeScript
に限った機能ではない。
C#
やJava
といった言語もGenericsを搭載されている。
Generics
は抽象的な型引数(よくライブラリで見かける<T>
みたいなやつ)を使用して、実際に利用されるまで型が確定しないクラス・関数・インターフェイス
を実現する為に使用される。
もっと砕けた言い方をするとすれば、関数を作る際に引数が必要だけど、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>
参考記事
infer
inferとは?
Conditional Types(条件付き型*)で使用するextends内の推論される型を導入するための宣言。
※条件付き型
↓のこと
T extends U ? X : Y
inferの使われ方
ReturnType
Utility Typesの中のReturnTypeなどでinferが使われている。
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で推測し、型として定義する。
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
条件付きタイプをネストして、次の順序で評価されるパターン一致のシーケンスを形成する。
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
参考記事
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で導入された書き方。
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}`
};
参考記事
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の書き換え
下記コードでも上記で書いたコードと同じ意味になる。
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
を発生させるにはnamespace
かimport *
のどちらか。
好みの問題にはなるが、個人的にはimport *
で事足りるかと。
参考記事
Discussion