Object
とobject
Object
のインスタンスを記述するこの章では、TypeScriptでオブジェクトとプロパティがどのように静的に型付けされるかを探ります。
JavaScriptでは、オブジェクトは2つの役割を果たすことができます(常に少なくとも1つ、時には混在)。
レコードは、開発時に既知の固定量のプロパティを持ちます。各プロパティは異なる型を持つことができます。
辞書は、開発時に名前が不明な任意の数のプロパティを持ちます。すべてのプロパティキー(文字列やシンボル)は同じ型を持ち、プロパティ値も同様です。
まず、オブジェクトをレコードとして探ります。辞書としてのオブジェクトには、この章の後半で簡単に触れます。
オブジェクトには2つの異なる一般的な型があります。
大文字の「O」を持つObject
は、クラスObject
のすべてのインスタンスの型です。
: Object; let obj1
小文字の「o」を持つobject
は、すべての非プリミティブ値の型です。
: object; let obj2
オブジェクトは、そのプロパティを通じて型付けすることもできます。
// Object type literal
: {prop: boolean};
let obj3
// Interface
interface ObjectType {: boolean;
prop
}: ObjectType; let obj4
次のセクションでは、これらのオブジェクトの型付け方法を詳しく見ていきます。
Object
とobject
Object
のインスタンスプレーンなJavaScriptでは、重要な区別があります。
一方、ほとんどのオブジェクトはObject
のインスタンスです。
> const obj1 = {};
> obj1 instanceof Objecttrue
これはつまり
Object.prototype
がそれらのプロトタイプチェーンにある
> Object.prototype.isPrototypeOf(obj1)true
それらはそのプロパティを継承します。
> obj1.toString === Object.prototype.toStringtrue
他方、プロトタイプチェーンにObject.prototype
を持たないオブジェクトを作成することもできます。たとえば、次のオブジェクトにはプロトタイプがまったくありません。
> const obj2 = Object.create(null);
> Object.getPrototypeOf(obj2)null
obj2
は、クラスObject
のインスタンスではないオブジェクトです。
> typeof obj2'object'
> obj2 instanceof Objectfalse
Object
(大文字の「O」):クラスObject
のインスタンス各クラスC
は2つのエンティティを作成することを思い出してください。
C
。C
。同様に、TypeScriptには2つの組み込みインターフェースがあります。
インターフェースObject
は、Object.prototype
から継承されたプロパティを含む、Object
のインスタンスのプロパティを指定します。
インターフェースObjectConstructor
は、クラスObject
のプロパティを指定します。
これらがインターフェースです。
// (A)
interface Object { : Function;
constructortoString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
interface ObjectConstructor {/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
?: any): any;
(value
readonly prototype: Object; // (B)
getPrototypeOf(o: any): any;
// ···
}declare var Object: ObjectConstructor; // (C)
観察
Object
という名前の変数(C行)と、Object
という名前の型(A行)の両方があります。Object
の直接のインスタンスは独自のプロパティを持たないため、Object.prototype
もObject
と一致します(B行)。object
(小文字の「o」):非プリミティブ値TypeScriptでは、object
はすべての非プリミティブ値(プリミティブ値はundefined
、null
、ブール値、数値、bigint、文字列)の型です。この型では、値のプロパティにアクセスすることはできません。
Object
とobject
:プリミティブ値興味深いことに、型Object
はプリミティブ値とも一致します。
function func1(x: Object) { }
func1('abc'); // OK
なぜでしょうか?プリミティブ値はObject.prototype
を継承するため、Object
に必要なすべてのプロパティを持っています。
> 'abc'.hasOwnProperty === Object.prototype.hasOwnPropertytrue
逆に、object
はプリミティブ値と一致しません。
function func2(x: object) { }
// @ts-expect-error: Argument of type '"abc"' is not assignable to
// parameter of type 'object'. (2345)
func2('abc');
Object
とobject
:互換性のないプロパティ型型Object
を使用すると、オブジェクトにインターフェースObject
の対応するプロパティと競合する型のプロパティがある場合、TypeScriptは警告を発します。
// @ts-expect-error: Type '() => number' is not assignable to
// type '() => string'.
// Type 'number' is not assignable to type 'string'. (2322)
: Object = { toString() { return 123 } }; const obj1
型object
では、TypeScriptは警告を発しません(object
はプロパティを指定せず、競合が発生する可能性がないため)。
: object = { toString() { return 123 } }; const obj2
TypeScriptには、非常に似たオブジェクト型を定義する2つの方法があります。
// Object type literal
type ObjType1 = {
: boolean,
a: number;
b: string,
c;
}
// Interface
interface ObjType2 {: boolean,
a: number;
b: string,
c }
区切り文字としてセミコロンまたはカンマを使用できます。末尾の区切り文字は許可されており、オプションです。
このセクションでは、オブジェクト型リテラルとインターフェースの最も重要な違いを見ていきます。
オブジェクト型リテラルはインライン化できますが、インターフェースはできません。
// Inlined object type literal:
function f1(x: {prop: number}) {}
// Referenced interface:
function f2(x: ObjectInterface) {}
interface ObjectInterface {: number;
prop }
重複する名前を持つ型エイリアスは違法です。
// @ts-expect-error: Duplicate identifier 'PersonAlias'. (2300)
type PersonAlias = {first: string};
// @ts-expect-error: Duplicate identifier 'PersonAlias'. (2300)
type PersonAlias = {last: string};
逆に、重複する名前を持つインターフェースはマージされます。
interface PersonInterface {: string;
first
}
interface PersonInterface {: string;
last
}: PersonInterface = {
const jane: 'Jane',
first: 'Doe',
last; }
マップ型(A行)の場合、オブジェクト型リテラルを使用する必要があります。
interface Point {: number;
x: number;
y
}
type PointCopy1 = {
keyof Point]: Point[Key]; // (A)
[Key in ;
}
// Syntax error:
// interface PointCopy2 {
// [Key in keyof Point]: Point[Key];
// };
マップ型に関する詳細情報
マップ型は、本書の現在の範囲を超えています。詳細については、TypeScript Handbookを参照してください。
this
型多態性this
型は、インターフェースでのみ使用できます。
interface AddsStrings {add(str: string): this;
;
}
class StringBuilder implements AddsStrings {= '';
result add(str: string) {
.result += str;
this;
return this
} }
これからは、「インターフェース」は「インターフェースまたはオブジェクト型リテラル」を意味します(特に明記されていない限り)。
インターフェースは構造的に機能します。一致するために実装する必要はありません。
interface Point {: number;
x: number;
y
}: Point = {x: 1, y: 2}; // OK const point
このトピックの詳細については、[コンテンツは含まれていません]を参照してください。
インターフェースとオブジェクト型リテラルの本体内の構造は、そのメンバーと呼ばれます。これらが最も一般的なメンバーです。
interface ExampleInterface {// Property signature
: boolean;
myProperty
// Method signature
myMethod(str: string): number;
// Index signature
: string]: any;
[key
// Call signature
: number): string;
(num
// Construct signature
new(str: string): ExampleInstance;
} interface ExampleInstance {}
これらのメンバーを詳しく見ていきましょう。
プロパティシグネチャはプロパティを定義します。
: boolean; myProperty
メソッドシグネチャはメソッドを定義します。
myMethod(str: string): number;
注:パラメーターの名前(この場合はstr
)は、動作の仕組みを文書化するのに役立ちますが、他の目的はありません。
インデックスシグネチャは、辞書として使用される配列またはオブジェクトを記述するために必要です。
: string]: any; [key
注:名前key
はドキュメントの目的でのみ存在します。
呼び出しシグネチャにより、インターフェースで関数を記述できます。
: number): string; (num
コンストラクトシグネチャにより、インターフェースでクラスとコンストラクタ関数を記述できます。
new(str: string): ExampleInstance;
プロパティシグネチャは自明である必要があります。呼び出しシグネチャとコンストラクトシグネチャについては、本書の後半で説明します。次に、メソッドシグネチャとインデックスシグネチャを詳しく見ていきます。
TypeScriptの型システムに関する限り、メソッド定義と値が関数であるプロパティは同等です。
interface HasMethodDef {simpleMethod(flag: boolean): void;
}
interface HasFuncProp {: (flag: boolean) => void;
simpleMethod
}
: HasMethodDef = {
const objWithMethodsimpleMethod(flag: boolean): void {},
;
}: HasFuncProp = objWithMethod;
const objWithMethod2
: HasMethodDef = {
const objWithOrdinaryFunction: function (flag: boolean): void {},
simpleMethod;
}: HasFuncProp = objWithOrdinaryFunction;
const objWithOrdinaryFunction2
: HasMethodDef = {
const objWithArrowFunction: (flag: boolean): void => {},
simpleMethod;
}: HasFuncProp = objWithArrowFunction; const objWithArrowFunction2
私の推奨事項は、プロパティをどのように設定する必要があるかを最もよく表現する構文を使用することです。
これまで、固定キーを持つレコードとしてのオブジェクトにのみインターフェースを使用してきました。オブジェクトを辞書として使用することを示すにはどうすればよいでしょうか?たとえば、次のコードフラグメントでTranslationDict
は何であるべきでしょうか?
function translate(dict: TranslationDict, english: string): string {
;
return dict[english] }
TranslationDict
が文字列キーを文字列値にマッピングするオブジェクト用であることを示すために、インデックスシグネチャ(A行)を使用します。
interface TranslationDict {:string]: string; // (A)
[key
}= {
const dict 'yes': 'sí',
'no': 'no',
'maybe': 'tal vez',
;
}.equal(
asserttranslate(dict, 'maybe'),
'tal vez');
インデックスシグネチャキーは、string
またはnumber
のいずれかである必要があります。
any
は許可されていません。string|number
)は許可されていません。ただし、インターフェースごとに複数のインデックスシグネチャを使用できます。プレーンなJavaScriptと同様に、TypeScriptの数値プロパティキーは文字列プロパティキーのサブセットです(「JavaScript for impatient programmers」を参照)。したがって、文字列インデックスシグネチャと数値インデックスシグネチャの両方がある場合、前者のプロパティ型は後者のスーパータイプである必要があります。次の例は、Object
がRegExp
のスーパータイプであるため機能します。
interface StringAndNumberKeys {: string]: Object;
[key: number]: RegExp;
[key
}
// %inferred-type: (x: StringAndNumberKeys) =>
// { str: Object; num: RegExp; }
function f(x: StringAndNumberKeys) {
: x['abc'], num: x[123] };
return { str }
インターフェースにインデックスシグネチャとプロパティまたはメソッドシグネチャの両方がある場合、インデックスプロパティ値の型も、プロパティ値またはメソッドの型のスーパータイプである必要があります。
interface I1 {: string]: boolean;
[key
// @ts-expect-error: Property 'myProp' of type 'number' is not assignable
// to string index type 'boolean'. (2411)
: number;
myProp
// @ts-expect-error: Property 'myMethod' of type '() => string' is not
// assignable to string index type 'boolean'. (2411)
myMethod(): string;
}
対照的に、次の2つのインターフェースではエラーは発生しません。
interface I2 {: string]: number;
[key: number;
myProp
}
interface I3 {: string]: () => string;
[keymyMethod(): string;
}
Object
のインスタンスを記述するすべてのインターフェースは、Object
のインスタンスであり、Object.prototype
のプロパティを継承するオブジェクトを記述します。
次の例では、型{}
のパラメーターx
は、戻り値の型Object
と互換性があります。
function f1(x: {}): Object {
;
return x }
同様に、{}
にはメソッド.toString()
があります。
function f2(x: {}): { toString(): string } {
;
return x }
例として、次のインターフェースを検討してください。
interface Point {: number;
x: number;
y }
このインターフェースを解釈できる方法は(特に)2つあります。
.x
と.y
を正確に持つすべてのオブジェクトを記述できます。言い換えれば、これらのオブジェクトは過剰なプロパティ(必要なプロパティよりも多いプロパティ)を持ってはなりません。.x
と.y
を少なくとも持つすべてのオブジェクトを記述できます。言い換えれば、過剰なプロパティは許可されています。TypeScriptは両方の解釈を使用します。それがどのように機能するかを探るために、次の関数を使用します。
function computeDistance(point: Point) { /*...*/ }
デフォルトでは、過剰なプロパティ.z
は許可されています。
= { x: 1, y: 2, z: 3 };
const obj computeDistance(obj); // OK
ただし、オブジェクトリテラルを直接使用すると、過剰なプロパティは禁止されます。
// @ts-expect-error: Argument of type '{ x: number; y: number; z: number; }'
// is not assignable to parameter of type 'Point'.
// Object literal may only specify known properties, and 'z' does not
// exist in type 'Point'. (2345)
computeDistance({ x: 1, y: 2, z: 3 }); // error
computeDistance({x: 1, y: 2}); // OK
オブジェクトリテラルに対するより厳格な規則はなぜでしょうか?それらはプロパティキーのスペルミスに対する保護を提供します。次のインターフェースを使用して、それが何を意味するかを示します。
interface Person {: string;
first?: string;
middle: string;
last
}function computeFullName(person: Person) { /*...*/ }
プロパティ.middle
はオプションであり、省略できます(オプションのプロパティについては、この章の後半で説明します)。TypeScriptにとって、その名前のタイプミスは、それを省略し、過剰なプロパティを提供しているように見えます。ただし、この場合は過剰なプロパティが許可されないため、タイプミスを検出できます。
// @ts-expect-error: Argument of type '{ first: string; mdidle: string;
// last: string; }' is not assignable to parameter of type 'Person'.
// Object literal may only specify known properties, but 'mdidle'
// does not exist in type 'Person'. Did you mean to write 'middle'?
computeFullName({first: 'Jane', mdidle: 'Cecily', last: 'Doe'});
考え方としては、オブジェクトがどこか別の場所から来たものであれば、それはすでに精査済みで、タイプミスはないと想定できるということです。そうなれば、それほど注意深くなくても大丈夫です。
もしタイプミスが問題にならないのであれば、私たちの目標は柔軟性を最大化することであるべきです。次の関数を考えてみましょう。
interface HasYear {: number;
year
}
function getAge(obj: HasYear) {
= new Date().getFullYear();
const yearNow - obj.year;
return yearNow }
getAge()
に渡されるほとんどの値に対して余分なプロパティを許可しないと、この関数の有用性は非常に限られたものになるでしょう。
インターフェースが空の場合(またはオブジェクト型リテラル{}
が使用されている場合)、余分なプロパティは常に許可されます。
interface Empty { }
interface OneProp {: number;
myProp
}
// @ts-expect-error: Type '{ myProp: number; anotherProp: number; }' is not
// assignable to type 'OneProp'.
// Object literal may only specify known properties, and
// 'anotherProp' does not exist in type 'OneProp'. (2322)
: OneProp = { myProp: 1, anotherProp: 2 };
const a: Empty = {myProp: 1, anotherProp: 2}; // OK const b
オブジェクトにプロパティがないことを強制したい場合は、次のトリックを使用できます(クレジット:Geoff Goodman)
interface WithoutProperties {: string]: never;
[key
}
// @ts-expect-error: Type 'number' is not assignable to type 'never'. (2322)
: WithoutProperties = { prop: 1 };
const a: WithoutProperties = {}; // OK const b
オブジェクトリテラルで余分なプロパティを許可したい場合はどうすればよいでしょうか?例として、インターフェースPoint
と関数computeDistance1()
を考えてみましょう。
interface Point {: number;
x: number;
y
}
function computeDistance1(point: Point) { /*...*/ }
// @ts-expect-error: Argument of type '{ x: number; y: number; z: number; }'
// is not assignable to parameter of type 'Point'.
// Object literal may only specify known properties, and 'z' does not
// exist in type 'Point'. (2345)
computeDistance1({ x: 1, y: 2, z: 3 });
1つのオプションは、オブジェクトリテラルを中間変数に割り当てることです。
= { x: 1, y: 2, z: 3 };
const obj computeDistance1(obj);
2つ目のオプションは、型アサーションを使用することです。
computeDistance1({ x: 1, y: 2, z: 3 } as Point); // OK
3つ目のオプションは、型パラメータを使用するようにcomputeDistance1()
を書き直すことです。
function computeDistance2<P extends Point>(point: P) { /*...*/ }
computeDistance2({ x: 1, y: 2, z: 3 }); // OK
4つ目のオプションは、余分なプロパティを許可するようにインターフェースPoint
を拡張することです。
interface PointEtc extends Point {: string]: any;
[key
}function computeDistance3(point: PointEtc) { /*...*/ }
computeDistance3({ x: 1, y: 2, z: 3 }); // OK
TypeScriptが余分なプロパティを許可しないことが問題になる2つの例を続けます。
Incrementor
この例では、Incrementor
を実装したいのですが、TypeScriptは余分なプロパティ.counter
を許可しません。
interface Incrementor {inc(): void
}function createIncrementor(start = 0): Incrementor {
return {// @ts-expect-error: Type '{ counter: number; inc(): void; }' is not
// assignable to type 'Incrementor'.
// Object literal may only specify known properties, and
// 'counter' does not exist in type 'Incrementor'. (2322)
: start,
counterinc() {
// @ts-expect-error: Property 'counter' does not exist on type
// 'Incrementor'. (2339)
.counter++;
this,
};
} }
残念ながら、型アサーションを使用しても、まだ1つの型エラーがあります。
function createIncrementor2(start = 0): Incrementor {
return {: start,
counterinc() {
// @ts-expect-error: Property 'counter' does not exist on type
// 'Incrementor'. (2339)
.counter++;
this,
}as Incrementor;
} }
インターフェースIncrementor
にインデックスシグネチャを追加することができます。または、特にそれが不可能な場合は、中間変数を導入できます。
function createIncrementor3(start = 0): Incrementor {
= {
const incrementor : start,
counterinc() {
.counter++;
this,
};
};
return incrementor }
.dateStr
次の比較関数は、プロパティ.dateStr
を持つオブジェクトをソートするために使用できます。
function compareDateStrings(
: {dateStr: string}, b: {dateStr: string}) {
aif (a.dateStr < b.dateStr) {
+1;
return if (a.dateStr > b.dateStr) {
} else -1;
return
} else {0;
return
} }
たとえば、単体テストでは、オブジェクトリテラルでこの関数を直接呼び出したい場合があります。TypeScriptはこれを許可せず、いずれかの回避策を使用する必要があります。
これらは、さまざまな手段で作成されたオブジェクトに対してTypeScriptが推論する型です。
// %inferred-type: Object
= new Object();
const obj1
// %inferred-type: any
= Object.create(null);
const obj2
// %inferred-type: {}
= {};
const obj3
// %inferred-type: { prop: number; }
= {prop: 123};
const obj4
// %inferred-type: object
= Reflect.getPrototypeOf({}); const obj5
原則として、Object.create()
の戻り値の型はobject
になる可能性があります。ただし、any
を使用すると、結果のプロパティを追加および変更できます。
プロパティ名の後に疑問符(?
)を付けると、そのプロパティはオプションになります。同じ構文を使用して、関数、メソッド、コンストラクターのパラメータをオプションとしてマークします。次の例では、プロパティ.middle
はオプションです。
interface Name {: string;
first?: string;
middle: string;
last }
したがって、そのプロパティを省略しても問題ありません(行A)。
: Name = {first: 'Doe', last: 'Doe'}; // (A)
const john: Name = {first: 'Jane', middle: 'Cecily', last: 'Doe'}; const jane
undefined|string
.prop1
と.prop2
の違いは何でしょうか?
interface Interf {?: string;
prop1: undefined | string;
prop2 }
オプションのプロパティは、undefined|string
ができることすべてを実行できます。前者には値undefined
を使用することもできます。
: Interf = { prop1: undefined, prop2: undefined }; const obj1
ただし、省略できるのは.prop1
だけです。
: Interf = { prop2: undefined };
const obj2
// @ts-expect-error: Property 'prop2' is missing in type '{}' but required
// in type 'Interf'. (2741)
: Interf = { }; const obj3
undefined|string
やnull|string
などの型は、省略を明示的にしたい場合に役立ちます。このような明示的に省略されたプロパティを見ると、それらは存在しているがオフにされていることを知ることができます。
次の例では、プロパティ.prop
は読み取り専用です。
interface MyInterface {readonly prop: number;
}
その結果、読み取ることはできますが、変更することはできません。
: MyInterface = {
const obj: 1,
prop;
}
console.log(obj.prop); // OK
// @ts-expect-error: Cannot assign to 'prop' because it is a read-only
// property. (2540)
.prop = 2; obj
TypeScriptは、自身のプロパティと継承されたプロパティを区別しません。それらはすべて単にプロパティであると見なされます。
interface MyInterface {toString(): string; // inherited property
: number; // own property
prop
}: MyInterface = { // OK
const obj: 123,
prop; }
obj
はObject.prototype
から.toString()
を継承します。
このアプローチの欠点は、JavaScriptの一部の現象をTypeScriptの型システムで記述できないことです。良い点は、型システムがよりシンプルになることです。