• ..

TypeScript

    string 型

    stringは TypeScript で文字列だということを表現する型です。以下は変数の宣言時に一緒に使っている例ですがstringを使うと、その変数は変なことをしなければstringが入ってるということにできます。

    const str: string = 'text';
    // Type '123' is not assignable to type 'string'.
    const numStr: string = 123;

    TypeScript は賢いので型を指定せず入れてもstringとしてくれます。

    let str = 'foo';
    // ok
    str = 'bar'

    また String Literal Types と言われる、文字列だけどある特定の文字列を型として扱うことができます。この場合はある特定の文字列しか入れることができません。
    これは型定義時にある特定の文字列stringの代わりに使うだけです。

    const foo: 'foo' = 'foo';
    // Type '"foo"' is not assignable to type '"bar"'.
    const bar: 'bar' = 'foo';
    // typescript@^3.4.0 から以下でも同じ意味
    const foo = 'foo' as const;

    Union と一緒に使うことで「Aという文字列」か「Bという文字列」というように扱える値を増やすことができます。以下の'foo' | 'bar'は、'foo''bar'だけ扱える型という意味になります。

    // ok
    let foo: 'foo' | 'bar' = 'foo';
    // ok
    foo = 'bar';
    // Type '"baz"' is not assignable to type '"foo" | "bar"'.
    foo = 'baz';

    この String Literal Types はstringを継承している型ということなので、stringで宣言されている部分でも代入したり渡したりできます。

    const fooOrBar: 'foo' | 'bar' = 'foo';
    let value: string = fooOrBar;

    文字列に絞り込む

    ある Union 型などで「この値はstringか?」を調べるにはtypeof value === 'string'が使えます。

    const a: string | number = 'foo';
    
    if (typeof a === 'string') {
      a; // a: string
    } 

    number 型

    number型は、 TypeScript で数値だと表現する為の型です。他の言語のように小数点のものは別名だったりはしません。 TypeScript ではすべての数値がnumberになります。

    numberを指定すると、代入を数値だけに絞れます。

    const num: number = 123;
    // Type '"123"' is not assignable to type 'number'.
    const strNum: number = '123';

    TypeScript は賢いので以下のように指定せずに代入しても、そこから推論してnumber型としてくれます。

    const num = 123;
    // ok
    num = 456

    boolean 型

    booleanは、 TypeScript で真偽値だと表現する為の型です。

    以下のようにbooleanを指定すると、代入を真偽値だけに絞れます。

    const bool: boolean = true;
    // Type '"true"' is not assignable to type 'boolean'.
    const boolStr: boolean = 'true';

    TypeScript は賢いので以下のように型を省略しても推論してbooleanとして扱ってくれます。

    let bool = true;
    // ok
    bool = false

    object 型

    オブジェクトな値なら何でも入れることができる型です。
    型にはobjectを使います。

    const obj: object = {};

    構造化したオブジェクトを Interface で定義する

    ある程度構造化したobjectが使いたいなら、interfaceで型を作ります。以下はfoobarというプロパティを持つValue型の例です。

    interface Value {
      foo: number;
      bar: number;
    }
    
    const value: Value = {
      foo: 0,
      bar: 1
    };

    ちなみに、以下はbazは持っていないプロパティなのでエラーになります。結構厳密です。

    const value: Value = {
      foo: 0,
      bar: 1,
      baz: 2,
    };

    bazが静的であればValue型にbazを追加することで直せます。また動的な場合はインデックスシグネチャを型に追加してあげます。

    interface Value {
      [key:string]: number;
      foo: number;
      bar: number;
    }

    Interface で構造化したもののプロパティを変数からアクセスdけいるようにする

    例えば'foo'はどこからからやってきた変数propに入っている値だとして、value[prop]で取れそうですがこれはエラーになります。僕的にはできるようにしてほしいなと思うのですが、とりあえ今(2019-01)時点ではできません。
    これができるようにするにはインデックス署名をその Interface に追加する必要があります。

    以下は、value[prop]で値を取れるようにしたものです。
    [index: string]: number | string部分がインデックス署名ですが、これはstringが来た時にfoobarの値どちらかが取得できるということを表しています。このように、値側は取得できるうるすべての値をUnionなどで羅列する必要があります。

    interface Value {
      [index: string]: number | string;
      foo: number;
      bar: string;
    }

    また[index: string]部分は[index: number]とも定義できます。これはvalue[number]のようなアクセスの為に必要です

    Interface のプロパティを Partial 化する

    1部だけ

    Partial Propertyとは、あってもなくてもどちらでも構わないというようなプロパティのことです。これはプロパティ名の後:の代わりに?:とするだけです。

    試しにbarを Partial 化するとこうなります。無くてもエラーになりません。

    interface Value {
      foo: number;
      bar?: number;
    }
    
    const value: Value = {
      foo: 'foo'
    }

    すべてのプロパティを Partial 化

    すべてを Partial 化したいなら全てに一々?を付ける必要はありません。Partialという TypeScript 自身が提供している Generics 化を使うことですべてを Partial 化できます。

    interface Value {
      foo: number;
      bar: number;
    }
    
    type PartialValue = Partial<Value>;

    このpartialValueは、次のように書くのと同じ意味になります。

    interface PartialValue {
      foo?: number;
      bar?: number;
    }

    Record 型で一気に定義する

    すべてのプロパティが同じ型の時便利なのがRecord<P extends string, V>型です。Pは、UnionstringVはそれぞれプロパティとなる値を設定します。つまり、P = 'foo' | 'bar'V = numberの場合、それは{foo: number, bar: number}となります。
    つまり、次の2つはまったく同じ型になります。

    type Value = Record<'foo' | 'bar', number>;
    interface Value {
      foo: number;
      bar: number;
    }

    Interface の extends を使ってある構造を継承した新たな構造を作る

    以下はValueのプロパティにbarを増やしたものになります。extendsで継承しているのでfoobarの記述はいりません。

    interface ValueWithBaz extends Value {
      baz: number;
    }
    // 同じ
    interface ValueWithBaz {
      foo: number;
      bar: number;
      baz: number;
    }

    Interface の単純分割

    同じ名前でインターフェースを定義すると勝手にtype A = Foo & {children: unknown[]}のようにインターセプトしたタイプのように設定することができます。

    interface A {
      foo: string
    }
    
    interface A {
      children: unknown[];
    }
    
    const a: A = {
      foo: 'foo',
      children: []
    };
    

    厳密な定数オブジェクトにする

    例えば以下のとき、

    const a = {
      foo: 'foo',
      bar: 123,
      baz: true
    };

    自動的に設定される型は{ foo: string; bar: number; baz: boolean }です。
    この時、オブジェクト宣言の後ろにas constと付けてあげます。

    const a = {
      foo: 'foo',
      bar: 123,
      baz: true
    } as const;

    すると型は{ readonly foo: 'foo'; readonly bar: 123; readonly baz: true }とすることができます。

    配列

    配列の型の定義はArray<T>型を使う方法と型の後ろにT[]を付ける方法の2通りあります。Array<T>T[]の違いはありません。

    const foo: Array<string> = [
      'foo',
      'bar',
      'baz',
    ];
    
    const foo2: string[] = foo;

    タプル

    もし個数や順番、または型がバラバラな型の集まりが決まっているのであれば[A, B, C, D, E][]の間に型を羅列した型を定義します。

    const foo: [string, number, boolean] = [
      'foo',
      123,
      true,
    ];

    もしタプルを定数化したいなら、指定する型も厳密にしてあげるだけです。

    const FOO: readonly ['foo', 123, true] = [
      'foo',
      123,
      true,
    ];

    ですがこれであれば後ろに値宣言後にas constを付けることで同じ意味にできます。

    const FOO = [
      'foo',
      123,
      true,
    ] as const;
    // foo: readonly ["foo", 123, true]

    配列の絞り込み

    Union 型などを配列に絞り込みたい場合は、Array.isArrayメソッドを使います。

    const foo: string | string[] = [];
    
    if (Array.isArray(foo)) {
      foo; // foo: string[]
    }

    keyof

    TypeScript 独自のシンタックスです。オブジェクト型からその型が持つプロパティ名からなる文字列リテラル型の Union が取得できます。

    interface Value {
      foo: string;
      bar: string;
      baz: string;
    }
    
    type ValueKey = keyof Value;
    // type ValueKey = "foo" | "bar" | "baz"

    void 型

    何も当てはまらない・無いを表現する型です。

    void 型を関数の戻り値で使う

    関数の戻り値に使うときは、returnも使わないような本当に何も返さない時に使います。基本省略することがほとんどかなと思います。

    const fn: (text: string) => void = text => {
      console.log(text);
    }

    void 型を関数の引数に使う

    引数に使うとその引数は無いものとすることができます。 Pertial (?)を付けなくても渡さなくていい引数扱いになります。

    const fn = (arg: void) => {/* ... */};
    fn(); // ok
    
    const fn2 = (arg: undefined) => {/* ... */};
    fn2(); // Expected 1 arguments, but got 0.
    fn2(undefined);

    関数の引数に使うのはどういう時か

    恐らくこれは自分でそういう関数型をわざわざ定義することは99%無いです。
    これが便利なのは恐らく、自作の Generic なタイプから引数型に変換するような時に Pertial 化(?)を付けなくてもしなくても引数の数を(型チェック的には)制御できることだと思います。

    interface Arguments {
      foo: string;
      bar: void;
    }
    
    const foo = (val: Arguments['foo']) => /* ... */;
    const bar = (val: Arguments['bar']) => /* ... */;
    
    foo('foo'); // ok
    foo(); // Expected 1 arguments, but got 0.
    
    bar(); // ok
    bar('bar'); // Argument of type '"bar"' is not assignable
                // to parameter of type 'void'.

    unknown 型

    anyと似ていますがタイプセーフな型です。型を特定するまであらゆる操作が制限されるみたいな型です。unknownは存在しうるすべての型の Union のようなものかなと思います。(anyも)

    const foo: unknown = 'foo';
    // これは以下のようなもの(`IDO5`などは適当)
    const foo: IDO5 | n0lM | 'sowe' | ... | string | number | boolean | undefined | null = 'foo'

    上記では'foo'を入れていますが、これは Union にstringもしくは'foo'型が含まれる(イメージ)ので代入することができます。

    ですがunknownの特徴として、unknownのままではプロパティやプロトタイプメソッドなどすべてのアクセスが制限されてしまいます。

    const a: unknown = 'a';
    a.foo
    // Object is of type 'unknown'.

    型を特定する

    そのまま使えるのならそのままでいいかもしれないですが、unknownで何かしようと思ったら使う前にまず型変換してあげる必要があります。

    例えばstring | undefinedという型valueの時、if (value !== undefined) {...}とすることで...ではvaluestring型だということに絞り込むことができます。

    「存在しうるすべての型の Union のようなもの」と言ったようにunknown肯定的if文などで特定の型にキャストすることができます。また他の同じようにtypeofinstanceof,inなどを使った絞り込みも効いてくれます。

    例えば以下のようにunknown型をstringに絞り込めます。

    const a: unknown = 'a';
    // const a: unknown
    if (typeof a === 'string') {
      // const a: string
    }

    タイプガードの場合。これはFooBarに絞り込みます。

    interface Foo {
      foo: string;
    }
    interface Bar {
      bar: string;
    }
    
    const isFoo = (value: unknown): value is Foo => {
      return typeof value === 'object' && typeof (value as Foo).foo === 'string';
    }
    
    const isBar = (value: unknown): value is Bar => {
      return typeof value === 'object' && typeof (value as Bar).bar === 'string';
    }
    
    
    const a: unknown = {};
    if (isFoo(a)) {
      // const a: Foo
    } else if (isBar(a)) {
      // const a: Bar
    }

    使い所

    1. スコープの中で最初からあるわけではないデータ
    2. API をinterfaceで定義していく時に後回しにしたいプロパティの型
    3. 関数のオーバーロード定義時ただ引数の数を合わせたいだけの時の型
    4. 型の種類が多い(そして数が不明かつ API ドキュメントの無いカオスプロジェクト)

    みたいな所とかは一旦unknownで置いておいて、使う時になってから実際の型に置き換えたり、キャストしたりするといいかなと思います。

    例えばhttp.createServerbody-parserなどの組み合わせで API を実装する時にbodyParser.json()(req, res, next)が終わるまではreq.bodyへのアクセスはさせたくありません。
    そのような時に、最初{body: unknown}としておいてbodyParserのミドルウェア部分が終わった後{body: 実際の方}と設定してあげたりするといいかなと考えます。ミドルウェア部分が終わる前のreq.body

    あと最近だと強制型変換などで一旦unknownに置いてから目当ての型に再度変換しないと駄目な ESLint ルールとかありしますね。

    const a: Foo = {...};
    (a as unknown) as Bar

    any 型

    unknown型にとても良く似ています。違うのはあらゆるプロパティにアクセスできる点です。この型を使うと JavaScript のコードを書いてるように型を気にせず書くことができます。

    anyは以下のように、そんなプロパティは無いので普通なら型エラーになる所ですが、型エラーにならずに記述することができます。もちろんこれは、実行すると「 Uncaught TypeError: Cannot read property 'b' of undefined (undefinedからbプロパティへアクセスはできせん)」エラーになります。

    const num: any = 1;
    num.a.b.c
    // Uncaught TypeError: Cannot read property 'b' of undefined

    使い所はディレクターに実装を急かされてるときです。

    never 型

    neverは、 TypeScript で「あるはずない」を表現するための型です。

    正常に return されない可能性のある関数で使う

    これは例えば、

    1. エラーでthrowされる
    2. process.exitで途中で処理を途中で中断する

    などの時neverが当てはまります。

    const fn: () => never = () => {
      throw new Error('...');
      // process.exit(1)
    }

    ただDefinitelyTyped/DefinitelyTypedで型ファイルを追加しようとした時に、never型を使っていたら「neverいらないから削除して」と言われたのでvoidと同じで基本省略する形で統一すればいいかもしれません。

    Conditional Types でありえない状態の戻り型に指定

    Conditional Types で型を変換する時にstringnumberしか期待しないのにFooという良くわからないものが来てしまった時に、想定外なのでneverに設定するということが良くあると思います。

    type StringOrNumber<T> =
      T extends string
        ? string
        : T extends number
        ? number
        : never

    enum

    enum のイメージ

    同レベルで細分化できるものを羅列したものです。例えばGitHubには Free プランと$7 の Pro プランがあるのでこれをenum化するとこうなります。

    enum Plan {
      Free,
      Pro
    }

    型として

    例えば以下Personインターフェースは人の名前とプラン情報を持ちます。planではPlanの何かしらの値を渡さなければならないので値を限定できます。

    interface Person {
      name: string;
      plan: Plan;
    }
    
    const nju33: Person = {
      name: 'nju33',
      plan: Plan.Pro
    };

    生成後

    上のPlanはトランスパイル後このように変換されます。うーん、なんだこりゃ。

    var Plan;
    (function(Plan) {
      Plan[(Plan['Free'] = 0)] = 'Free';
      Plan[(Plan['Developer'] = 1)] = 'Developer';
    })(Plan || (Plan = {}));

    コードがここだけだとして、ちと簡単にしてみます。

    var Plan = {};
    Plan['Free'] = 0;
    Plan[0] = 'Free';
    Plan['Developer'] = 0;
    Plan[1] = 'Developer';

    なるほど。Plan.Freeではその値が、Plan[値]ではそのプロパティ名が取れるようです。このようなオブジェクトということですね。

    {
      0: "Free"
      1: "Pro"
      Free: 0
      Pro: 1
    }

    値からも取得できるようにすることをリバースマッピングと言うようです。

    const enum

    これを const enum とするとリバースマッピングが無くなり、Plan.Freeのようなオブジェクト値へアクセスの形でしか使えなくなります。

    const enum Plan {
      Free,
      Pro
    }
    
    console.log(Plan.Free);

    これがトランスパイルされるとこうなります。

    console.log(0);

    定義も無くなっています。外からの影響が無いものに使うと良いと思います。

    カスタム値

    =を使って値を代入することができます。

    数値

    enumはデフォルトで0,1...が入るようになっているのでもちろん入れれます。ただ好きな値を入れれます。

    enum Plan {
      Free = 2
      Pro = 3
    }

    文字列

    文字列を代入できます。1つでも文字列にした場合、それ以外の値も文字列を入れる必要があります。。

    enum Plan {
      Free = 'Free',
      Pro = 'Pro'
    }

    オブジェクト

    残念ながらオブジェクトは代入できません(でした)。ざっくり調べた感じでは代入できるのは上記の数値と文字列だけのようです。

    オブジェクトをなんとかenumのように使いたい場合は、その列挙値をswitchに渡してその値でオブジェクト値をたぐり寄せる感じで実装する方法があります。

    enum Plan {
      Free
      Pro
    }
    
    const getPlanObject = (plan: Plan) => {
      switch (plan) {
        case Plan.Free: {
          return {/*...*/};
        }
    
        case Plan.Pro: {
          return {/*...*/};
        }
      }
    }

    前の何かしら値を使う

    定義の中でなら前に宣言したある enum の値を使って新しい値を作ることができます。

    enum Foo {
      A = 1;
      B = A + 1;
      C = A + 2;
    }

    これはこのように、計算済みの値が使われる形でトランスパイルされます。

    var Foo;
    (function (Foo) {
        Foo[Foo["A"] = 1] = "A";
        Foo[Foo["B"] = 2] = "B";
        Foo[Foo["C"] = 3] = "C";
    })(Foo || (Foo = {}));

    Intersection

    単にその型の持つプロパティを足し算したようなものです。JavaScriptが分かる人ならオブジェクト同士をObject.assignしたようなものだと想像すると分かりやすいと思います。

    Intersection の例

    interface Foo {
      foo: string;
    }
    
    interface Bar {
      bar: string;
    }
    
    type FooBar = Foo & Bar;
    // 以下と同等
    // interface FooBar {
    //   foo: string;
    //   bar: string;
    // }
    
    const fooBar: FooBar = {
      foo: 'foo',
      bar: 'bar'
    };

    使用場所

    個人的には、自分で渡したいのはTだけだけど、実際の関数渡されるのはT & Uにしたいというのような場合に使っています。ここで言うUという型は、デフォルト値だったり別な所から渡ってきたりするものです。

    interface Props {
      something: any;
    }
    
    interface RouterProps {
      history: any;
    }
    
    const Router = {
      // モック
      get(): RouterProps {
        return;
      }
    }
    
    // こっちは自分から呼び出すことはしない
    const realRender: (props: Props & RouterProps) => null = props => null;
    const render: (props: Props) => null = props => {
      const routerProps = Router.get();
      const realProps = {
        ...props,
        ...routerProps,
      };
      return realRender(realProps);
    };

    例えばこれはDecoratorで同じプロパティを使う時とか、ReactのContextとpropsで一緒に渡したい時とかに使うことができます。

    Union

    TUどちらでも扱えるときに間に|を挟んでT | Uという感じで記述します。飲む関数の場合Water | Curryどちらも成り立つというような感じです。

    Union の例

    以下はNumberでキャストするのでstringnumberどちらでも大丈夫です。

    const onePlus = (num: string | number) => Number(num) + 1;
    console.log(onePlus('1')); // 2

    次もそれぞれに存在するプロパティを使うので大丈夫な関数です。

    interface Value {
      value: number;
    }
    
    interface Foo extends Value {
      foo: any;
    }
    
    interface Bar extends Value {
      bar: any;
    }
    
    const onePlus = (value: Foo | Bar) => value.value + 1;

    JavaScript 構文で絞り込み

    次のコードはstring | string[]を受け取って必ずstring[]で返したい関数です。つまりstringだった時は配列に変換してあげる必要があります。   こういう時に使うのがtypeofArray.isArrayです。TypeScriptは賢いのでこの辺りを使うと型を絞り込んでくれます。

    const ensureArray = (value: string | string[]) => {
      if (typeof value === 'string') {
        // ここで`value`は`string`型
        value = [value];
      }
      // または `!Array.isArray(value)`を使う
      return value;
    }

    他に型の絞り込みに使えるもので

    • instanceof
    • in
      • 例えば 'foo' in valueFooに絞込

    があります。

    複雑な絞り込みは Type Guards を使う

    複雑な絞り込みの時はType Guardsを使うとスッキリします。これにはarg is Tという奇妙な戻り値を返すようにします。
    例えば以下のisFooは値のtype: 'foo'hogeプロパティを持ってたらFooということにしてる関数です。

    interface Foo {
      type: 'foo';
      hoge: string;
    }
    
    interface Bar {
      type: 'bar';
      fuga: string;
    }
    
    function isFoo(arg: Foo | Bar): arg is Foo {
      return (
        arg.type === 'foo' && Object.prototype.hasOwnProperty.call(arg, 'hoge')
      );
    }
    
    // モック
    const arg: Foo | Bar = undefined as any;
    if (isFoo(arg)) {
      // ここで`arg`は`Foo`型
      console.log(arg.hoge);
    }

    Conditional Type

    Tというジェネリック型がIFooならA、違うならB」というように与えられた型によって分岐した結果の型を返すことができます。

    基本

    例えばこれはこのような形です。

    /**
     * P(arent), C(hild)
     */
    type T<U, V> = C extends P ? U : V;

    これはCPの拡張形式ならU型に、違うならV型になるという意味になります。

    同じプロパティを持ってればtrueな感じ、なので継承はtrue

    class P {}
    class C {}
    // type T = U
    
    class P {
      foo: any;
    }
    class C extends P {}
    // type T = U
    
    class P {
      foo: any;
    }
    class C {}
    // type T = V

    Generics で複数の型を提供する

    if-else-if-elseのように 2 つ以上の条件も設定できます。

    これは属すからtrueな感じ

    enum StatusCode {
      BadRequest = 400,
      NotFound = 404,
      InternalServerError = 500
    }
    /** @alias */
    type Code = StatusCode;
    
    interface BadRequestResponse {
      code: Code.BadRequest;
      fields: any[];
    }
    
    interface NotFoundResponse {
      code: Code.NotFound;
      errors: any[];
    }
    
    interface InternalServerErrorResponse {
      code: Code.InternalServerError;
      message: any;
    }
    
    type Response<C extends Code> = Code.BadRequest extends C
      ? BadRequestResponse
      : Code.NotFound extends C
      ? NotFoundResponse
      : InternalServerErrorResponse;
    
    type A = Response<Code.NotFound>;
    // type A = NotFoundResponse
    type B = Response<Code.InternalServerError>;
    // type B = InternalServerErrorResponse

    Generic Type の再帰はできない

    以下のReturnTypeRecursiveは関数の場合は再帰的に結果を辿っていって最終的な結果の型を取得しようと思ったものですが、これは「循環参照してますよ(Type alias 'ReturnTypeRecursive' circularly references itself.ts(2456))」エラーを起こします。

    type ReturnTypeRecursive<
      Fn extends (...args: any[]) => any
    > = ReturnType<Fn> extends (...args: any[]) => any
      ? ReturnTypeRecursive<Fn>
      : ReturnType<Fn>;

    Generic じゃないなら再帰できるようです

    type AFunction = (...args: any[]) => any | AFunction;

    ネストを深くすればそのようなことはできる

    ネストが浅いのであれば使用したいケースまでの再帰ライクに定義するといった使い方は可能です。

    export type ReturnTypeRecursive<Fn extends AFunction> = ReturnType<
      Fn
    > extends AFunction
      ? ReturnType<ReturnType<Fn>> extends AFunction
        ? ReturnType<ReturnType<ReturnType<Fn>>> extends AFunction
          ? never
          : ReturnType<ReturnType<ReturnType<Fn>>>
        : ReturnType<ReturnType<Fn>>
      : ReturnType<Fn>;
    
    const aFn = () => () => () => 'foo' as const;
    type A = ReturnTypeRecursive<typeof aFn>;
    // type A = "foo"

    infer で一部分の型を取得する

    以下は関数の戻り値を取得できます。inferはパターンマッチみたく、その場の型を条件結果の部分で使うことができます。

    type ReturnType<T extends Function> = T extends (...args: any[]) => infer F
      ? F
      : never;
    type A = ReturnType<() => number>;
    // type A = number;

    実はReturnTypeは最初から定義されてるので実装はいりません。

    type A = ReturnType<Fn>;

    例えば、第 2 引数の型が取りたい場合は、引数の型指定部分でinferを使います。

    // tslint:disable:no-unused
    type SecondArg<T extends Function> = T extends (
      a1: any,
      //  F が [tslint] TypeParameter 'F' is unused. [no-unused]
      // になってしまうので tslint:disable してます
      a2: infer F,
      ...args: any[]
    ) => any
      ? F
      : never;
    // tslint:enable:no-unused
    type A = SecondArg<(_a: number, _b: string, _c: boolean) => {}>;
    // type A = string

    TypeScript 自身が提供する役立つ Type まとめ

    Partial

    type Partial<T> = { [P in keyof T]?: T[P]; }で定義されてます。Tはオブジェクトです。全部のプロパティを?:へ変換しています。これによりすべてのプロパティが省略できる新しい型が取得できます。

    type Foo = {
      value: string;
    };
    
    type Result = Partial<Foo>;
    // type Result = { value?: string; }

    Required

    type Required<T> = { [P in keyof T]-?: T[P]; }で定義されてます。これはPartialの完全に逆です。そのオブジェクトが持っている?:なプロパティを:へ変換した新しい型が取得できます。

    type Foo = {
      value?: string;
    };
    
    type Result = Required<Foo>;
    // type Result = { value: string; }

    Record

    type Record<K extends string | number | symbol, T> = { [P in K]: T; }で定義されてます。オブジェクトの値の型(T)がすべて同じ時に、それらプロパティ(K)の型をすべてTとなるようにまとめて定義することができます。

    type Result = Record<'foo' | 'bar' | 'baz', string>;
    // type Result = {
    //   foo: string;
    //   bar: string;
    //   baz: string;
    // }

    Pick

    type Pick<T, K extends keyof T> = { [P in K]: T[P]; }で定義されてます。ある Interface からその型だけの新たな Interface を作ります。

    interface Values {
      foo: string;
      bar: number;
      baz: boolean;
    }
    
    type Result = Pick<Values, 'foo'>
    // type Result = { foo: string; }

    Omit

    (TypeScript v3.5 から)これはPickの逆の動きをします。

    interface Values {
      foo: string;
      bar: number;
      baz: boolean;
    }
    
    type Result = Pick<Values, 'foo'>
    // type Result = { bar: number; baz: boolean }

    Exclude

    type Exclude<T, U> = T extends U ? never : Tで定義されてます。TUは文字列リテラル Union 型でTUを比較し、Tにしかない文字列リテラル (Union) 型を返します。

    type Result = Exclude<'foo' | 'bar' | 'qux', 'foo' | 'baz'>;
    // type Result = 'bar' | 'qux'

    TypeScript v3.5 以前ではOmit型の代わりに使うことができます。

    interface Values {
      foo: string;
      bar: number;
      baz: boolean;
    }
    
    type Result = Pick<Values, Exclude<keyof Values, 'foo'>>
    // type Result = { bar: number; baz: boolean }

    Extract

    type Extract<T, U> = T extends U ? T : neverで定義されてます。Excludeに似てますが、こちらは両方にある文字列リテラル (Union) 型を返します。

    type Result = Extract<'foo' | 'bar' | 'qux', 'foo' | 'baz'>;
    // type Result = 'foo'

    NonNullable

    type NonNullable<T> = T extends null ? never : Tで定義されてます。Tは何でも受け取れます。もしそのTが Union でnullundefinedを含んでいる場合、それらを取り除いてくれます。

    type Result = NonNullable<true | 1 | 'a' | null | undefined>;
    // type Result = true | 1 | 'a'

    ReturnType

    type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : anyで定義されてます。Tは関数型になります。これはTの戻り値の型を取得できます。

    type Result = ReturnType<() => number>;
    // type Result = number

    InstanceType

    type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : anyで定義されてます。Tにはクラスを渡してそのインスタンスの型を取得できます。

    class Foo {
      static staticMethodFn() {}
      methodFn() {}
    }
    
    type Result = InstanceType<typeof Foo>;
    // type Result = Foo ( === {methodFn() {}} )

    ただあまりこれは使い方が分からない。(typeof Foo)['methodFn']とかはできないので、それをInstanceType<typeof Foo>['methodFn']とかする為なのかなとか思います。

    関数

    関数は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という戻り型になります。

    クラス

    クラスはClassで定義できます。 JavaScript と同じですね。同じなのでメソッドの定義の仕方やゲッターセッターなどはだいたい同じです。

    値の初期化

    クラスなのでconstructorで定義するのが一般的です。

    class A {
      value: number;
    
      constructor(value: number) {
        this.value = value;
      }
    }

    先に使うプロパティの型だけ定義しておいて、constructorでそこに代入する感じです。ですがこれだけなら、 TypeScript ではもっと短縮して書くことができます。以下は上の書き方と結果がまったく同じです。

    class A {  
      constructor(public value: number) {
      }
    }

    publicアクセス修飾子を付けています。これは他にprivateprotectedなどがありますがこれらの意味は、

    1. publicはどこからでもアクセス可能
    2. protectedはそれ自身、または継承したものからだけアクセス可能
    3. privateはそれ自身からしかアクセスできない

    です。例えば、privateを使う場合はこうなります。

    class A {  
      constructor(private value: number) {
      }
    }

    これを逆に短縮じゃない方法にすると、

    class A {
      private value: number;
    
      constructor(value: number) {
        this.value = value;
      }
    }

    のようになります。定義のほうでアクセス修飾子を付ける感じですね。

    constructor 以外で値の初期化

    TypeScript のstrictPropertyInitialization設定を有効にしているとconstructorで初期化していないプロパティは警告を受けます。例えば以下のような感じのときです。

    class A {
      // Property 'value' has no initializer 
      // and is not definitely assigned in the constructor.
      value: number;
    
      componentDidMount() {
        this.value = 123;
      }
    }

    もしcomponentDidMountが100%呼ばれるからvalueにはnumberが入っているんだと自信がある場合は、

    class A {
      value!: number;
    
      componentDidMount() {
        this.value = 123;
      }
    }

    value!: number;のように!を付けることで警告を回避できます。

    スタティック値

    スタティック値とは、プロトタイプではないそれ自信のプロパティ(クラス自体に生やす)の値のことです。これを定義するにはpublicのように頭にstaticと書くだけです。

    class A {
      static foo = 'static foo';
    }
    
    A.foo; // 'static foo'

    ジェネリクスなクラス

    クラス名のあとにジェネリクス型の設定を書くことができます。

    class A<T> {
      // ...
    }

    constructorまで自分で定義しているなら動的にTを解決できるかもしれませんし、もし設定してからじゃないと使えない場合は、

    const a = new A<Foo>();
    // T === Foo

    のように型を指定して使うことができます。

    抽象クラス

    抽象クラス(abstract class)を使うと、あるプロパティ値の定義を必ず行ってほしいことを継承先のクラスへ伝えることができます。

    抽象クラスは、単体では使うことができません。必ずクラスで継承して使います。
    インターフェース(interface)との違いは、抽象クラスは共通になりそうな部分はプロパティ値を入れたりやメソッドを実装した状態で用意できる点です。

    抽象クラスを定義する

    クラスはclassというワードで定義していましたが、これはabstract classというワードで定義します。

    abstract class A {
      abstract foo: string;
    
      abstract bar(): string;
    
      baz() {
        console.log('baz');
      }
    }

    そして、クラスの継承のようにextendsのあとに置くだけです。以下のように書いただけの時AAは型エラーになります。

    class AA extends A {}
    // Non-abstract class 'AA' does not implement inherited 
    // abstract member 'bar' from class 'A'.
    //
    // Non-abstract class 'AA' does not implement inherited
    // abstract member 'foo' from class 'A'.

    これはabstractアクセス修飾子を付けて宣言したものは、継承先で必ず実装しなければならない為です。foobarを以下のように実装すれば型エラーは解消できます。

    class AA extends A {
      foo = 'foo';
      bar() {
        return 'bar';
      }
    }

    インスタンスを作成した時に、bazは抽象クラスで宣言したものがそのまま使うことができます。もちろんAAで実装したfoobarも使えます。

    const aa = new AA();
    aa.baz();
    // 'baz'
    
    aa.foo;
    aa.bar();

    抽象クラスの継承

    抽象クラスは以下のように継承することができます。

    abstract class AA extends A {
      foo = 'foo';
      abstract qux: string;
    }

    fooを抽象プロパティではなくしたり、新たな抽象プロパティ・メソッドを宣言することができます。

    これを継承して作るクラスはbarquxの実装が必須になります。

    class AAA extends AA {
      bar() {
        return 'bar';
      }
      qux = 'qux';
    }

    ゲッター、セッター

    JavaScript と同じようにクラスのプロパティ宣言時に名前の頭にgetを付けるとゲッター、setを付けるとセッターを定義できます。

    class Foo {
      private _value: string;
    
      public get value() {
        // Type 'string | undefined' is not assignable to type 'string'.
        //   Type 'undefined' is not assignable to type 'string'
        return this._value;
      }
    
      public set value(value: string) {
        this._value = value;
      }
    }

    ゲッターの戻り値はセッターの引数の型になる

    以下のコードはエラーになります。

    class Foo {
      private _value: string | undefined;
    
      public get value() {
        return this._value;
      }
    
      public set value(value: string) {
        this._value = value;
      }
    }

    セッターでstringを代入していることでゲッターはstringの戻り値を期待するようになりますが、肝心の_valueundefinedである可能性があるということになるからです。

    コメント

    コメントは JavaScript と同じ、一行コメント// .../* ... */ブロックコメントが使えます。

    // 1行...
    // 1行...
    // 1行...
    
    /*
     * 1行
     * 2行
     * 3行
     */

    特殊なコメント

    // @ts-ignoreというコメントを TypeScript の型エラーが置きている部分の1行上に書くと、その型エラーを無視することができます。例えば以下のようなコードのとき、

    const a = { a: 1 };
    // @ts-ignore
    a.foo;

    a.fooの箇所は本来Property 'foo' does not exist on type '{ a: number; }'.というエラーになりますが、これを無視しています。

    型定義ファイル(d.ts)ファイルのみ出力する

    tsconfig.jsondeclarationtrueにする必要があります。またoutDirで出力先も指定します。

    {
      "compilerOptions": {
        "declaration": true,
        "outDir": "./dist/types"
      }
    }

    あとは、--emitDeclarationOnlyフラグをtscコマンドにつけて実行するだけです。

    tsc --emitDeclarationOnly
    # 設定ファイル指定
    # tsc -P tsconfig.types.json --emitDeclarationOnly

    設定晒し

    僕はtsconfig.types.jsonでこんな設定でやってます。

    {
      "extends": "@nju33/tsconfig-types",
      "include": ["src/**/*"],
      "exclude": ["src/__tests__/**/*", "src/**/*test*"],
      "compilerOptions": {
        "outDir": "dist/types"
      }
    }

    おおまかなところは自分の設定ファイルとして別パッケージにまとめてしまって、プロジェクト毎にそれをインストールしてincludeなどパス関連のものだけ再設定している感じです。

    tsconfig.json の内容を確認する

    tscコマンドに--showConfigを付けることで「ちゃんと対象のファイルがincludeに含まれているか」など内容を確認できます。

    tsc --showConfig

    もし、tsconfig.*.jsonのように複数の設定ファイルがあるなら-Pオプションフラグで指定することで、その設定ファイルでの内容を確認できます。

    tsc --showConfig -P tsconfig.types.json

    モジュールパスのエイリアス

    大きなプロジェクトになるとパスが複雑になってくることがあります。例えば../../../../foo/barのように深いネストにあるファイルから別の深いネストにあるファイルを参照したい場合などです。

    そのような場合はtsocnfig.jsonpathsセクションを設定することでfoo/barのように短いパスとすることができます。先に「foo/以下はアレを入れるディレクトリだ」というように決めてから設定しましょう。

    エイリアスを設定する

    設定が必要なセクションは2つです。

    セクション 説明
    baseUrl pathsの共通パス
    paths baseUrl以降のパスのマップ

    このように設定してみました。

    {
      "baseUrl": "src",
      "paths": {
         "foo/*": [
          "foo/*",
          "foo/*/src/index.ts"
        ],
        "foo": [
          "foo",
          "foo/src/index.ts"
        ]
      }
    }

    これはfoofoo/bar/bazというエイリアス名と何かしらの対象ファイルを紐付ける設定です。

    例えば、foo/*であれば["foo/*","foo/*/src/index.ts"]を見ると設定していますね。これはfoo/bar/bazという名前でインポートしようとした時に、まずはsrc/foo/bar/baz/package.jsonmainmoduleなどの記載があり、その対象のファイルが存在すればそれを使ってくださいという設定です。もし、それが無ければ配列の次の要素の「src/foo/bar/baz/src/index.tsを使ってね」となります。

    2つ設定しているのは、残念ながらfooで読み込みたい時にfoo/*という設定は効かない為、同じ用な設定が必要になります。

    注意

    僕がよくエイリアスの設定が効かない問題に引っかかります。そんな時は大抵これです。

    1. この.tsファイルはどのtsconfigを見ているのか
    2. そのtsconfigfilesに対象の.tsファイルは含まれているか

    を確認しましょう。

    関数の静的プロパティの型付け

    こんな感じで静的プロパティに値を定義しているような値を想定します。

    function foo() {
      /* ... */
    }
    foo.hoge = '...';
    foo.fuga = () => {
      /* ... */
    };

    依存パッケージの型付け

    上のfoofooパッケージで提供されていて、型ファイルが提供されていない時はこのように定義できます。

    declare module 'foo' {
      function foo(): any;
      namespace foo {
        export let hoge: string;
        export let fuga: () => any;
      }
    
      export = foo;
    }

    これをどこかプロジェクトにfoo/index.d.tsとして作成します。そのモジュールを読み込むと型が効いているはずです。

    import foo from 'foo';
    
    foo();
    foo.hoge = 'ads';
    foo.fuga();

    ソースコードの中で

    同じようにコードの中で定義すればいいです。
    functionで定義するものはそのまま値を入れることで型が付きます。

    function foo() {}
    foo.hoge = 'string';
    // `foo.hoge` は `string`
    foo.hoge = 123;
    // `foo.hoge` は `string | number`

    厳密にしたいならnamespaceを使います。

    function foo() {}
    namespace hoge {
      export let paths: string;
    }
    foo.hoge = 'string';
    // `foo.hoge = 123` は型エラー

    しかし、この方法は関数の中で定義する関数には効きません。
    やっぱり、変数に置いて変数の型を設定してあげるといいです。

    const foo: {(): void; hoge: string} = function() {};
    foo.hoge = 'string';
    
    const createFn = () => {
      const fn: {(): void; hoge: string} = function() {};
      fn.hoge = 'string';
    
      return fn;
    };

    配列型 T[] から T だけ取り出して使う方法

    以下はちゃんと管理されているプロジェクトでは、こんなことはあまり起こらないだろうなという内容です。

    僕は Flux の State 部分をこのように定義して使ったりすることが多いです。

    interface State {
      values: {foo: any; bar: any}[];
    }

    オブジェクト部分なども直で書いてます。(APIの仕様がはっきりしないままで、どの値を使えばいいのか曖昧なことが多いというのが大きい、今の会社)

    そして、例えばそのvaluesのうちの1つ、つまり{foo: any; bar: any}だけを受け取る関数などを定義したいときに、指定に例えば(value: {foo: any; bar: any}) => voidのようにしても問題はあまりないですが、できれば先程定義したStateから使いたいです。

    TypeScript の型は子プロパティ名で絞り込んでいくことができます。つまり、{foo: any; bar: any}[]State['values']とも書くことができます。ただここで「[]はどうすれば取り払えるんだろう」と頭をひねりました。ただそれはすぐ解決することができました。

    配列型 T[] から T だけ取り出して使う

    ここでいうT[]ですが、実は TypeScript では以下のように定義したものとほぼ同じです。

    interface StateValues extends Array<string> {
      [n: number]: string
    }
    
    const state: StateValues = ['foo'];

    この[n: number]: string部分はインデックス署名と言い、extends numberなプロパティ名ならプロパティへのアクセスが許可されるようになります。これは型のプロパティ名での絞り込みでextends numberな型を指定することで、さらに絞ることができるということになります。

    つまり、以下のような絞り込み方法があります。(僕的にはnumberがいいかなと思います)

    const value0: State['values'][number] = 'foo';
    const value1: State['values'][0] = 'foo';
    const value2: State['values'][1] = 'foo';
    const value99: State['values'][99] = 'foo';

    ちなみに、State['values'][]とするとこれは{foo: any; bar: any}[][]となってしまうので注意です。

    環境構築から JavaScript に変換まで

    ざっとこのようになります。ちなみにyarnを使います。

    1. package.jsonを作ってプロジェクト化する
    2. typescriptインストール
    3. TypeScript の設定ファイルを作る
    4. 適当に.tsファイルを作る
    5. 変換して.jsファイルを作る

    package.json を作ってプロジェクト化する

    適当なディレクトリに移動するか作成して、yarn init -ypackage.jsonを生成します。

    以下の例では/tmp/ts2jsというディレクトリで作業する想定です。

    mkdir -p /tmp/ts2js
    cd /tmp/ts2js
    yarn init -y

    typescript インストール

    /tmp/ts2jsディレクトリ上で以下でインストールします。

    yarn add -D typescript

    package.jsondevDependenciesセクションにtypescriptが追加されれば完了です。

    {
      "devDependencies": {
        "typescript": "^3.4.4"
      }
    }

    TypeScript の設定ファイルを作る

    /tmp/ts2js/tsconfig.jsonとなるようにtsconfig.jsonを作ります。これは「どのファイルを変換対象にするか」や「どこまで TypeScript の機能を使うか」などを設定する為のファイルです。

    ここでは/tmp/ts2js/src/index.tsをこれから作り、/tmp/ts2js/dist/index.jsとなるように出力させたいと思います。なのでそのindex.tsが変換対象できるような設定を追加します。

    これにはincludeセクションとcompilerOptions.outDirセクションをtsconfig.jsonへ置きます。それぞれ、「どのファイルを変換対象にするか」と「どこへ出力するか」の設定です。

    {
      "include": ["src"],
      "compilerOptions": {
        "outDir": "dist"
      }
    }

    srcと設定していますがこれはsrc/**/*のようなものでそのディレクトリ以下すべてのファイルという意味になります。

    では確認するためにindex.tsファイルを作ります。

    mkdir -p /tmp/ts2js/src/
    touch /tmp/ts2js/src/index.ts
    yarn tsc --showConfig

    yarn tsc --showConfigは先ほど作ったtsconfig.jsonを見てちゃんと設定されているか確認するためのコマンドです。実はincludeというのは動的なオプションで変換コマンド実行時にsrc以下のファイルをチェックして都度変換ファイルに含めてくれるセクションです。

    うまく行けばこのようにfilesというセクションが追加された状態のものが表示されると思います。これが動的に設定された変換対象ファイルになります。

    {
      "compilerOptions": {
        "outDir": "dist"
      },
      "files": [
        "./src/index.ts"
      ],
      "include": [
        "src"
      ]
    }

    index.ts ファイルを編集する

    確認のために型を使う簡単な.tsファイルを作ります。こんな感の1足すだけのplusOneを定義・使ってみました。

    const plusOne = (num: number) => num + 1;
    console.log(plusOne(2));

    変換して JavaScript ファイルを作る

    これも以下を実行するだけです。

    yarn tsc

    問題が無ければ/tmp/ts2js/dist/index.jsというファイルが作られているはずです。ついでに実行してみます。

    node dist/index.js
    # 3

    ちゃんと3が得られたのでうまく動いてるようです。

    エラー

    ビルド時

    TS2564: Property '...' has no initializer and is not definitely assigned in the constructor.

    tsconfig.jsonstrictPropertyInitializationfalseにするか、対象ファイルのプロパティ型宣言部分をfoo!: stringのように!を付ける。

    TS2339: Property 'includes' does not exist on type '...'

    tsconfig.jsonlibes7を含める。

    TS2339: Property 'entries' does not exist on type '...'

    tsconfig.jsonlibesnextを含める。

    import {...} from 'microrouter'; SyntaxError: Unexpected token {

    commonjsでの実行が必要な部分にesnextを使っている可能性があります。compilerOptions.modulecommonjsになってないならそれかもしれません。

    'this' implicitly has type 'any' because it does not have a type annotation.

    functionthisの型を指定してあげる。

    function foo(this: Foo) {
      /*...*/
    }

    Duplicate identifier '...'

    tsconfig.jsoncompilerOptions.typesを使っているパッケージだけにする。
    直接使っている型なら、node_modulesの削除とyarn cache clean、またモノレポであればその型パッケージバージョンをすべてのワークスペースで統一するなど。

    jest

    Cannot find name 'test'. Do you need to install type definitions for a test runner? Try npm i @types/jest or npm i @types/mocha and then add jest or mocha to the types field in your tsconfig.ts(2593)

    compilerOptions.typeRootscompilerOptions.typesが変なことになっていないか確認する。上のDuplicate identifier '...'の対応でjestを含めていないなど。