基礎 | TypeScript
「プロを目指す人のためのTypeScript入門」の読書メモです。
TypeScript とは
TypeScript は JavaScript に 静的型付け を追加した言語です。
開発環境
- Node.js をインストール
- TypeScript コンパイラは Node.js を使用
- TypeScript をインストール
[!NOTE] コード内で Node.js を使用する場合(
requireなど)は @types/node もインストールします。 逆を言えばコード内で Node.js を使用しない場合は @types/node は必要ありません。
TSC(TypeScript Compiler)
- 型チェック
- JavaScript へのトランスパイル
どのバージョンの JavaScript ( ECMAScript )1 にトランスパイルするのかを tsconfig の target プロパティで指定します。
[!NOTE] tsc に個別ファイルを指定した場合は tsconfig を無視します(例:
$ npx tsc /path/to/file.ts)。
開発の流れ
- TypeScript から JavaScript にトランスパイル( tsc )
1.でトランスパイルした JavaScript を Node.js で実行( node )
型推論
型推論の基本は式から型を推論します。
// 式 10 は number なので num の型は number (厳密には数値 `10` のリテラル型)
const num = 10;
// 式 test は string なので s の型は string(厳密には文字列 `test` のリテラル型
const s = 'test';
型 から関数式の内部(ここでは引数型)を推論(逆方向の型推論と呼ぶ)できる場合は型アノテーションを省略できます。
逆方向の型推論を contextual typing と呼びます。
type F = (value: string) => string;
// 引数の型アノテーションを省略する場合
const getName: F = (name) => name;
// 引数の型アノテーションを省略しない場合
const getName: F = (name: string): string => {
return name;
};
JavaScript の値と対応する型(基本型)
TypeScript プログラムにおける値は、プリミティブと、あとに説明するオブジェクトの2種類に大別されます。 出典:プロを目指す人のためのTypeScript入門 p34
※ 関数も関数オブジェクトと呼ばれることからわかるようにオブジェクトです。
- オブジェクト型
- 関数型2
- プリミティブ型
- 文字列型
- 数値型
- 真偽値型
- BigInt型
- null型: null 型の値は null のみ
- undefined型: undefined 型の値は undefined のみ3
- シンボル型
文( sentence ) と式( expression )
文
文 はプログラムを構成する実行単位です。セミコロンで終わる文も多いですが、JavaScript にはセミコロン自動挿入 (ASI) があり、ブロックや制御構文など例外もあります。4
文 は 宣言( declaration )とその他に分けられます。
例) 例えば(正式には) 変数宣言文とは呼ばす 変数宣言 、関数宣言文とは呼ばず 関数宣言 です。
式
- 式は結果がある
- 式(及び後述する式文)で文を構成する
式は一般に何らかの計算を表します。そして、その計算の結果が式の結果となるのです。
出典:プロを目指す人のためのTypeScript入門 p27
式文
式文は、式のあとにセミコロンを書く文です。
文と式の違い
文と式の決定的な違いは何でしょうか。それは結果があるかどうかです
出典:プロを目指す人のためのTypeScript入門 p27
サンプル
/*
* 文の例
*/
// 文1 変数宣言
const hello = 'Hello';
// 文2 関数宣言
function getHello(name: string): string {
return `Hello ${name}!`;
}
function loggingHello(name: string): void {
console.log(`Hello ${name}!`);
}
/*
* 式
*/
// 例1
'Hello'
// 例2
3 + 5 // 式 + 式
// 例3(変数名も式であることに注意)
foo
/*
* 式文
*/
// 式文1 代入
message = 'Hello World'; // 代入演算子の左辺にある変数名( message )も式になる
// 式文2 戻り値のない関数呼び出し
loggingHello('Hiroshi');
ブロック
ブロックとは、{ }の中に文をいくつでも書けるとう構文で、ブロック自体は1つの文として扱われます。ブロックを文として実行する場合、その中に書かれた文が上から順番に実行されます。
出典:プロを目指す人のためのTypeScript入門 p63
ブロックが文の一種であるということは、if (条件式) 文という構文の文のところにブロックを置くことができるということです。
出典:プロを目指す人のためのTypeScript入門 p63
ブロックの使い道
-
複数の文を1つの文にまとめる 出典:プロを目指す人のためのTypeScript入門 p63
-
ブロックスコープの導入 出典:プロを目指す人のためのTypeScript入門 p63
リテラル
リテラルとは、何らかの値を生み出すための式のことで、生み出したい値に応じていくつかの種類があります。
出典:プロを目指す人のためのTypeScript入門 p35
つまり リテラル は TypeScript の 値 を生み出す式。 TypeScript の 値 は前述のとおり オブジェクト と プリミティブ に大別されます。
- 文字リテラル
- 数値リテラル
- オブジェクトリテラル
- ….
注意すべき等価演算子
== と null
x == null は x が null または undefined のときのみ true になります。実務では意図的に両方をまとめて判定したい場合にのみ使用します。
null が良くて undefined は駄目な場合は x === null を使います 。
### === NaN
x === NaN は x がどのような値でも( NaN であっても)必ず false になります。
NaN を判定したいときは Number.isNaN() を使用します。
### ?? ( null 合体演算子)
x ?? yは x が null または undefined のときのみ y を返します。出典:プロを目指す人のためのTypeScript入門 p56
x || y は x が 0 や 空文字や false の場合も y を返します。
オブジェクト
部分型関係( subtyping relation )
簡潔にまとめると、T が要求される場所で S を渡せるなら S は T の部分型です。
型Sと型Tがオブジェクト型だとして、次の2つの条件が満たされればSがTの部分型になります。
- Tが持つプロパティはすべてSにも存在する。
- 要件1の各プロパティについて、Sにおけるそのプロパティの型はTにおけるプロパティの方の部分型(または同じ型)である。
出典;プロを目指す人のためのTypeScript入門 p97
type5 文 で型に別名を付ける
type 文は オブジェクト型 だけでなく プリミティブな型 の別名(型名)を作成できます。
type文は決して「新たに型を作って利用可能にする」ものではなく、「すでにある型に別名をつける」だけのもの
出典:プロを目指す人のためのTypeScript入門 p89
// オブジェクト型
type MyObject = {
name: string;
age: string;
};
// string 型
type MyString = string;
// 型チェックでエラー
const myString: MyString = 28; // Type 'number' is not assignable to type 'string'.
// ※ JavaScript は変数の大文字・小文字を区別します。かつ型名、変数名は別の名前空間です(なので 重複する名前の型名と変数名が共存できます)。
type 文による型宣言はその型名の使用より後でも問題ありません(型チェックはコンパイル時に実行されるためです)。
型引数( type parameters )
型引数は、型を定義するときにパラメータをもたせることができるというもので、ジェネリクスに少し似ています。
出典:プロを目指す人のためのTypeScript入門 p100ジェネリクス(generics)とは、型引数を受け取る関数を作る機能のことです。
出典:プロを目指す人のためのTypeScript入門 p168
type Family<Parent, Child> = {
mother: Parent;
father: Parent;
child: Child;
};
const family: Family<number, string> = {
mother: 0,
father: 100,
child: "1000"
};
type OtherFamily = Family<string, string>;
const otherFamily: OtherFamily = {
mother: "0",
father: "100",
child: "1000"
}
適切な数の型引数を指定せずに型を使用した場合はコンパイルエラーという結果になります。 出典:プロを目指す人のためのTypeScript入門 p101
// エラー: Generic type 'Family' requires 2 type argument(s).
const family: Family = {
mother: 0,
father: 100,
child: "1000",
}
部分型関係による型引数の制約
type 文において型引数を宣言するとき、extends という構文を使うことができます。具体的には、型引数の宣言の後ろに extends 型を付加することができます。この構文は、「この型引数は常に型の部分型でなければならない」という制約(constraint)を意味します。 出典:プロを目指す人のためのTypeScript入門 p101
type HasName = {
name: string;
};
type Family<Parent extends HasName, Child extends HasName> = {
mother: Parent;
father: Parent;
child: Child;
};
for-of 文とイテレータ
TypeScriptにはイテレータ(Iterator)という概念が存在します。 … イテレータは繰り返し処理のための汎用的なインターフェースです。イテレータを通じて繰り返し処理ができる値のことをJavaScript用語でIterableと呼びます。for-of文のofの右の式には、厳密には配列だけでなくそれ以外のIterableな値を与えることができまるのです。 出典:プロを目指す人のためのTypeScript入門 p111)
イテレータープロトコル
より具体的に言うと、イテレーターは、次の 2 つのプロパティを持つオブジェクトを返す next() メソッドを持つことによってイテレータープロトコルを実装するオブジェクトです。 value 反復シーケンスの次の値. done シーケンスの最後の値が既に消費されている場合に true となります。done と並んで value が存在する場合、それがイテレーターの戻り値となります。
出典:イテレーターとジェネレーター
参考:Generator
イテレーターの例(ジェネレーター)
/*
* ジェネレーターはイテレータープロトコルを実装している
*/
function* createIterator() {
const limit = 10;
for (let i = 0; i < limit; i++ ) {
yield i;
}
}
const iterator = createIterator();
for (const value of iterator) {
console.log(value);
}
/*
* ジェネレーターはイテレータープロトコルを実装しているので `next()` を持っており以下のようにも書けます。
*/
const otherIterator = createIterator();
while(true) {
const result = otherIterator.next();
if ( result.done ) {
break;
}
console.log(result.value)
}
スプレッド構文( Spread syntax )
const one = {
name: 'John',
age: 30,
}
const other = {
hobby: 'Reading',
...one,
}
console.log(other);
// {
// "hobby": "Reading",
// "name": "John",
// "age": 30
// }
// typeof キーワードで動的に型名作成
type Other = typeof other;
// type Other = {
// name: string;
// age: number;
// hobby: string;
// }
typeof
typeof には TypeScript の型空間で使う typeof キーワードと、実行時に値の型を調べる typeof 演算子の2種類があります。混同しないように注意が必要です。
- typeof キーワード: 型を作るキーワード type 文 で使用
- typeof 演算子6
分割代入( Destructuring assignment )
オブジェクトのプロパティの中身をプロパティと同名の変数に入れたい場合にしかこのパターンを用いることができません。
出典:プロを目指す人のためのTypeScript入門 p114
const rect = {width: 100, height: 200, depth: 300};
const {width, height, depth} = rect;
console.log(width, height, depth);
// 100, 200, 300
ref. https://typescript-jp.gitbook.io/deep-dive/future-javascript/destructuring
分割を使用して構造体の深いデータを取得
出典: https://typescript-jp.gitbook.io/deep-dive/future-javascript/destructuring
const page = {
title: 'Hello World',
content: {
catchCopy: 'HELLO WORLD',
lead: 'Welcome to my page.',
body: 'This is content'
}
}
const {content: {catchCopy}} = page;
console.log(catchCopy);
// HELLO World
console.log(content);
// ERR content is not defined
const { content } = page;
console.log(content);
//
{
body: "This is content",
catchCopy: "HELLO WORLD",
lead: "Welcome to my page."
}
プロパティ名に別の変数名を使う
content を foo に代入します。
const page = {
title: 'Hello World',
content: {
catchCopy: 'HELLO WORLD',
lead: 'Welcome to my page.',
body: 'This is content'
}
}
const {content: foo} = page;
console.log(foo);
// {
// "catchCopy": "HELLO WORLD",
// "lead": "Welcome to my page.",
// "body": "This is content"
// }
スプレッド構文や分割代入を使ったオブジェクトのコピー
スプレッド構文
const one = {
foo: 'a',
bar: 'b',
baz: 'c',
};
const other = {...one};
console.log(other);
// {
// "foo": "a",
// "bar": "b",
// "baz": "c"
// }
one.foo = 'x';
console.log(other.foo); // a
分割構文
const one = {
foo: 'a',
bar: 'b',
baz: 'c',
};
const {...other} = one;
console.log(other);
// {
// "foo": "a",
// "bar": "b",
// "baz": "c"
// }
one.foo = 'x';
console.log(other.foo); // a
関数
TypeScript は return 文を書かない関数の戻り値を void と推論します。
オプショナルな引数
オプショナルな引数の型は undefined とのユニオン型になります。
// オプショナルな引数の型は undefined とのユニオン型になる
// (parameter) a: string | undefined
function foo(a?: string){
// 呼び出しで引数が省略されたときの a は undefined
if (a) {
console.log(a)
} else {
console.log('省略')
}
}
foo('test');
// test
foo();
// 省略
/*
* オプショナルではない引数が省略されたらコンパイルエラー
*/
function bar(a: string){
// 呼び出しで引数が省略されたときの a は undefined
if (a) {
console.log(a)
} else {
console.log('省略')
}
}
bar();
// Expected 1 arguments, but got 0.
関数型の型名
関数型宣言: type 型名 = (引数名: 型名) => 戻り値型
type User = {
name: string;
age: number;
}
// 関数型の型を明記
type GetNameFunc = (user: User) => string;
const getName: GetNameFunc = (user: User): string => {
return user.name;
}
console.log(getName({ name: 'John', age: 30 })); // John
/*
* コンパイル時の型チェックでエラー
* // 関数の型チェックエラー文
* > Type '(name: string) => string' is not assignable to type 'GetNameFunc'.Types of parameters 'name' and 'user' are incompatible.
*
* // 呼び出し時の型チェックのエラー文
* > Type 'User' is not assignable to type 'string'. const getNameFaulty: GetNameFunc = (name: string):string => {
* return name;
* }
*/
console.log(getNameFaulty('jhon')) // 文字型を渡しているので型チェックエラー
ジェネリクス
ジェネリクス(generics)とは型引数を受け取る関数を作る機能のことです。
出典:プロを目指す人のためのTypeScript入門 p168
// 出典:プロを目指す人のためのTypeScript入門 p169
function repeat <T>(elem: T, num: number): T[] {
const result: T[] = [];
for (let i = 0; i < num; i++ ) {
result.push(elem)
}
return result;
}
console.log(repeat('a', 5)); // ["a", "a", "a", "a", "a"]
console.log(repeat(123, 3)); // [123, 123, 123]
// 関数の型名をジェネリクスで定義
type Echo<T> = (value: T) => T;
const echoString: Echo<string> = (str: string) => {
return str;
}
const echoNumber: Echo<number> = (num: number) => {
return num;
}
console.log(echoString('Hello')); // Hello
console.log(echoNumber(123)); // 123
以下のように簡潔にかけます。
const echo = <T>(value:T): T => {
return value;
}
console.log(echo('Hello'));
console.log(echo(123));
実行結果を表示します。
コールバック関数
type MyCallback<T> = (value: T[]) => T[];
const caller = <T>(callback: MyCallback<T>, args: T[]): T[] => {
return callback(args);
}
const result = caller((nums: number[]) => {
return nums.map(num => num * 2);
}, [1, 2, 3]);
console.log(result); // 2 4 6
関数の部分型関係
親と代替できることが重要です。
より少ない入力(引数)からより多い出力(戻り値)を返します。
出典:プロを目指す人のためのTypeScript入門 p165
引数の型アノテーションを省略可能な場合
型 から関数式の内部(ここでは引数型)を推論(逆方向の型推論と呼ぶ)できる場合は型アノテーションを省略できます。
逆方向の型推論を contextual typing と呼びます。
type F = (value: string) => string;
// 引数の型アノテーションを省略する場合
const getName: F = (name) => name;
// 引数の型アノテーションを省略しない場合
const getName: F = (name: string): string => {
return name;
};
クラス
クラス宣言の重要な特徴のひとつは、クラスオブジェクトという値を作るものであると同時に、インスタンスの型を宣言するものであるということです。
出典:プロを目指す人のためのTypeScript入門 p201
変数名と型名の名前空間
名前には2種類あることを理解する必要があります。すなわち、変数名と型名です。これらは別々の名前空間に属しています。
出典:プロを目指す人のためのTypeScript入門 p203
instanceof 演算子
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const user1 = new User('user1', 18);
// user1 は User のインスタンス
console.log(user1 instanceof User) // true
// class User { } で User 型が定義されていることに注意
const user2: User = {
name: 'user2',
age: 19
}
// user2 は User 型として扱えるが User のインスタンスではない
// {} は new Object の糖衣構文なので Object もインスタンスになる
console.log(user2 instanceof User) // false
console.log(user2 instanceof Object); // true
// typeof 演算子は object
console.log(typeof user1, typeof user2); // "object", "object"
クラス宣言とプロトタイプ
クラスについて MDN に以下のように記載されています。
JS のクラスはプロトタイプに基づいて構築されていますが、一部の構文や意味はクラスに固有です。
出典:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes
「一部の構文や意味はクラスに固有です」との記載があるのでクラスが ES 5 のプロトタイプベースの糖衣構文と言い切れるかはわかりません。
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
/*
* TypeScript は JavaScipt のスーパセットなので prototype 継承も有効
*/
// new User() でインスタン( user1 )を作成
const user1 = new User('John', 25);
// インスタンス( user1 )はコンストラクタ( User )の prototype へのリンクを持つ
console.log(User.prototype === Object.getPrototypeOf(user1)); // true
console.log(user1.constructor === User); // true
const user2: User = {
name: 'John',
age: 25
}
// user2 は( User 型のオブジェクトだが)User のインスタンスではない
// ↑より User.prototype へのリンクは持たない
console.log(User.prototype === Object.getPrototypeOf(user2)); // false
console.log(user2.constructor === User); // false
// user2 は Object のインスタンス(オブジェクトリテラル {} は new Object の糖衣構文)
// ↑より user2 は Object.prototype へのリンクを持つ
console.log(Object.prototype === Object.getPrototypeOf(user2)); // true
console.log(user2.constructor === Object); // true
クラスのメソッドは prototype のメソッドになる。
class Product {
#name: string;
#price: number;
constructor(name: string, price: number) {
this.#name = name;
this.#price = price;
}
getOrder(num: number): string {
return `${this.#name} is ${this.#price * num}`
}
}
const product1 = new Product('product1', 100);
const product2 = new Product('product2', 200);
console.log(typeof product1.getOrder, typeof product2.getOrder); // function, function
// どちらも同じメソッド( Product.prototype.getOrder )を参照
console.log(product1.getOrder === product2.getOrder); // true
console.log(product1.getOrder === Product.prototype.getOrder); // true
console.log(product2.getOrder === Product.prototype.getOrder); // true
継承
また、コンストラクタもオーバーライドすることができます。ただし、その場合は子クラスのコンストラクタの引数の中にsuper呼び出しを含める必要があります。
プロを目指す人のためのTypeScript入門 p212
メソッドと prototype
クラスのメソッドは prototype に定義されます。
プロパティはインスタンスのプロパティになります。
class Product {
#name: string;
#price: number;
constructor(name: string, price: number) {
this.#name = name;
this.#price = price;
}
getOrder(num: number): string {
return `${this.#name} is ${this.#price * num}`
}
}
const product1 = new Product('product1', 1000);
const product2 = new Product('product2', 2000);
console.log(typeof product1.getOrder, typeof product2.getOrder); // function, function
console.log(product1.getOrder === product2.getOrder); // true
console.log(product1.getOrder === Product.prototype.getOrder); // true
console.log(product2.getOrder === Product.prototype.getOrder); // true
// メソッドは prototype ベースなので Product.prototype.getOrder を Function.prototype.apply() で呼び出すこともできる
console.log(Product.prototype.getOrder.apply(product1, [2])); // 2000
console.log(typeof product1.getOrder); // function
// なので Function.prototype.apply() を使用できる
console.log(product1.getOrder.apply(product2, [2])); // this に product2 を指定しているので 4000 になる
TypeScript クラスを ES 5 にトランスパイル
// typescript
class Foo {
public x: number;
public echo: (x:number) => number;
constructor(x:number) {
this.x = x;
// インスタンスメソッドを定義
this.echo = x => x
};
// プロトタイプベースのメソッド定義
double(): number {
return this.x**2
}
}
const foo1 = new Foo(3);
foo1.double();
tsconfig の target を es5 にしてトランスパイルされたコード。
"use strict";
// JavaScript
var Foo = /** @class */ (function () {
function Foo(x) {
this.x = x;
// インスタンスメソッドを定義
this.echo = function (x) { return x; };
}
;
// プロトタイプベースのメソッド定義
Foo.prototype.double = function () {
return Math.pow(this.x, 2);
};
return Foo;
}());
var foo1 = new Foo(3);
foo1.double();
例外処理
例外とは、ランタイムエラーのことです。
プロを目指す人のためのTypeScript入門 p231
ランタイムエラー(例外)を発生させる
エラーを発生させたいとき、普通はまずエラーを表すオブジェクトを用意します。エラーを表すオブジェクトとは、Errorのインスタンスです。
出典:プロを目指す人のためのTypeScript入門 p231
finallyブロックの内容はエラーが発生してもしなくても実行されます。
出典:プロを目指す人のためのTypeScript入門 p238
(存在するオブジェクトの)存在しないプロパティにアクセス
- 型に存在しない:
コンパイル時の型チェックでエラーを発生させる - 実体(ランタイム)に存在しない:
undefinedを返す
※ 存在しないオブジェクトのプロパティにアクセスした場合は JavaScript はランタイムエラーを発生させます。
TypeScript は オプショナルチェイン を使ってオブジェクトが存在しない場合の処理をスキップできます。
高度な型
型推論
-
通常の型推論は「式からその式自体の型が推論される」という挙動を指します。
-
式の型が先にわかっている場合に、それをもとに式の内部に対して推論が働くことをさします。
出典:プロを目指す人のためのTypeScript入門 p157
オプショナルチェイン
JavaScript は null や undefined なオブジェクトに対するプロパティアクセスはランタイムエラーになります。
例: obj?.prop // または obj?.[‘age’]
一方で obj?.prop の場合、obj が null や undefined の場合でもランタイムエラーは発生せず、結果はundefinedとなります。 出典:プロを目指す人のためのTypeScript入門 p260
?.から続く、まとめて飛ばされるひとまとまりの部分をオプショナルチェイン(optional chain)と呼びます。 出典:プロを目指す人のためのTypeScript入門 p263
リテラル型
リテラル型はプリミティブ型をさらに細分化した型です。例えば 、 ”foo” という型(文字列リテラル型ではく型です!)が存在し、これは一種のリテラル型(その中でも文字列のリテラル型)です。 出典:プロを目指す人のためのTypeScript入門 p263
// これは"foo"という文字列のみが属するリテラル型
type FooString = "foo";
// これは OK
const foo: FooString = "foo";
// あまり意味はないが以下のようにも書ける
const foo: "foo" = "foo";
// エラー: Type "bar" is not assignable to type '"foo2"'.
const bar: FooString = "bar";
リテラル型の種類
- 文字列のリテラル型
- 数値のリテラル型
- 真偽値のリテラル型
- BigInt のリテラル型
const foo: "foo" = "foo";
const one: 1 = 1;
const t: true = true;
const three: 3n = 3n;
また、リテラル型は、我々が明示的に書かなくても型推論によって登場します。実は、(値としての)”foo” や 26 といったリテラルをプログラム中に書くと、これらの式の型としてリテラル型が推論されます。よって、リテラルを変数に代入することによって、その変数はリテラル型を得ることになります。
// 変数uhyoNameは”uhyo”型 const uhyoName = “uhyo”; // 半数ageは26型 const age = 26; 出典:プロを目指す人のためのTypeScript入門 p264
テンプレートリテラル型
type StringHelloLiteral = `Hello, ${string}`;
type NumberHelloLiteral = `Hello, ${number}`;
const stringValue = 'Taro';
const numberValue = 26;
const stringHello: StringHelloLiteral = `Hello, ${stringValue}`;
const numberHello: NumberHelloLiteral = `Hello, ${numberValue}`;
console.log(stringHello, numberHello);
型の絞り込み
- 等価演算子
- typeof 演算子
- switch 文
- 型述語(ユーザー定義型ガード)
型の絞り込みは、コントロールフロー解析(control flow analysis)と呼ばれることもあります。 出典:プロを目指す人のためのTypeScript入門 p271
代数的データ型を(リテラル型を使って)ユニオン型で表す
強力な型システムを持つプログラム言語はよく代数型データ型(algebraic data type ADT)の機能を持っています。 これはいくつかの種類に分類されるデータを表すための型・データ構造で、タグ付きユニオン(tagged uniton) や直和といった別名もあります。TypeScriptには代数的データ型の機能がありませんが、オブジェクト型とユニオン型を用いて擬似的に代数データ型を再現することができます。 出典:プロを目指す人のためのTypeScript入門 p274
扱うデータの形と可能性を型で正確に表現する
type Animal = {
tag: "animal"; // 文字列 "animal" のリテラル型
species: string;
}
type Human = {
tag: "human";
name: string;
}
type User = Animal | Human
function getUserName(user: User): string {
switch(user.tag) {
case "human":
return user.name
case
return "名無し";
}
}
lookup 型
type Human = {
type: "human";
name: string;
age: number;
};
function setAge(human: Human, age: Human["age"]) {
return {
...human,
age
}
}
const uhyo: Human = {
type: "human",
name: "uhyo",
age: 26
};
const uhyo2 = setAge(uhyo, 27);
console.log(uhyo2);
keyof 型
keyof 型は、オブジェクト型からそのオブジェクトのプロパティ名の型を得る機能です。具体的には、keyof型は型Tに対してkeyof T と書きます。
プロパティが複数ある場合は、このようにそれらすべてのユニオン型となります。 出典:プロを目指す人のためのTypeScript入門 p280
重要:「プロパティが複数ある場合は、このようにそれらすべてのユニオン型となります」
keyof 型、lookup 型とジェネリクス
function get<T, K extends keyof T>(obje: T, key: K): T[K] {
return obje[key];
}
type Human = {
name: string;
age: number;
}
const uhyo: Human = {
name: "uhyo",
age: 26
};
// uhyoName は string 型
const uhyoName = get(uhyo, "name");
// uhyoAge は number 型
const uhyoAge = get(uhyo, "age");
- 第2引数
keyof Tは keyof 型で具体的にはユニオン型"name" | "age" - 第2引数
K extends keyof Tはユニオン型"name" | "age"の部分型 extends keyof Tを省略すると戻り型T[K]および関数内のobje[key]が Tのプロパティ( obje のプロパティ)であることが保証できない
as による型アサーション
型アサーションは式 as 型という構文で、その式の型を強制的に変えるという意味です。 …. 型アサーションでは、実際の「値」に対しては何も起こらずに、TypeScriptコンパイラが認識する「型」だけが変化します。 出典:プロを目指す人のためのTypeScript入門 p286
上記より型アサーションに誤りがあるとランタイムでエラーが発生します。
なので「型アサーションの使用はできるだけ避けるべき」(出典:プロを目指す人のためのTypeScript入門 p286 )です。
型述語(ユーザー定義型ガード)
ユーザー定義型ガードとは戻り値に型述語が書かれた関数です。
ユーザー定義型ガード(user-defined type guards)とは、型の絞り込みを自由に行うためのしくみです。 出典:プロを目指す人のためのTypeScript入門 p299
ユーザー定義型ガードは、返り値の型として型述語(type predicates)が書かれた特殊な関数です。型述語には2種類の形があります。 出典:プロを目指す人のためのTypeScript入門 p299
// 出典:プロを目指す人のためのTypeScript入門 p299
function isStringOrNumber(value: unknown): value is string | number {
return typeof value === "string" || typeof value === "number";
}
const something: unknown = 123;
if (isStringOrNumber(something)) {
// ここではsomethingは string | number 型
console.log(something.toString());
}
モジュールシステム
Node.js でモジュールシステム ES Modules を使用するための設定を記載します。
Node.js:package.jsonの type フィールドにmoduleを指定する- CommonJS 形式にする場合は
commonjs
- CommonJS 形式にする場合は
TypeScriptmodule コンパイラオプション7でモジュールシステムを指定する- ES Modules:es2015, es2020, es2022, esnext など
- CommonJs: commonjs
TypeScript:moduleResolution コンパイラオプションでnodeやnodenextを指定すると import にパス( ./relative/path.js )ではなくモジュール名(例 express, jquery )が設定されると外部モジュールとみなす
※ target コンパイルオプションはどのバージョンの JavaScript にトランスパイルするかを指定します。設定値は es5, es6/es2015, es2024, esnext などです。esnext は TypeScript がサポートしている最新のターゲットバージョンを参照します。 ref. https://www.typescriptlang.org/tsconfig/#target
Appendix
- 「JavaScriptではオブジェクトの存在しないプロパティにアクセスした結果はundefinedとなります」(プロを目指す人のためのTypeScript入門 p91)
- 「日次データをプログラムで扱う際は(ISO 8801 形式というフォーマットで扱うのが一般的ですが、Dateオブジェクトあこの形式にも対応しています。」(プロを目指す人のためのTypeScript入門 p123)
1900-01-01T00:00:00+09:00というフォーマット
- プリミティブに対してプロパティアクセスを行うたびに一時的にオブジェクトが作られます(プロを目指す人のためのTypeScript入門 p129)
function foo () {
//...
return
a + b
}
// return の後にセミコロンが追加さえるのでコンパイルエラーが発生します。
アンビエント宣言 (または環境宣言; ambient declaration)
ambient: 環境
JavaScriptコードを生成せず、型推論器にだけ情報を渡すのに使われるのが アンビエント宣言 (または環境宣言; ambient declaration) です。 declare のついた宣言がアンビエント宣言になります。 (declare なのに呼び名が「アンビエント」なのは不思議ですが、declareは「宣言する」という意味なので仕方なさそうです)
https://zenn.dev/qnighy/articles/9c4ce0f1b68350
Ref.
- https://www.typescriptlang.org/
- https://typescript-jp.gitbook.io/deep-dive/
-
例) es5, es2015, es2020 など。 ↩
-
関数もオブジェクトですが、TypeScript では関数型は
(引数) => 戻り値の形で表現されます。オブジェクト型とは性質が異なるため分けています。 ↩ -
JavaScript で存在しない
プロパティにアクセスした場合はundefinedを返します。一方存在しないオブジェクトにアクセスした場合はランタイムエラーになります。 ↩ -
制御構文、関数宣言(
function Foo() {})の巻き上げ、type 文のコンパイル時チェックなどの例外があります。また行末のセミコロンは省略可能です。 ↩ -
interface 宣言で作成できる型名はオブジェクト型のみです。多くのの場合はtype 文で代用可能です。 ↩ -
typeof null が
objectになることに注意してください(歴史的経緯)。 ↩ -
tsconfig の module プロパティでも指定可能(一般的)。 ↩