🤏

【Typescript】ちょっと応用的な使い方 🤏

2022/12/03に公開

Typescriptの応用的な使い方学習メモ。
下記記事の続きになります。
【TypeScript】入門してみたまとめ ✍🏻

&を用いてインターセクション型の定義

インターセクション型:
交差型。AかつBのような使い方。ユニオン型はAまたはBのような使い方だが、つまりその逆。

// typeで書いた場合
type Engineer = {
  name: string;
  role: string;
};

type Blogger = {
  name: string;
  follower: number;
};

// type Engineer、Bloggerが合併した型。nameもroleもfollowerもないとだめ。
type EngineerBlogger = Engineer & Blogger;

const quill: EngineerBlogger = {
  name: 'Quill',
  role: 'front-end',
  follower: 1000
}
// interfaceで書いた場合
interface Engineer {
  name: string;
  role: string;
}

interface Blogger {
  name: string;
  follower: number;
}

interface EngineerBlogger extends Engineer, Blogger {}

const quill: EngineerBlogger = {
  name: "Quill",
  role: "front-end",
  follower: 1000,
};

Type Guard

条件文を使って、型を絞り込む方法のことをType Guardという。

型ガードを使用することによってifのブロックで特定の型に絞りこむことができます。

サバイバルTypescript:型ガード

例1)
7つの型の結果を返すtypeofを使う場合。

function toUpperCase(x: string | number) {
  if(typeof x === 'string') {
    return x.toLocaleUpperCase()
  }
  return '';
}

例2)
そのプロパティの有無がわかるinを使う場合。

interface Engineer {
  name: string;
  role: string;
}

interface Blogger {
  name: string;
  follower: number;
}

type NomadWorker =  Engineer | Blogger;
function describeProfile(nomadWorker: NomadWorker) {
  if('role' in nomadWorker) { // roleプロパティを持つオブジェクトだったらtrue
    console.log(nomadWorker.role)
  }
}

例3)
classの場合に使用できるinstanceofを使う場合。

class Dog {
  speak() {
    console.log("bow-wow");
  }
}

class Bird {
  speak() {
    console.log("tweet-tweet");
  }
  fly() {
    console.log("flutter");
  }
}

type Pet = Dog | Bird;
function havePet(pet: Pet) {
  pet.speak();
  if (pet instanceof Bird) { // Birdクラスから生成されたインスタンスかどうか
    pet.fly();
  }
}

havePet(new Bird()); // → tweet-tweet flutter

タグ付きUnionを使って型を絞り込む方法

判別可能なユニオン型は、タグ付きユニオン(tagged union)や直和型と呼ぶこともあります。

サバイバルTypescript:判別可能なユニオン型とは

class Dog {
  kind: "dog" = "dog"; // リテラル型で追加
  speak() {
    console.log("bow-wow");
  }
}

class Bird {
  kind: "bird" = "bird";
  speak() {
    console.log("tweet-tweet");
  }
  fly() {
    console.log("flutter");
  }
}

type Pet = Dog | Bird;
function havePet(pet: Pet) {
  pet.speak();
  switch (pet.kind) { // kindを起点に条件分岐追加
    case "bird":
      pet.fly();
  }
}

havePet(new Bird()); // → tweet-tweet flutter

型アサーションとは

手動で型を上書きする方法。

例えばDOM操作で下記のように取得する際に、型推論はHTMLElement | nullになる。
スクリーンショット 2022-11-27 17.30.04.png

しかしHTMLElementは抽象的なinterfaceで、今回の例でいうと本来inputタグにはvalueにアクセスできるはずだがinputタグだと正常に認識できずアクセスしようとするとエラーになる。
スクリーンショット 2022-11-27 17.32.18.png

もう少し具体的なinterface、今回のinputタグで言うとHTMLInputElementとして認識させることでvalueへアクセスできるようになる。
HTMLInputElementHTMLElementを継承したもの

じゃあHTMLInputElementで型注釈すれば良い?
スクリーンショット 2022-11-27 17.36.16.png

だめでした。getElementByIdはnullを返す可能性もあるからとのこと。

そこで型アサーションを使って無理矢理、HTMLInputElementって言い切る。

例1)

const input = <HTMLInputElement>document.getElementById('input')

例2)

const input = document.getElementById("input") as HTMLInputElement;

上記のどちらかの型アサーションをすることでHTMLInputElement型にすることでvalueへアクセス可能になる。
jsxなどを考慮すると、基本的にはasを使った型アサーションでよさそう。
スクリーンショット 2022-11-27 17.43.33.png

!(non-null assertion operator)の使用方法

!を使うと絶対にnullじゃないと言い切る文法になる。

例)

const input = document.getElementById("input")

hoverしてみると型推論によってHTMLElement | nullであることがわかる。
スクリーンショット 2022-11-29 16.07.40.png

!を追記するとnullは消える。
スクリーンショット 2022-11-29 16.08.54.png

インデックスシグネシャ

オブジェクトのプロパティを追加できる。
後述する注意点にある通り、存在しないプロパティにアクセスしようとしてもエラーを吐かなくなり、使用すべき場面は限られそう。

インデックスシグネチャ(Index Signatures、インデックス型)とは、インデックス(添字)を利用してオブジェクトのプロパティ(キー)の型を定義する機能のことをいいます。

【TypeScript】インデックスシグネチャ(Index Signatures)の概要と利用方法

通常、interfaceに存在しないプロパティは追加できない

// interface定義
interface Designer {
  name: string
}

const designer: Designer = {
  name: 'Quill',
  role: 'web' // → エラー!
}

インデックスシグネチャを使用し、追加可能に。

// interface定義
interface Designer {
  name: string;
  [index: string]: string; // インデックスシグネチャ追加
}

const designer: Designer = {
  name: "Quill",
  role: "web", // → いくつでも追加できるようになる
  aaa: 'aaa',
  iii: 'iii',
  ...
};

型エイリアス(interface)によるオブジェクト型の型注釈によって、オブジェクトから1つ1つキーと値の型を定義するのは面倒..。
インデックスシグネチャを使うことで、オブジェクトがより多くのキーを含む可能性があることをTypescriptに伝える事ができる。

注意

  1. インデックスシグネチャを使用した場合、他の全てのvalueの型もインデックスシグネチャの型にしないといけない

    interface Designer {
      name: number; // 型をnumberに変更
      [index: string]: string;
    }
    
    const designer: Designer = {
      name: 1,
      role: "web", 
    };
    

    エラー
    スクリーンショット 2022-11-29 19.46.55.png

  2. [index: string]: string;のinterfaceのプロパティ型部分をstringにした場合、そのinterfaceを使った変数のプロパティの型はstringでもnumberでもいいが、逆はできない

    interface Designer {
      name: string;
      [index: string]: string; // → プロパティの型はstring
    }
    
    const designer: Designer = {
      name: "Quill",
      1: "web", // → プロパティの型はstringだがnumberでもok
    };
    
    interface Designer {
      name: string;
      [index: number]: string; // → プロパティの型はnumber
    }
    
    const designer: Designer = {
      name: "Quill",
      role: "web", // → エラー!プロパティの型はnumberのみ
    };
    
  3. 存在しないプロパティにもエラーを吐かないようになる

関数のオーバーロード

関数の戻り値の型を正しくTypescriptに認識させる方法。

要するに、異なる引数や戻り値のパターンがいくつかある関数をオーバーロード関数と言います。

サバイバルTypescript:オーバーロード関数

例えば、引数がstringなら大文字に変換して返し、数値の場合はそのまま返す下記関数があったとする。

function toUpperCase(x: string | number) {
  if (typeof x === "string") {
    return x.toUpperCase();
  }
  return x;
}
// 定数upperHelloに返ってくる値を代入する。
const upperHello =  toUpperCase('hello')

upperHelloをhoverして型を確認すると、string | numberのまま..。
この時点で、stringなのに正しく認識できていない。
スクリーンショット 2022-11-29 20.08.47.png

この場合正しく認識させる方法として、前述した型アサーションが使える。

const upperHello =  toUpperCase('hello') as string;

ただ、この関数を実行するたび、型アサーションしたり、引数がstringかnumberによって返ってくる型も決まっているのにこの方法はよくなさそう。そんな時に関数のオーバーロード

オーバーロード関数は、関数シグネチャと実装の2つの部分に分けて書く。

関数シグネチャとは、どのような引数を取るか、どのような戻り値を返すかといった関数の型のことです。

function toUpperCase(x: string): string; // 関数シグネチャ
function toUpperCase(x: number): number; // 関数シグネチャ
function toUpperCase(x: string | number) {
  if (typeof x === "string") {
    return x.toUpperCase();
  }
  return x;
}
const upperHello =  toUpperCase('hello');

関数シグネチャ(関数の型)をパターンの数だけ記述しておくと、型アサーションとかしなくても引数に応じた型を認識してくれるようになる。
スクリーンショット 2022-11-29 20.26.31.png

Optional Chainingの使い方

参照先がnullやundefinedだった場合、エラーを吐くが、?.をつけるとエラーを吐かせずundefinedを返すようになる

JavaScriptではnullやundefinedのプロパティを参照するとエラーが発生します。
オプショナルチェーン?.は、オブジェクトのプロパティが存在しない場合でも、エラーを起こさずにプロパティを参照できる安全な方法です。

サバイバルTypescript:オプショナルチェーン

interface DownloadedDate {
  id: number;
  // ?をつけるとあってもなくてもいいプロパティになる
  user?: {
    name?: {
      first: string;
      last: string;
    };
  };
}

const downloadedData: DownloadedDate = {
  id: 1,
};
console.log(downloadedData.user.name); // エラー!オブジェクトは 'undefined' である可能性があります。

スクリーンショット 2022-11-30 19.24.46.png

オプショナルチェーン?.を追記するとエラーを吐かなくなり、参照先がundefinedかnullだった場合、undefinedを返し、何かデータがあったらそれを返すようになる。

console.log(downloadedData.user?.name); // '?'追記

Nullish Coalescingの使い方

??の左辺がnullかundefinedなら右の値を返す。
||や三項演算子みたいだが、それらと違って、Falsyな値かどうかではなく、nullかundefinedの時のみ右の値を返すことに注意。

左の値がnullまたはundefinedのときに右の値を返します。そうでない場合は左の値を返します。

サバイバルTypescript:Null合体 (nullish coalescing operator)

例) undefinedが返されるコード

interface DownloadedDate {
  id: number;
  user?: {
    name?: {
      first: string;
      last: string;
    };
  };
}

const downloadedData: DownloadedDate = {
  id: 1,
};
console.log(downloadedData.user?.name);
const userData = downloadedData.user // 追加
console.log(userData); // → undefined

例) Nullish Coalescing追加してundefinedかnullの場合は、右辺の値を返す

interface DownloadedDate {
  id: number;
  user?: {
    name?: {
      first: string;
      last: string;
    };
  };
}

const downloadedData: DownloadedDate = {
  id: 1,
};
console.log(downloadedData.user?.name);
const userData = downloadedData.user ?? "no-user"; // Nullish Coalescing追加
console.log(userData); // → no-user

LookUp型を使って、オブジェクトのメンバーの型を取得

オブジェクト型に対してプロパティ名でアクセスするようなものを型レベルにしたようなもの。

interface DownloadedDate {
  id: number;
  user: {
    name?: {
      first: string;
      last: string;
    };
  };
}
type id = DownloadedDate["id"] // → type id = number

or文や、階層の書き方もある

type id = DownloadedDate["id" | "user"]
type user = DownloadedDate["user"]["name"]

型の中でtypeofを使って、値の型を取得する

typeof 値で値の型を取得できる。

TypeScriptのtypeofは変数から型を抽出する型演算子です

サバイバルTypescript:typeof演算子

const peter = {
  name: "Peter",
  age: 38,
};

type PeterType = typeof peter;

Discussion