TypeScript:数组/数字联合类型属性调用push报错的原因解析
这事儿其实是TypeScript的联合类型收窄规则在搞鬼,我给你一步步理清楚:
为什么原代码会报错?
你定义的Gt类型里,$gt是Array<any> | number的联合类型。当你写let gt: Gt = {$gt: []}时,虽然你给$gt初始赋值了一个空数组,但TypeScript依然会把gt.$gt的类型保留为Array<any> | number——因为Gt类型允许$gt是数字,它不会因为你初始值是数组就自动收窄类型。
这时候你调用gt.$gt.push(),TypeScript就犯难了:它不确定$gt到底是数组还是数字,而number类型根本没有push方法,所以直接抛出错误。
为什么改成type Gt = {$gt: any}就正常了?
any类型是TypeScript的“逃生舱”——它会绕过所有类型检查,不管$gt实际是什么类型,TypeScript都会允许你调用任何方法(哪怕不存在)。但这种做法非常不安全,相当于放弃了TypeScript的类型保护,不推荐在实际项目中这么用。
正确的解决方法
这里有几种更安全的方式,既保留类型检查,又能解决报错:
方法1:局部变量用更精确的类型
在处理数组分支时,先创建一个明确是数组类型的对象,再返回(TypeScript会自动把它适配成Gt类型):
type Gt = { $gt: Array<any> | number }; export default (expression: Array<any> | number): Gt => { if (Array.isArray(expression)) { // 这里直接声明$gt为数组类型,TypeScript能明确识别 const gt = { $gt: [] as any[] }; const [key, value] = expression; gt.$gt.push(key, value); return gt; } else { return { $gt: expression }; } };
方法2:类型断言(适合快速修复)
如果你不想修改变量声明,可以用类型断言告诉TypeScript“这里的$gt肯定是数组”:
type Gt = { $gt: Array<any> | number }; export default (expression: Array<any> | number): Gt => { if (Array.isArray(expression)) { let gt: Gt = { $gt: [] }; const [key, value] = expression; // 断言成数组类型,确保push能正常调用 (gt.$gt as any[]).push(key, value); return gt; } else { return { $gt: expression }; } };
注意:因为你已经通过Array.isArray(expression)确认了当前分支是数组场景,所以这个断言是安全的。
方法3:用泛型让类型更严谨
如果想让类型更安全(避免any),可以用泛型约束expression的类型:
type Gt<T> = { $gt: T[] | T }; export default <T>(expression: T[] | T): Gt<T> => { if (Array.isArray(expression)) { const gt = { $gt: [] as T[] }; const [key, value] = expression; gt.$gt.push(key, value); return gt; } else { return { $gt: expression }; } };
这样不管传入的是数字还是特定类型的数组,TypeScript都能提供准确的类型提示。
内容的提问来源于stack exchange,提问作者Julian

