第一回はこちら
https://qiita.com/KuwaK/items/b5de29fe21af3724f316
構造的部分型
TypeScriptは構造的部分型という型システムを採用しています。これは、ある型Aを満たす条件(例えばstring型のnameというプロパティをもっている。など)を別の型Bも満たしている場合、型Bは型Aとマッチする、というものです。
let a = {
name: 'aaa'
}
let b = {
name: 'bbb',
age: 0
}
a = b // エラーにならない(bの型はaの型に代入できる)
b = a // エラーになる(aの型はbの型に代入できない)
ここで、bの型がaの型に必要な条件(name: string
を持っている)を満たしているのがわかるでしょうか?この場合、変数bを変数aに代入することができます。bがname以外にどんなプロパティを持っていても関係ありません。
逆にbをaに代入することはできません。bの型に必要なage
プロパティを持っていないからです。
逆に以下のような場合は代入できません。aのnameプロパティとcのnameプロパティの型に互換性がないからです。
let a = {
name: 'aaa'
}
let c = {
name: 123,
age: 1
}
a = c // エラーになる(cの型はaの型に代入できない)
c = a // エラーになる(aの型はcの型に代入できない)
ちなみに、TypeScriptではクラスが違っていても、互換性がある型であれば代入ができます。
class A {
name: string = ''
}
class B {
name: string = ''
age:number = 0
}
let a = new A()
let b = new B()
a = b // エラーにならない(bの型はaの型に代入できる)
b = a // エラーになる(aの型はbの型に代入できない)
関数型における構造的部分型
type F = (a: string,b: number) => number
function execFunc(func:F){
return func('a',0)
}
execFunc((a: string,b:number) => 0) // エラーにならない
execFunc((a: string,b:number) => {}) // 返り値の型に互換性がないのでエラーになる
execFunc(() => 0) // 返り値の型があっていれば、引数が足りてなくてもエラーにならない
execFunc((a:string) => 0) // 同上
execFunc((a:number) => 0) // 引数の型似互換性がない場合はエラーになる
execFunc((a:string | number) => 0) // string型が許可されているのでエラーにならない
execFunc((a:string,b:number,c:boolean) => 0) // 必須引数が多い場合もエラーになる
execFunc((a:string,b:number,c = true) => 0) // デフォルト値が設定された引数が増えた場合はエラーにならない
execFunc((a:string,b:number,c?: boolean) => 0) // 必須ではない引数が増えた場合もエラーにならない
関数型の場合、どのように型の互換性が判断されるかを一言で説明するのは難しいですが、今回の場合であれば。func('a',0)
を実行したときに問題なく動くかどうかを基準に考えれば大体判断できるんじゃないかなと思います。
リテラル型とUnion type(共用体型)
TypeScriptでは、特定の文字列や数字、boolの値を型として利用できる。これをリテラル型という。
リテラル型はUnion typeと一緒に用いられる事が多い。Union Typeは、複数の型を|
で並べることで、並べた型のどれかにマッチする型を表すことができる
const position: 'static' | 'absolute' | 'relative' | 'fixed' = 'relative' // 文字列リテラル
const binary : 0 | 1 = 0 // 数字リテラル
const bool: true | false = true // 真偽値リテラル
交差型(Intersection Types)
交差型は、&
で複数の型を並べることで、並べた全ての型にマッチする新しい型を返します
例えば以下のような感じです
type Human = {
name: string
}
type Person = {
age: number
}
type User = Human & Person // => HumanにもPersonにもマッチする型。以下と同じ
// {
// name: string
// age: number
// }
関数型の交差型
交差型はobject型以外にも使えます。例えば関数型通しの交差型をとった場合、ジェネリクスの代わりとして使うことができる場合があります。
type F = ((arg: string) => string) & ((arg: number) => number)
const getArg: F = (arg: any) => {
return arg
} // 引数の型をanyで定義しないとうまく動かないことに注意
const a = getArg(0) // 引数がnumberのときは返り値もnumberになる
const b = getArg('fawfjei') // 引数がstringのときは返り値もstringになる
const c = getArg(true) // 引数がstringでもnumberでも無いときはエラーになる
関数型の交差型の例
上記のような関数型の交差型の例として、vueのcomposion apiの関数の一つ computed
関数が挙げられます。
computed
関数は以下のように、関数が引数に指定されたときはComputedRef<T>
を返し、getterとsetterをもつオブジェクトが指定されたらWritableComputedRef<T>
型を返します。このように引数と返り値のペアが全く異なるような関数の型をつける場合はConditional Typesを使うことが多いですが、交差型を使うとよりかんたんに表すことができます。
const state = reactive({ count: 0 })
const double = computed(() => state.count * 2) // ComputedRef<number>を返す
const double2 = computed({
get(){
return state.count * 2
},
set(value){
state.count = value / 2
}
}) // WritableComputedRef<number> を返す
具体的な実装例は以下になります
// この型はvue composion apiに組み込みの型
type ComputedRef<T> = {
readonly value: T
}
// この型はvue composion apiに組み込みの型
type WritableComputedRef<T> = {
value: T
}
type WComputedArg<T> = {
get: () => T
set: (value: T) => void
}
type Computed = (<T>(getters: () => T) => ComputedRef<T>) & (<T>(options: WComputedArg<T>) => WritableComputedRef<T>)
const computed: Computed = (arg: any) => {
// 中身の実装は適当
return arg
}
const state = reactive({ count: 0 })
const a = computed(() => state.count) // ⇛ ComputedRef<number>
const b = computed({
get(){
return state.count
},
set(value:number){
state.count = value
},
}) // ⇛ WritableComputedRef<number>