zooo-log

読んだものとか、学んだこととか

プログラミングTypeScriptを読んだ(4章)

前回記事(プログラミングTypeScriptを読んだ(2章・3章) - zooo-log)からの続きです。 4章終わりました。 ポリモーフィズムの話は、C++を大学の頃にやっていた時ぶりでした。 久しぶりに読むと、やはり色々忘れていて、すごい時間がかかってしまいました。。

4章(関数)

関数宣言と呼び出し

関数宣言

  • 5種類の書き方が存在する。そのうち、最後の書き方は、絶対に使わないこと(型安全のためのTypeScriptなのに、型安全が保証されなくなる)
// その1:名前付き関数
function greet(name: string) {
  return 'hello ' + name
} 

// その2:関数式
let greet2 =  function(name: string) {
  return 'hello ' + name
} 

// その3:アロー関数式
let greet3 = (name: string) => {
  return 'hello ' + name
}

// その4:アロー関数式の省略記法
let greet4 =  (name: string) => 
  'hello ' + name

// その5:関数コンストラクタ
let greet5 = new Function('name', 'return "hello " + name')
※これはFunction型として定義され、戻り値もパラメータも任意の型を与えることができてしまう
  型安全にならないので、利用しないこと
  • パラメータは、省略可能にするための?修飾子の利用、レストパラメータによる可変長引数が定義できる
  • 省略可能な引数や可変長引数を定義する場合は、引数を列挙する際に一番最後にすること
  • レストパラメータをもつ可変長引数は、関数ごとに一つしか定義できない
// ?修飾子の利用
function sample( firstName: string, lastName?: string ){
  if( lastName )
    return 'Hello ' + firstName + ' ' + lastName
  else
    return 'Hello ' + firstName
}

// 可変長引数の定義
function sample2( ...numbers: number[] ) {
  return numbers.reduce(( total, n ) => total + n, 0 )
}
  • 基本的に、パラメータの型推論は文脈的型付けのケースを除いて実施されない
  • 文脈的型付けが用いられるのは、呼び出しシグネチャが定義されているときと、コールバック関数の2パターン
  • 呼び出しシグネチャは、関数のパラメータと戻り値の型を定義するために用いる
type Log = (message: string, userId?: string) => void // string型の引数を二つ持ち、後者はオプショナル、戻り値はvoid型、という関数であるLog型
  • コールバック関数におけるパラメータは、コールバックを渡す先の関数から推論される

関数呼び出し

  • 関数の実行方式は、以下の4種類があり、それぞれ特徴がある
function add(a: number, b: number): number {
  return a + b
}

add(10, 20) // いつもの呼び出し方
add.apply(null, [10,20]) // applyは2つの引数を持ち、1番目はthisにバインドされ、2番目は関数のパラメータとして展開される
add.call(null, 10, 20) // callは1番目については同様で、2番目以降はaddで必要な引数をそれぞれ記述する必要がある
add.bind(null, 10, 20)() // bindはapplyやcallと違って、関数自体(ここでいうadd)は実行されず、新たな関数が返される
  • bindは、clickHandlerとかでイベント変数渡してそのコンポーネントごとのクリック処理をさせる、とかするためによく使う
  • thisはJavaScriptの世界では呼び出され方によって値が変化してしまう問題がある、TypeScriptは、関数内でthisを用いる場合に、引数時に期待するthisの型を宣言することで、実行時エラーを回避する
// thisがDate型であることを宣言している
function fancyDate(this: Date) {
  // 処理
}

// 以下はコンパイル時にエラーが出る
fancyDate() // voidのthisが与えられるため、コンパイラからDate型のthisを求められる
  • ジェネレータにおける型の定義は、function* 関数名とすることで宣言可能
  • 反復可能オブジェクト= Symbol.iterator というプロパティを持つオブジェクト
  • イテレーター = next というメソッドを定義しているオブジェクト。valueとdoneのプロパティを持つオブジェクトを返す

ポリモーフィズム

ジェネリック

  • <>を利用することで、ジェネリック型パラメータを宣言することができる
  • エイリアスに対してジェネリック型を宣言した場合と、呼び出しシグネチャの一部としてジェネリック型を宣言した場合で、バインドするタイミングが異なる
  • 前者は、型エイリアスの利用時、後者は、関数の実行時となる
  • 完全な呼び出しシグネチャ、省略記法の呼び出しシグネチャ、名前付き関数の呼び出しシグネチャそれぞれに対して宣言の仕方は異なる
  • 完全な呼び出しシグネチャ、省略記法の呼び出しシグネチャは、シグネチャ全体、シグネチャの一部、どちらでもジェネリック型を宣言することができる
  • 名前付き関数の呼び出しシグネチャは、シグネチャ全体に対してのみ宣言ができる
  • シグネチャ全体に行う場合は、シグネチャ名の横 type FIlter<T> = (・・・)function filter<T>(・・・)といった形式
  • シグネチャの一部に行う場合は、スコープの直前 type Filter = <T>(・・・) といった形式
  • ジェネリック型の推論は、すべてをアノテートするか、しないかの2択で、2つのジェネリック型を宣言している場合に、片一方のみアノテートすることは許可されない
  • Promiseを使った処理を書こうとすると、 promise.then( result => result * 4 ) としている場合に、resultの型はresult自身(=関数の引数)を見て推論されるので、unknown型と推論されてしまう(then関数で、引数はunknonw型で定義されているため)
    f:id:zoo666:20200421024220p:plain
    then関数の定義
  • 制限付きポリモーフィズムによって、ただジェネリック型を定義するのではなく、ある条件を満たすジェネリック型として定義する。これによって、すべての型を許容しないが、数種類の型に対して型安全を保証したいケースをサポートしている
  • 例えば、関数に対して、ある型とそのサブタイプの変数のみを許容したい場合に用いる 例:function mapNode<T extends TreeNode> // TreeNode型とそのサブタイプのみ許可
  • 複数の制限をつけたい場合は、型を交差させる 例: function mapNode<T extends TreeNode & Node // TreeNode型とNode型の交差を表現
  • 可変長引数でも、制限付きポリモーフィズムはTypeScriptの型推論によって宣言可能
  • 以下の例では、callが実行されるときに、引数に与えられた関数から「何個のどんな型の引数」と「戻り値の型」を関数定義から推論し、チェックしている
    f:id:zoo666:20200424004555p:plain
    型推論の強力さがわかる
  • ジェネリック型のデフォルトの方を定義することで、デフォルトの型は手動でバインドする必要がない
  • 例: type MyEvent<T extends string = string> // ジェネリック型Tのデフォルトの型をstring型で定義
  • ジェネリック型の配列の要素を個別に指定したい場合、次のように書く `` funciton call(){} // 2番目の要素がstring型の配列のジェネリック型Tを宣言している
  • (TypeScriptの話はないけれど、知らなかったので)b.every( _ => _ === a) // 配列bの要素が 配列aの要素とすべて一致したらtrue、そうでなければfalseを返す、という関数