TypeScript, Node.js, React, React Native
アプリ、ブラウザ、サーバぜんぶTS
言語の中身読んだり AST 解析で遊ぶのが好き
HTTPメソッド
// ちゃんと渡せばOK
const httpMethod: string = 'GET'
// 正しくない値を渡してもエラーになってくれない
const httpMethod: string = 'hogehoge'
type HTTPMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE' // ...
// ちゃんと渡せばOK
const httpMethod: HTTPMethod = 'GET'
// 正しくない値を渡すと型エラー
const httpMethod: HTTPMethod = 'x'
オブジェクトのゲッター
const obj = {
a: 'x',
b: 'y',
c: 'z',
}
function get(
obj: { [key: string]: string },
key: string
) {
return obj[key]
}
get(obj, 'a') // OK
get(obj, 'x') // NG(no error!!)
const obj = {
a: 'x',
b: 'y',
c: 'z',
}
function get<T extends {}, K extends keyof T>(
obj: T,
key: K
) {
return obj[key]
}
get(obj, 'a') // OK
get(obj, 'x') // NG(error)
これらの公式ドキュメントを読むとイメージが湧くかも
入門してた頃の私的バイブル
疲弊を招く、費用対が悪い
const user = {
name: 'Leko',
age: 27
}
type UserKeys = 'name' | 'age'
型定義と事実が乖離しないようにすると楽
const user = {
name: 'Leko',
age: 27
}
type UserKeys = keyof typeof user // 'name | 'age'
typeof
が適切ではないケースはたくさんある
const initialState = {
fetching: false,
user: null
}
type State = typeof initialState
// 期待通りに推論されない
export function userReducer(
state: State,
action: ...
) {
switch (action.type) {
// ...
}
}
// 意図はこう
type User = { ... }
type State = {
fetching: boolean,
user: User | null
}
ex. HTTPリクエストのハンドラ
事実と乖離してる。型エラーは起きないが実行時エラー
試す
type UpperEchoPayload = {
name: string
}
const app = express()
// ここ
app.get('/upper-echo', (req, res) => {
const { name } = req.body as UpperEchoPayload
res.send(`Hello, ${name.toUpperCase()}`)
})
reduxのaction creatorとか。as const
がないと推論結果がstring
になっちゃう
const addUser = (user: User) => ({
type: 'user/add' as const,
user,
})
type AddUserAction = ReturnType<typeof addUser>
//
// いちいちこう書かなくていい
type AddUserAction = {
type: 'user/add',
user: User,
}
型だけを提供するTSのためのnpmパッケージ登場
TSのbuild-in typeもかなり充実しており、これだけでも足りてることは多い
あくまでユーティリティであり要件を叶える型とは限らない
— sindresorhus/type-fest: A collection of essential TypeScript types
— Announcing TypeScript 3.5 | TypeScript
全部は時間が足りず話しきれない
実行時には安全なのだが、型検査上は安全ではない
type VerifiedUser = {
emailVerified: true,
profile: UserProfile,
}
type UnverifiedUser = {
emailVerified: false
}
type User = VerifiedUser | UnverifiedUser
const users: User[] = [...]
// メール確認済みユーザだけなんか処理したい
users
.filter(user => user.emailVerified)
.map(user => user.profile)
// Property 'profile' does not exist on type 'User'.
// Property 'profile' does not exist on type 'UnverifiedUser'.
function isEmailVerifieid (obj: unknown): obj is VerifiedUser {
return obj.emailVerified
}
const users: User[] = [...]
// やってることは同じ、type guard付けた関数に分けただけ
users
.filter(isEmailVerifieid)
.map(user => user.profile) // エラーにならない!
const map = new Map<string, number>([...])
// hasでチェックしてるのにgetの結果が | undefinedになっちゃう
if (map.has('a')) {
const item = map.get('a')
item.toFixed() // ERROR
// Object is possibly 'undefined'.
}
has(...): this is ...
const map = new Map<string, number>([...])
const item = map.get('x')
if (typeof item !== 'undefined') {
item.toFixed()
}
カテゴリに対応する文言とアイコン画像
export type Category = 'food' | 'drink'
// ----------------------
import { Category } from './Category'
const categoryNames: { [K in Category]: string } = {
food: '食べ物',
drink: '飲み物',
}
// ----------------------
import { Category } from './Category'
import iconFood from './icons/category/food.png'
import iconDrink from './icons/category/drink.png'
const categoryIcons: { [K in Category]: string } = {
food: iconFood,
drink: iconDrink,
}
build-inのRecordで書いても似たような結果が得られる
export type Category = 'food' | 'drink'
// ----------------------
import { Category } from './Category'
const categoryNames: Record<Category, string> = {
food: '食べ物',
drink: '飲み物',
xxx: 'hoge', // ERROR(余計なプロパティ)
}
// ----------------------
import { Category } from './Category'
import iconFood from './icons/category/food.png'
import iconDrink from './icons/category/drink.png'
const categoryIcons: Record<Category, string> = {
food: iconFood,
// drink: iconDrink, // ERROR(プロパティが足りない)
}
たまにConditional typeたっぷりな型書くが頻出するものではない
※ユーザ人口とコミュニティにフォーカスした場合
@L_e_k_o