LoginSignup
6
8

More than 3 years have passed since last update.

知識ゼロから始めるTypeScript 〜応用編〜

Posted at

はじめに

クラス編の続きです。
知識ゼロから始めるTypeScriptシリーズのラストパート応用編です。

予習はこちらから:point_down:
知識ゼロから始めるTypeScript 〜基本編〜
知識ゼロから始めるTypeScript 〜関数編〜
知識ゼロから始めるTypeScript 〜クラス編〜

様々な応用パターンを見ていきましょう。

型の互換性

  • 型の互換性は常にコンパイラーによって評価されている
  • 型の互換性が無いと警告で教えてくれる
  • 型の互換性があると型が上書きされる

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型で定義された変数namelengthメソッドを使用して、変数lengthに代入する場合、本来であれば変数lengthnumber型である必要があるのに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を許容できる状態になる

tsconfig.json
{
  "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' }

まとめ

応用の使い所がまだわからないけど、完璧より前進:runner_tone3:

6
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8