TypeScript 允许在创建新对象时根据其他属性缩小类型,

up9lanfz  于 6个月前  发布在  TypeScript
关注(0)|答案(3)|浏览(44)

搜索词

  • 缩小类型

建议

目前,TypeScript无法根据创建新对象时定义的其他属性来缩小类型。例如,假设我们有以下函数:

type TParams = { kind: 'name', value: string } | { kind: 'age', value: number }
const example = (params: TParams) => {
  ...
}

我们可以使用类似以下代码调用它:

example({ kind: 'name', value: 'macabeus' })
example({ kind: 'age', value: 23 })

但是,编译像这样的代码是不可能的:

let nameOrAge: 'name' | 'age' = 'name'

example({
    kind: nameOrAge,
    value: (nameOrAge === 'name' ? 'macabeus' : 23),
})
  • Playground*

但我们知道,如果 nameOrAge'name' ,那么属性 value 将是一个字符串,否则 nameOrAge 将是 'age' ,而 value 将是 23 -这是正确的。
我认为如果TS能检查这种行为会很有用。

用例

在上面描述的上述情况中减少代码量是有用的。
目前,为了获得相同的行为,必须编写更多的代码:

let nameOrAge: 'name' | 'age' = 'name'

if (nameOrAge === 'name') {    
    example({
        kind: nameOrAge,
        value: 'macabeus',
    })
} else {
    example({
        kind: nameOrAge,
        value: 23,
    })
}
  • Playground*

相关

我知道有很多关于基于变量缩小类型的问题的讨论,例如:

  • that 关于缩小数组值的问题
  • that 关于调用函数时的缩小问题

但在这里我只说的是创建新对象时的情况,而且据我所知,与这些其他问题相比,这是一个更简单的情况,因为不需要跟踪整个代码库。
在我提到的情况下,TS只需要检查在同一对象上定义的其他属性 以及 变量名是否已经被使用。例如,TS不需要在这种情况下改变其行为:

example({
  kind: nameOrAge,
  value: otherVariabledDeclaredOnOtherScope ? 'macabeus' : 23,
}) // still should fail

也许这不是最好的解决方案,但我认为它遵循帕累托效率原则。

检查表

我的建议满足以下准则:

  • 这不会对现有的TypeScript/JavaScript代码造成破坏性的变化
  • 这不会改变现有JavaScript代码的运行时行为
  • 这可以在不根据表达式的类型生成不同的JS的情况下实现
  • 这不是一个运行时特性(例如库功能、JavaScript输出的非ECMAScript语法等)
  • 这个特性将与其他 TypeScript's Design Goals 保持一致。
t3irkdon

t3irkdon1#

这是一个特定于 #12184 的使用案例,类似于我为 #25051 提出的一个激励示例(参见此评论)。

这里似乎建议的是,每当创建一个新的对象字面量时,编译器应该检查是否有任何联合类型的值被多次提及,然后自动地将控制流分析分布在该值的不同缩小版本上。因此,如果你有以下代码:

declare const x: A | B | C;
declare const f: (t: T) => F;
const obj = {x: x, fx: f(x)};

你希望编译器能注意到 xobj 中被多次使用,然后依次缩小 xABC,在每种情况下计算 obj 的类型,然后将它们合并为 {x: A, fx: F<A>} | {x: B, fx: F<B>} | {x: C, fx: F<C>}

如果这样的自动分析能够发生,那将太好了,但我预计性能影响会是不可接受的,尤其是考虑到类似 {x: x, y: y, z: z, fxyz: f(x, y, z)} 的组合问题。

在我幻想的世界(即 #25051)里,我希望能够手动请求编译器进行这样的分析,以便在保留正常代码的行为和性能的同时获得这种好处。在你的情况下,它看起来可能像这样:

example({
kind: nameOrAge,
value: (nameOrAge === 'name' ? 'macabeus' : 23),
} as if switch(nameOrAge))


哦,好吧!:wistful-sigh:
abithluo

abithluo2#

感谢您的回复!

看起来这里的建议是,每当创建一个新的对象字面量时

是的。这个提案的想法是在创建一个新的对象字面量时进行检查,以避免性能问题并使其更容易实现。

我遵循帕累托原则。我知道这不是最佳方法,但它非常简单。

编译器应该检查是否有任何联合类型的值被多次提及,然后自动将控制流分析分布在该值的不同缩小版本上

我不知道是否真的需要为枚举上的每个值自动分配。例如,在这种情况下只需要分配两次:

let nameOrSomething: 'name' | 'age' | 'height' | 'money'

example({
    kind: nameOrSomething,
    value: (nameOrSomething === 'name' ? 'macabeus' : 20),
})

只需要检查:

  • nameOrSomething'name'value'macabeus' 时,这个对象是否可以在 example 调用中使用?
  • 以及当 nameOrSomething'age' | 'height' | 'money'value20 时,这个对象是否有效?

您是否在寻找其他需要进行更多检查的情况?

wh6knrhe

wh6knrhe3#

可处理案例的存在并不意味着组合爆炸性案例仍然不存在。算法不能是“寻找判别式的一次比较,然后做正确的事情”。
如果你写了类似这样的代码:

let nameOrSomething: 'name' | 'age' | 'height' | 'money' | (30 other values)

example({
    kind1: nameOrSomething,
    kind2: nameOrSomething === "age" ? "age2" : nameOrSomething,
    value: (nameOrSomething === 'name' ? 'macabeus' : 20),
    value1: (nameOrSomething === 'age' ? nameOrSomething : 20),
    value2: (nameOrSomething === 'height' ? 'macabeus' : 20),
    value3: (nameOrSomething === 'money' ? 'macabeus' : nameOrSomething),
    value4: (nameOrSomething !== 'age' ? 'macabeus' : 20),
    value5: (nameOrSomething === 'money' && nameOrSomething === 'age' ? 'macabeus' : 20)
})

然后将其与 example 的复杂定义进行比较,对我来说,很明显你如何避免多次迭代整个并集。考虑到人们在 JSX 中生成的属性类型,这不是一个不切实际的场景。

相关问题