TypeScriptの型 typescript
const obj = {
s: "foo",
f: () => "bar"
}
const get1 = (key: keyof typeof obj) => {
return obj[key]
}
// const get1: (key: "s" | "f") => string | (() => string)
const get2 = (key: keyof typeof obj) => {
const value = obj[key]
if (typeof value === "string") {
return value
} else {
return () => value()
}
}
// const get2: (key: "s" | "f") => string | (() => string)
元ネタ https://twitter.com/teramotodaiki/status/1214149145935532033
ts
function get4<Key extends keyof typeof obj>(key: Key): typeof obj[Key] {
return obj[key];
}
// ERROR
// function get5<Key extends keyof typeof obj>(key: Key): typeof obj[Key] extends string ? string : () => string {
// return obj[key];
// }
function get6<Key extends keyof typeof obj>(key: Key): any {
const value = obj[key]
if (typeof value === "string") {
return value
} else {
return () => value() // ERROR
// This expression is not callable.
// Not all constituents of type 'string | (() => string)' are callable.
// Type 'string' has no call signatures.
}
}
key
- get2
keyof typeof obj
==="s" | "f"
なので(parameter) key: "s" | "f"
- get4
(parameter) key: Key extends "s" | "f"
const value = obj[key]
- get2
const value: string | (() => string)
- get6 ts
const value: {
s: string;
f: () => string;
}[Key]
要するに元ネタのコードでは余計なextendsのせいでkeyの型が"s" | "f"
だとわからなくなっており、
その結果obj[key]
もstring | (() => string)
だとわからなくなっている
string | (() => string)
ならUnion TypeなのでType Guardsが使えるがget6の方は複雑な型なのでそれができない
(可能だが現時点のTSがサポートしてない)
なのでelse節のvalueが() => string
であることがわからずType 'string' has no call signatures
になる
const x = get2("f") // const x: string | (() => string)
うーん、そりゃそうか、関数が呼ばれる前にobjが書き換わる可能性があるからな
ts
const x2 = get7("s") // const x2: string
const x3 = get7("f") // const x3: () => string
こういう挙動が欲しい場合、
ts
function get7(key: "s"): string;
function get7(key: "f"): (() => string);
function get7(key: "s" | "f"): (string | (() => string)) {
const value = obj[key]
if (typeof value === "string") {
return value
} else if (typeof value === "function") {
return () => value()
}
}
となるか
TypeScriptの型の練習