「プロを目指す人のためのTypeScript入門」の読書メモです。
TypeScript は JavaScript
に 静的型付け
を追加した言語です。
[!NOTE] コード内で Node.js を使用する場合(
require
など)は @type/node もインストールします。
トランスパイルでどのバージョンの JavaScript(ECMAScript)[^1] に変換するのかを ts.config の target プロパティで指定します。
[^1]: 例) es5, es2015, es2020 など。
[!NOTE] tsc に個別ファイルを指定した場合は ts.config を無視します(
$ npx tsc /path/to/file.ts
)。
1.
でトランスパイルした JavaScript を Node.js で実行( node )JavaScript は存在しないプロパティにアクセスした場合は undefined
を返します。
文
はセミコロンで区切られて上から順に実行します[^1]。
文
は 宣言
( declaration )とその他に分けられます。
例) 例えば(正式には) 変数宣言文とは呼ばす 変数宣言
、関数宣言文とは呼ばず 関数宣言
です。
[^1]: 制御構文、関数宣言( function Foo() {}
)の巻き上げ、type 文のコンパイル時チェックなどの例外があります。また行末のセミコロンは省略可能です。
式は一般に何らかの計算を表します。そして、その計算の結果が式の結果となるのです。
出典:プロを目指す人のための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
x == null
は x が null
か undefined
のときのみ ture になります。
null
は良くて undefined
は駄目な場合は x === null
を使います 。
x === NaN は x がどのような値でも( NaN であっても)必ず false になります。
NaN を判定したいときは Number.isNaN()
を使用します。
x ?? y
は x が null または undefined のときのみ y を返します。出典:プロを目指す人のためのTypeScript入門 p56
x || y
は x が 0 や 空文字や false の場合も y を返します。
簡潔にまとめると部分型は親の代替ができる。
型Sと型Tがオブジェクト型だとして、次の2つの条件が満たされればSがTの部分型になります。
- Tが持つプロパティはすべてSにも存在する。
- 要件1の各プロパティについて、Sにおけるそのプロパティの型はTにおけるプロパティの方の部分型(または同じ型)である。
出典;プロを目指す人のためのTypeScript入門 p97
type 文は オブジェクト型
だけでなく プリミティブな型
の別名(型名)を作成できます。
type文は決して「新たに型を作って利用可能にする」ものではなく、「すでにある型に別名をつける」だけのもの
出典:プロを目指す人のためのTypeScript入門 p89
// オブジェクト型
type MyObject: {
name: string;
age: string;
}
// string 型
type MyString = string;
type
文による型の作成はその型名の使用より後でも問題ありません(型チェックはコンパイル時に実行するため)。
型引数は、型を定義するときにパラメータをもたせることができるというもので、ジェネリクスに少し似ています。
出典:プロを目指す人のためのTypeScript入門 p100
ジェネリクス(generics)とは、型引数を受け取る関数を作る機能のことです。
出典:プロを目指す人のためのTypeScript入門 p168
type Family<Parent, Child> = {
mother: Parent;
father: Parent;
child: Child;
}
const obj: Family<number, string> = {
mother: 0,
father: 100,
child: "1000",
}
適切な数の型引数を指定せずに型を使用した場合はコンパイルエラという結果になります。 出典:プロを目指す人のためのTypeScript入門 p101
// エラー: Generic type 'Family' requires 2 type argument(s).
const obj: Family = {
mother: 0,
father: 100,
child: "1000",
}
interface 宣言
で作成できる型名はオブジェクト型のみです。
多くのの場合は type 文
で代用可能です。
TypeScriptにはイテレータ(Iterator)という概念が存在します。 ... イテレータは繰り返し処理のための汎用的なインターフェースです。イテレータを通じて繰り返し処理ができる値のことをJavaScript用語でIterableと呼びます。for-of文のofの右の式には、厳密には配列だけでなくそれ以外のIterableな値を与えることができまるのです。 出典:プロを目指す人のためのTypeScript入門 p111)
より具体的に言うと、イテレーターは、次の 2 つのプロパティを持つオブジェクトを返す next() メソッドを持つことによってイテレータープロトコルを実装するオブジェクトです。value 反復シーケンスの次の値
done シーケンスの最後の値が既に消費されている場合に true となります。done と並んで value が存在する場合、それがイテレーターの戻り値となります。
function *createGenerator() {
const limit = 10;
for (let i = 0; i < limit; i++ ) {
yield i;
}
}
const iterator = createGenerator();
for (const value of iterator) {
console.log(value);
}
// 以下と同じ挙動
/* while(true) {
const result = iterator.next();
if ( result.done ) {
break;
}
console.log(result.value)
} */
const obj1 = {
name: 'John',
age: 30,
}
const obj2 = {
hobby: 'Reading',
...obj1,
}
console.log(obj2);
/*
{
"hobby": "Reading",
"name": "John",
"age": 30
}
*/
// typeof で動的に型名を作成
type Obj2nd = typeof obj2;
typeof には typeof キーワードと typeof 演算子の 2 種類ります。混同しないように注意が必要です。
[^typeof]: typeof null が object
になることに注意してください(歴史的経緯)。
オブジェクトのプロパティの中身をプロパティと同名の変数に入れたい場合にしかこのパターンを用いることができません。
出典:プロを目指す人のためのTypeScript入門 p114
const rect = {width: 100, height: 200, depth: 300};
const {width, height, depth} = rect;
console.log(width, height, depth);
// 100, 200, 300
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'
}
}
var {content: {catchCopy}} = page;
console.log(catchCopy);
// HELLO World
console.log(content);
// ERR content is not defined
var { 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 obj1 = {
foo: 'a',
bar: 'b',
baz: 'c',
};
const obj2 = {...obj1};
console.log(obj2);
/*
{
"foo": "a",
"bar": "b",
"baz": "c"
}
*/
obj1.foo = 'x';
console.log(obj2.foo); // a
const obj1 = {
foo: 'a',
bar: 'b',
baz: 'c',
};
const {...obj2} = obj1;
console.log(obj2);
/*
{
"foo": "a",
"bar": "b",
"baz": "c"
}
*/
obj1.foo = 'x';
console.log(obj2.foo); // a
function foo( a?: string ){
// 呼び出しで引数が省略されたときの a は undefined
if (a) {
//
} else {
//
}
}
関数型宣言構文: 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('HelHello'));
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) => return name;
// 引数の型アノテーションを省略しない場合
const getName: F = (name: string): string => return name;
クラス宣言の重要な特徴のひとつは、クラスオブジェクトという値を作るものであると同時に、インスタンスの型を宣言するものであるということです。
出典:プロを目指す人のためのTypeScript入門 p201
名前には2種類あることを理解する必要があります。すなわち、変数名と型名です。これらは別々の名前空間に属しています。
出典:プロを目指す人のためのTypeScript入門 p203
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 のインスタンスではない
console.log(user2 instanceof User) // false
// 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('Jhon', 25);
// インスタンス( user1 )はコンストラクタ( User )の prototype へのリンクを持つ
console.log(User.prototype === Object.getPrototypeOf(user1)); // true
console.log(user1.constructor === User); // true
const user2: User = {
name: 'Jhon',
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.protype へのリンクを持つ
console.log(Object.prototype === Object.getPrototypeOf(user2)); // true
console.log(user2.constructor === Object); // true
クラスのメソッドは protoype のメソッドになる。
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
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 に定義されます。
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 ベースなので Production.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 に production2 を指定しているので 4000 になる
// 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();
ts.config の 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 は オプショナルチェイン
を使ってオブジェクトが存在しない場合の処理をスキップできます。
JavaScript
は null
や undefined
なオブエクトに対するプロパティアクセスはランタイムエラーになります。
例: obj?.prop
// または obj?.['age']
一方で obj?.prop の場合、obj が null や undefined の場合でもランタイムエラーは発生せず、結果はundefinedとなります。 出典:プロを目指す人のためのTypeScript入門 p260
?.から続く、まとめて飛ばされるひとまとまりの部分をオプショナルチェイン(optional chain)と呼びます。 出典:プロを目指す人のためのTypeScript入門 p263
プロを目指す人のためのTypeScript入門 p274
型アサーションは式 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();
}
1900-01-01T00:00:00+09:00
というフォーマットfunction foo () {
//...
return
a + b
}
// return の後にセミコロンが追加さえるのでコンパイルエラーが発生します。
ambient: 環境
JavaScriptコードを生成せず、型推論器にだけ情報を渡すのに使われるのが アンビエント宣言 (または環境宣言; ambient declaration) です。 declare のついた宣言がアンビエント宣言になります。 (declare なのに呼び名が「アンビエント」なのは不思議ですが、declareは「宣言する」という意味なので仕方なさそうです)