• ..

TypeScript

    関数

    関数はfunctionから始まる書き方のものとアロー関数どちらも JavaScript のものと同じように書けます。違いは型を追加するだけです。

    plusOne 関数

    ある数値に1足すだけのplusOne関数を定義してみます。まずfunctionのほうで書くと、

    function plusOne(num: number) {
      return num + 1;
    }
    // 暗黙的に戻り値は`number`

    アロー関数で書くと、

    const plusOne = (num: number) => {
      return num + 1;
    };

    のようになります。引数で変数名: 型, 変数名: 型の形で定義していきます。上記のような関数は単純でだいたい何をしているか分かるので TypeScript が暗黙的に設定した戻り値の型(number)で済ますしても大丈夫そうですが、もし複雑で他人が見た時にすぐに判断できないような内容になっていると思ったら戻り値も明確にしておくといいと思います。
    戻り値は(...引数): 型のように引数の括弧の後に型を書きます。上記で書いた関数にそれぞれ戻り値情報を足すと、

    function plusOne(num: number): number {
      return num + 1;
    }
    
    const plusOne = (num: number): number => {
      return num + 1;
    };

    という形になります。

    ジェネリック 関数

    引数がobjectのように単純ではないけれど、ある程度制約を持たせたい(fooというプロパティを持っている構造体を受け取ってその構造体を返したい)ような場合に便利です。ジェネリックは関数名のあと<T, U>のような感じで定義できるもので、これにより型定義を柔軟にすることができます。

    interface A {
      value: number;
    }
    
    interface B extends A {}
    interface C extends A {}
    
    function plusOne<T extends A>(obj: T): T {
      obj.value = obj.value + 1;
      return obj;
    };
    
    const b: B = {value: 1};
    const c: C = {value : 2}
    
    plusOne(b); // {value: 2};
    plusOne(c); // {value: 3};

    extendsを使うことでTAを持っている、またはAに属していなければいけないというような制約付けができます。つまり、plusOne({foo: 1})というなものはvalueプロパティがないので型エラーになります。

    また、上記では動的にTの型を設定しています。

    1. plusOne(b)bが渡ることで(obj: T)(obj: B)ということになります。
    2. (obj: B)からfunction plusOne<B>(obj: B)とジェネリクスが埋まり
    3. つまり、function plusOne<B>(obj: B): B

    のように解決されます。もちろんジェネリクスは自分で設定することもできます。その場合は関数呼び出しの名前のあとにジェネリクスを設定します。

    plusOne<A>(b);

    ただし、このようにジェネリクスを自分で設定できるのはfunctionから書く記法だけです。アロー関数の場合自分でジェネリクスを設定するにはキャストが必要ですが、以下のようにジェネリクスなアロー関数を定義することはできます。

    const plusOne = <T extends A>(obj: T): T {
      obj.value = obj.value + 1;
      return obj;
    };

    動的解決でプロパティを絞り込む

    動的の利点の1つにどのプロパティ値を受け取ったか分かるというのがあると思います。

    interface A {
      str: string;
      num: number;
    }
    
    function getProp<T extends A, P extends keyof T>(obj: T, prop: P): T[P] {
      return obj[prop];
    };
    
    const a: A = {
      str: 'a',
      num: 1,
    }
    
    getProp(a, 'str');
    getProp(a, 'num');

    それぞれ戻り値がgetProp(a, 'str')とした時はstringgetProp(a, 'num')とした時はnumberになります。これはP extends keyof T(つまりここではP extends 'str' | 'num')のPに実際に指定されたプロパティの値を設定してくれることで、T[P]T['str'],T['num']と解決してくれるためです。

    例えばこれは、getProp<A, keyof A>(a, 'num')と使ったらどうなるでしょう。これはPにすべてのプロパティが来る可能性がでてくるので、オブジェクトの値の型の Union 、今回であればstring | numberという戻り型になります。