プロを目指す人のための TypeScript 入門
オブジェクトの基本とオブジェクトの型
一部のみ
オブジェクトとは
- 近年はオブジェクトの書き換えは禁忌される傾向にある
readonly
やas const
の積極的な利用を検討
- スプレッド演算子
- ネストしたオブジェクトの参照先は変わらない
- ネストしたオブジェクトも含めて全部複製する標準的な方法は今のところない
オブジェクトの型
- 近年は interface より type を優先して使う傾向あり
インデックスシグネチャ
- 使うな。代わりに Map を使え。
- 「どんな名前のプロパティにもアクセスできる」という特性により型安全を破壊してしまうため
// ブラケットを使った定義
type PriceData = {
[key: string]: number;
};
// Recordを使った定義
type PriceData = Record<string, number>;
変数から型を作る
typeof
を使う- 変数が持つ型を取得する
- 非常に強力な機能
- ただし乱用すべきでない
- Source of truth をどこに置くか、で利用を判断する
- 定数など、値が根源であるものには最適
- Source of truth をどこに置くか、で利用を判断する
部分型関係
部分型とは
- B が A の部分型であるということは
- B は A としても使えるということ
- B が A の持っている全ての性質を持っているということ
- B が A を「包含する」の方が直感的かも?
- partial という言葉から受けるイメージとは逆なので注意
- 構造的部分型 structural subtyping
- プロパティを実際に比較して動的に決める
- TypeScript はコレ
- 名前的部分型 nominal subtyping
- 明示的に宣言されたものだけが部分型とみなされる
型引数を持つ型
- ジェネリック型、型関数とも呼ばれる
type User<T> = {
namr: string;
child: T;
};
- 「型引数を持つ型(ジェネリック型)」は:
- 型を作るためのもの
- それ自体は型ではない
<>
を用いて全ての型引数を指定することで、初めて型となる- 構造にのみ言及する
- ある種の抽象化
- 型引数に制約をかけるには
extends
を使う
type User<T extend HasName> = {}
- オプショナルな型引数を設定するには
= 型
を使う
type User<T = SomeType> = {};
配列
- Iterable
- 配列、Map、文字列などは Iterable である
for-of
文などで扱える
- 配列のインデックスアクセスは避けろ
for-of
文などを使え
分割代入
- デフォルト値の設定は undefined にのみ適用される点に注意
const a = { b: null };
const { b = 123 } = a; // bはnullのままになる
その他の組み込みオブジェクト
Map
- ただのオブジェクトよりも連想配列として優れている
- キーとしてオブジェクトを使える
- メソッド
set
get
has
delete
clear
keys
values
entries
Set
- キーだけで値のない Map
- メソッド
add
delete
has
WeakMap, WeakSet
- キーとして使えるのはオブジェクトのみ
- 列挙系のメソッド(
keys
,values
,entries
)がない- Gabage Collection を可能にするため
- Gabage Collection されるオブジェクトをキーにしたいときは使うと良い
プリミティブなのにプロパティがあるように見える件
- 実はプリミティブに対してプロパティーアクセスを行うたびに一時的にオブジェクトが作られている
const str = 'hello';
console.log(str.length); // 5
- 実は
{}
型は中身が本当にオブジェクトであるかを確認しない
type HasLength = { length: number };
const a: HasLength = 'asdf'; // ok
- 真にオブジェクトである値のみを扱いたいときは
object
型を使う
type HasLength = { length: number } & object;
const a: HasLength = 'asdf'; // error
雑学(プロパティアクセス可能かどうか)
value != null
で以下に絞り込める{[key: string]: unknown}
(同義 →Record<string, unknown>
)
- どんなプロパティ名でアクセスしても unknown 型になるの意
- JS の仕様上、null と undefined 以外の値はプロパティアクセスが可能
TypeScript の関数
関数の作り方
- 関数は、関数オブジェクトという値が変数に代入されたものである
- このことはどの方法で関数を作ったとしても共通する
- 関数宣言で作る - function declaration
function myFunc(a: number): number {}
// 返り値がない関数
function myFunc(a: number): void {}
- 関数式で作る - function expression
- hoisting は行われない
- 使う機会はほぼない
const myFunc = function (a: number): number {};
- アロー関数式で作る - arrow function expression
- hoisting は行われない
const myFunc = (a: number): number => {};
- メソッド記法で作る
- 部分型の扱いで問題点があるため、原則使うな。通常の記法を使え。
const obj = {
// メソッド記法
myFunc(a: number): number {},
// 通常の記法
myFunc: (a: number): number => {},
};
可変長引数とスプレッド構文
- この二つは組み合わせで使われることが多い
const sum = (...args) => {
return otherFunc(...args);
};
高階関数
- higher-order function
- コールバック関数を受け取る関数のこと
map
やfilter
などは全て高階関数
関数の型
- 関数型という
- 引数部分はアロー関数と同じ記法が使える
- 引数名の情報はエディタ支援を充実させるために書くもの
type MyFunc = (num: number) => string;
- 返り値の型は推論される。
- ただし、明示的に書くことで意図がコンパイラに正しく伝わるので、よりわかりやすいエラーメッセージを得られる。
- Source of truth をどこにおくかで判断するとよい
引数の型注釈を省略する
- 逆方向の型推論(Contextual Typing)が働く場合
- つまり、式の型が先にわかっている場合
- コールバック関数などでよく見かける
コールシグネチャ
- ほぼ使われないので雑学程度に
- 「プロパティを持つ関数」を定義 するために使う
type MyFunc = {
isUsed: boolean;
(arg: number): void;
};