Relationships Between Types
Subtypes and Supertypes
- 型A,Bがあり、AがBのsubtypeであるとき、Bが必要なところでAを安全に使える
- 型A,Bがあり、AがBのsupertypeであるとき、Aが必要なところでBを安全に使える
-
表記法の導入
A <: B
… AはBのsubtypeまたは同一の型A >: B
… AはBのsupertypeまたは同一の型
Variance
-
ジェネリクスが絡むとそう簡単な話ではなくなってくる
A[]
はB[]
のsubtypeであるか?
- 言語によりけり
-
TSは…
- 複雑な型はだいたい共変
- 関数のパラメータだけは例外的に反変
type Base = {
x: number
}
type Derived = Base & {
y: string
}
type Bs = Base[]
type Ds = Derived[]
const b: Base = { x: 1 }
const d: Derived = { x: 2, y: 'z' }
const b2: Base = d
const bs: Bs = [b]
const ds: Ds = [d]
const bs2: Bs = ds // !!! covariant
bs.push(b)
ds.push(b) // Derived[]にBaseはpushできない
bs2.push(b) // Base[] (実際に入っているのはDerived[])にBaseをpushできてしまう
type Klass = {
f: (n: number) => number
}
const foo: Klass = {
f: (n: number | null): number => {
return 1
}
}
// 利用者はKlass型だと思って使うので、実際に入っているものの引数はKlassよりも広くないといけない
TSC Flag: strictFunctionTypes
- 関数の引数はデフォルト双変
- tsconfigで
"strictFunctionTypes": true,
を設定すると反変になる
type Base = {
a: number
}
type Derived = Base & {
b: string
}
type AcceptBase = {
(b: Base): void
}
type AcceptDerived = {
(d: Derived): void
}
const acceptBase: AcceptBase = (b: Base) => { }
const acceptDerived: AcceptDerived = (d: Derived) => { }
const acceptBase2: AcceptBase = acceptDerived // Error: Type 'AcceptDerived' is not assignable to type 'AcceptBase'.
const acceptDerived2: AcceptDerived = acceptBase
Assignability
-
enum以外の場合、下記のいずれかを満たせばAはBに代入可能
- A <: B
- A がany型
-
JSとの相互運用のための例外
- any自体はunknownを除いてtop型
-
enumの場合、下記のいずれかを満たせばAはBに代入可能
- Aがenum Bのメンバである
- Bがnumber型のメンバを持ち、Aがnumberである
- 数値enumの罠
Type Widening
const a = 'x' // 'X'
const b = 3 // 3
const c = true // true
const d = { x: 3 } // {x: number;}
enum E { X, Y, Z }
const e = E.X // E.X
let a2 = 'x' // string
let b2 = 3 // number
var c2 = true // boolean
let e2 = E.X // E
- nullやundefinedで初期化されるとanyに拡大される
- ただし、戻り値は狭い
function x() {
let a = null // widened to any
a = 3 // any
a = 'b' // any
return a // string
} // returns string
The const type
let a = { x: 3 } // { x: number; }
const b = { x: 3 } // { x: number; }
let c: { x: 3 } // { x: 3; }
let d = { x: 3 } as const // { readonly x: 3;}
as const
はネストの中身もreadonlyにする
let xs = [1, 2, 3] // number[]
let ys = [1, 2, 3] as const // readonly [1, 2, 3]
let zx = [1, { x: 2 }] as const // readonly [1, { readonly x: 2; }]
Excess porperty checking
- 余計なメンバがあると怒るやつ
type Options = {
url: string
method: 'GET' | 'POST'
data?: object
}
function call(options: Options) {
}
call({
url: 'http://localhost/users',
method: 'GET',
dataa: {} // Error: Argument of type '{ url: string; method: "GET"; dataa: {}; }' is not assignable to parameter of type 'Options'.
})
Refinement
-
flow-basedの型推論
if
,?:
,||
,switch
等で狭まるやつ
Discriminated union types
type UserTextEvent = { value: string, target: HTMLInputElement }
type UserMouseEvent = { value: [number, number], target: HTMLElement }
type UserEvent = UserTextEvent | UserMouseEvent
function handle(event: UserEvent) {
if (typeof event.value === 'string') {
event // UserEvent (!)
event.value // string
event.target // HTMLInputElement | HTMLElement (!!!)
return
}
event // UserEvent (!)
event.value // [number, number]
event.target // HTMLInputElement | HTMLElement (!!!)
}
- 意図した挙動にしたいときはtagged unionにしよう
type UserTextEvent = { type: 'UserTextEvent', value: string, target: HTMLInputElement }
type UserMouseEvent = { type: 'UserMouseEvent', value: [number, number], target: HTMLElement }
type UserEvent = UserTextEvent | UserMouseEvent
function handle(event: UserEvent) {
if (event.type === 'UserTextEvent') {
event // UserTextEvent
event.value // string
event.target // HTMLInputElement
return
}
event // UserMouseEvent
event.value // [number, number]
event.target // HTMLElement
}
英語
-
crotchetiest
- crotchetyの最上級
-
crotchety
- 奇狂な、気難しい