初心者向け TypeScript 入門 Interface の使い方①

こんにちわ。

本日は、TypeScriptのInterface に関して基礎的な書き方を見ていきたいと思います。

 

まず初めに、下記のような関数呼び出しのコードを見てみましょう。

 

function printLabel(labeledObj: { label: string }) {
  console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj); //Size 10 Object

 

こんな風に書くことが出来ます。

 

printLabelを呼び出していますが、{label : string} のパラメータがあります。

 

僕たちが呼び出した関数では、オブジェクトには実際にはこれよりも多くのプロパティがありますが、

 

コンパイラは少なくとも必要なものが存在し、必要なタイプと一致することだけをチェックします。

 

では、上記のコードをInterfaceを利用して、関数呼び出しに必要な型をInterfaceで定義してみみましょう。

 

interface LabeledValue {
  label: string;
}

function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

 

TypeScriptの型チェックは、プロパティの順番とかも特に気にしないようなので、

 

インターフェースに必要なプロパティが存在して、必要なタイプを持ってるかが必要です。

 

Optional プロパティ

 

もしInterfafaceを定義するときに、全てのプロパティを活用する必要がない場合は、

 

Interfaceにオプショナルな型を定義することが可能です。

 

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
  let newSquare = { color: "white", area: 100 };
  if (config.color) {
    newSquare.color = config.color;
  }
  if (config.width) {
    newSquare.area = config.width * config.width;
  }
  return newSquare;
}

let mySquare = createSquare({ color: "black" });
console.log(mySquare)

//{
//  "color": "black",
//  "area": 100
//}

 

Readonly プロパティ

 

プロパティの中には、オブジェクトが最初に作成されたときにのみ変更可能であるべきものがあります。プ

 

ロパティの名前の前に readonly を付けることで、これを指定することができます。

 

interface Point {
  readonly x: number;
  readonly y: number;
}

 

オブジェクトリテラルを代入することでPointを構成することができます。代入後、xとyは変更できません。

 

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
Cannot assign to 'x' because it is a read-only property.

 

TypeScriptにはReadonlyArray<T>という型があり、

 

これはArray<T>からすべての変異メソッドを取り除いたもので、

 

作成後の配列を変更しないようにすることができます。

 

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;

ro[0] = 12; // error!
Index signature in type 'readonly number[]' only permits reading.
ro.push(5); // error!
Property 'push' does not exist on type 'readonly number[]'.
ro.length = 100; // error!
Cannot assign to 'length' because it is a read-only property.
a = ro; // error!
The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.

 

このスニペットの最後の行では、ReadonlyArray全体を通常の配列に戻すことも、一応できます。

 

タイプアサーションで上書きしてみましょう。

 

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;

a = ro as number[];

 

readonly vs const

 

readonlyとconstのどちらを使うかを覚える最も簡単な方法は、

 

変数とプロパティのどちらに使うかを尋ねることです。

 

変数はconstを使い、プロパティはreadonlyを使います。

 

余剰プロパティのチェック

 

この記事のインターフェイスを使った最初の例では、

 

{ label: string; }

 

だけを期待していたものに、TypeScriptで{ size: number; label: string; }を渡すことができました。

 

しかし、この2つを単純に組み合わせてしまうと、エラーが発生してしまいます。

 

例えば、先ほどのcreateSquareを使った例を見てみましょう。

 

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
  return {
    color: config.color || "red",
    area: config.width ? config.width * config.width : 20,
  };
}

let mySquare = createSquare({ colour: "red", width: 100 });
 
  //Argument of type '{ colour: string; width: number; }' is not assignable to parameter of type 'SquareConfig'.
  //Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?

 

createSquareの引数がcolorではなくcolourとなっているわけですが、

 

widthプロパティは互換性があり、余分なcolourプロパティは重要ではないので、

 

このプログラムは正しく型付けされていると主張することができます。

 

しかし、TypeScriptでは、このコードはバグがあるというスタンスをとられます。

 

オブジェクトリテラルが「対象となる型」が持っていないプロパティを持っていると、エラーが発生してしまうのです。

 

これらのチェックを回避する方法は、型のアサーションを使うことで回避できます。

 

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

 

しかし、より良い方法は、

 

オブジェクトが何らかの特別な方法で使用されるいくつかの追加プロパティを持つことができると確信している場合、

 

文字列インデックス署名を追加することです。

 

もしSquareConfigが上記のタイプのcolorとwidthのプロパティを持つことができ、

 

さらに他のプロパティをいくつでも持つことができるのであれば、

 

次のように定義することができます。

 

interface SquareConfig {
 color?: string;
 width?: number;
 [propName: string]: any;
}

 

インデックスシグネチャについては後ほど説明しますが、

 

ここではSquareConfigはいくつでもプロパティを持つことができ、

 

colorやwidth以外のプロパティであれば、その型は問題ではないということです。

 

interface SquareConfig {
  color?: string;
  width?: number;
  [propName: string]: any;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
  return {
    color: config.color || "red",
    area: config.width ? config.width * config.width : 20,
  };
}

let mySquare = createSquare({ color: "red", sssss: 100, aaaa: 100 } );

console.log(mySquare)

//{
//  "color": "red",
//  "area": 20
//}

 

また、これらのチェックを回避する最後の方法がもう一つありまして、ちょっと意外かもしれませんが、

 

オブジェクトを別の変数に割り当てることです。

 

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
  return {
    color: config.color || "red",
    area: config.width ? config.width * config.width : 20,
  };
}

let squareOptions = { color: "red", sssss: 100, aaaa: 100 };
let mySquare = createSquare(squareOptions);

console.log(mySquare)

//{
//  "color": "red",
//  "area": 20
//}

 

上記の回避策は、

 

squareOptionsとSquareConfigの間に共通のプロパティがあれば、うまくいきます。

 

この例では、それはプロパティ width でした。

 

しかし、変数が共通のオブジェクト・プロパティを持っていない場合は失敗します。

 

let squareOptions = { colour: "red" };
let mySquare = createSquare(squareOptions);
// Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.

 

上記のような単純なコードでは、これらのチェックを「回避」しようとするべきではないことを覚えておいてください。

 

メソッドを持ち、状態を保持するような複雑なオブジェクトリテラルの場合は、

 

これらのテクニックを念頭に置く必要があるかもしれませんが、過剰なプロパティエラーの大半は実際にはバグですし、

 

つまり、オプションバッグなどで過剰なプロパティチェックの問題が発生している場合は、

 

型宣言の一部を修正する必要があるかもしれません。

 

今回の例では、createSquareにcolorやcolorプロパティを持つオブジェクトを渡しても問題ないのであれば、

 

SquareConfigの定義を修正して反映させる必要があります。

 

今日は以上になります。

 

今回の記事は上記の記事を参考に書きまして、説明が面倒くさいところは、一部、翻訳の文章もコピペしてますが、

 

言わんとしてることが伝わってると思います。

藤沢瞭介(Ryosuke Hujisawa)
  • りょすけと申します。18歳からプログラミングをはじめ、今はフロントエンドでReactを書いたり、AIの勉強を頑張っています。off.tokyoでは、ハイテクやガジェット、それからプログラミングに関する情報まで、エンジニアに役立つ情報を日々発信しています!

未整理記事

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です