Skip to content

Instantly share code, notes, and snippets.

@Ryomasao
Last active August 25, 2020 07:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ryomasao/f7360af2f22ae8c5f26c0162ce84d567 to your computer and use it in GitHub Desktop.
Save Ryomasao/f7360af2f22ae8c5f26c0162ce84d567 to your computer and use it in GitHub Desktop.
速習タイプスクリプト

適当なつまみ食いに限界を感じるので、テキストを見つつ、実装しつつメモ

嵌りそうな箇所をメモ。

オブジェクト

オブジェクトの型は、基本的にObjectのshape(形状)を表すことで宣言できる shapeではくオブジェクトリテラル表記って呼んでもいい。

  // 変数aは、keyがbでvalueがstringのプロパティを必ず持ってる
  let a: { b: string } = { b: "1" };
  let a:{} = {}
  let a:object = {}

空のオブジェクトリテラル表記は避けたほうがいい。 オブジェクトしか許容しないとおもいきや、nullundefined以外はいけちゃう。 objectObjectも似たような感じ。細かい違いはあるんだけど、使わない前提でしばらくいくので、気にしないでおこう。

  // こういうやつ
  let a:{} = 1
  let a:object = 1
  let a:Object = 1

関数

  • 関数の引数に関しては、基本的に型推論は行われない。

任意の引数

  • 任意のpropsには?をつけることで表現できる
  const someFunc = (must: string, option?: string) => {};
  someFunc("1");
  • デフォルト値がある場合には?はいらないし、型定義もいらない
  • 関数の引数としては?よりデフォルト値を使ってくほうがよさげ
  const someFunc = (must: string, option = 'a') => {};
  someFunc("1");
  const someFunc = (
    must: string,
    option = {
      param1: "",
      param2: "",
    }
  ) => {};

  // OK
  someFunc("1");
  // OK
  someFunc("1", { param1: "a", param2: "b" });
  // NG param2がない
  someFunc("1", { param1: "a"});
  // NG param1のvalueの型が違う
  someFunc("1", { param1: 1, param2: "b"});

混乱してて、こんな書き方をしてたけど、デフォルト値があるので、optionの型定義はいらない

  const someFunc = (
    must: string,
    // :{ param1: ""...}の部分は不要
    option: { param1: ""; param2: "" } = {
      param1: "",
      param2: "",
    }
  ) => {};

とはいえ、こんな感じに、オブジェクトinオブジェクトの一部は任意みたいなやつは?パターンにする必要があるんだと思う。

  const someFunc = (
    // 前述のとおり、デフォルト値をもとに型推論が行われる
    // なので、valuesとerrorMessageは必須になる
    state = {
      values: {},
      errorMessages: {},
    },
  ) => {};

  // ↓はNG。引数を渡してるので、stateのデフォルト値の設定が行われないので、errorMessageがないってなる
  someFunc({ values: {} });

なので、明示的に型を定義する必要がある。

  // stateに直接型を書くより、typeとして切り出したほうがみやすいね
  type StateProps = {
    values: {};
    errorMessages?: {};
  };

  const someFunc = (
    state:StateProps = {
      values: {},
      errorMessages: {},
    },
  ) => {};
  
  // OK
  someFunc({ values: {} });

さらにいえば、オブジェクトの定義を{}とするのは前述の通り避けたほうがいいので、 key:valueをもつっていうふうに書くといいみたい。

  type stateProps = {
    values: { [name: string]: string };
    errorMessages?: { [name: string]: string };
  };

関数の定義とオーバロード

callbackなど、関数を渡したり、返す場合、関数を型として定義する必要がある。

type Fn = (name:string) => void

関数の引数は、基本的に型推論されないと書いたけど、例外がある。 その一つが、上記の関数の型を宣言した場合。

type Fn = (name:string) => void
// nameはanyにならない
const someFunc: Fn = (name) => {};

また以下のように、callbackで型が定義してある場合も、アノテーションする必要がない。

  const someFunc = (callback: (name:string) => void) => {
    callback("tarou");
  };
  // nameはanyではなく、stringと解釈される
  // nameを渡さなくてもエラーならなかったのが意外
  someFunc((name) => {
    console.log(name);
  });

関数の型宣言を完全に書く場合以下の形式になる

  type Fn = {
    (name: string): void;
  };

OOPとかで使うオーバロードの型宣言が上記でできる。 ※ OOPのオーバロードとオーバライドで混乱する。 https://qiita.com/ShirakawaMaru/items/058484a8fb584eb452b2

オーバーロードとは、「引数や戻り値が異なるが名称が同一のメソッドを複数定義する」というオブジェクト指向プログラミングのテクニックである。

使い所がいまいちわからない。

  //関数Fnはfromとfrom・toのシグネチャを持つ2つのパターンあることを表現。
  type Fn = {
    (from: string): string;
    (from: string, to: string): string;
  };

  // といっても実装では、↑の定義を手動で結合する必要がある。
  const someFunc: Fn = (name: string, to?: string) => name;

createElementの例が乗っていて、引数によって返す型が変わる場合に便利なのかしら。

うーん、↓のようにtagの値によって返す型がかわる型宣言書いてみたけ通らないや。 実装はどうやって書くんだろ。

  type Fn = {
    (tag: "a"): string;
    (tag: "b"): number;
    (tag: string): string;
  };

  const someFunc: Fn = (tag: "a" | "b" | string) => {
    if (tag === "a") {
      return "a";
    }
    if (tag === "b") {
      return 1;
    }
    return "a";
  };

あーanyにすると、コンパイルは通るし、引数によって型を検出してる

  const someFunc: Fn = (tag: any):any => {}

��すこしだけ理解できた。引数によって、返す値がかわる例

type ExecAsync = {
  // この関数は、Promiseを引数にもらったらPromiseを返す
  // ※ async await でreturnすると普通の値もPromiseになるからね
  <T>(args: Promise<T>): Promise<T>;
  // Promiseが入った配列であれば、PromiseでT型の配列を返す
  // Promise<T>[]は、Promise<T[]>になることにしばらく気づかなかった。
  <T>(args: Promise<T>[]): Promise<T[]>;
};

// 実装はこう書く
const execAsync: ExecAsync = async (
  promises: Promise<any> | Promise<any>[]
): Promise<any | any[]> => {// 省略}

ジェネリック

配列と配列をフィルターをする関数filter。 配列の要素は文字列でも、数値でもオブジェクトにも対応できるようにしたい。 以下は、オーバロードを使って、文字列と数値に対応したとこまで。

  type Filter = {
    (array: number[], f: (item: number) => boolean): number[];
    (array: string[], f: (item: string) => boolean): string[];
  };

  const filter: Filter = (array: any[], f: (item: any) => boolean): any[] => {
    let result = [];
    for (let i = 0; i < array.length; i++) {
      let item = array[i];
      if (f(item)) {
        result.push(item);
      }
    }
    return result;
  };

毎回、型を追加してくのはしんどい。

ジェネリックを使うところで<T>の箇所は同一って宣言できる。

  type Filter = {
    <T>(array: T[], f: (item: T) => boolean): T[];
  };

  // 関数もアノテートする必要がなくってすっきりした
  const filter: Filter = (array, f) => {
    let result = [];
    for (let i = 0; i < array.length; i++) {
      let item = array[i];
      if (f(item)) {
        result.push(item);
      }
    }
    return result;
  };

  const arrayNum = [1];
  const arrayString = ["1"];
  const arrayObject = [{ name: "tarou" }, { age: "age" }];
  filter(arrayNum, (item) => true);
  filter(arrayString, (item) => true);
  filter(arrayObject, (item) => item.name === "tarou");

<T>を宣言する箇所がちょっとわかりにくい。

  type Filter = {
    <T>(array: T[], f: (item: T) => boolean): T[];
  };

  //↓のように使う
  //const filter:Filter =  ...

  type Filter<T> = {
    (array: T[], f: (item: T) => boolean): T[];
  };

  // ↑の形にすると、型を宣言する必要がでてくる
  // なんだか、これに衝撃を受けた。この場合関数を格納したfilterは、stringしか扱えなくなる。
  // あー、オーバロードと一緒で、anyにするって方法もある?←だめ。Tが全部anyになる。
  const filter: Filter<string> = (array, f) => {
    let result = [];
    for (let i = 0; i < array.length; i++) {
      const item = array[i];
      if (f(item)) {
        result.push(item);
      }
    }
    return result;
  };

 


  // 関数宣言の話と、↑のタイプの話�は別軸なのか、いまいちよくわからん

  //`type`を使わずに関数に書く例
  // typeを使わずというか、これは名前付き関数であって式ではないから、typeは使えない?
  function filter<T>(array: T[], f: (item: T) => boolean): T[] {}

  // アロー関数式
  const filter = <T>(array: T[], f: (item: T) => boolean): T[] => {}
  // Reactだと↑で書くとjsxとして認識されちゃうので、カンマをつけるといける
  // https://github.com/Microsoft/TypeScript/issues/15713
  const filter = <T,>(array: T[], f: (item: T) => boolean): T[] => {};

mapの例

  function map<T, U>(array: T[], f: (item: T) => U): U[] {
    let result = [];

    for (let i = 0; i < array.length; i++) {
      const item = array[i];
      result.push(f(item));
    }
    return result;
  }

ジェネリクスは関数の引数の型だけを使って型推論がされる。 Promiseのように、callbackで渡した関数の実行結果を受け取るような場合は、明示的にアノテートする必要がある。

  const promise = new Promise<number>((resolve) => {
    resolve(40);
  });

  promise.then((value) => {
    return value * 2;
  });
  const createState = <T,>(initialState: T) => {
    //createStateは、initialStateのshapeを気にしないんであれば、Tにしとけばいいのかな
  };
  createState({ hoge: "fuga" });

Tにextends加えることで、Tに制限をもたせることができる

  type TreeNode = {
    value: string;
  };

  type LeafNode = TreeNode & {
    isLeaf: true;
  };

  type InnerNode = TreeNode & {
    children: [TreeNode] | [TreeNode, TreeNode];
  };

  let a: TreeNode = { value: "a" };
  let b: LeafNode = { value: "b", isLeaf: true };
  let c: InnerNode = { value: "c", children: [b] };

  function mapNode<T extends TreeNode>(
    node: T,
    f: (value: string) => string
  ): T {
    // T extends TreeNodeがないと、node.valueが存在していない可能性がある
    return { ...node, value: f(node.value) };
  }

クラス

Factoryパターン

  type Shoe = {
    purpose: string;
  };

  class Boot implements Shoe {
    purpose = "woodcutting";
  }

  class Sneaker implements Shoe {
    purpose = "wakling";
    public price = 1000;
  }

  // Objectの要素に直接関数書くとびっくりする
  // この場合、Shoe.createを実行した結果の返り値は、Shoe
  // ファクトリパターン的には、Shoeであることを知っていれば十分
  const Shoe = {
    create(type: "boot" | "sneaker"): Shoe {
      switch (type) {
        case "boot":
          return new Boot();
        case "sneaker":
          return new Sneaker();
      }
    },
  };
  // 仮に、typeに応じて返す型がかわるってことを明示したい場合
  // オーバロードで実現できる
  type ShoeCreator = {
    create(type: "boot"): Boot;
    create(type: "sneaker"): Sneaker;
  };

  // 返り値をShoe→anyにしてる。Shoeのままだと、Sneakerだけにもってるpropsがないって怒られる
  // anyにすることでコンパイルも通って、かつ型もちゃんと拾える
  // ここいまいちよくわからん
  const Shoe: ShoeCreator = {
    create(type: "boot" | "sneaker"): any {
      switch (type) {
        case "boot":
          return new Boot();
        case "sneaker":
          return new Sneaker();
      }
    },
  };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment