TypeScriptの型の練習
code:typescript
const obj = {
s: "foo",
f: () => "bar"
}
const get1 = (key: keyof typeof obj) => {
}
// const get1: (key: "s" | "f") => string | (() => string)
const get2 = (key: keyof typeof obj) => {
if (typeof value === "string") {
return value
} else {
return () => value()
}
}
// const get2: (key: "s" | "f") => string | (() => string)
code:ts
function get4<Key extends keyof typeof obj>(key: Key): typeof objKey { }
// ERROR
// function get5<Key extends keyof typeof obj>(key: Key): typeof objKey extends string ? string : () => string { // }
function get6<Key extends keyof typeof obj>(key: Key): any {
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
code:ts
const value: {
s: string;
f: () => string;
要するに元ネタのコードでは余計な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が書き換わる可能性があるからな
code:ts
const x2 = get7("s") // const x2: string
const x3 = get7("f") // const x3: () => string
こういう挙動が欲しい場合、
code:ts
function get7(key: "s"): string;
function get7(key: "f"): (() => string);
function get7(key: "s" | "f"): (string | (() => string)) {
if (typeof value === "string") {
return value
} else if (typeof value === "function") {
return () => value()
}
}
となるか