日常的な型

この章では、JavaScriptコードでよく見られる最も一般的な値の型について説明し、TypeScriptでそれらの型を記述する対応する方法について説明します。これは網羅的なリストではなく、今後の章では、他の型を命名して使用するためのより多くの方法を説明します。

型は、型注釈だけでなく、より多くの場所にも現れる可能性があります。型自体について学習するにつれて、これらの型を参照して新しい構成を形成できる場所についても学習します。

JavaScriptまたはTypeScriptコードを記述する際に遭遇する可能性のある最も基本的で一般的な型を見直すことから始めます。これらは後で、より複雑な型のコアビルディングブロックを形成します。

プリミティブ: stringnumber、およびboolean

JavaScriptには、非常によく使用される3つのプリミティブがあります:stringnumber、およびboolean。それぞれにTypeScriptの対応する型があります。ご想像のとおり、これらは、これらの型の値に対してJavaScriptのtypeof演算子を使用したときに表示されるものと同じ名前です。

  • stringは、"Hello, world"のような文字列値を表します
  • numberは、42のような数値用です。JavaScriptには整数の特別なランタイム値がないため、intまたはfloatに相当するものはありません - すべてが単にnumberです
  • booleanは、2つの値truefalse用です

型名StringNumber、およびBoolean(大文字で始まる)は有効ですが、コードにほとんど表示されない特別な組み込み型を参照します。型の使用には、常にstringnumber、またはbooleanを使用してください。

配列

[1, 2, 3]のような配列の型を指定するには、構文number[]を使用できます。この構文は任意の型で機能します(例:string[]は文字列の配列など)。これはArray<number>と記述することもできます。これは同じ意味です。ジェネリクスについて説明するときに、構文T<U>について詳しく学びます。

[number]は別のものなので、タプルのセクションを参照してください。

any

TypeScriptには、特定の値を型チェックエラーの原因にしたくない場合に使用できる特別な型anyもあります。

値がany型の場合、その任意のプロパティにアクセスし(その結果、any型になります)、関数のように呼び出し、任意の型の値との間で割り当てることができます。または、構文的に合法な他のほとんどのことができます

ts
let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
Try

any型は、特定のコード行が正しいことをTypeScriptに納得させるためだけに長い型を記述したくない場合に役立ちます。

noImplicitAny

型を指定しない場合、かつTypeScriptがコンテキストから型を推論できない場合、コンパイラーは通常、デフォルトでanyになります。

しかし、通常これは避けるべきです。なぜなら、anyは型チェックされないからです。コンパイラーフラグ noImplicitAny を使用して、暗黙的なanyをエラーとしてフラグを立ててください。

変数への型注釈

constvar、またはletを使用して変数を宣言する場合、オプションで型注釈を追加して変数の型を明示的に指定できます。

ts
let myName: string = "Alice";
Try

TypeScriptは、int x = 0;のような「左側に型」の宣言を使用しません。型注釈は常に型付けされるもののにきます。

ただし、ほとんどの場合、これは必要ありません。可能な限り、TypeScriptはコード内の型を自動的に推論しようとします。たとえば、変数の型は初期化子の型に基づいて推論されます。

ts
// No type annotation needed -- 'myName' inferred as type 'string'
let myName = "Alice";
Try

ほとんどの場合、推論のルールを明示的に学ぶ必要はありません。始めたばかりの場合は、考えるよりも少ない型注釈を使用してみてください。TypeScriptが何を理解しているかを完全に理解するために必要なものが、どれほど少ないかに驚くかもしれません。

関数

関数は、JavaScriptでデータをやり取りする主な手段です。 TypeScriptでは、関数の入力値と出力値の両方の型を指定できます。

パラメーターの型注釈

関数を宣言する場合、各パラメーターの後ろに型注釈を追加して、関数が受け入れるパラメーターの型を宣言できます。パラメーターの型注釈は、パラメーター名の後に記述します。

ts
// Parameter type annotation
function greet(name: string) {
console.log("Hello, " + name.toUpperCase() + "!!");
}
Try

パラメーターに型注釈がある場合、その関数への引数がチェックされます。

ts
// Would be a runtime error if executed!
greet(42);
Argument of type 'number' is not assignable to parameter of type 'string'.2345Argument of type 'number' is not assignable to parameter of type 'string'.
Try

パラメーターに型注釈がない場合でも、TypeScriptは正しい数の引数を渡したかどうかをチェックします。

戻り値の型注釈

戻り値の型注釈を追加することもできます。戻り値の型注釈は、パラメーターリストの後ろに記述します。

ts
function getFavoriteNumber(): number {
return 26;
}
Try

変数の型注釈と非常によく似ていますが、通常は戻り値の型注釈は必要ありません。なぜなら、TypeScriptは関数のreturnステートメントに基づいて関数の戻り値の型を推論するからです。上記の例の型注釈は何も変更しません。一部のコードベースでは、ドキュメント化の目的、偶発的な変更を防ぐため、または単に個人の好みとして、戻り値の型を明示的に指定します。

Promiseを返す関数

Promiseを返す関数の戻り値の型に注釈を付ける場合は、Promise型を使用する必要があります。

ts
async function getFavoriteNumber(): Promise<number> {
return 26;
}
Try

匿名関数

匿名関数は、関数宣言とは少し異なります。関数がTypeScriptによってどのように呼び出されるかが判断できる場所に現れる場合、その関数のパラメーターには自動的に型が与えられます。

例を示します。

ts
const names = ["Alice", "Bob", "Eve"];
 
// Contextual typing for function - parameter s inferred to have type string
names.forEach(function (s) {
console.log(s.toUpperCase());
});
 
// Contextual typing also applies to arrow functions
names.forEach((s) => {
console.log(s.toUpperCase());
});
Try

パラメーターsに型注釈がなくても、TypeScriptはforEach関数の型と配列の推論された型を使用して、sが持つ型を決定しました。

このプロセスは、関数が発生したコンテキストによって、関数が持つべき型が通知されるため、*コンテキスト型付け*と呼ばれます。

推論ルールと同様に、これがどのように起こるかを明示的に学習する必要はありませんが、これが*起こる*ことを理解することで、型注釈が必要ないときに気付くのに役立ちます。後で、値が発生するコンテキストがその型にどのように影響するかについて、さらに多くの例を見ていきます。

オブジェクト型

プリミティブを除いて、最も一般的な種類の型はオブジェクト型です。これは、プロパティを持つ任意のJavaScript値を指し、ほとんどすべてがそうです。オブジェクト型を定義するには、そのプロパティとその型をリストするだけです。

たとえば、以下は点のようなオブジェクトを受け取る関数です。

ts
// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });
Try

ここでは、パラメーターに、number型のxyの2つのプロパティを持つ型注釈を付けました。プロパティを区切るには,または;を使用でき、最後の区切り文字はどちらでもオプションです。

各プロパティの型部分もオプションです。型を指定しない場合は、anyと見なされます。

オプションのプロパティ

オブジェクト型では、プロパティの一部またはすべてが*オプション*であることも指定できます。これを行うには、プロパティ名の後に?を追加します。

ts
function printName(obj: { first: string; last?: string }) {
// ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
Try

JavaScriptでは、存在しないプロパティにアクセスすると、実行時エラーではなく値undefinedが得られます。このため、オプションのプロパティから*読み取る*ときは、それを使用する前にundefinedを確認する必要があります。

ts
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
'obj.last' is possibly 'undefined'.18048'obj.last' is possibly 'undefined'.
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
 
// A safe alternative using modern JavaScript syntax:
console.log(obj.last?.toUpperCase());
}
Try

ユニオン型

TypeScriptの型システムでは、さまざまな演算子を使用して既存の型から新しい型を構築できます。いくつかの型の記述方法がわかったので、それらを興味深い方法で結合し始める時が来ました。

ユニオン型の定義

最初に見られる型の組み合わせ方は、ユニオン型です。ユニオン型は、2つ以上の他の型から形成される型であり、これらの型のいずれかになり得る値を表します。これらの各型をユニオンのメンバーと呼びます。

文字列または数値で操作できる関数を記述しましょう。

ts
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.2345Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
Try

ユニオン型の操作

ユニオン型に一致する値を*提供する*のは簡単です。ユニオンのメンバーのいずれかに一致する型を提供するだけです。ユニオン型の値を持っている場合、どのように操作しますか?

TypeScriptでは、ユニオンのすべてのメンバーに対して有効な場合にのみ、操作が許可されます。たとえば、string | numberのユニオンがある場合、stringでのみ使用可能なメソッドは使用できません。

ts
function printId(id: number | string) {
console.log(id.toUpperCase());
Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'.2339Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'.
}
Try

解決策は、型注釈なしの JavaScript で行うのと同様に、コードでユニオンを絞り込むことです。絞り込みは、TypeScript がコードの構造に基づいて値のより具体的な型を推論できる場合に発生します。

たとえば、TypeScript は、string 値のみが typeof"string" を持つことを認識しています。

ts
function printId(id: number | string) {
if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}
Try

別の例として、Array.isArray のような関数を使用することがあります。

ts
function welcomePeople(x: string[] | string) {
if (Array.isArray(x)) {
// Here: 'x' is 'string[]'
console.log("Hello, " + x.join(" and "));
} else {
// Here: 'x' is 'string'
console.log("Welcome lone traveler " + x);
}
}
Try

else ブランチでは、特別なことは何もする必要がないことに注意してください。もし xstring[] でなかった場合、それは string であったはずです。

ユニオンのすべてのメンバーに共通のものがある場合があります。たとえば、配列と文字列の両方に slice メソッドがあります。ユニオンのすべてのメンバーに共通のプロパティがある場合、絞り込みなしでそのプロパティを使用できます。

ts
// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
Try

型のユニオンが、それらの型のプロパティの交差を持っているように見えるのは混乱するかもしれません。これは偶然ではありません。ユニオンという名前は型理論に由来します。ユニオン number | string は、各型からの値のユニオンを取得することによって構成されます。各セットに関する対応する事実を持つ2つのセットが与えられた場合、それらの事実の交差のみがセット自体のユニオンに適用されることに注意してください。たとえば、帽子をかぶった背の高い人々の部屋と、帽子をかぶったスペイン語話者の別の部屋があった場合、それらの部屋を組み合わせた後、すべての人について知っている唯一のことは、帽子をかぶっている必要があるということです。

型エイリアス

型注釈でオブジェクト型とユニオン型を直接記述することで使用してきました。これは便利ですが、同じ型を複数回使用し、1つの名前で参照したいことがよくあります。

型エイリアスはまさにそれです。任意の名前です。型エイリアスの構文は次のとおりです。

ts
type Point = {
x: number;
y: number;
};
 
// Exactly the same as the earlier example
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });
Try

実際には、型エイリアスを使用して、オブジェクト型だけでなく、あらゆる型に名前を付けることができます。たとえば、型エイリアスはユニオン型に名前を付けることができます。

ts
type ID = number | string;
Try

エイリアスは単なるエイリアスであることに注意してください。型エイリアスを使用して、同じ型の異なる/個別の「バージョン」を作成することはできません。エイリアスを使用すると、エイリアス化された型を記述した場合とまったく同じです。言い換えれば、このコードは不正に見えるかもしれませんが、両方の型が同じ型のエイリアスであるため、TypeScript では OK です。

ts
type UserInputSanitizedString = string;
 
function sanitizeInput(str: string): UserInputSanitizedString {
return sanitize(str);
}
 
// Create a sanitized input
let userInput = sanitizeInput(getInput());
 
// Can still be re-assigned with a string though
userInput = "new input";
Try

インターフェース

インターフェース宣言は、オブジェクト型に名前を付ける別の方法です。

ts
interface Point {
x: number;
y: number;
}
 
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });
Try

上記の型エイリアスを使用したときと同様に、例は匿名オブジェクト型を使用した場合とまったく同じように機能します。TypeScript は、printCoord に渡した値の構造のみに関心があります。必要なプロパティがあるかどうかだけに関心があります。型の構造と機能のみに関心があるため、TypeScript を構造的に型付けされた型システムと呼びます。

型エイリアスとインターフェースの違い

型エイリアスとインターフェースは非常によく似ており、多くの場合、自由にどちらかを選択できます。interface のほぼすべての機能は type で使用できます。主な違いは、型は新しいプロパティを追加するために再度開くことができないのに対し、インターフェースは常に拡張可能であるということです。

インターフェース

インターフェースの拡張

interface Animal {
  name: string;
}
interface Bear extends Animal { honey: boolean; }
const bear = getBear(); bear.name; bear.honey;

交差による型の拡張

type Animal = {
  name: string;
}
type Bear = Animal & { honey: boolean; }
const bear = getBear(); bear.name; bear.honey;

既存のインターフェースへの新しいフィールドの追加

interface Window {
  title: string;
}
interface Window { ts: TypeScriptAPI; }
const src = 'const a = "Hello World"'; window.ts.transpileModule(src, {});

型は作成後に変更できません

type Window = {
  title: string;
}
type Window = { ts: TypeScriptAPI; }
// Error: Duplicate identifier 'Window'.

これらの概念については後の章で詳しく説明するので、すぐにすべてを理解できなくても心配しないでください。

ほとんどの場合、個人の好みに基づいて選択でき、TypeScript は、どちらかの種類の宣言にする必要がある場合に教えてくれます。ヒューリスティックが必要な場合は、type の機能を使用する必要があるまで interface を使用してください。

型アサーション

TypeScript が認識できない値の型に関する情報がある場合があります。

たとえば、document.getElementById を使用している場合、TypeScript はこれが何らかの HTMLElement を返すことしか認識していませんが、ページに特定の ID を持つ HTMLCanvasElement が常にあることを知っている場合があります。

このような状況では、型アサーションを使用してより具体的な型を指定できます。

ts
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
Try

型注釈と同様に、型アサーションはコンパイラーによって削除され、コードの実行時の動作には影響しません。

また、同等の角度付き括弧構文を使用することもできます(ただし、コードが .tsx ファイルにある場合を除く)。

ts
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
Try

注意:型アサーションはコンパイル時に削除されるため、型アサーションに関連付けられたランタイムチェックはありません。型アサーションが間違っていても、例外や null は生成されません。

TypeScript は、型のより具体的またはあまり具体的ではないバージョンに変換する型アサーションのみを許可します。このルールは、次のような「不可能」な強制を防止します。

ts
const x = "hello" as number;
Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.2352Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Try

このルールは保守的すぎる場合があり、有効な可能性があるより複雑な強制を許可しない場合があります。このような場合は、最初に any (または後で紹介する unknown)へのアサーションを 2 つ使用し、次に目的の型にアサーションを使用できます。

ts
const a = expr as any as T;
Try

リテラル型

一般的な型 stringnumber に加えて、型位置で特定の文字列と数値を参照できます。

これについて考える1つの方法は、JavaScript に変数を宣言するためのさまざまな方法が付属していることを検討することです。varlet の両方で、変数内に保持されているものを変更できますが、const は変更できません。これは、TypeScript がリテラルの型を作成する方法に反映されています。

ts
let changingString = "Hello World";
changingString = "Olá Mundo";
// Because `changingString` can represent any possible string, that
// is how TypeScript describes it in the type system
changingString;
let changingString: string
 
const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
const constantString: "Hello World"
Try

それ自体では、リテラル型はあまり価値がありません。

ts
let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
Type '"howdy"' is not assignable to type '"hello"'.2322Type '"howdy"' is not assignable to type '"hello"'.
Try

1つの値しか持つことができない変数を持つことはあまり役に立ちません!

ただし、リテラルをユニオンに組み合わせることで、より便利な概念を表現できます。たとえば、特定の既知の値のセットのみを受け入れる関数などです。

ts
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.2345Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
Try

数値リテラル型は同じように機能します。

ts
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
Try

もちろん、これらを非リテラル型と組み合わせることができます。

ts
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.2345Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.
Try

リテラル型にはもう1種類あります。それはブールリテラルです。ブールリテラル型は 2 つしかなく、ご推測のとおり、それらは型 truefalse です。型 boolean 自体は、実際にはユニオン true | false のエイリアスにすぎません。

リテラル推論

オブジェクトで変数を初期化する場合、TypeScript はそのオブジェクトのプロパティの値が後で変更される可能性があると想定します。たとえば、次のようなコードを書いたとします。

ts
const obj = { counter: 0 };
if (someCondition) {
obj.counter = 1;
}
Try

TypeScript は、以前に `0` が入っていたフィールドに `1` を代入することをエラーとはみなしません。別の言い方をすると、`obj.counter` は `0` ではなく `number` 型でなければなりません。なぜなら、型は *読み取り* と *書き込み* の両方の動作を決定するために使用されるからです。

文字列についても同様です。

ts
declare function handleRequest(url: string, method: "GET" | "POST"): void;
 
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.2345Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
Try

上記の例では、`req.method` は `"GET"` ではなく `string` 型として推論されます。コードは `req` の作成と `handleRequest` の呼び出しの間で評価される可能性があり、その間に `req.method` に `GUESS` のような新しい文字列が代入される可能性があるため、TypeScript はこのコードをエラーとみなします。

これを回避する方法は2つあります。

  1. どちらかの場所で型アサーションを追加することで、推論を変更できます。

    ts
    // Change 1:
    const req = { url: "https://example.com", method: "GET" as "GET" };
    // Change 2
    handleRequest(req.url, req.method as "GET");
    Try

    変更 1 は、「`req.method` が常に *リテラル型* の `"GET"` であることを意図している」という意味であり、そのフィールドへの `"GUESS"` の代入を後で防ぎます。変更 2 は、「他の理由から `req.method` が `"GET"` の値を持つことを知っている」という意味です。

  2. `as const` を使用して、オブジェクト全体を型リテラルに変換できます。

    ts
    const req = { url: "https://example.com", method: "GET" } as const;
    handleRequest(req.url, req.method);
    Try

`as const` サフィックスは、型システムに対する `const` のように機能し、すべてのプロパティに `string` や `number` のようなより一般的な型ではなく、リテラル型が割り当てられるようにします。

null および undefined

JavaScript には、欠落した値または初期化されていない値を通知するために使用される2つのプリミティブ値、`null` と `undefined` があります。

TypeScript には、同じ名前の2つの対応する *型* があります。これらの型の動作は、`strictNullChecks` オプションがオンになっているかどうかによって異なります。

`strictNullChecks` オフ

`strictNullChecks` が *オフ* の場合、`null` または `undefined` になる可能性がある値にも通常どおりにアクセスでき、値 `null` および `undefined` は任意の型のプロパティに割り当てることができます。これは、null チェックがない言語(C#、Java など)の動作に似ています。これらの値のチェックがないことは、バグの主な原因となる傾向があります。可能であれば、常に `strictNullChecks` をオンにすることをお勧めします。

`strictNullChecks` オン

`strictNullChecks` が *オン* の場合、値が `null` または `undefined` のときは、その値に対してメソッドやプロパティを使用する前に、これらの値をテストする必要があります。オプションのプロパティを使用する前に `undefined` をチェックするのと同様に、*絞り込み* を使用して `null` になる可能性のある値をチェックできます。

ts
function doSomething(x: string | null) {
if (x === null) {
// do nothing
} else {
console.log("Hello, " + x.toUpperCase());
}
}
Try

非 null アサーション演算子 (接尾辞 `!`)

TypeScript には、明示的なチェックを行わずに型から `null` と `undefined` を削除するための特別な構文もあります。任意の式の後に `!` を記述することは、その値が `null` または `undefined` ではないという型アサーションとして効果的です。

ts
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
Try

他の型アサーションと同様に、これはコードの実行時の動作を変更しないため、値が `null` または `undefined` *になりえない* ことを知っている場合にのみ `!` を使用することが重要です。

Enum

Enum は、TypeScript によって JavaScript に追加された機能で、名前付き定数のセットのいずれかになりうる値を記述できます。ほとんどの TypeScript 機能とは異なり、これは JavaScript への *型レベル* の追加ではなく、言語とランタイムに追加されたものです。このため、これは存在を知っておくべき機能ですが、確信がない限り使用を控えるべきかもしれません。enum の詳細については、Enum のリファレンスページを参照してください。

あまり一般的でないプリミティブ

型システムで表現される JavaScript の残りのプリミティブについても言及する価値はあります。ここでは詳しく説明しません。

bigint

ES2020 以降、JavaScript には非常に大きな整数に使用されるプリミティブ `BigInt` があります。

ts
// Creating a bigint via the BigInt function
const oneHundred: bigint = BigInt(100);
 
// Creating a BigInt via the literal syntax
const anotherHundred: bigint = 100n;
Try

BigInt の詳細については、TypeScript 3.2 のリリースノートを参照してください。

symbol

JavaScript には、関数 `Symbol()` を介してグローバルに一意な参照を作成するために使用されるプリミティブがあります。

ts
const firstName = Symbol("name");
const secondName = Symbol("name");
 
if (firstName === secondName) {
This comparison appears to be unintentional because the types 'typeof firstName' and 'typeof secondName' have no overlap.2367This comparison appears to be unintentional because the types 'typeof firstName' and 'typeof secondName' have no overlap.
// Can't ever happen
}
Try

詳細については、Symbols のリファレンスページを参照してください。

TypeScript のドキュメントはオープンソースプロジェクトです。これらのページを改善するために、プルリクエストを送信してご協力ください ❤

このページの貢献者
RCRyan Cavanaugh (56)
OTOrta Therox (22)
UGUtku Gultopu (3)
ABAndrew Branch (2)
DRDaniel Rosenwasser (2)
29+

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