我正在尝试为 npm 制作一个像 zod 这样的包,但是在解析它时它不会更新以前的类型:
function isString(val: unknown): asserts val is string {
if (typeof val !== 'string') throw new Error()
}
const x = {
string: () => ({
parse(arg: unknown): string {
isString(arg)
return arg
}
})
}
let a: unknown = 'asd' // a: unknown
x.string().parse(a) // returns: 'asd'
a // a:unknown
为什么不起作用?
直接使用
isString
函数时,它确实会改变类型。
首先:简单地在另一个函数中调用一个断言函数不会将该外部函数本身变成断言函数。调用断言函数的行为与任何“缩小”类似,它对同一范围内的代码产生影响,但通常不会传播到外部。正确地建模控制流分析应该跨功能边界做什么是非常困难或不可能的;在 microsoft/TypeScript#9998 上有一个非常冗长的 GitHub 问题,他们在其中讨论了选项,本质上,TypeScript 就放弃了。函数内部发生的任何缩小都会在函数外部丢失。 因此,如果您希望外部函数在被调用时影响控制流,则必须显式地将其设为
自定义类型保护函数或断言函数本身(请注意,这将在 TypeScript 5.5 中发生变化,因为microsoft/TypeScript#57465,其中“简单”boolean
-返回函数将被推断为自定义类型保护函数)。
parse()
方法不能是类型保护函数(因为它不返回确定是否应该进行缩小的
boolean
),并且它不能是断言函数,因为它返回 string
。断言函数必须返回 void
。 microsoft/TypeScript#40562有一个功能请求可以放宽该要求,但现在您陷入困境。 如果你愿意让
parse()
返回
void
那么你技术上可以让它工作,但是断言函数有一个恼人的要求,即只有在它们具有“显式类型注释”中定义的情况下才能调用它们microsoft/TypeScript#32695。因此,您需要先将
x.parse()
分配给其自己的显式 带注释变量,然后才能使用它。可能看起来像这样:
const x = {
string: () => ({
parse(arg: unknown) {
return isString(arg);
}
})
}
let a: unknown = 'asd' // a: unknown
// explicitly annotate as having an assertion method
const xStringRet: { parse(arg: unknown): asserts arg is string; } =
x.string();
xStringRet.parse(a);
a // a: string
这似乎不值得。所以我想说,你所要求的在最坏的情况下是不可能的,在最好的情况下是令人不愉快的。Playground 代码链接