オブジェクト型

JavaScriptでは、データをグループ化してやり取りする基本的な方法はオブジェクトです。TypeScriptでは、それらをオブジェクト型で表現します。

見てきたように、それらは匿名にすることができます。

ts
function greet(person: { name: string; age: number }) {
return "Hello " + person.name;
}
Try

または、インターフェースを使用するか

ts
interface Person {
name: string;
age: number;
}
 
function greet(person: Person) {
return "Hello " + person.name;
}
Try

型エイリアスを使用して名前を付けることができます。

ts
type Person = {
name: string;
age: number;
};
 
function greet(person: Person) {
return "Hello " + person.name;
}
Try

上記の3つの例ではすべて、`name`プロパティ(`string`でなければならない)と`age`プロパティ(`number`でなければならない)を含むオブジェクトを受け取る関数を記述しました。

クイックリファレンス

重要な日常的な構文を一目で確認したい場合は、`type`と`interface`の両方のチートシートをご利用いただけます。

プロパティ修飾子

オブジェクト型の各プロパティは、型、プロパティがオプションかどうか、プロパティに書き込みができるかどうかを指定できます。

オプションのプロパティ

多くの場合、プロパティが設定されている可能性のあるオブジェクトを扱うことになります。そのような場合、名前の最後に疑問符(`?`)を追加することで、これらのプロパティをオプションとしてマークできます。

ts
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
 
function paintShape(opts: PaintOptions) {
// ...
}
 
const shape = getShape();
paintShape({ shape });
paintShape({ shape, xPos: 100 });
paintShape({ shape, yPos: 100 });
paintShape({ shape, xPos: 100, yPos: 100 });
Try

この例では、`xPos`と`yPos`の両方がオプションと見なされます。どちらか一方を提供するか、両方とも提供しないかを選択できます。そのため、上記の`paintShape`への呼び出しはすべて有効です。オプション性は、プロパティが設定されている場合は特定の型でなければならないことを意味するだけです。

これらのプロパティから読み取ることもできますが、`strictNullChecks`の下では、TypeScriptはそれらが潜在的に`undefined`であることを教えてくれます。

ts
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos;
(property) PaintOptions.xPos?: number | undefined
let yPos = opts.yPos;
(property) PaintOptions.yPos?: number | undefined
// ...
}
Try

JavaScriptでは、プロパティが設定されたことがなくても、アクセスできます。`undefined`という値が返されるだけです。`undefined`を特別な値として処理することで、それを処理できます。

ts
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos === undefined ? 0 : opts.xPos;
let xPos: number
let yPos = opts.yPos === undefined ? 0 : opts.yPos;
let yPos: number
// ...
}
Try

指定されていない値のデフォルト値を設定するこのパターンは非常に一般的であるため、JavaScriptにはそれをサポートする構文があります。

ts
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
console.log("x coordinate at", xPos);
(parameter) xPos: number
console.log("y coordinate at", yPos);
(parameter) yPos: number
// ...
}
Try

ここでは、`paintShape`のパラメータにデストラクチャリングパターンを使用し、`xPos`と`yPos`にデフォルト値を指定しました。これで、`xPos`と`yPos`は`paintShape`の本体内では確実に存在しますが、`paintShape`を呼び出すユーザーにとってはオプションになります。

現在、デストラクチャリングパターン内に型注釈を配置する方法はありません。これは、次の構文がJavaScriptで既に異なる意味を持つためです。

ts
function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
render(shape);
Cannot find name 'shape'. Did you mean 'Shape'?2552Cannot find name 'shape'. Did you mean 'Shape'?
render(xPos);
Cannot find name 'xPos'.2304Cannot find name 'xPos'.
}
Try

オブジェクトのデストラクチャリングパターンでは、`shape: Shape`は「`shape`プロパティを取得し、`Shape`という名前のローカル変数として再定義する」という意味です。同様に、`xPos: number`は、値がパラメータの`xPos`に基づく`number`という名前の変数を作成します。

readonly プロパティ

TypeScriptでは、プロパティをreadonlyとしてマークすることもできます。実行時の動作は変わりませんが、readonlyとしてマークされたプロパティは、型チェック中に書き込むことができません。

ts
interface SomeType {
readonly prop: string;
}
 
function doSomething(obj: SomeType) {
// We can read from 'obj.prop'.
console.log(`prop has the value '${obj.prop}'.`);
 
// But we can't re-assign it.
obj.prop = "hello";
Cannot assign to 'prop' because it is a read-only property.2540Cannot assign to 'prop' because it is a read-only property.
}
Try

readonly修飾子を使用しても、値が完全に不変であること、つまり内部の内容を変更できないことが必ずしも意味するわけではありません。単に、プロパティ自体を書き換えることができないという意味です。

ts
interface Home {
readonly resident: { name: string; age: number };
}
 
function visitForBirthday(home: Home) {
// We can read and update properties from 'home.resident'.
console.log(`Happy birthday ${home.resident.name}!`);
home.resident.age++;
}
 
function evict(home: Home) {
// But we can't write to the 'resident' property itself on a 'Home'.
home.resident = {
Cannot assign to 'resident' because it is a read-only property.2540Cannot assign to 'resident' because it is a read-only property.
name: "Victor the Evictor",
age: 42,
};
}
Try

readonlyが何を意味するのかという期待値を管理することが重要です。オブジェクトの使用方法に関する開発時の意図をTypeScriptに伝えるのに役立ちます。TypeScriptは、2つの型のプロパティがreadonlyかどうかを、それらの型が互換性があるかどうかをチェックする際に考慮しないため、readonlyプロパティはエイリアシングによって変更される可能性もあります。

ts
interface Person {
name: string;
age: number;
}
 
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
 
let writablePerson: Person = {
name: "Person McPersonface",
age: 42,
};
 
// works
let readonlyPerson: ReadonlyPerson = writablePerson;
 
console.log(readonlyPerson.age); // prints '42'
writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'
Try

マッピング修飾子を使用すると、readonly属性を削除できます。

インデックスシグネチャ

型のプロパティの名前をすべて事前に知らない場合がありますが、値の形状はわかっています。

そのような場合は、インデックスシグネチャを使用して可能な値の型を記述できます。例:

ts
interface StringArray {
[index: number]: string;
}
 
const myArray: StringArray = getStringArray();
const secondItem = myArray[1];
const secondItem: string
Try

上記では、インデックスシグネチャを持つStringArrayインターフェースがあります。このインデックスシグネチャは、StringArraynumberでインデックス付けされると、stringを返すことを示しています。

インデックスシグネチャのプロパティには、stringnumbersymbol、テンプレート文字列パターン、およびこれらの型のみで構成されるユニオン型などの一部の型のみが許可されます。

両方のタイプのインデクサをサポートすることは可能です...

両方のタイプのインデクサをサポートすることは可能ですが、数値インデクサから返される型は、文字列インデクサから返される型のサブタイプである必要があります。これは、JavaScriptが数値でインデックス付けを行う場合、実際にはオブジェクトにインデックス付けする前にそれを文字列に変換するためです。つまり、100number)でインデックス付けすることは、"100"string)でインデックス付けすることと同じなので、両方が一致する必要があります。

ts
interface Animal {
name: string;
}
 
interface Dog extends Animal {
breed: string;
}
 
// Error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
[x: number]: Animal;
'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.2413'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
[x: string]: Dog;
}
Try

文字列インデックスシグネチャは「辞書」パターンを記述する強力な方法ですが、すべてのプロパティがその戻り値の型と一致することを強制します。これは、文字列インデックスがobj.propertyobj["property"]として使用できることを宣言するためです。次の例では、nameの型は文字列インデックスの型と一致せず、型チェッカーはエラーを返します。

ts
interface NumberDictionary {
[index: string]: number;
 
length: number; // ok
name: string;
Property 'name' of type 'string' is not assignable to 'string' index type 'number'.2411Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}
Try

ただし、インデックスシグネチャがプロパティ型のユニオンである場合、異なる型のプロパティが許容されます。

ts
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}
Try

最後に、インデックスシグネチャをreadonlyにすることで、そのインデックスへの代入を防ぐことができます。

ts
interface ReadonlyStringArray {
readonly [index: number]: string;
}
 
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory";
Index signature in type 'ReadonlyStringArray' only permits reading.2542Index signature in type 'ReadonlyStringArray' only permits reading.
Try

インデックスシグネチャがreadonlyなので、myArray[2]を設定することはできません。

超過プロパティチェック

オブジェクトに型が割り当てられる場所と方法は、型システムに影響を与える可能性があります。その重要な例の一つが超過プロパティチェックです。これは、オブジェクトが作成され、作成中にオブジェクト型に割り当てられるときに、オブジェクトをより徹底的に検証します。

ts
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'?2345Argument 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'?
Try

createSquareに渡された引数は、colorではなくcolourとスペルミスされています。プレーンなJavaScriptでは、このようなことはサイレントに失敗します。

widthプロパティは互換性があり、colorプロパティが存在せず、余分なcolourプロパティは重要ではないため、このプログラムは正しく型付けされていると主張するかもしれません。

しかし、TypeScriptは、このコードにバグがある可能性が高いという立場を取っています。オブジェクトリテラルは特別な扱いを受け、他の変数に割り当てたり、引数として渡したりするときに、超過プロパティチェックが行われます。オブジェクトリテラルに「ターゲット型」にないプロパティがあると、エラーが発生します。

ts
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'?2345Argument 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'?
Try

これらのチェックを回避するのは実際には非常に簡単です。最も簡単な方法は、型アサーションを使用することです。

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

ただし、オブジェクトに特別な方法で使用される余分なプロパティがあることが確かな場合は、文字列インデックスシグネチャを追加する方が良いアプローチかもしれません。SquareConfigは、上記の型でcolorwidthプロパティを持つことができますが、その他に任意の数のプロパティを持つこともできる場合、次のように定義できます。

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

ここでは、SquareConfigは任意の数のプロパティを持つことができ、colorまたはwidthでない限り、その型は問題ないことを示しています。

これらのチェックを回避するもう1つの方法(少し驚くかもしれませんが)は、オブジェクトを別の変数に割り当てることです。squareOptionsの割り当ては超過プロパティチェックを受けないので、コンパイラはエラーを返しません。

ts
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);
Try

上記の回避策は、squareOptionsSquareConfigに共通のプロパティがある限り機能します。この例では、widthプロパティでした。ただし、変数に共通のオブジェクトプロパティがない場合は失敗します。例:

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

上記のような単純なコードの場合、これらのチェックを「回避」しようとするべきではないことに注意してください。メソッドを持ち、状態を保持するより複雑なオブジェクトリテラルの場合、これらのテクニックを念頭に置いておく必要があるかもしれませんが、大部分の超過プロパティエラーは実際にはバグです。

つまり、オプションバッグのようなものに対して超過プロパティチェックの問題が発生している場合は、いくつかの型宣言を修正する必要があるかもしれません。この場合、colorまたはcolourプロパティの両方を持つオブジェクトをcreateSquareに渡しても問題ない場合は、それを反映するようにSquareConfigの定義を修正する必要があります。

型の拡張

他の型のより具体的なバージョンである可能性のある型を持つことは非常に一般的です。たとえば、米国で手紙や小包を送信するために必要なフィールドを記述するBasicAddress型があるとします。

ts
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}
Try

状況によってはそれで十分ですが、住所には、住所にある建物に複数のユニットがある場合、ユニット番号が関連付けられていることがよくあります。次に、AddressWithUnitを記述できます。

ts
interface AddressWithUnit {
name?: string;
unit: string;
street: string;
city: string;
country: string;
postalCode: string;
}
Try

これは機能しますが、変更が純粋に追加されただけなのに、BasicAddressの他のすべてのフィールドを繰り返す必要があったという欠点があります。代わりに、元のBasicAddress型を拡張し、AddressWithUnitに固有の新しいフィールドを追加できます。

ts
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}
 
interface AddressWithUnit extends BasicAddress {
unit: string;
}
Try

interfaceextendsキーワードを使用すると、他の名前付き型のメンバーを効果的にコピーし、必要な新しいメンバーを追加できます。これは、記述する必要がある型宣言のボイラープレートの量を削減し、同じプロパティのいくつかの異なる宣言が関連している可能性があることを示すために役立ちます。たとえば、AddressWithUnitstreetプロパティを繰り返す必要がなく、streetBasicAddressに由来するため、読者はこれらの2つの型が何らかの方法で関連していることを知ることができます。

interfaceは複数の型から拡張することもできます。

ts
interface Colorful {
color: string;
}
 
interface Circle {
radius: number;
}
 
interface ColorfulCircle extends Colorful, Circle {}
 
const cc: ColorfulCircle = {
color: "red",
radius: 42,
};
Try

交差型

interfaceを使用すると、拡張することで他の型から新しい型を構築できました。TypeScriptは、主に既存のオブジェクト型を組み合わせるために使用される交差型と呼ばれる別の構成を提供します。

交差型は、&演算子を使用して定義されます。

ts
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
 
type ColorfulCircle = Colorful & Circle;
Try

ここでは、ColorfulCircleを交差させて、ColorfulCircleのすべてのメンバーを持つ新しい型を作成しました。

ts
function draw(circle: Colorful & Circle) {
console.log(`Color was ${circle.color}`);
console.log(`Radius was ${circle.radius}`);
}
 
// okay
draw({ color: "blue", radius: 42 });
 
// oops
draw({ color: "red", raidus: 42 });
Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'. Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?2345Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'. Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?
Try

インターフェースと交差型の比較

類似していますが、実際には微妙に異なる2つの型の組み合わせ方法を見てきました。インターフェースでは、extends句を使用して他の型から拡張することができ、交差型でも同様のことを行い、型エイリアスで結果に名前を付けることができました。両者の主な違いは、競合の処理方法であり、その違いは通常、インターフェースと交差型の型エイリアスのどちらを選択するかを決定する主な理由の1つです。

ジェネリックオブジェクト型

任意の値(stringnumberGiraffeなど)を含むことができるBox型を考えてみましょう。

ts
interface Box {
contents: any;
}
Try

現時点では、contentsプロパティはanyとして型付けされていますが、これは機能しますが、将来的に問題が発生する可能性があります。

代わりにunknownを使用することもできますが、そうすると、contentsの型が既にわかっている場合に、予防的なチェックを行う必要が生じたり、エラーが発生しやすい型アサーションを使用する必要が生じたりします。

ts
interface Box {
contents: unknown;
}
 
let x: Box = {
contents: "hello world",
};
 
// we could check 'x.contents'
if (typeof x.contents === "string") {
console.log(x.contents.toLowerCase());
}
 
// or we could use a type assertion
console.log((x.contents as string).toLowerCase());
Try

型安全なアプローチの1つは、contentsの各型に対して異なるBox型を構築することです。

ts
interface NumberBox {
contents: number;
}
 
interface StringBox {
contents: string;
}
 
interface BooleanBox {
contents: boolean;
}
Try

しかし、それはこれらの型を操作するために異なる関数、または関数のオーバーロードを作成する必要があることを意味します。

ts
function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
box.contents = newContents;
}
Try

それは多くのボイラープレートコードになります。さらに、後で新しい型とオーバーロードを導入する必要があるかもしれません。これは、ボックス型とオーバーロードがすべて事実上同じであるため、イライラさせられます。

代わりに、型パラメータを宣言するジェネリックBox型を作成できます。

ts
interface Box<Type> {
contents: Type;
}
Try

これは「Type型のBoxとは、contentsの型がTypeである何か」と読むことができます。後でBoxを参照する際には、Typeの代わりに型引数を指定する必要があります。

ts
let box: Box<string>;
Try

Boxを実際の型のテンプレートと考えてください。Typeは、他の型に置き換えられるプレースホルダーです。TypeScriptがBox<string>を認識すると、Box<Type>内のTypeのすべてのインスタンスをstringに置き換え、{ contents: string }のようなものを使用して処理します。言い換えれば、Box<string>と以前のStringBoxは同じように動作します。

ts
interface Box<Type> {
contents: Type;
}
interface StringBox {
contents: string;
}
 
let boxA: Box<string> = { contents: "hello" };
boxA.contents;
(property) Box<string>.contents: string
 
let boxB: StringBox = { contents: "world" };
boxB.contents;
(property) StringBox.contents: string
Try

Boxは、Typeを何でも置き換えることができるため、再利用可能です。つまり、新しい型のボックスが必要な場合、新しいBox型を宣言する必要はありません(もちろん、必要であれば宣言することもできます)。

ts
interface Box<Type> {
contents: Type;
}
 
interface Apple {
// ....
}
 
// Same as '{ contents: Apple }'.
type AppleBox = Box<Apple>;
Try

これにより、ジェネリック関数を使用することで、オーバーロードを完全に回避することもできます。

ts
function setContents<Type>(box: Box<Type>, newContents: Type) {
box.contents = newContents;
}
Try

型エイリアスもジェネリックになり得ることに注意する価値があります。新しいBox<Type>インターフェースを定義できましたが、

ts
interface Box<Type> {
contents: Type;
}
Try

代わりに型エイリアスを使用して定義しました。

ts
type Box<Type> = {
contents: Type;
};
Try

型エイリアスは、インターフェースとは異なり、オブジェクト型以上のものを記述できるため、他の種類のジェネリックヘルパー型を作成するためにも使用できます。

ts
type OrNull<Type> = Type | null;
 
type OneOrMany<Type> = Type | Type[];
 
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
type OneOrManyOrNull<Type> = OneOrMany<Type> | null
 
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
type OneOrManyOrNullStrings = OneOrMany<string> | null
Try

型エイリアスについては、もう少し後で詳しく説明します。

Array

ジェネリックオブジェクト型は、多くの場合、含まれる要素の型とは独立して動作する何らかのコンテナ型です。データ構造がこのように動作すると、異なるデータ型間で再利用できるため理想的です。

このハンドブック全体を通して、まさにそのような型を使用してきました。それはArray型です。number[]string[]のような型を記述するたびに、それは実際にはArray<number>Array<string>の省略形です。

ts
function doSomething(value: Array<string>) {
// ...
}
 
let myArray: string[] = ["hello", "world"];
 
// either of these work!
doSomething(myArray);
doSomething(new Array("hello", "world"));
Try

上記のBox型と同様に、Array自体はジェネリック型です。

ts
interface Array<Type> {
/**
* Gets or sets the length of the array.
*/
length: number;
 
/**
* Removes the last element from an array and returns it.
*/
pop(): Type | undefined;
 
/**
* Appends new elements to an array, and returns the new length of the array.
*/
push(...items: Type[]): number;
 
// ...
}
Try

最新のJavaScriptでは、Map<K, V>Set<T>Promise<T>など、ジェネリックな他のデータ構造も提供されています。これは、MapSetPromiseの動作方法により、任意の型の集合で動作できることを意味します。

ReadonlyArray

ReadonlyArrayは、変更してはならない配列を記述する特別な型です。

ts
function doStuff(values: ReadonlyArray<string>) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
 
// ...but we can't mutate 'values'.
values.push("hello!");
Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.
}
Try

プロパティのreadonly修飾子と同様に、主に意図を伝えるためのツールです。ReadonlyArrayを返す関数を見ると、内容を変更してはならないことがわかります。ReadonlyArrayを受け取る関数を見ると、内容が変更される心配なく、任意の配列をその関数に渡すことができます。

Arrayとは異なり、使用できるReadonlyArrayコンストラクタはありません。

ts
new ReadonlyArray("red", "green", "blue");
'ReadonlyArray' only refers to a type, but is being used as a value here.2693'ReadonlyArray' only refers to a type, but is being used as a value here.
Try

代わりに、通常のArrayReadonlyArrayに代入できます。

ts
const roArray: ReadonlyArray<string> = ["red", "green", "blue"];
Try

TypeScriptはArray<Type>に対してType[]という省略記法を提供するように、ReadonlyArray<Type>に対してreadonly Type[]という省略記法も提供しています。

ts
function doStuff(values: readonly string[]) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
 
// ...but we can't mutate 'values'.
values.push("hello!");
Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.
}
Try

最後に注意すべき点として、readonlyプロパティ修飾子とは異なり、通常のArrayReadonlyArrayの間では、代入可能性は双方向ではありません。

ts
let x: readonly string[] = [];
let y: string[] = [];
 
x = y;
y = x;
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.4104The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
Try

タプル型

タプル型は、含まれる要素の数と、特定の位置に含まれる型を正確に知っている別の種類のArray型です。

ts
type StringNumberPair = [string, number];
Try

ここで、StringNumberPairstringnumberのタプル型です。ReadonlyArrayと同様に、ランタイムでは表現されませんが、TypeScriptにとって重要です。型システムにとって、StringNumberPairは、インデックス0string、インデックス1numberを含む配列を表します。

ts
function doSomething(pair: [string, number]) {
const a = pair[0];
const a: string
const b = pair[1];
const b: number
// ...
}
 
doSomething(["hello", 42]);
Try

要素数を超えるインデックスを使用しようとすると、エラーが発生します。

ts
function doSomething(pair: [string, number]) {
// ...
 
const c = pair[2];
Tuple type '[string, number]' of length '2' has no element at index '2'.2493Tuple type '[string, number]' of length '2' has no element at index '2'.
}
Try

JavaScriptの配列デストラクチャリングを使用して、タプルをデストラクチャリングすることもできます。

ts
function doSomething(stringHash: [string, number]) {
const [inputString, hash] = stringHash;
 
console.log(inputString);
const inputString: string
 
console.log(hash);
const hash: number
}
Try

タプル型は、各要素の意味が「明らか」である、規約に依存したAPIで役立ちます。これにより、デストラクチャリングする際に変数名を自由に選択できます。上記の例では、要素01に任意の名前を付けることができました。

しかし、すべてのユーザーが同じ「明らか」な見解を持っているわけではないため、記述的なプロパティ名を持つオブジェクトを使用する方がAPIにとって適切かどうかを再考する価値があるかもしれません。

これらの長さチェック以外では、このような単純なタプル型は、特定のインデックスのプロパティを宣言し、数値リテラル型でlengthを宣言するArrayのバリエーションである型と同等です。

ts
interface StringNumberPair {
// specialized properties
length: 2;
0: string;
1: number;
 
// Other 'Array<string | number>' members...
slice(start?: number, end?: number): Array<string | number>;
}
Try

要素の型の後に疑問符(?)を付けることで、タプルはオプションのプロパティを持つことができることも興味深い点です。オプションのタプル要素は末尾にのみ配置でき、lengthの型にも影響します。

ts
type Either2dOr3d = [number, number, number?];
 
function setCoordinate(coord: Either2dOr3d) {
const [x, y, z] = coord;
const z: number | undefined
 
console.log(`Provided coordinates had ${coord.length} dimensions`);
(property) length: 2 | 3
}
Try

タプルにはrest要素を含めることもでき、rest要素は配列/タプル型でなければなりません。

ts
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];
Try
  • StringNumberBooleansは、最初の2つの要素がそれぞれstringnumberであるが、その後に任意の数のbooleanを持つ可能性のあるタプルを表します。
  • StringBooleansNumberは、最初の要素がstringで、その後任意の数のbooleanがあり、最後にnumberで終わるタプルを表します。
  • BooleansStringNumberは、先頭の要素が任意の数のbooleanで、最後にstringnumberが続くタプルを表します。

rest要素を持つタプルには設定された「長さ」がありません。異なる位置にある既知の要素の集合のみを持っています。

ts
const a: StringNumberBooleans = ["hello", 1];
const b: StringNumberBooleans = ["beautiful", 2, true];
const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];
Try

オプション要素とrest要素が役立つのはなぜでしょうか?それは、TypeScriptがタプルをパラメーターリストに対応させることができるためです。タプル型はrestパラメーターと引数で使用できるため、次のようになります。

ts
function readButtonInput(...args: [string, number, ...boolean[]]) {
const [name, version, ...input] = args;
// ...
}
Try

これは基本的に次と同等です。

ts
function readButtonInput(name: string, version: number, ...input: boolean[]) {
// ...
}
Try

これは、restパラメーターを使用して可変数の引数を受け取り、最小限の要素数が必要だが、中間変数を導入したくない場合に便利です。

readonlyタプル型

タプル型に関する最後の注意点として、タプル型にはreadonlyのバリアントがあり、配列の省略記法と同様に、前にreadonly修飾子を付けることで指定できます。

ts
function doSomething(pair: readonly [string, number]) {
// ...
}
Try

予想されるように、TypeScriptではreadonlyタプルのプロパティへの書き込みは許可されません。

ts
function doSomething(pair: readonly [string, number]) {
pair[0] = "hello!";
Cannot assign to '0' because it is a read-only property.2540Cannot assign to '0' because it is a read-only property.
}
Try

タプルはほとんどのコードで作成されて変更されないままである傾向があるため、可能な限り型をreadonlyタプルとしてアノテーションすることは良いデフォルトです。これは、constアサーションを含む配列リテラルがreadonlyタプル型として推論されることを考えると、重要です。

ts
let point = [3, 4] as const;
 
function distanceFromOrigin([x, y]: [number, number]) {
return Math.sqrt(x ** 2 + y ** 2);
}
 
distanceFromOrigin(point);
Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.2345Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.
Try

ここで、distanceFromOriginは要素を変更することはありませんが、変更可能なタプルを期待しています。pointの型はreadonly [3, 4]として推論されたため、その型はpointの要素が変更されないことを保証できないため、[number, number]と互換性がありません。

TypeScriptドキュメントはオープンソースプロジェクトです。Pull Requestを送信して、これらのページの改善にご協力ください by sending a Pull Request

このページへの貢献者
DRDaniel Rosenwasser (52)
OTOrta Therox (16)
338elements  (2)
BRBruce Robertson (2)
ARAlan Rempel (2)
18+

最終更新日:2024年3月21日