#はじめに
この前、ReactにおけるTypeScriptの利用に関する記事を書いたら、思った以上にハネて初めてQiitaトレンドにも載りました。
自分以外にもTypeScriptのwarningに悩まされている人たちがたくさんいるんだなぁ...と安心するとともに、もしかしたら**「現場で初めてTypeScript を使うときに最低限何を理解しておくべきなのか」**ということを知りたい人も結構いるんじゃないかと思いました。
ということで今回は、**「初級エンジニアの私がTypeScriptを現場で使う際に理解しておいてよかったこと」**というテーマで記事を書いてみることにしました。
#keyofとtypeofの利用
keyofはオブジェクトのキー(プロパティ名)をliteral union型として取得するときに使います。
以下では、プロパティの型を定義しているobjTypeにkeyofを使うことで、let objKey: "foo" | "bar" | "baz"
とliteral union型のように扱うことができています。
type objType = {
foo: string;
bar: string;
baz: string;
};
let objKey: keyof objType; //let objKey: "foo" | "bar" | "baz"
また、typeofでは宣言済み変数の型を取得することができます。
以下のように、typeof myObjでmyObjの型を取得して別のanotherObjという変数に型注釈することができます。
typeofでオブジェクトの型を取得した後に、keyofでオブジェクトのプロパティをliteral union型として取得することもできるので、これらの組合せを使いこなすとかなり便利です。
const myObj = {
foo: 'FOO',
bar: 'BAR',
baz: 'BAZ',
};
let anotherObj: typeof myObj = { foo: 'foo', bar: 'bar', baz: 'baz' }; // {foo: string; bar: string; baz: string;}
let myObjKey: keyof typeof myObj; // "foo" | "bar" | "baz"
myObjKey = 'bar'; //OK
myObjKey = 'baaar'; //エラー
#constで宣言した変数への厳密なliteral型の付与
const
で宣言された値は再代入を行うことができない(=値が固定値)ので、適用される型推論はliteral型になります。
しかし、通常のliteral型とは異なり、let
で宣言した変更可能な変数に代入するとliteral型ではなくなってしまいます。
例えば以下のように、const
で宣言したwideningZeroをlet
で宣言したzeroAに代入してみると、let zeroA: number
のようにnumber型になってしまっています。
一方、const nonWideningZero: 0
のように型注釈(アノテーション)した変数や、const asNonWideningZero = 0 as 0
のように型アサーションした変数をlet
で宣言した変数(zeroB, zeroC)に代入すると、literal型のまま保持されます。
厳密なliteral型を期待する場合には、このように明示的に型を付与してあげる必要があります。
const wideningZero = 0; //const wideningZero: 0
const nonWideningZero: 0 = 0; //const nonWideningZero: 0
const asNonWideningZero = 0 as 0; //const asNonWideningZero: 0
let zeroA = wideningZero; //let zeroA: number
let zeroB = nonWideningZero; //let zeroB: 0
let zeroC = asNonWideningZero; //let zeroC: 0
#constアサーション
constアサーションは、宣言時にハードコーディングした値をliteral型として適用するために使うものです。
例えば、以下のexample1
をtuple型として定義(アサーション)するには以下のようになりますが、これだと冗長な記述になってしまいます。
const example1 = [false, 1, '3'] as [false, 1, '3'] //const example1: [false, 1, '3']
一方、以下のようにas const
と記述すると、tuple型を簡単にアサーションすることができます。
const example2 = [false, 1, '3'] as const //const example2: readonly [false, 1, '3']
#オブジェクトの型推論
const
で宣言される変数はliteral型になりますが、オブジェクトのプロパティは再代入可能なのでliteral型として推論されません。
const obj = {
foo: false,
bar: 1,
baz: '2',
};
obj['foo'] = true; //literal型ではなくboolean型として推論される
各プロパティをliteral型として扱うためには、型アサーションを利用する必要があります。
以下のように、falseの型アサーションを行ったfooプロパティにtrueを代入しようとするとエラーになります。
const obj = {
foo: false as false,
bar: 1 as 1,
baz: '2' as '2',
};
obj['foo'] = true; //エラーになる
#インデックスシグネチャ
以下のように、nameプロパティをもったUser型を変数(UserInfo)に付与すると、ageのような定義されていないプロパティを代入することはできません。
オブジェクトに動的に値を追加したい場合、インデックスシグネチャを使用します。
type User = {
name: string
}
const UserInfo: User = {
name: 'Yamada',
age: 45 //Error
}
インデックスシグネチャは[k: string]: string | number
のように記述します。
stringとnumberのunion型にしているのは、nameのstring型と互換性がないとname側でエラーが発生してしまうためです。
インデックスシグネチャを利用することで、以下のように、number型のageやstring型のhobbyなどのプロパティを動的に追加することができます。
type User = {
name: string
[k: string]: string | number //nameのstring型と互換性が必要
}
const UserInfo: User = {
name: 'Yamada',
age: 40,
hobby: 'SASUKE'
}
const x = UserInfo.name //string
const y = UserInfo.age //string | number
const z = UserInfo.hobby //string | number
オブジェクトのプロパティ名に制限をかけたい場合には、literal union型(以下のQuestion)を定義した上でin
キーワードを使用します。
また、[K in Question]?
のようにオプショナルを表す?
を付与することで、const x = userInfo.questionnaires['walking']
のような意図しないプロパティの参照を防ぐことができます。
要は[K in Question]: Answer | undefined
のように、literal union型でundefined型を付与するのと同じことですね。
type Question = 'exercise' | 'sleeping'
type Answer = 'mighty' | 'lot' | 'few' | 'entirely'
type User = {
name: string
questionnaires: { [K in Question]?: Answer }
}
#おわりに
記事の内容についてもっと深い内容を知りたい方は、参考資料の書籍をご覧ください。
オライリーよりおすすめです。
#参考資料