もっとTypeScriptと仲良くなる 逆引き.ts
パッケージ側で定義されているもの・自動生成されるものに乗っかっているだけでも十分に便利なTypeScript。ですが、思わぬエラーに悩まされる・エラーメッセージが読み解けなかったり、やりたいことを実現する手段がわからないこともしばしばあります。
そんな時に役立つなんやかんやが書いてある記事を目指して書きます。 ※随時アップデート
d.tsファイルとは
型定義ファイルです。アンビエント宣言というものを書くのに使われます。
例えばTypeScriptをインストールする際に付属している型定義ファイルlib.d.ts
には window
やdocument
などのアンビエント宣言が書かれています。
アンビエント宣言 declare とは
JavaScriptに型情報を付加するための宣言です。JavaScriptで書かれたパッケージに型情報を定義するために利用されます。
ts
ファイルに書いてしまっても動作しますが、 d.ts
ファイルにまとめるのが一般的です。
packageVar = 'hoge' // Cannot find name 'packageVar'.
declare var packageVar: string
packageVar = 'hoge' // var packageVar: string
d.ts
を利用する
// package.d.ts
declare var packageVar: string
packageVar = 'hoge' // var packageVar: string
もっと詳しく
https://typescriptbook.jp/reference/declaration-file
型の定義方法 interfaceとtypeについて
概ねtype(型エイリアス)を使ってしまえばよいのではないでしょうか。
interfaceでできること → typeでできること
- オブジェクトとクラスの型だけ定義できる → オブジェクト以外の型も定義できる
- extendsキーワードによって拡張ができる → 交差型によって拡張できる
- 同名の宣言で拡張される → エラーになる
interfaceの優位性
ライブラリ側でinterfaceを使用していると利用する側で好きに拡張が行えて便利。
一方でうっかり同名のinterfaceを宣言すると勝手に拡張されて困ることがありそう。
const todo: <ここの型> = {
id: 'id',
title: 'title',
done: false
}
上記のtodoに適した型を、拡張を使いつつ実装するには以下のようなパターンがある。
interfaceの場合:
interface Todo {
id: string
title: string
}
interface TodoWithStatus extends Todo {
done: boolean
}
もしくは
interface Todo {
id: string
title: string
}
interface Todo {
done: boolean
}
typeの場合:
type Todo = {
id: string
title: string
}
type TodoWithStatus = Todo & {
done: boolean
}
もっと詳しく
https://typescriptbook.jp/reference/object-oriented/interface/interface-vs-type-alias
特定の値だけを受け入れる型をつくりたい Enum, ユニオン型, オブジェクトリテラル
じゃんけんの手は3パターンしかない。stringでは広すぎる。
function battleJanken(hand: じゃんけんの手) {
if (hand === 'グー')
...
}
Enum
中身はnumber。
enum Hand {
Gu,
Choki,
Pa
}
function battleJanken(hand: Hand) {
if (hand === Hand.Gu)
...
}
console.log(Hand.Gu) // 0
console.log(Hand.Choki) // 1
console.log(Hand.Pa) // 2
任意のstringを割り当てることもできる。
enum Hand {
Gu = 'グー',
Choki = 'チョキ',
Pa = 'パー'
}
console.log(Hand.Gu) // グー
console.log(Hand.Choki) // チョキ
console.log(Hand.Pa) // パー
存在しない値や、割り当てたstring自体を指定するとエラーになる。
battleJanken(Hand.Rock) // Property 'Rock' does not exist on type 'typeof Hand'.
battleJanken('グー') // Argument of type '"グー"' is not assignable to parameter of type 'Hand'.
ユニオン型
すごくシンプル。
type Hand = 'gu' | 'choki' | 'pa'
battleJanken('choki')
ユニオンに含まれない値を指定するとエラーになる。
battleJanken('rock') // Argument of type '"rock"' is not assignable to parameter of type 'Hand'.
オブジェクトリテラル
const Hand = {
Gu: 0,
Choki: 1,
Pa: 2
} as const
type HandType = typeof Hand[keyof typeof Hand];
function battleJanken(hand: HandType) {
if (hand === Hand.Gu)
...
}
battleJanken(Hand.Gu)
関数に型をつけたい
引数と戻り値を明示したい。
文法は以下の通り。
function 関数名(引数名: 引数の型): 戻り値の型 {
...
}
const 関数名 = (引数名: 引数の型): 戻り値の型 => {
...
}
分割代入引数を使った具体例。型をインラインで書くとごちゃごちゃして読みづらくなりがち。
function hogeFunction({ hogeArgOne, hogeArgTwo }: { hogeArgOne: string, hogeArgTwo: number }): { hogeOne: string, hogeTwo: number } {
...
}
以下の内容と等価です。
type Argument = { hogeArgOne: string, hogeArgTwo: number }
type HogeFunctionResult = { hogeOne: string, hogeTwo: number }
function hogeFunction({ hogeArgOne, hogeArgTwo }: Argument): HogeFunctionResult {
...
}
戻り値が誤っていると、returnにエラーメッセージが表示される。
function hogeFunction({ hogeArgOne, hogeArgTwo }: Argument): HogeFunctionResult {
return {
hogeOne: 'sample',
} // Property 'hogeTwo' is missing in type '{ hogeOne: string; }' but required in type 'HogeFunctionResult'.
}
関数を受け取る型を定義したい
以下のようにコールバック関数を受け取るときとか。
function 関数(処理後に実行する関数) {
const result = await doSomething()
処理後に実行する関数(result)
}
型の書き方は以下の通り。
type AfterDoSomething = (argument: string) => boolean
function 関数(処理後に実行する関数: AfterDoSomething) {
const result: string = await doSomething()
処理後に実行する関数(result)
}
変数の型を特定したい 型ガード
ユニオン型の変数は、typeofとif文を使えば型を特定できる。
URLのクエリパラメータでは取ってきた値が string | string[]
になってたりするので、それを例にしてみる。
function useQueryParameter(): string | string[] {
return 'dummyParameter'
}
// この時点ではsomeQueryParameterはstring | string[]
const someQueryParameter = useQueryParameter()
if (typeof someQueryParameter === 'string') {
// someQueryParameterはstringになっている
}
ユーザー定義型ガード
「こういう条件ならこの変数はこの型だ!」というのを自前で定義できる。
戻り値を argument is someType
にした関数でbooleanを返せばよい。
以下微妙なケースですが目を瞑ってください。
type User = {
type: string
name: string // diff
}
type Admin = {
type: string
id: number // diff
}
// is 演算子を使い、argument is typeを返す関数を定義する
function isUser(object: unknown): object is User {
// booleanを返せばOK
return object.name != null
}
const unknownObjectOfUser = {
type: 'user',
name: 'user name'
}
const unknownObjectOfAdmin = {
type: 'admin',
id: 1
}
if (isUser(unknownObjectOfUser)) {
unknownObjectOfUser // {name: string, type: string}
}
if (!isUser(unknownObjectOfAdmin)) {
unknownObjectOfAdmin // {type: string, email: string}
}
nullチェック
===
ではなく、 == null
でnullとundefined両方チェックできる。
nullとundefinedを区別したい場合は ===
を使う。
const nullVar: string | null
const undefinedVar: string | undefined
nullVar != null // string確定
undefinedVar != null // string確定
プロパティの変更を防ぎたい readonly
readonlyが付与されたプロパティには再代入ができない。
type User = {
readonly id: number
name: string
}
const sampleUser: User = {
id: 1,
name: 'name'
}
sampleUser.id = 2 // Cannot assign to 'id' because it is a read-only property
執筆予定
- never
- ジェネリクス
- 型のループ
- 型の条件分岐
etc.
REF
普段はmofmofという会社でRailsやReact, GraphQLなどを取り入れたアジャイルな受託開発の仕事をしてます。
mofmof 開発チームレンタル
Discussion