• 作成日:

【TypeScript】実務でよく使う!型指定の応用編

【TypeScript】実務で使う!型指定の応用編

今回は実務でよく使うTypeScriptの型指定について説明していきます。

  • macOS Catalina v10.15.5
  • Visual Studio Code v1.57.0
  • TypeScript v4.3.5
  • ESLintでエラーチェック
この記事の目次

実務でよく使う型指定の応用編

基礎的な型のつけ方については以下を参照してください。
今回はその応用編になります。

あるかわからないプロパティに「?」を使う

workerオブジェクトにはincomeプロパティがあるかわからない場合、型側のプロパティに「?」をつけることができます。

type Worker = {
   job: string;
   income?: number; //あるかわからないので?をつける
};

const worker: Worker = {
  job: '事務',
  //income: 100 この時点で指定してなくてもエラーにならない
};

worker.income = 100; //あとからincomeプロパティを追加できる

オブジェクトに複数階層がある場合、プロパティを参照するときに「?」をつけることでそのプロパティが無い場合はundefinedを返してくれます。

type Worker = {
  job: string;
  income: number;
  detail?: {
    name: string;
    age: number;
  };
};

const worker: Worker = {
  job: '事務',
  income: 100,
};

console.log(worker.detail?.name); //エラーは起こさず、undefinedを返す

nullかundefinedのときに別の値を返してほしい場合は?

今回の例ではworker.detail?.nameがnullもしくはundefinedの場合、「??」の右の値が適用されます。

type Worker = {
  job: string;
  income: number;
  detail?: {
    name: string;
    age: number;
  };
};

const worker: Worker = {
  job: '事務',
  income: 100,
};
const workersDetail = worker.detail?.name ?? '何もないようです。';
console.log(workersDetail); //何もないようです。 と表示される

型を使った絞り込み

Type gardと言って、型を使った絞り込み方法は3つあります。

  • typeofで絞り込み
  • in演算子で絞り込み
  • instanceofで絞り込み(Classを使用した場合)

typeofで絞り込み

関数のパラメーターがUnion型(AかBのどちらか)の場合、typeofを使うことで型で条件分岐ができます。

const hello = (x: string | number) => { // xがUnion型
  if (typeof x === 'number') { //xがnumber型だったら
    console.log(x * 10);
  } else { //number型以外(string型)なら
    console.log(`こんにちは私は${x}です。`);
  }
};

in演算子

10行目にあるように、WorkerまたはMenのtypeエイリアスを持ったResidentsを、personのパラメーターに指定しています。

そしてin演算子によって、jobプロパティがある場合とadressプロパティがある場合で条件分岐をしています。(13行目、15行目)

type Worker = {
  job: string;
  income: number;
};
type Men = {
  marriage: boolean;
  adress: string;
};

type Residents = Worker | Men;

const office = (person: Residents) => {
  if ('job' in person) { //jobプロパティがあれば
    console.log(`Workerの中の${person.job}や${person.income}にアクセスできる`);
  }else if("adress" in person){ //adressプロパティがあれば
    console.log(`Menの中の${person.marriage}や${person.adress}にアクセスできる`);
  }
};

in演算子についてはTypeScriptの特有の機能ではなく、JavaScriptにもともと備わっている機能です。

instanceofで絞り込み

19行目で2つのClassをUnion型で、typeエイリアスのSchollに指定しています。そして21行目の関数のパラメーターにSchollを指定しています。

Classはインスタンスを生成したときに、同じClass名の型も作ります。

class Students { //Studentsという型を持つ
  hello() {
    console.log('私は生徒です');
  }
  study() {
    console.log('勉強します');
  }
}

class Teacher { //Teacherという型を持つ
  hello() {
    console.log('私は先生です');
  }
  teach() {
    console.log('教えます');
  }
}

type Scholl = Students | Teacher; //StudentsもしくはTeacherという型を持つ

const learning = (scholl: Scholl) => {
  scholl.hello; //StudentsもTeacherもhelloメソッドを持っているので使える
  if (scholl instanceof Students) { //schollがStudentsから作られたインスタンスだった場合は
    scholl.study();
  } else if (scholl instanceof Teacher) { //schollがTeacherから作られたインスタンスだった場合は
    scholl.teach();
  }
};
learning(new Teacher());

instanceofを使うことで、どのClassから作られたインスタンスなのか判定できます。

タグ付きUnion型で絞り込む

typeエイリアスやinterfaceなどのプロパティに共通する型をリテラル型で指定します。(2行目、7行目)

そのリテラル型で指定した型を条件分岐で使います。

type Worker = {
  type: 'worker';
  job: string;
  income: number;
};
interface Men {
  type: 'men';
  marriage: boolean;
  adress: string;
}

type Residents = Worker | Men;

const office = (person: Residents) => {
  if (person.type === 'worker') {
    console.log(`Workerの中の${person.job}や${person.income}にアクセスできる`);
  } else if (person.type === 'men') {
    console.log(`Menの中の${person.marriage}や${person.adress}にアクセスできる`);
  }
};

手動で型を上書きする型アサーション

Domの操作をするときに、HTMLElementやHTMLCollection、NodeListを取得することがありますが、それがInputタグなのかButtonタグなのかTypeScriptに明確に教える必要があります。

明確に教えるためには以下のように指定する必要があります。

以下はHTMLElementの場合は書き方が2パターンあります。ReactでTypeScriptを使う場合はasで書くとReactの書き方と被らないのでオススメです。

/*-- パターン1 --*/
//id名hogeでa要素の場合
const htmlElement = <HTMLLinkElement>document.getElementById('hoge');

/*-- パターン2 --*/
const htmlElement2 = <HTMLLinkElement>document.getElementById('hoge') as HTMLLinkElement;

HTMLCollectionの場合は以下

// クラス名pikeでdiv要素の場合
const htmlCollection =  document.getElementsByClassName('pike') as HTMLCollectionOf<HTMLDivElement>;

NodeListの場合は以下

// クラス名pokeでp要素の場合
const nodeList = document.querySelectorAll('.poke') as NodeListOf<HTMLParagraphElement>;

型アサーションの注意点は、手動で型を上書きするので型の指定を間違えればエラーが表示されません。使うときは指定する型に間違いがないか確認するようにしましょう。

nullじゃないと言い切る「!」の使い方

例えばdocument.getElementByIdなどHTMLElementとnullの2つ型をUnion型で持っています。

// HTMLElement | null の型を持っている
const htmlElement = document.getElementById('hoge');

「!」を使うことでこのnullを消すことができます。これはasでHTMLElementを指定した場合と同じ意味です。

// HTMLElement の型だけになる
const htmlElement = document.getElementById('hoge')!;

//こっちの書き方と意味は同じ
//const htmlElement = document.getElementById('hoge') as HTMLElement;

本来はnullを消さず、if文を使ってその要素があるか判定することが望ましいです。ただ状況によって書き方が助長になってしまう場合は「!」を検討してみましょう。

オブジェクトに後からプロパティを追加できるようにする

例えば以下のように型のプロパティを固定していると後からオブジェクト側にプロパティが追加できません。

type Worker = {
  job: string;
  income: number;
};

const worker: Worker = { //型にjobとincomeが指定されているため、それ以上追加できない
  job: '事務',
  income: 100,
};

オブジェクトに後から追加できるようにするには以下の4行目ように書きます。

type Worker = {
  job: string;
  income: string;
  [items: string]: string;
};

const worker: Worker = {
  job: '事務',
  income: '100',
  adress: '東京'
};

[items:string]と指定すれば、キーはstringかnumberのどちらか割り当てることができます。ここはstringで問題ないでしょう。

注意する部分は、[items:string]の値に指定されているstringですが、ここがstringだとすべてstringの値にする必要があります。以下の例を見てみましょう。

type Worker = {
  job: string;
  income: number; //ここもstringにする必要がある
  [items: string]: string; //ここの値がstringなら、すべてstringにする必要がある
};

const worker: Worker = {
  job: '事務',
  income: 100, //stringではないのでエラー
  adress: '東京'
};

柔軟にオブジェクトを追加できる反面、オブジェクトにないプロパティを指定してもエラーにならないので使い方には注意しましょう。

オブジェクトで指定されている型情報を取得する

typeエイリアスのWorkerにあるjobの型を、typeエイリアスのWorkerJobで取得しています。interfaceで指定されている型も同じようにして取得できます。

type Worker = {
  job: string;
  income: number;
  detail: {
    name: string;
    age: number;
  };
};

type WorkerJob = Worker['job']; //stringが取得できる

型の階層が深い場合は以下。

type Worker = {
  job: string;
  income: number;
  detail: {
    name: string;
    age: number;
  };
};

type WorkersAge = Worker['detail']['age']; //numberが取得できる

Union型でも指定できます。

type Worker = {
  job: string;
  income: number;
  detail: {
    name: string;
    age: number;
  };
};

type WorkerJobIncome = Worker['job' | 'income']; //string | number になる

constアサーションの使い方

以下の型はstring[]になります。

const weather = ['雨', '晴れ']; //型はstring[]

constアサーションを使うとreadonlyがついたリテラル型になります。
つまり、weatherの変数には雨と晴れの文字列で固定されて、かつ配列に何かを加えることができなくなります。

const weather = ['雨', '晴れ'] as const; //constアサーション

/*--型は以下です--
const weather: readonly ['雨', '晴れ'] = ['雨', '晴れ'];
--*/

オブジェクトの場合は以下。readonlyがつくので、値を変えようとするとエラーになる。

const weather = {
  sunny: '晴れ',
  rain: '雨',
  cloudy: '雲',
} as const; //constアサーション

/*--型は以下です--
const weather = {
  readonly sunny: '晴れ',
  readonly rain: '雨',
  readonly cloudy: '雲',
};
--*/

weather.sunny = '大晴れ'; //readonlyがつくのでエラーになる

値から型を取得するtypeof

JavaScriptでは値に応じた型を返すtypeofがあります。

const animal = '子豚';
console.log(typeof animal); //stringを返す

TypeScriptでは、typeofを使って値から型を取得することができます。

const weather = {
  sunny: '晴れ',
  rain: '雨',
  cloudy: '雲',
};

type Weather = typeof weather; //値から型を取得する

/*--型は以下です--
type Weather = {
    sunny: string;
    rain: string;
    cloudy: string;
}
--*/

さいごに

今回は実務でよく使うTypeScriptの型指定について説明しました。今回は紹介できませんでしたが、ジェネリクスやTypeScriptでClassを使う方法も今後説明していきます。