undefined
と null
は型に含まれませんundefined|T
この章では、TypeScript の要点について説明します。
この章を読んだ後、次の TypeScript コードを理解できるようになります。
<T> {
interface Arrayconcat(...items: Array<T[] | T>): T[];
reduce<U>(
: (state: U, element: T, index: number, array: T[]) => U,
callback?: U
firstState: U;
)// ···
}
これは難解だと感じるかもしれません。私も同意見です!しかし(証明できると期待していますが)、この構文は比較的簡単に習得できます。そして、一度理解すれば、英語で長い説明を読む必要がなく、コードの動作についての即時的で正確かつ包括的な要約が得られます。
TypeScript コンパイラには、多くの構成方法があります。重要なオプションのグループの 1 つは、コンパイラが TypeScript コードをどの程度徹底的にチェックするかを制御します。最大の設定は--strict
で有効になり、常に使用することをお勧めします。プログラムの作成は少し難しくなりますが、静的型チェックのメリットを最大限に享受できます。
今のところ、
--strict
について知っておくべきことは以上です
詳細を知りたい場合は、読み進めてください。
--strict
をtrue
に設定すると、次のすべてのオプションがtrue
に設定されます。
--noImplicitAny
:TypeScript が型を推論できない場合、型を指定する必要があります。これは主に、関数とメソッドのパラメータに適用されます。この設定では、それらに注釈を付ける必要があります。--noImplicitThis
:this
の型が不明な場合に警告します。--alwaysStrict
:可能な限り JavaScript の厳格モードを使用します。--strictNullChecks
:null
は(独自の型であるnull
以外)どの型にも含まれておらず、許容される値である場合は明示的に記述する必要があります。--strictFunctionTypes
:関数型のより厳格なチェックを有効にします。--strictPropertyInitialization
:クラス定義のプロパティは、undefined
の値を持つことができる場合を除き、初期化する必要があります。この書籍では、npm パッケージとウェブアプリを TypeScript で作成する際に、さらにコンパイラオプションについて説明します。TypeScript ハンドブックには、それらに関する包括的なドキュメントがあります。
この章では、型は単なる値の集合です。JavaScript 言語(TypeScript ではありません!)には、8 つの型しかありません。
undefined
を持つ集合null
を持つ集合false
とtrue
を持つ集合これらの型はすべて動的です。実行時に使用できます。
TypeScript は JavaScript に追加のレイヤーをもたらします:静的型。これらは、ソースコードのコンパイル時または型チェック時のみ存在します。各格納場所(変数、プロパティなど)には、動的値を予測する静的型があります。型チェックは、これらの予測が実現することを保証します。
そして、(コードを実行せずに)静的にチェックできるものがたくさんあります。たとえば、関数toString(num)
のパラメータnum
の静的型がnumber
の場合、引数'abc'
の静的型が間違っているため、関数呼び出しtoString('abc')
は無効です。
function toString(num: number): string {
String(num);
return }
前の関数宣言には、2 つの型注釈があります。
num
:コロンの後にnumber
が続きます。toString()
の結果:コロンの後にstring
が続きます。number
とstring
はどちらも、格納場所の型を指定する型式です。
多くの場合、型注釈がない場合、TypeScript は静的型を推論できます。たとえば、toString()
の戻り値の型を省略した場合、TypeScript はそれがstring
であると推論します。
// %inferred-type: (num: number) => string
function toString(num: number) {
String(num);
return }
型推論は推測ではありません。明示的に指定されていない型の導出には、明確なルール(算術と同様)に従います。この場合、戻り文は、任意の値を文字列にマッピングする関数String()
を、型number
の値num
に適用し、結果を返します。そのため、推論された戻り値の型はstring
です。
場所の型が明示的に指定されておらず、推論もできない場合、TypeScript はその型にany
を使用します。これはすべての値の型であり、ワイルドカードです。つまり、値がその型を持っている場合、すべてを行うことができます。
--strict
では、any
は明示的に使用する場合のみ許可されます。言い換えると:すべての場所に、明示的または推論された静的型が必要です。次の例では、パラメータnum
にはどちらもなく、コンパイル時エラーが発生します。
// @ts-expect-error: Parameter 'num' implicitly has an 'any' type. (7006)
function toString(num) {
String(num);
return }
型注釈のコロンの後の型式は、単純なものから複雑なものまでさまざまで、次のように作成されます。
基本型は有効な型式です。
undefined
、null
boolean
、number
、bigint
、string
symbol
object
.Array
(技術的には JavaScript では型ではありません)any
(すべての値の型)基本型を組み合わせて新しい複合型を作成する方法はたくさんあります。たとえば、集合演算子和集合(∪
)と積集合(∩
)が集合を組み合わせるのと同様に、型を組み合わせる型演算子を使用します。その方法をすぐに見ていきます。
TypeScript には 2 つの言語レベルがあります。
構文でこれらの 2 つのレベルを確認できます。
: undefined = undefined; const undef
動的レベルでは、JavaScript を使用して変数undef
を宣言し、値undefined
で初期化します。
静的レベルでは、TypeScript を使用して、変数undef
の静的型がundefined
であることを指定します。
同じ構文undefined
は、動的レベルで使用されるか静的レベルで使用されるかによって、異なる意味を持つことに注意してください。
2 つの言語レベルを意識するようにしてください
これは、TypeScript を理解する上で大いに役立ちます。
type
を使用して、既存の型に新しい名前(エイリアス)を作成できます。
type Age = number;
: Age = 82; const age
配列は JavaScript で 2 つの役割を果たします(どちらか一方または両方)。
配列arr
が、すべての要素が数値であるリストとして使用されていることを表現する方法は 2 つあります。
: number[] = [];
let arr1: Array<number> = []; let arr2
通常、TypeScript は代入がある場合、変数の型を推論できます。この場合、空の配列では要素の型を決定できないため、実際には助ける必要があります。
角括弧表記(Array<number>
)については、後で詳しく説明します。
2 次元点を配列に格納する場合は、その配列をタプルとして使用しています。次のようになります。
: [number, number] = [7, 5]; let point
配列リテラルの場合、TypeScript はタプル型ではなくリスト型を推論するため、配列としてのタプルには型注釈が必要です。
// %inferred-type: number[]
= [7, 5]; let point
タプルのもう 1 つの例は、Object.entries(obj)
の結果です。obj
のプロパティごとに 1 つの [キー、値] ペアを持つ配列です。
// %inferred-type: [string, number][]
= Object.entries({ a: 1, b: 2 });
const entries
.deepEqual(
assert,
entries'a', 1 ], [ 'b', 2 ]]); [[
推論された型は、タプルの配列です。
これは関数型の例です。
: number) => string (num
この型は、型 number の単一パラメータを受け取り、文字列を返すすべての関数を網羅しています。この型を型注釈で使用してみましょう。
: (num: number) => string = // (A)
const toString: number) => String(num); // (B) (num
通常、関数の場合はパラメータ型を指定する必要があります。しかし、この場合、B 行のnum
の型は A 行の関数型から推論できるため、省略できます。
: (num: number) => string =
const toString=> String(num); (num)
toString
の型注釈を省略した場合、TypeScript はアロー関数から型を推論します。
// %inferred-type: (num: number) => string
= (num: number) => String(num); const toString
今回は、num
に型注釈が必要です。
次の例はさらに複雑です。
function stringify123(callback: (num: number) => string) {
callback(123);
return }
stringify123()
のcallback
パラメータを記述するために関数型を使用しています。この型注釈により、TypeScript は次の関数呼び出しを拒否します。
// @ts-expect-error: Argument of type 'NumberConstructor' is not
// assignable to parameter of type '(num: number) => string'.
// Type 'number' is not assignable to type 'string'.(2345)
stringify123(Number);
しかし、この関数呼び出しは受け入れます。
.equal(
assertstringify123(String), '123');
TypeScript は通常、関数の戻り値の型を推論できますが、明示的に指定することもでき、場合によっては役立ちます(少なくとも害はありません)。
stringify123()
の場合、戻り値の型を指定するのはオプションであり、次のようになります。
function stringify123(callback: (num: number) => string): string {
callback(123);
return }
`void` は関数の特殊な戻り値型です。TypeScript に対し、その関数は常に `undefined` を返すことを示します。
明示的にそうする場合もあります。
function f1(): void {
;
return undefined }
暗黙的にそうする場合もあります。
function f2(): void {}
しかし、そのような関数は `undefined` 以外の値を明示的に返すことはできません。
function f3(): void {
// @ts-expect-error: Type '"abc"' is not assignable to type 'void'. (2322)
'abc';
return }
識別子の後に続く疑問符は、そのパラメータがオプションであることを意味します。例えば
function stringify123(callback?: (num: number) => string) {
if (callback === undefined) {
= String;
callback
}callback(123); // (A)
return }
TypeScript は、`callback` が `undefined` でないことを確認した場合にのみ、A 行の関数呼び出しを許可します(パラメータが省略された場合は `undefined` です)。
TypeScript はパラメータのデフォルト値をサポートしています。
function createPoint(x=0, y=0): [number, number] {
, y];
return [x
}
.deepEqual(
assertcreatePoint(),
0, 0]);
[.deepEqual(
assertcreatePoint(1, 2),
1, 2]); [
デフォルト値により、パラメータはオプションになります。TypeScript は型を推論できるため、通常は型アノテーションを省略できます。例えば、`x` と `y` の両方が `number` 型であると推論できます。
型アノテーションを追加する場合は、次のようになります。
function createPoint(x:number = 0, y:number = 0): [number, number] {
, y];
return [x }
TypeScript のパラメータ定義ではレストパラメータも使用できます。その静的型は配列(リストまたはタプル)でなければなりません。
function joinNumbers(...nums: number[]): string {
.join('-');
return nums
}.equal(
assertjoinNumbers(1, 2, 3),
'1-2-3');
変数に保持される値(一度に1つの値)は、異なる型のメンバーである場合があります。その場合、ユニオン型が必要です。例えば、次のコードでは、`stringOrNumber` は `string` 型または `number` 型のいずれかです。
function getScore(stringOrNumber: string|number): number {
if (typeof stringOrNumber === 'string'
&& /^\*{1,5}$/.test(stringOrNumber)) {
return stringOrNumber.length;
else if (typeof stringOrNumber === 'number'
} && stringOrNumber >= 1 && stringOrNumber <= 5) {
return stringOrNumber
else {
} throw new Error('Illegal value: ' + JSON.stringify(stringOrNumber));
}
}
.equal(getScore('*****'), 5);
assert.equal(getScore(3), 3); assert
`stringOrNumber` の型は `string|number` です。型式 `s|t` の結果は、型 `s` と `t` の集合論的和集合です(集合として解釈されます)。
多くのプログラミング言語では、`null` はすべてのオブジェクト型の一部です。例えば、Java で変数の型が `String` の場合、`null` に設定しても Java はエラーを報告しません。
逆に、TypeScript では、`undefined` と `null` は別個の非交差型として扱われます。許可する必要がある場合は、`undefined|string` や `null|string` などのユニオン型が必要です。
: null|number = null;
let maybeNumber= 123; maybeNumber
そうでない場合、エラーが発生します。
// @ts-expect-error: Type 'null' is not assignable to type 'number'. (2322)
: number = null;
let maybeNumber= 123; maybeNumber
(変数を初期化前に読み取らない限り) TypeScript は即時の初期化を強制しません。
: number; // OK
let myNumber= 123; myNumber
以前のこの関数を思い出してください。
function stringify123(callback?: (num: number) => string) {
if (callback === undefined) {
= String;
callback
}callback(123); // (A)
return }
`stringify123()` を書き直し、パラメータ `callback` をオプションではなくします。呼び出し側が関数を提供したくない場合は、`null` を明示的に渡す必要があります。結果は次のようになります。
function stringify123(
: null | ((num: number) => string)) {
callback= 123;
const num if (callback === null) { // (A)
= String;
callback
}callback(num); // (B)
return
}
.equal(
assertstringify123(null),
'123');
// @ts-expect-error: Expected 1 arguments, but got 0. (2554)
.throws(() => stringify123()); assert
再び、B 行で関数呼び出しを行う前に、`callback` が関数でない場合(A 行)を処理する必要があります。そうしなかった場合、TypeScript はその行でエラーを報告します。
次の3つのパラメータ宣言は非常に似ています。
パラメータがオプションの場合、省略できます。その場合、値は `undefined` になります。
function f1(x?: number) { return x }
.equal(f1(123), 123); // OK
assert.equal(f1(undefined), undefined); // OK
assert.equal(f1(), undefined); // can omit assert
パラメータにデフォルト値がある場合、パラメータが省略されているか `undefined` に設定されている場合、そのデフォルト値が使用されます。
function f2(x = 456) { return x }
.equal(f2(123), 123); // OK
assert.equal(f2(undefined), 456); // OK
assert.equal(f2(), 456); // can omit assert
パラメータにユニオン型がある場合、省略できませんが、`undefined` に設定できます。
function f3(x: undefined | number) { return x }
.equal(f3(123), 123); // OK
assert.equal(f3(undefined), undefined); // OK
assert
// @ts-expect-error: Expected 1 arguments, but got 0. (2554)
f3(); // can’t omit
配列と同様に、オブジェクトは JavaScript で2つの役割を果たします(場合によっては混在します)。
レコード: 開発時に既知の固定数のプロパティ。各プロパティは異なる型を持つことができます。
辞書: 開発時に名前が不明な任意の数のプロパティ。すべてのプロパティは同じ型を持ちます。
この章では、辞書としてのオブジェクトは無視します。これは§15.4.5「インデックスシグネチャ: 辞書としてのオブジェクト」で扱われています。ちなみに、辞書には通常、Map の方が適しています。
インターフェースはレコードとしてのオブジェクトを記述します。例えば
interface Point {: number;
x: number;
y }
コンマでメンバーを区切ることができます。
interface Point {: number,
x: number,
y }
TypeScript の型システムの大きな利点の1つは、名義的ではなく構造的に機能することです。つまり、インターフェース `Point` は、適切な構造を持つすべてのオブジェクトに一致します。
interface Point {: number;
x: number;
y
}function pointToString(pt: Point) {
return `(${pt.x}, ${pt.y})`;
}
.equal(
assertpointToString({x: 5, y: 7}), // compatible structure
'(5, 7)');
逆に、Java の名義的型システムでは、各クラスで実装するインターフェースを明示的に宣言する必要があります。そのため、クラスは作成時に存在するインターフェースのみ実装できます。
オブジェクトリテラル型は匿名インターフェースです。
type Point = {
: number;
x: number;
y; }
オブジェクトリテラル型の利点の1つは、インラインで使用できることです。
function pointToString(pt: {x: number, y: number}) {
return `(${pt.x}, ${pt.y})`;
}
プロパティを省略できる場合は、名前の後に疑問符を付けます。
interface Person {: string;
name?: string;
company }
次の例では、`john` と `jane` の両方がインターフェース `Person` に一致します。
: Person = {
const john: 'John',
name;
}: Person = {
const jane: 'Jane',
name: 'Massive Dynamic',
company; }
インターフェースにはメソッドを含めることもできます。
interface Point {: number;
x: number;
ydistance(other: Point): number;
}
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
プロパティの設定方法を最も適切に表現する構文を使用することをお勧めします。
TypeScript の2つの言語レベルを思い出してください。
同様に
通常の関数は動的レベルに存在し、値のファクトリであり、値を表すパラメータを持ちます。パラメータは括弧で宣言されます。
= (x: number) => x; // definition
const valueFactory = valueFactory(123); // use const myValue
ジェネリック型は静的レベルに存在し、型のファクトリであり、型を表すパラメータを持ちます。パラメータは山括弧で宣言されます。
type TypeFactory<X> = X; // definition
type MyType = TypeFactory<string>; // use
型パラメータの命名
TypeScript では、型パラメータには大文字の単一文字(`T`、`I`、`O` など)を使用するのが一般的です。ただし、有効な JavaScript 識別子であれば何でも使用でき、長い名前の方がコードの理解が容易になることがよくあります。
// Factory for types
<Value> {
interface ValueContainer: Value;
value
}
// Creating one type
type StringContainer = ValueContainer<string>;
`Value` は型変数です。山括弧で1つ以上の型変数を導入できます。
クラスにも型パラメータを持たせることができます。
<Elem> {
class SimpleStack: Array<Elem> = [];
#datapush(x: Elem): void {
.#data.push(x);
this
}pop(): Elem {
= this.#data.pop();
const result if (result === undefined) {
new Error();
throw
};
return result
}get length() {
.#data.length;
return this
} }
クラス `SimpleStack` には型パラメータ `Elem` があります。クラスをインスタンス化するとき、型パラメータの値も提供します。
= new SimpleStack<string>();
const stringStack .push('first');
stringStack.push('second');
stringStack.equal(stringStack.length, 2);
assert.equal(stringStack.pop(), 'second'); assert
Map は TypeScript でジェネリックに型付けされています。例えば
: Map<boolean,string> = new Map([
const myMap, 'no'],
[false, 'yes'],
[true; ])
(`new Map()` の引数に基づく) 型推論のおかげで、型パラメータを省略できます。
// %inferred-type: Map<boolean, string>
= new Map([
const myMap , 'no'],
[false, 'yes'],
[true; ])
関数定義では、次のように型変数を導入できます。
function identity<Arg>(arg: Arg): Arg {
;
return arg }
関数は次のように使用します。
// %inferred-type: number
= identity<number>(123); const num1
型推論により、型パラメータを再び省略できます。
// %inferred-type: 123
= identity(123); const num2
TypeScript が `123` という型を推論したことに注意してください。これは、1つの数値を持つ集合であり、`number` 型よりも具体的な型です。
アロー関数にも型パラメータを持たせることができます。
= <Arg>(arg: Arg): Arg => arg; const identity
これはメソッドの型パラメータ構文です。
= {
const obj identity<Arg>(arg: Arg): Arg {
;
return arg,
}; }
function fillArray<T>(len: number, elem: T): T[] {
new Array<T>(len).fill(elem);
return }
このコードでは、型変数 `T` が4回出現します。
TypeScript がパラメータ `elem` から `T` を推論できるため、`fillArray()` を呼び出すとき(A 行)には型パラメータを省略できます。
// %inferred-type: string[]
= fillArray<string>(3, '*');
const arr1 .deepEqual(
assert, ['*', '*', '*']);
arr1
// %inferred-type: string[]
= fillArray(3, '*'); // (A) const arr2
学んだことを使って、前に見たコードを理解しましょう。
<T> {
interface Arrayconcat(...items: Array<T[] | T>): T[];
reduce<U>(
: (state: U, element: T, index: number, array: T[]) => U,
callback?: U
firstState: U;
)// ···
}
これは、要素の型が `T` である配列のインターフェースです。
メソッド `.concat()` には、(レストパラメータで定義された) 0個以上のパラメータがあります。これらのパラメータのそれぞれは `T[]|T` 型です。つまり、`T` 値の配列または単一の `T` 値のいずれかです。
メソッド `.reduce()` は独自の型変数 `U` を導入します。`U` は、次のエンティティがすべて同じ型であることを表現するために使用されます。
`state` に加えて、`callback()` には次のパラメータがあります。