TypeScriptの「型指定の方法」と「基本型の種類」について解説します。型指定することで、どういった制約が掛けられるのか確認していきます。
型指定の影響
JavaScript
と TypeScript
には以下の違いがあります。
- JavaScript
- 動的型付け
- 弱い型付け(他の型の代入も許可する)
- TypeScript
- 静的型付け
- 強い型付け(他の型の代入を許可しない)
具体的に確認します。以下のようなコードがあります。
let xxx = 'hello'
xxx = 33
console.log(xxx)
xxx
には 文字列(string)
を格納して、その後に 数値(number)
を格納しています。
JavaScriptは弱い型付けのため、問題なく実行できます。そのため、意図しない型の値が格納されて不具合が生まれる可能性が高まります。
TypeScriptであれば、以下のようにコンパイルエラーとなります。
$ tsc test1.ts
test1.ts:2:1 - error TS2322: Type '33' is not assignable to type 'string'.
2 xxx = 33
~~~
Found 1 error.
TypeScriptは、強い型付けのため、xxx
に string
以外の値を代入しようとしたらエラーとなります。厳格に型の管理をすることで、意図しない不具合を防ぐことができます。
型指定の方法
以下のように、関数の引数
関数の戻り値
変数
に : 型
の形式で明示的に型指定することができます。
const twice = (count: number): string => {
return `2倍にすると ${count * 2} です。`
}
let myCount: number
myCount = 100
console.log(twice(myCount))
もし、型指定せずに宣言だけした場合 any型
(後述) になります。
基本型の種類
boolean
真偽値です。true
false
number
数値です。浮動小数も含みます。100
12.3
string
文字列です。'abcde'
any
any
を指定すると、どの型でも格納できるようになります。
let xxx: any
xxx = 1
xxx = false
xxx = 'hello'
型指定が何も指定されていな場合もany型となります。多用するとTypeScriptを利用しているメリットが薄くなります。
unknown
any
と同様で、型が不明なときに活用できます。
any
を利用していた場合、実行時にエラーが発生していた箇所が、unknown
を利用することで、コンパイル時にエラーを検知することができます。
any
を利用していたため、実行時でないと検知できなかったエラーが、unknown
を利用することで事前に検知できるようになります。
コードを実行する前に、実装者が、型判定を行うなどの修正が必要であるという判断ができます。
const unknown1: unknown = 1234;
if (typeof unknown1 === 'string') {
unknown1.charAt(1);
}
補足
TypeScript4.0から catch句
にて unknown
を指定できるようになりました。
- Allow unknown type annotation on catch clause variable · Issue #36775 · microsoft/TypeScript
- https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#unknown-on-catch-clause-bindings
array
配列です。配列の要素もチェックします。
例えば、以下はエラーになります。
let xxx = [1, 2, 3]
xxx = ["aaa"]
$ tsc test1.ts
test1.ts:2:8 - error TS2322: Type 'string' is not assignable to type 'number'.
2 xxx = ["aaa"]
~~~~~
Found 1 error.
要素の型のチェックを緩めたいのであれば、以下のようにします。
let xxx: any[] = [1, 2, 3]
xxx = ["aaa"]
主な型指定方法です。
let array1: number[]
let array2: string[]
let array3: any[]
let array4: Array<number>
let array5: Array<string>
let array6: Array<any>
array in array
多次元配列の場合、以下のようになります。
let nestedArray: number[][] = [[11, 12, 13], [23, 24, 25]]
tuple
配列の各要素ごとに型指定できます。
以下ケースでエラーとなります。
- 要素ごとに指定した型と異なる型である
- 要素数が違う
enum
enumを利用すると、数値集合を名前付して管理しやすくできます。
enum Kanto {
Gunma, // 0
Tochigi, // 1
Ibaraki = 20, // 20
Saitama, // 20
Tokyo, // 22
Chiba = 30, // 30
Kanagawa // 31
}
let prefecture: Kanto = Kanto.Gunma
数値列挙の場合、値は0から自動で割り当てられます。
以下のように文字列を値にすることもできます。
enum Kanto {
Gunma = 'Gunma',
Tochigi = 'Tochigi',
Ibaraki = 'Ibaraki',
Saitama = 'Saitama',
Tokyo = 'Tokyo',
Chiba = 'Chiba',
Kanagawa = 'Kanagawa',
}
void
voidは 関数の戻り値
で活用されます。
以下のように 戻り値がない
ときにvoidを指定します。
const echo = (name: string): void => {
console.log(`Hello, ${name}`)
}
never
以下のように処理が終了しないので、決して何も返さない場合には、neverを指定します。
let error = (message: string): never => {
throw new Error(message)
}
object
objectのプロパティも型の制約を持ちます。
以下のコードを例に確認します。
let xxx = {
aaa: 111,
bbb: 'hello'
}
xxx = {
aaa: true,
bbb: 222
}
xxx = {
yyy: 111,
zzz: 'hello'
}
コンパイルすると以下のエラーが検出されました。
$ tsc test1.ts
test1.ts:7:5 - error TS2322: Type 'true' is not assignable to type 'number'.
7 aaa: true,
~~~
test1.ts:2:5
2 aaa: 111,
~~~~~~~~
The expected type comes from property 'aaa' which is declared here on type '{ aaa: number; bbb: string; }'
test1.ts:8:5 - error TS2322: Type 'number' is not assignable to type 'string'.
8 bbb: 222
~~~
test1.ts:3:5
3 bbb: 'hello'
~~~~~~~~~~~~
The expected type comes from property 'bbb' which is declared here on type '{ aaa: number; bbb: string; }'
test1.ts:12:5 - error TS2322: Type '{ yyy: number; zzz: string; }' is not assignable to type '{ aaa: number; bbb: string; }'.
Object literal may only specify known properties, and 'yyy' does not exist in type '{ aaa: number; bbb: string; }'.
12 yyy: 111,
~~~~~~~~
Found 3 errors.
- 値が型と一致しない
- 存在しないプロパティが定義されている
というエラーです。
なお、明示的に型指定を記述するには以下のようにします。
let xxx: { aaa: number, bbb: string } = {
aaa: 111,
bbb: 'hello'
}
objectのkeyに変数利用
以下のように、変数をkeyにすることもできます。
const key = 'ccc';
const xxx = {
aaa: 111,
bbb: 'hello',
[key]: 'world',
};
console.log(xxx['aaa']); // 111
console.log(xxx['bbb']); // hello
console.log(xxx['ccc']); // world
console.log(xxx[key]); // world
object in array
Arrayの中にObjectが存在する場合、以下のようになります。
let objectInArray1: Array<{ [field: string]: any }>
objectInArray1 = [
{aaa: 1, bbb: 2},
{aaa: 20, ccc: 2},
{aaa: 22, ddd: "abc"}
]
Objectの中が想定できるのであれば、上記指定より、以下のように interface を定義した方が良いです。
interface Xxx {
aaa: number;
bbb?: number;
ccc?: number;
ddd?: string;
}
let objectInArray2: Array<Xxx>
objectInArray2 = [
{aaa: 1, bbb: 2},
{aaa: 20, ccc: 2},
{aaa: 22, ddd: "abc"}
]
null
null
のみ格納できます。
以下のコードは tsconfig.json
にて、"strictNullChecks": true
となっている場合にエラーとなります。
let xxx: string
xxx = null
もし strictNullChecks
を有効なままで null
も許容するのであれば、以下のように実装する必要があります。
let xxx: string | null
xxx = null
undefined
undefined
のみ格納できます。
以下のように、関数の引数をオプションにした場合、引数x
は string | undefined
と推測されます。
const fn = (x?: string): string => {
if (x === undefined) {
return ''
}
return x.toUpperCase()
}
fn('aaa')
fn()
複数型の指定
intersection ( T1 &
T2 )
すべて満たす
T1 & T2
のように &
で複数の型を指定します。
以下例において、Xxx
は InterfaceA
と InterfaceB
のプロパティを備えている必要があります。
interface InterfaceA {
aaa: string;
bbb: string;
}
interface InterfaceB {
ccc: string;
}
type Xxx = InterfaceA & InterfaceB
const fnXxx = (): Xxx => {
return {
aaa: 'aaa',
bbb: 'bbb',
ccc: 'ccc',
}
}
union ( T1 |
T2 )
いずれかの型を満たす
T1 | T2
のように |
で複数の型を指定します。
unionは、特定条件のときに必須のプロパティを用意したい時などに活用できます。
interface InterfaceA {
hasContent: true;
name: string;
body: string;
}
interface InterfaceB {
hasContent: false;
}
const xxx = (yyy: InterfaceA | InterfaceB) => {
if (yyy.hasContent) {
// yyy.hasContent === trueなので、yyyはInterfaceAの型に絞り込まれました。
const zzz = `${yyy.name}_${yyy.body}`;
}
};