6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年3月22日 りあクト! 第4章 TypeScriptで型をご安全に(p.162~168)

タプル型

タプル型とは

  • タプル型とは、配列内の要素の型と、その順番や個数があらかじめ指定できる特殊な配列の型です。要素の型は、それぞれの要素ごとに決めることが出来ます。
  • レストパラメーターを使うと、いくつでも引数を受け取ることができるようになります。
    ※ Rest parametersは、仮引数名の前に...をつけた仮引数のことで、残余引数とも呼ばれます。 Rest parametersには、関数に渡された値が配列として代入されます。
  • タプル型を使うことによって、引数として受け取る値の制限ができるイメージです。より厳密に受け取りたい値を指定できるので、バグにつながる値の受け取りを排除できます。

以下の場合にタプル型が使用されます。

  • 関数の引数
    • 関数の引数としてまとめて配列で渡したい時に仮引数としてタプル型であらかじめ型、個数、順番の要素を指定して渡すイメージです。
  • API関数の戻り値(分割代入を使うことが多いです。)
const charAttrs: [number, string, boolean] = [1, 'party', true];
let array0: [number, string] = [1, 'a'];

let array1: [number, ...string[]] = [1, 'a', 'b'];

// タプル型の内部で、...string[]が使われている場合、
// string型の要素を追加できます
array1[3] =  'c';

let array2: string[] = ['a', 'b'];

array2[3] = 'c';
  • array0のarray[0]には、number型、array[1]にはstring型の要素のみを受け付るようにしています。
  • array1ではレストパラメーターを使っているので、第一要素以外は、string型の要素を幾つでも受け付けることが出来ます。
  • array2では、String型の要素を受け付けることができる配列となっています。

使用用途

まとめて配列で実引数を渡すときに、仮引数にまとめて型アノテーションをつけられるので関数の利用時によく使われます。
また、仮引数にRestParametersを使用する事もできます。
仮引数の第一要素、第二要素の順番で型アノテーションをつけられて、RestParametersを使わなければ引数の数も指定できます。

Any, Unknown, Never

何者でもあったり、何者でもなかったりする型です。

Any型とは? 

  • Any型とは、いかなる型の値も受け付けるようにできる型のことです。JavaScriptと同じ挙動になってしまいます。
  • データ型が不明なまま書く必要がある場合に使われますが、問題点もある型なので次で説明するunknownを使う方が良いでしょう。
  • any型の変数については、コンパイラーが型チェックを行いません。実行してみるとエラーになるようなコードでも、コンパイラーはその問題を指摘しないのです。

JSON.parse()の戻り値は、Object, Array, 文字列, 数値, 論理値, null値です。そのため、JSON.parse()の戻り値の型は、TypeScriptによってany型と推論されます。

user.address.zipCode の ようなアクセスもコンパイラは通してしまいます。

const str = '{"id": 1, "username": "john"}';
const user = JSON.parse(str);

console.log(user.id, user.adress.zipCode); // => [ERR]: Cannot read properties of undefined (reading 'zipCode') undefined
  • 通常TypeScriptでは、値の代入前に型指定をするため、誤った型の値を入れようとした時点で、エラーを出します。
  • anyを使うと、コンパイル時にエラーを出さずに実行時にエラーが発生してしまいます。
  • 今回の場合は、JSON.parseのメソッドの戻り値がany型で返されるため、代入した変数のuserもanyとして型推論されています。
  • any型と判断されるのは、JSON.parse()の返り値がObject, Array, 文字列, 数値, 論理値, null 値と様々な値が返るため、TypeScript型推論するとany型に変換されるからです。
  • TypeScriptの静的型付け言語の恩恵が受けられていません。

any型は、unknownとは違いプロパティやメソッドを使用できるが、any型の使用 = TypeScriptが型のチェックを放棄した型となるので、Typescriptの利点を活かしきれていません。

unknown型

  • anyの型安全版です。任意の型の値を代入できる点はany型と同じです。
  • 何のプロパティもプロトタイプメソッドも持たない型です。
  • 今回userまでは代入できますが、userにプロパティ名を指定して呼び出そうとするとコンパイルの時点でエラーになります。
const str = '{"id": 1, "username": "john"}';
const user: unknown = JSON.parse(str);

console.log(user); /* => [LOG]: {
  "id": 1,
  "username": "john"
} */

console.log(user.id, user.username);  // => Object is of type 'unknown'.

never型

  • 何者も代入できない型です。

使用用途が想像しづらい型ですが、書籍にあった例では

const greet = (friend: 'Serval' | 'Caracal' | 'Cheetah') => {
  switch (friend) {
    case 'Caracal':
      return `Hi, ${friend}!`;
    case 'Cheetah':
      return `Hiya, ${friend}!`;
    default: {
      const check: never = friend;
    }
  }
};

console.log(greet('Serval'));
  • greetの仮引数にはリテラル型でServalは許容されていますがcase文ではありません。
  • 実引数にServalを渡しているのでswitch文ではdefaultが呼び出されます。
  • デフォルトにはnever型を指定しているので何者も代入できないのでエラーになります。case文の漏れをチェックできます。

関数とクラスの型

  • TypeScriptではコンパイラオプションに"noImplicitAny": trueが指定されていないと引数の型定義がない場合に暗黙的にany型があてがわれてコンパイルが通ってしまいます。この設定をtrueにしないと、any型と推論する値(関数の引数など)に対してエラーを出しません。

  • 関数は、様々な値を受け取ることが出来るため、TypeScriptではany型と型推論されてしまいます。

tsconfig.jsonファイル

"noImplicitAny": true,

関数の型定義

関数では、引数と戻り値に対してアノテーションを設定します。戻り値の型は型推論できますが、戻り値の型を明示するのが昨今のトレンドなので、本ブログでも明示します。 関数における、引数と戻り値の型を別々に定義する記法でまず紹介します。

const add = (n: number, m: number): number => n + m;
  • arrow関数を使って、関数を定義し変数addに格納しています。
  • (n: number, m: number)で引数の型を指定しています。
  • (n: number, m: number)の後ろの: numberで戻り値の型を指定しています。
const hello = (): void => {
    console.log('Hello');
};

hello();  // => Hello
  • arrow関数を使って関数を定義しています。
  • 戻り値は何も返さないので戻り値の型アノテーションは何も返さないvoidになります。

呼び出し可能オブジェクト(呼び出しシグネチャ)

呼び出し可能オブジェクトとして定義すると、関数の引数と戻り値の型をまとめて定義できます。

呼び出し可能オブジェクトとは、どのような関数なのかを表現する型定義のことです。 これをインターフェースとして定義すると、関数定義の際にスッキリとした記述ができることを以下で説明しています。

インターフェースはオブジェクトの型付けを行うことができる機能です。

インターフェースを呼び出し可能オブジェクトとして定義し、それを関数式に使う定義法

// 呼び出し可能オブジェクトをインターフェースとして定義する
interface Mononoke {
  (name: string): string;
};

// 関数の引数と戻り値に対して、型アノテーションをつける場合
const getMononoke1 = (name: string): string => {
  return name; 
}

// 呼び出し可能オブジェクトをインターフェースとして定義したものを使う場合
const getMononoke2: Mononoke = (name) => {
  return name;
}
  • インターフェースMononokeで、関数の引数の型と戻り値の型を指定しています。今回の場合、引数nameは、string型、戻り値は、string型を指定しています。
  • getMononoke2では、インターフェースMononokeを使っているので、関数の仮引数(name)の型を指定しないでスッキリと記述できます。

呼び出し可能オブジェクトの省略記法

// 呼び出し可能オブジェクトの省略記法
(message: string, userId?: string) => void;

// 明示的な呼び出し可能オブジェクト
{
  (message: string, userId?: string): void
}

参考

りあクト! 【Ⅰ. 言語・環境編】 p.162~

【TypeScript入門】呼び出しシグネチャとは