はじめに
クラス編の続きです。
知識ゼロから始めるTypeScriptシリーズのラストパート応用編です。
予習はこちらから
知識ゼロから始めるTypeScript 〜基本編〜
知識ゼロから始めるTypeScript 〜関数編〜
[知識ゼロから始めるTypeScript 〜クラス編〜]
(https://qiita.com/yukiji/items/3db06601ece7f080b0d0)
様々な応用パターンを見ていきましょう。
型の互換性
- 型の互換性は常にコンパイラーによって評価されている
- 型の互換性が無いと警告で教えてくれる
- 型の互換性があると型が上書きされる
any型
にstring型
を代入してみる
let fooCompatible: any
let barCompatible: string = 'TypeScript'
console.log(typeof fooCompatible) // undefined
fooCompatible = barCompatible // 代入できる any型はstring型に互換性がある
console.log(typeof fooCompatible) // string
string型
にnumber型
を代入してみる
そうすると互換性が無いのでエラーが起きる
let fooInCompatible: string
let barInCompatible: number = 1
fooInCompatible = barInCompatible // エラー string型にnumber型は互換性が無い
ジェネリクス型
- 抽象的な型宣言
-
引き数の型
と戻り値
の型が一致している必要がある - 扱う型が違うだけで同じ処理をしている関数を使い回す事で、型の実装コストが削減できる
こんな感じでやってることは同じなのに扱う型だけ違う時に大活躍
// 型だけ違うけどやってることはほとんど一緒な関数
const echo = (arg: number): number => {
return arg
}
const echo = (arg: string): string => {
return arg
}
上記の関数をまとめてジェネリクスで定義してみる
型定義は慣習的にT
とすることが一般的らしい
関数の引き数の前に<T>
を定義する
関数を実行するときは<>
内に実行したい型を定義する
// ジェネリクスで定義
const echo = <T>(arg: T): T => {
return arg
}
// 実行
console.log(echo<number>(100)) // 100
console.log(echo<string>('Hello')) // Hello
console.log(echo<boolean>(true)) // true
クラスの場合はクラス名の後ろにジェネリクス型を定義する
class Mirror<T> {
constructor(public value: T) {}
echo(): T {
return this.value
}
}
console.log(new Mirror<string>('Hello').echo()) // Hello
console.log(new Mirror<number>(123).echo()) // 123
型アサーション
- 型の互換性がある時に、手動で型を変換させることができる
- 互換性がある場合のみに限定される
これはコードを見た方が分かりやすいかも
以下の様にany型
で定義された変数name
をlengthメソッド
を使用して、変数length
に代入する場合、本来であれば変数length
はnumber型
である必要があるのにany型
で定義されてしまう。
let name: any = 'foo'
let length = name.length
length = 'foo' // 文字列を代入してもlengthはany型
厳密な型定義がTypeScriptの醍醐味なのでこれは避けたい。
これを実現するのが型アサーション
変数を()
で括りas
の後ろに型を手動で定義する
こうすることで変数length
は本来あるべき姿(number型
)になる
let length = (name as string).length
length = 'foo' // lengthはnumber型なのでコンパイルエラーになる
ちなみ下記の書き方は非推奨
JSXの記法と似ているのが理由らしい
// この書き方は非推奨
let length = (<string>name).length
length = 'foo' // lengthはnumber型なのでコンパイルエラーになる
constアサーション
- 設定した値の再代入しかできない
- データの値を書き換えないことをコンパイラーに伝える
let nickName = 'foo' as const // as constを定義
nickName = 'buzz' // コンパイルエラー
これ普通にconst
で定義すればよくない?と思いつつ
オブジェクトに対しても設定できるのが便利そう
オブジェクトの場合はreadonly
修飾子が設定される
どれだけネストされていてもreadonly
になる
let profile = {
name: 'foo',
height: 175
} as const
// readonlyなのでエラーが発生する
profile.name = 'foo'
profile.height = 175
Nullable Types
- 不確定な状態を持ちうる型
- ある値を設定したい時に、必ずしも設定したい値は確定しているとは限らない
- とりあえずまだわからないという状態を設定しておきたい
TypeScriptにはコンパイルオプションがある
コンパイルオプションはtsconfig.json
で管理している
tsconfig.json
の"strictNullChecks"
をfalse
に設定すると、どんな変数にもnull
を許容できる状態になる
{
"compilerOptions": {
~ 省略 ~
"strictNullChecks": false
}
let profile: { name: string; age: number } = {
name: 'foo',
age: null //nullでもエラーにならない
}
しかし、これは秩序のないコードになってしまう
このような場合はunion型
を使う
let profile: { name: string; age: number | null } = {
name: 'foo',
age: null
}
tscofig.json
でグローバルな設定をするのではなく、union型
を使用して局所的に対応する
インデックスシグネチャ
オブジェクトにプロパティを新規で追加する度に型を定義する手間を防ぐ
基本的な書き方は[index: string]: 型
interface Profile {
// オブジェクトのバリューには以下の型のいずれかを許容する
[index: string ]: string | number | boolean
}
// 空のオブジェクトを設定
let profile: Profile = {}
profile.name = 'buzz'
profile.age = 30
profile.nationality = 'Japan'
console.log(profile) // { name: 'buzz', age: 30, nationality: 'Japan' }
まとめ
応用の使い所がまだわからないけど、完璧より前進