プログラミングTypeScript 6章〜最後まで読んで
この記事は、
の続きの記事になります。
流石に、本も後半になると難易度がぐっとが上がり、へぇの連続でした。
その中でもへぇレベルが高かったものをピックアップしていきます。
6章 高度な型
型のルックアップ
type APIResponse = {
fishId: string
similarFishList: {
count: number
fishes: {
name: string
}[]
}
}
type FishList = APIResponse['similarFishList']
このようにオブジェクトみたいに、型を取得(ルックアップ)することができます。
keyof演算子を利用して、型安全なゲッター関数を実装する
これは、動きは分かるけど、モチベーションがいまいちわからなかった話。
type ResponseKeys = keyof APIResponse // 'fishId' | 'similarFishList'
type FishId = keyof APIResponse['similarFishList'] // 'count' | 'fishes'
// 型安全なゲッター関数を実装
function get<
O extends object,
K extends keyof O
>(
o: O,
k: K
): O[K] {
return o[k]
}
let fishResponse: APIResponse = {
fishId: 'asdfghjkl',
similarFishList: {
count: 2,
fishes: [
{ name: 'tuna' },
{ name: 'shark' },
]
}
}
let fishId = get(fishResponse, 'fishId') // let fishId: string
let fishId2 = fishResponse.fishId // let fishId2: string
fishId2
のようにドット演算子でもstring型であることは分かるが、get関数を型安全でとてもメリットがあるらしいが、よくわからなかった orz
レコード型
type Weekday = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri'
type Day = Weekday | 'Sat' | 'Sun'
let nextDay: Record<Weekday, Day> = {
Mon: 'Tue'
}
// Type '{ Mon: "Tue"; }' is missing the following properties from type 'Record<Weekday, Day>': Tue, Wed, Thu, Frits(2739)
レコードは完全性を担保しようと、エラー出してくれます。
マップ型
let nextDay: {[K in Weekday]: Day} = {
Mon: 'Tue'
}
// Type '{ Mon: "Tue"; }' is missing the following properties from type '{ Mon: Day; Tue: Day; Wed: Day; Thu: Day; Fri: Day; }': Tue, Wed, Thu, Frits(2739)
インデックスシグネチャの型安全性をより高めた感じ
マップ型を使って、更なる制約を加える
type Role = {
id: number
policies: string[]
}
// 全てのフィールドを省略可能に
type OptionalRole = {
[K in keyof Role]?: Role[K]
}
// 全てのフィールドを読み取り専用に
type ReadonlyRole = {
readonly [K in keyof Role]: Role[K]
}
こんな形で型の拡張ができます。
コンパニオンオブジェクトパターン
Fish.tsにFishの型定義を書き
type WeightUnit = 'kg' | 'g'
export type Fish = {
weightUnit: WeightUnit
weightValue: number
}
export let Fish = {
from(weightValue: number, weightUnit: WeightUnit): Fish {
return {
weightUnit,
weightValue
}
}
}
関数を利用する側で、import
import { Fish } from './Fish'
// こっちのFishは型
let fish: Fish = {
weightUnit: 'kg',
weightValue: 100,
}
// こっちのFishは値
let otherFish = Fish.from(200, 'g')
TypeScriptでは、型と値で名前空間が異なるので、こんなことができる。
メリットは、型と値の情報をまとめることができる。
利用する側も一度にインポートできメリットあるのは分かるものの、強いメリットを今んところ感じないが、いつか感じるだろう。
コンパニオンオブジェクトパターン!
ユーザー定義型ガード
function isString(a: unknown): boolean {
return typeof a === 'string'
}
function parseInput(input: string | number) {
let formatedInput: string
if (isString(input)) {
formatedInput = input.toUpperCase()
}
}
// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.ts(2339)
isString()
がtrue
の時点で、input
はstring
型確定なので、そのまま通るかと思いきや、TypeScriptでは異なるスコープの場合エラーとなる。
そんな時に使えるのが、ユーザー定義型ガード
function isString(a: unknown): a is string {
return typeof a === 'string'
}
こうすることで型チェッカーに型を伝えてあげることができ、エラーもなくなる。
条件型で型レベルの三項演算子
type IsString<T> = T extends string ? true : false
type A = IsString<string> // true
type B = IsString<number> // false
T
がstring
型のサブタイプの場合、リテラル型としてtrue
or false
を宣言という、なんとも驚き。
これも、いつ使うんだろうと思いつつ、面白い。
7章 エラー処理
例外を返す
type Fish = {
isAged: boolean
}
class AlreadyAgedFishError extends Error {}
const age = (fish: Fish): Fish | AlreadyAgedFishError => {
if (fish.isAged) {
return new AlreadyAgedFishError('The fish already aged.')
}
fish.isAged = true
return fish
}
let agedFish = age({isAged: false}) // Fish | AlreadyAgedFishError
if (agedFish instanceof AlreadyAgedFishError) {
console.error(agedFish.message)
} else {
console.info('The fish aged successfully')
}
こうすることで、想定されるエラーを関数シグネチャに含めれる。
8章 非同期プログラミングと並行、並列処理
PromiseやAsync、Awaitまでは分かるが、非同期ストリーム以降はよく理解できなかった。
9章 フロントエンドとバックエンドのフレームワーク
ReactとAngularが紹介されていたが、Reactはよく触っているので目新しいものはなし。
Angularは見慣れていないせいか、やっぱりReactが良いな〜という感想のみ。
10章 名前空間とモジュール, 11章 JavaScriptとの相互運用, 12章 TypeScriptのビルドと実行
さらさら〜っと読んだので、ちゃんとは理解できていないと思われる。
終わりに
ライブラリを使ってアプリを作っているだけだと、ここまで深く型の理解をすることがなかったので、非常に勉強になった。
特に、これまで苦手意識があったジェネリック型の理解が深まったのは大きい。
職場で「なんか型エラー出てるけど、よく分からない」と言ってる方にとても強くオススメできる本であった。
あとは、練習のために、プロコンをTypeScriptで取り組んでみようかと思った。
お腹すいたので、まいばすけっとに晩御飯の具材を書いに行ってきます。
Discussion