Reading: TypeScript Official Handbook
ぼーっとメモ
Basics
-
string
-
number
-
boolean
-
string[]
-
number[]
-
any
-
変数
- あんまり型を書くことなさそう。infer してくれるから
-
関数
- パラメーターの型は書きそう
- 戻り値の型は書かなくても良さそう
-
あぁでも、array.map みたいなのに渡す anonymous function の場合はパラメーターの型は分かるから書かなくても良いのか
Object Types
function printCoord(pt: { x: number; y: number }) {
区切りは ;
でも ,
でもいいって。へー。
Optional
function printName(obj: { first: string; last?: string }) {
Type と Interface
type と interface はどっちが良いんだろうなぁ?個人的には type が好きだけど。
公式だと interface 推し。たしか前に読んだ本は type 推しだったっけな。
Type Assertions
へー
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
使うなら前者かな
Literal Types
面白いよねこれ
Literal Interface
method
を "GET" 固定にしたい場合
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");
// Change 3
const req = { url: "https://example.com", method: "GET" } as const;
Others
- Enum はあんまり使わない方が良いって聞いたことある
bigint
symbol
type guards
- "string"
- "number"
- "bigint"
- "boolean"
- "symbol"
- "undefined"
- "object"
- "function"
typeof null
は "object"
なので注意
type predicates
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
pet as Fish
で pet
を Fish
型とみなして、swim
が定義されてる場合は、Fish
そうじゃない場合は Bird
となる。
pet is Fish
の部分で、TypeScript に対して「戻り値が true
だったら pet
は Fish
だよ」って伝えてるっぽいな。false
なら Bird
ってのも型から判別してしまう。かしこい。
Exhaustiveness checking
こないはず、みたいなチェックが型レベルでできるのかー。面白いな。
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
この Shape
に Triangle
みたいなのを足すと、コンパイルエラーになる。
TypeScript 空気読んでくれる子だった
// Shorthand call signature
type Log = (a: string) => void
// Full call signature
type Log = {
(a: string): void
}
パラメーター名は必要
(string) => void
こうしてしまうと string
というパラメーター名で型が any
という意味になっちゃう
Construct Signatures
type SomeConstructor = {
new (s: string): SomeObject;
};
Date みたいに、new つけるやつとつけないやつがある場合はこんな風に書ける
interface CallOrConstruct {
new (s: string): Date;
(n?: number): number;
}
Generic Functions
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
Constraints
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
Overload Signatures
JS にはオーバーロードはないけど、引数に色んなパターンで渡せるから TS ではこういう書き方ができる
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
好きじゃない。別の関数にしてくれたらいいのに。ライブラリを使うときに読むことはあっても、自分で書くことはなさそうかな。
this
もそう。
Other Types to Know About
object
-
object
とObject
は違う。Object
は使わない。object
を使う。
unknown
-
unknown
はany
に似てるけど、型を明確にしてからじゃないと使えないので良い - 戻り値の型が決められない場合も使える
function safeParse(s: string): unknown {
return JSON.parse(s);
}
でも、戻り値は気をつけて使う
never
- 例外や exit で、関数から決して値が返されない場合に指定できる
- あとは、Exhaustive な場合にも使われる
function fn(x: string | number) {
if (typeof x === "string") {
// do something
} else if (typeof x === "number") {
// do something else
} else {
x; // has type 'never'!
}
}
Function
- 使わない
なんでもいいけど自分でコールしない場合は () => void
をつけとく
Rest Parameters
配列で定義すればいい
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
Argument でタプル的に展開して使うときは as const
してあげたらいい
// Inferred as 2-length tuple
const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);
Parameter Destructuring
こうなる
function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c);
}
Return type void
戻り値の型が void
の場合は、何でも返せる
type voidFunc = () => void;
const f1: voidFunc = () => {
return true;
};
ただ、戻り値を使わないよということ
実際は戻り値があるんだけど、それを期待していない場合のために使われる
Array.prototype.push
は数値を返すけど Array.prototype.forEach
は使わないので void
を期待してる
でも、関数宣言や関数式に書いてる場合はだめ
function f2(): void {
// これはエラーになる
return true;
}
readonly Properties
readonly
のこと忘れてた
interface SomeType {
readonly prop: string;
}
あんまり使わなさそうかなぁとは思う
Index Signatures
interface StringArray {
[index: number]: string;
}
index の type には number
か string
が使える。んーけどあんまり string
は使わなさそうかなぁ。
あとはさらっと
- Interface の継承
- Intersection
- Generics
-
Array<T>
,Map<K, V>
,Set<T>
,Promise<T>
-
ReadonlyArray<string>
はreadonly string[]
と同じ - タプルは
[string, number]
みたいに定義できる
関数宣言のとこだと
function identity<Type>(arg: Type): Type {
return arg;
}
関数式のとこだと
let myIdentity: <Type>(arg: Type) => Type = identity;
とか
let myIdentity: { <Type>(arg: Type): Type } = identity;
って書ける
インターフェース
インターフェースはこうなる
interface GenericIdentityFn {
<Type>(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
こんな風にも使える
interface GenericIdentityFn<Type> {
(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
そしたら number
型が適用された関数になる
Class
クラスでも使える
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
let myGenericNumber = new GenericNumber<number>();
インスタンスサイドとスタティックサイドのうち、インスタンスサイドにしか効かないので、スタティックなメンバーには型パラメーターは使えない
Generic Constraints
制約はこんな感じ
interface Lengthwise {
length: number;
}
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length);
return arg;
}
インターフェース使わずにこうでもいける
function loggingIdentity<Type extends {length: number}>(arg: Type): Type {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
Using Type Parameters in Generic Constraints
へー。自分で使うことはなさそうかなぁ
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
getProperty(x, "m"); // Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.ts(2345
Using Class Types in Generics
これも、とりあえず最初のうちは使うこともなさそう。インスタンス化したいなぁって思うときがあれば見直すくらいで
function create<Type>(c: { new (): Type }): Type {
return new c();
}
keyof ってのがあるよって覚えとくくらいでいっか
JS の typeof の拡張
↓面白い。f
は型じゃなくて値だから、それから型を取り出すのに typeof
を使ってる
使わないだろうと思うけど
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];
これで Age
は number
型になる
type I1 = Person["age" | "name"];
こうすると number | string
型になる
これも使わなさそう
type Example1 = Dog extends Animal ? number : string;
Type のキーを使って違う Type を作るっぽい。
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
使わなさそうかなぁ
面白いなー
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
あんまり使うことなさそうだけど、使うときはドキュメント見ながら、確認しながらだろうな。
Fields
フィールドは js だとコンストラクターで this にひっつけたらいいけど、ts だと class の中で宣言しないといけないのかーほほー
フィールドはコンストラクター自体の中で初期化されてないといけない。コンストラクターから呼び出されているメソッドまでは ts は見に行かない
コンストラクターで初期化しない場合は !
をつけとけば未初期化エラーがでない
class OKGreeter {
// Not initialized, but no error
name!: string;
}
readonly
修飾子をつけると読み取り専用になる
class Greeter {
readonly name: string = "world";
フィールドにアクセスするときには this
が必須
Interface
実装したメソッドでもパラメーターの型は指定してあげないといけない
また、オプショナルなプロパティは実装先では生成されない
Extends
継承のところは普通のことが書いてあるなぁ
Visibility
デフォルトは public
だから書かなくていい。理解のしやすさなどのために書いても良い。
public
protected
private
がある。ちょっと動作が Java とは違いそうだけど、そこまで使わなさそうなので使うときにまたチェックしよう。
TypeScript の private
修飾子と、JavaScript のプライベートフィールド(#
)を頭の中で区別して把握しとこ。#
は ES2022 で入るのかな?たぶん
Static Members
これもほぼ Java と同じかな
ただ、Static なメンバーはコンストラクター関数にひっつくので Function
にビルトインでついてるメンバーは使わない/使えない(name
, length
, call
など)
static block もある
this
JS の this
はコンテキストによって変わるからめんどくさそう
基本的には「this
が変わるような使い方をしない」で良さそうだけど、もしどうしても気にしないといけないなら、Arrow Function 使っとくといいのかなー。super
が使えないみたいだけど
Parameter Properties
へー。コンストラクターのパラメーターに修飾子をつけると、フィールドにしてくれる TS の機能
class Params {
constructor(
public readonly x: number,
protected y: number,
private z: number
) {
// No body necessary
}
}
const a = new Params(1, 2, 3);
これで、フィールド x
y
z
がそれぞれの修飾子付きのフィールドになる
素直にフィールドを定義してあげたほうが読みやすそうだと思うけどなー
Class Expressions
クラス式もある
Abstract
Abscract 修飾子もある
クラスはこんなとこか
ハンドブックはこれで最後だな
TypeScript Specific ES Module Syntax
JS のモジュールに対して TS が追加してる機能
export type
type
や interface
も export
できる
import type
import type
で type
や interface
などの型を import
できる
import type
で import
したものを値として使おうとするとコンパイルエラーになる:
インラインでも書ける
import { createCatName, type Cat, type Dog } from "./animal.js";