この章では、JavaScriptコードでよく見られる最も一般的な値の型について説明し、TypeScriptでそれらの型を記述する対応する方法について説明します。これは網羅的なリストではなく、今後の章では、他の型を命名して使用するためのより多くの方法を説明します。
型は、型注釈だけでなく、より多くの場所にも現れる可能性があります。型自体について学習するにつれて、これらの型を参照して新しい構成を形成できる場所についても学習します。
JavaScriptまたはTypeScriptコードを記述する際に遭遇する可能性のある最も基本的で一般的な型を見直すことから始めます。これらは後で、より複雑な型のコアビルディングブロックを形成します。
プリミティブ: string
、number
、およびboolean
JavaScriptには、非常によく使用される3つのプリミティブがあります:string
、number
、およびboolean
。それぞれにTypeScriptの対応する型があります。ご想像のとおり、これらは、これらの型の値に対してJavaScriptのtypeof
演算子を使用したときに表示されるものと同じ名前です。
string
は、"Hello, world"
のような文字列値を表しますnumber
は、42
のような数値用です。JavaScriptには整数の特別なランタイム値がないため、int
またはfloat
に相当するものはありません - すべてが単にnumber
ですboolean
は、2つの値true
とfalse
用です
型名
String
、Number
、およびBoolean
(大文字で始まる)は有効ですが、コードにほとんど表示されない特別な組み込み型を参照します。型の使用には、常にstring
、number
、またはboolean
を使用してください。
配列
[1, 2, 3]
のような配列の型を指定するには、構文number[]
を使用できます。この構文は任意の型で機能します(例:string[]
は文字列の配列など)。これはArray<number>
と記述することもできます。これは同じ意味です。ジェネリクスについて説明するときに、構文T<U>
について詳しく学びます。
[number]
は別のものなので、タプルのセクションを参照してください。
any
TypeScriptには、特定の値を型チェックエラーの原因にしたくない場合に使用できる特別な型any
もあります。
値がany
型の場合、その任意のプロパティにアクセスし(その結果、any
型になります)、関数のように呼び出し、任意の型の値との間で割り当てることができます。または、構文的に合法な他のほとんどのことができます
tsTry
letobj : 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";constn : number =obj ;
any
型は、特定のコード行が正しいことをTypeScriptに納得させるためだけに長い型を記述したくない場合に役立ちます。
noImplicitAny
型を指定しない場合、かつTypeScriptがコンテキストから型を推論できない場合、コンパイラーは通常、デフォルトでany
になります。
しかし、通常これは避けるべきです。なぜなら、any
は型チェックされないからです。コンパイラーフラグ noImplicitAny
を使用して、暗黙的なany
をエラーとしてフラグを立ててください。
変数への型注釈
const
、var
、またはlet
を使用して変数を宣言する場合、オプションで型注釈を追加して変数の型を明示的に指定できます。
tsTry
letmyName : string = "Alice";
TypeScriptは、
int x = 0;
のような「左側に型」の宣言を使用しません。型注釈は常に型付けされるものの後にきます。
ただし、ほとんどの場合、これは必要ありません。可能な限り、TypeScriptはコード内の型を自動的に推論しようとします。たとえば、変数の型は初期化子の型に基づいて推論されます。
tsTry
// No type annotation needed -- 'myName' inferred as type 'string'letmyName = "Alice";
ほとんどの場合、推論のルールを明示的に学ぶ必要はありません。始めたばかりの場合は、考えるよりも少ない型注釈を使用してみてください。TypeScriptが何を理解しているかを完全に理解するために必要なものが、どれほど少ないかに驚くかもしれません。
関数
関数は、JavaScriptでデータをやり取りする主な手段です。 TypeScriptでは、関数の入力値と出力値の両方の型を指定できます。
パラメーターの型注釈
関数を宣言する場合、各パラメーターの後ろに型注釈を追加して、関数が受け入れるパラメーターの型を宣言できます。パラメーターの型注釈は、パラメーター名の後に記述します。
tsTry
// Parameter type annotationfunctiongreet (name : string) {console .log ("Hello, " +name .toUpperCase () + "!!");}
パラメーターに型注釈がある場合、その関数への引数がチェックされます。
tsTry
// Would be a runtime error if executed!Argument of type 'number' is not assignable to parameter of type 'string'.2345Argument of type 'number' is not assignable to parameter of type 'string'.greet (42 );
パラメーターに型注釈がない場合でも、TypeScriptは正しい数の引数を渡したかどうかをチェックします。
戻り値の型注釈
戻り値の型注釈を追加することもできます。戻り値の型注釈は、パラメーターリストの後ろに記述します。
tsTry
functiongetFavoriteNumber (): number {return 26;}
変数の型注釈と非常によく似ていますが、通常は戻り値の型注釈は必要ありません。なぜなら、TypeScriptは関数のreturn
ステートメントに基づいて関数の戻り値の型を推論するからです。上記の例の型注釈は何も変更しません。一部のコードベースでは、ドキュメント化の目的、偶発的な変更を防ぐため、または単に個人の好みとして、戻り値の型を明示的に指定します。
Promiseを返す関数
Promiseを返す関数の戻り値の型に注釈を付ける場合は、Promise
型を使用する必要があります。
tsTry
async functiongetFavoriteNumber ():Promise <number> {return 26;}
匿名関数
匿名関数は、関数宣言とは少し異なります。関数がTypeScriptによってどのように呼び出されるかが判断できる場所に現れる場合、その関数のパラメーターには自動的に型が与えられます。
例を示します。
tsTry
constnames = ["Alice", "Bob", "Eve"];// Contextual typing for function - parameter s inferred to have type stringnames .forEach (function (s ) {console .log (s .toUpperCase ());});// Contextual typing also applies to arrow functionsnames .forEach ((s ) => {console .log (s .toUpperCase ());});
パラメーターs
に型注釈がなくても、TypeScriptはforEach
関数の型と配列の推論された型を使用して、s
が持つ型を決定しました。
このプロセスは、関数が発生したコンテキストによって、関数が持つべき型が通知されるため、*コンテキスト型付け*と呼ばれます。
推論ルールと同様に、これがどのように起こるかを明示的に学習する必要はありませんが、これが*起こる*ことを理解することで、型注釈が必要ないときに気付くのに役立ちます。後で、値が発生するコンテキストがその型にどのように影響するかについて、さらに多くの例を見ていきます。
オブジェクト型
プリミティブを除いて、最も一般的な種類の型はオブジェクト型です。これは、プロパティを持つ任意のJavaScript値を指し、ほとんどすべてがそうです。オブジェクト型を定義するには、そのプロパティとその型をリストするだけです。
たとえば、以下は点のようなオブジェクトを受け取る関数です。
tsTry
// The parameter's type annotation is an object typefunctionprintCoord (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 });
ここでは、パラメーターに、number
型のx
とy
の2つのプロパティを持つ型注釈を付けました。プロパティを区切るには,
または;
を使用でき、最後の区切り文字はどちらでもオプションです。
各プロパティの型部分もオプションです。型を指定しない場合は、any
と見なされます。
オプションのプロパティ
オブジェクト型では、プロパティの一部またはすべてが*オプション*であることも指定できます。これを行うには、プロパティ名の後に?
を追加します。
tsTry
functionprintName (obj : {first : string;last ?: string }) {// ...}// Both OKprintName ({first : "Bob" });printName ({first : "Alice",last : "Alisson" });
JavaScriptでは、存在しないプロパティにアクセスすると、実行時エラーではなく値undefined
が得られます。このため、オプションのプロパティから*読み取る*ときは、それを使用する前にundefined
を確認する必要があります。
tsTry
functionprintName (obj : {first : string;last ?: string }) {// Error - might crash if 'obj.last' wasn't provided!'obj.last' is possibly 'undefined'.18048'obj.last' is possibly 'undefined'.console .log (obj .last .toUpperCase ());if (obj .last !==undefined ) {// OKconsole .log (obj .last .toUpperCase ());}// A safe alternative using modern JavaScript syntax:console .log (obj .last ?.toUpperCase ());}
ユニオン型
TypeScriptの型システムでは、さまざまな演算子を使用して既存の型から新しい型を構築できます。いくつかの型の記述方法がわかったので、それらを興味深い方法で結合し始める時が来ました。
ユニオン型の定義
最初に見られる型の組み合わせ方は、ユニオン型です。ユニオン型は、2つ以上の他の型から形成される型であり、これらの型のいずれかになり得る値を表します。これらの各型をユニオンのメンバーと呼びます。
文字列または数値で操作できる関数を記述しましょう。
tsTry
functionprintId (id : number | string) {console .log ("Your ID is: " +id );}// OKprintId (101);// OKprintId ("202");// ErrorArgument 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'.printId ({myID : 22342 });
ユニオン型の操作
ユニオン型に一致する値を*提供する*のは簡単です。ユニオンのメンバーのいずれかに一致する型を提供するだけです。ユニオン型の値を持っている場合、どのように操作しますか?
TypeScriptでは、ユニオンのすべてのメンバーに対して有効な場合にのみ、操作が許可されます。たとえば、string | number
のユニオンがある場合、string
でのみ使用可能なメソッドは使用できません。
tsTry
functionprintId (id : number | string) {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'.console .log (id .()); toUpperCase }
解決策は、型注釈なしの JavaScript で行うのと同様に、コードでユニオンを絞り込むことです。絞り込みは、TypeScript がコードの構造に基づいて値のより具体的な型を推論できる場合に発生します。
たとえば、TypeScript は、string
値のみが typeof
値 "string"
を持つことを認識しています。
tsTry
functionprintId (id : number | string) {if (typeofid === "string") {// In this branch, id is of type 'string'console .log (id .toUpperCase ());} else {// Here, id is of type 'number'console .log (id );}}
別の例として、Array.isArray
のような関数を使用することがあります。
tsTry
functionwelcomePeople (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 );}}
else
ブランチでは、特別なことは何もする必要がないことに注意してください。もし x
が string[]
でなかった場合、それは string
であったはずです。
ユニオンのすべてのメンバーに共通のものがある場合があります。たとえば、配列と文字列の両方に slice
メソッドがあります。ユニオンのすべてのメンバーに共通のプロパティがある場合、絞り込みなしでそのプロパティを使用できます。
tsTry
// Return type is inferred as number[] | stringfunctiongetFirstThree (x : number[] | string) {returnx .slice (0, 3);}
型のユニオンが、それらの型のプロパティの交差を持っているように見えるのは混乱するかもしれません。これは偶然ではありません。ユニオンという名前は型理論に由来します。ユニオン
number | string
は、各型からの値のユニオンを取得することによって構成されます。各セットに関する対応する事実を持つ2つのセットが与えられた場合、それらの事実の交差のみがセット自体のユニオンに適用されることに注意してください。たとえば、帽子をかぶった背の高い人々の部屋と、帽子をかぶったスペイン語話者の別の部屋があった場合、それらの部屋を組み合わせた後、すべての人について知っている唯一のことは、帽子をかぶっている必要があるということです。
型エイリアス
型注釈でオブジェクト型とユニオン型を直接記述することで使用してきました。これは便利ですが、同じ型を複数回使用し、1つの名前で参照したいことがよくあります。
型エイリアスはまさにそれです。任意の型の名前です。型エイリアスの構文は次のとおりです。
tsTry
typePoint = {x : number;y : number;};// Exactly the same as the earlier examplefunctionprintCoord (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 });
実際には、型エイリアスを使用して、オブジェクト型だけでなく、あらゆる型に名前を付けることができます。たとえば、型エイリアスはユニオン型に名前を付けることができます。
tsTry
typeID = number | string;
エイリアスは単なるエイリアスであることに注意してください。型エイリアスを使用して、同じ型の異なる/個別の「バージョン」を作成することはできません。エイリアスを使用すると、エイリアス化された型を記述した場合とまったく同じです。言い換えれば、このコードは不正に見えるかもしれませんが、両方の型が同じ型のエイリアスであるため、TypeScript では OK です。
tsTry
typeUserInputSanitizedString = string;functionsanitizeInput (str : string):UserInputSanitizedString {returnsanitize (str );}// Create a sanitized inputletuserInput =sanitizeInput (getInput ());// Can still be re-assigned with a string thoughuserInput = "new input";
インターフェース
インターフェース宣言は、オブジェクト型に名前を付ける別の方法です。
tsTry
interfacePoint {x : number;y : number;}functionprintCoord (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 });
上記の型エイリアスを使用したときと同様に、例は匿名オブジェクト型を使用した場合とまったく同じように機能します。TypeScript は、printCoord
に渡した値の構造のみに関心があります。必要なプロパティがあるかどうかだけに関心があります。型の構造と機能のみに関心があるため、TypeScript を構造的に型付けされた型システムと呼びます。
型エイリアスとインターフェースの違い
型エイリアスとインターフェースは非常によく似ており、多くの場合、自由にどちらかを選択できます。interface
のほぼすべての機能は type
で使用できます。主な違いは、型は新しいプロパティを追加するために再度開くことができないのに対し、インターフェースは常に拡張可能であるということです。
インターフェース |
型 |
---|---|
インターフェースの拡張
|
交差による型の拡張
|
既存のインターフェースへの新しいフィールドの追加
|
型は作成後に変更できません
|
これらの概念については後の章で詳しく説明するので、すぐにすべてを理解できなくても心配しないでください。
- TypeScript バージョン 4.2 より前では、型エイリアス名がエラーメッセージに表示される可能性があり、場合によっては、同等の匿名型の代わりに表示されます(望ましい場合と望ましくない場合があります)。インターフェースは常にエラーメッセージに名前が付けられます。
- 型エイリアスは、宣言マージに参加できませんが、インターフェースは参加できます。
- インターフェースは、オブジェクトの形状を宣言するためだけに使用でき、プリミティブの名前を変更するために使用することはできません。
- インターフェース名は、エラーメッセージで常に元の形式で表示されますが、名前で使用された場合にのみ表示されます。
ほとんどの場合、個人の好みに基づいて選択でき、TypeScript は、どちらかの種類の宣言にする必要がある場合に教えてくれます。ヒューリスティックが必要な場合は、type
の機能を使用する必要があるまで interface
を使用してください。
型アサーション
TypeScript が認識できない値の型に関する情報がある場合があります。
たとえば、document.getElementById
を使用している場合、TypeScript はこれが何らかの HTMLElement
を返すことしか認識していませんが、ページに特定の ID を持つ HTMLCanvasElement
が常にあることを知っている場合があります。
このような状況では、型アサーションを使用してより具体的な型を指定できます。
tsTry
constmyCanvas =document .getElementById ("main_canvas") asHTMLCanvasElement ;
型注釈と同様に、型アサーションはコンパイラーによって削除され、コードの実行時の動作には影響しません。
また、同等の角度付き括弧構文を使用することもできます(ただし、コードが .tsx
ファイルにある場合を除く)。
tsTry
constmyCanvas = <HTMLCanvasElement >document .getElementById ("main_canvas");
注意:型アサーションはコンパイル時に削除されるため、型アサーションに関連付けられたランタイムチェックはありません。型アサーションが間違っていても、例外や
null
は生成されません。
TypeScript は、型のより具体的またはあまり具体的ではないバージョンに変換する型アサーションのみを許可します。このルールは、次のような「不可能」な強制を防止します。
tsTry
constConversion 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.x = "hello" as number;
このルールは保守的すぎる場合があり、有効な可能性があるより複雑な強制を許可しない場合があります。このような場合は、最初に any
(または後で紹介する unknown
)へのアサーションを 2 つ使用し、次に目的の型にアサーションを使用できます。
tsTry
consta =expr as any asT ;
リテラル型
一般的な型 string
と number
に加えて、型位置で特定の文字列と数値を参照できます。
これについて考える1つの方法は、JavaScript に変数を宣言するためのさまざまな方法が付属していることを検討することです。var
と let
の両方で、変数内に保持されているものを変更できますが、const
は変更できません。これは、TypeScript がリテラルの型を作成する方法に反映されています。
tsTry
letchangingString = "Hello World";changingString = "Olá Mundo";// Because `changingString` can represent any possible string, that// is how TypeScript describes it in the type systemchangingString ;constconstantString = "Hello World";// Because `constantString` can only represent 1 possible string, it// has a literal type representationconstantString ;
それ自体では、リテラル型はあまり価値がありません。
tsTry
letx : "hello" = "hello";// OKx = "hello";// ...Type '"howdy"' is not assignable to type '"hello"'.2322Type '"howdy"' is not assignable to type '"hello"'.= "howdy"; x
1つの値しか持つことができない変数を持つことはあまり役に立ちません!
ただし、リテラルをユニオンに組み合わせることで、より便利な概念を表現できます。たとえば、特定の既知の値のセットのみを受け入れる関数などです。
tsTry
functionprintText (s : string,alignment : "left" | "right" | "center") {// ...}printText ("Hello, world", "left");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"'.printText ("G'day, mate","centre" );
数値リテラル型は同じように機能します。
tsTry
functioncompare (a : string,b : string): -1 | 0 | 1 {returna ===b ? 0 :a >b ? 1 : -1;}
もちろん、これらを非リテラル型と組み合わせることができます。
tsTry
interfaceOptions {width : number;}functionconfigure (x :Options | "auto") {// ...}configure ({width : 100 });configure ("auto");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"'.configure ("automatic" );
リテラル型にはもう1種類あります。それはブールリテラルです。ブールリテラル型は 2 つしかなく、ご推測のとおり、それらは型 true
と false
です。型 boolean
自体は、実際にはユニオン true | false
のエイリアスにすぎません。
リテラル推論
オブジェクトで変数を初期化する場合、TypeScript はそのオブジェクトのプロパティの値が後で変更される可能性があると想定します。たとえば、次のようなコードを書いたとします。
tsTry
constobj = {counter : 0 };if (someCondition ) {obj .counter = 1;}
TypeScript は、以前に `0` が入っていたフィールドに `1` を代入することをエラーとはみなしません。別の言い方をすると、`obj.counter` は `0` ではなく `number` 型でなければなりません。なぜなら、型は *読み取り* と *書き込み* の両方の動作を決定するために使用されるからです。
文字列についても同様です。
tsTry
declare functionhandleRequest (url : string,method : "GET" | "POST"): void;constreq = {url : "https://example.com",method : "GET" };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"'.handleRequest (req .url ,req .method );
上記の例では、`req.method` は `"GET"` ではなく `string` 型として推論されます。コードは `req` の作成と `handleRequest` の呼び出しの間で評価される可能性があり、その間に `req.method` に `GUESS` のような新しい文字列が代入される可能性があるため、TypeScript はこのコードをエラーとみなします。
これを回避する方法は2つあります。
-
どちらかの場所で型アサーションを追加することで、推論を変更できます。
ts
Try// Change 1:constreq = {url : "https://example.com",method : "GET" as "GET" };// Change 2handleRequest (req .url ,req .method as "GET");変更 1 は、「`req.method` が常に *リテラル型* の `"GET"` であることを意図している」という意味であり、そのフィールドへの `"GUESS"` の代入を後で防ぎます。変更 2 は、「他の理由から `req.method` が `"GET"` の値を持つことを知っている」という意味です。
-
`as const` を使用して、オブジェクト全体を型リテラルに変換できます。
ts
Tryconstreq = {url : "https://example.com",method : "GET" } asconst ;handleRequest (req .url ,req .method );
`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` になる可能性のある値をチェックできます。
tsTry
functiondoSomething (x : string | null) {if (x === null) {// do nothing} else {console .log ("Hello, " +x .toUpperCase ());}}
非 null アサーション演算子 (接尾辞 `!`)
TypeScript には、明示的なチェックを行わずに型から `null` と `undefined` を削除するための特別な構文もあります。任意の式の後に `!` を記述することは、その値が `null` または `undefined` ではないという型アサーションとして効果的です。
tsTry
functionliveDangerously (x ?: number | null) {// No errorconsole .log (x !.toFixed ());}
他の型アサーションと同様に、これはコードの実行時の動作を変更しないため、値が `null` または `undefined` *になりえない* ことを知っている場合にのみ `!` を使用することが重要です。
Enum
Enum は、TypeScript によって JavaScript に追加された機能で、名前付き定数のセットのいずれかになりうる値を記述できます。ほとんどの TypeScript 機能とは異なり、これは JavaScript への *型レベル* の追加ではなく、言語とランタイムに追加されたものです。このため、これは存在を知っておくべき機能ですが、確信がない限り使用を控えるべきかもしれません。enum の詳細については、Enum のリファレンスページを参照してください。
あまり一般的でないプリミティブ
型システムで表現される JavaScript の残りのプリミティブについても言及する価値はあります。ここでは詳しく説明しません。
bigint
ES2020 以降、JavaScript には非常に大きな整数に使用されるプリミティブ `BigInt` があります。
tsTry
// Creating a bigint via the BigInt functionconstoneHundred : bigint =BigInt (100);// Creating a BigInt via the literal syntaxconstanotherHundred : bigint = 100n;
BigInt の詳細については、TypeScript 3.2 のリリースノートを参照してください。
symbol
JavaScript には、関数 `Symbol()` を介してグローバルに一意な参照を作成するために使用されるプリミティブがあります。
tsTry
constfirstName =Symbol ("name");constsecondName =Symbol ("name");if (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.firstName ===secondName ) {// Can't ever happen}
詳細については、Symbols のリファレンスページを参照してください。