比方说,我们正在为Post
编写数据库模型,并且由于数据库将所有内容存储为字符串,因此我们需要编写parse
函数,该函数将获取原始DB对象并将其转换为正确的Post
界面。
要复制集noImplicitReturns: true
interface Post {
id: number
text: string
}
function parse<K extends keyof Post>(k: K, v: any): Post[K] {
switch(k) {
case 'id': return parseInt(v)
case 'text': return v.toString()
}
}
该代码中有两个错误。首先-它不会编译,似乎TypeScript并未意识到我们的代码是正确的,并要求将default
语句添加到switch
中。
第二个错误-它不会检测到您正在检查错误的值。下面的错误代码将正确编译
function parse<K extends keyof Post>(k: K, v: any): Post[K] {
switch(k) {
case 'id': return parseInt(v)
case 'text': return v.toString()
case 'some': return v.toString() // <= error, no `some` key
default: return '' // <= this line is not needed
}
}
甚至还有第三个错误,TypeScript将允许为键返回错误的值,该错误的代码将被编译
function parse<K extends keyof Post>(k: K, v: any): Post[K] {
switch(k) {
case 'id': return parseInt(v)
case 'text': return 2 // <= error, it should be string, not number
default: return ''
}
}
这些TypeScript限制还是我错了?
答案 0 :(得分:2)
首先,K
扩展keyof Post
并不意味着K
是keyof Post
,因此必须使用{{1 }}以限制K
的潜在值。
第二,如果您在tsconfig.json中设置keyof Post
,则将强制使用默认选项。因此,将其设置为K
即可解决问题。
第三,"switch-default": true,
甚至false
将返回Post[K]
属性的所有可能类型,因此Post[keyof Post]
是可能的。 Typescript不会强制Post
为其number
属性的类型,除非您指出Post[K]
是哪个。否则,您必须定义映射类型,例如类型K
,并将K
定义为A = ['id', numer] | [text', string]
希望获得帮助
答案 1 :(得分:1)
这种替代方法对您有吸引力吗?
interface Post {
id: number
text: string
}
type Parsers = {
[k in keyof Post]: (v: any) => Post[k]
}
const parsers: Parsers = {
id (v: any) {
return parseInt(v)
},
text (v: any) {
return v.toString(v)
}
}
parsers.id('123') // number
parsers.text({}) // string
// In addition, adding extra parsers or returning a value of wrong type from a parser is type-checked.
更新
使Parser(renamed from Parsers)
类型声明通用,并利用箭头功能和上下文类型推断,可以将代码改进为:
type Parser<T> = {
[k in keyof T]: (v: any) => T[k]
}
interface Post {
id: number
text: string
}
const parser: Parser<Post> = {
id: v => parseInt(v),
text: v => v.toString()
}