Typescript 从条件类型化函数获取类似类型保护的功能

Typescript 从条件类型化函数获取类似类型保护的功能,typescript,Typescript,下面是一个使用条件类型的最小示例 const wrap=(val:number,foo:T):T扩展为真?{foo:number}:{bar:number}=>{ 如果(foo){ 将{foo:val}返回为任意值; } 返回{bar:val}如有; } 常数测试=数学随机()

下面是一个使用条件类型的最小示例

const wrap=(val:number,foo:T):T扩展为真?{foo:number}:{bar:number}=>{
如果(foo){
将{foo:val}返回为任意值;
}
返回{bar:val}如有;
}
常数测试=数学随机()<0.5;
const val=包裹(21,测试);
如果(测试){
console.log(val.foo);
}否则{
控制台日志(val.bar);
}
(In)

它会在控制台的两个
语句中抛出一个错误。log
语句:

Property 'foo' does not exist on type '{ foo: number; } | { bar: number; }'.
  Property 'foo' does not exist on type '{ bar: number; }'.(2339)
Property 'bar' does not exist on type '{ foo: number; } | { bar: number; }'.
  Property 'bar' does not exist on type '{ foo: number; }'.(2339)
但是,如果test为true,则输出应该永远是
{foo:number}
,如果为false,则应该永远是
{bar:number}

我知道我可以使用:

if('foo'在val中){
console.log(val.foo);
}否则{
控制台日志(val.bar);
}
但这似乎违背了函数定义中条件类型的目的


我想知道是否有办法使用函数中输入的值而不是函数返回的值来区分类型。非常感谢您的帮助。

此实现是否解决了您的问题

function wrap(val: number, foo: true): { foo: number }
function wrap(val: number, foo: false): { bar: number }
function wrap(val: number, foo: boolean): { foo: number } | { bar: number } {
    if (foo) return { foo: val }
    if (bar) return { bar: val }
}
打电话的时候,

wrap(2, true)  // returns { foo: 2 }
wrap(2, false) // returns { bar: 2 }

这个实现解决了您的问题吗

function wrap(val: number, foo: true): { foo: number }
function wrap(val: number, foo: false): { bar: number }
function wrap(val: number, foo: boolean): { foo: number } | { bar: number } {
    if (foo) return { foo: val }
    if (bar) return { bar: val }
}
打电话的时候,

wrap(2, true)  // returns { foo: 2 }
wrap(2, false) // returns { bar: 2 }

我认为这是TypeScript缺乏对我所说的“相关联合类型”的支持的一个例子。请参阅以进行讨论

编译器将
test
的类型视为
true | false
(显示为
boolean
,但被视为两个布尔文本类型的并集),将
val
视为类型
{bar:number}{foo:number}

const test = Math.random() < 0.5; // boolean
const val = wrap(21, test); // {bar: number} | {foo: number}
另一个通用解决方案涉及重复,通过控制流分析迫使编译器遍历不同的可能性:

if (test) {
  const val = wrap(21, test);
  console.log(val.foo);
} else {
  const val = wrap(21, test);
  console.log(val.bar);
}
wrap(21, true).foo; // okay
wrap(21, false).bar; // okay
test ? wrap(21, test).foo : wrap(21, test).bar; // okay

这两个都不令人满意。。。重构无疑会更好,这样编译器就不会跟踪
test
val
之间的关系。喜欢你的版本:

if ("foo" in val) {
  console.log(val.foo);
} else {
  console.log(val.bar);
}
同样,您可以重构
wrap()
,使其输出为true,其中
test
实际存储在输出中:

const discrimWrap = <T extends boolean>(val: number, test: T): (
  T extends true ? { test: T, foo: number } : { test: T, bar: number }) => {
  if (test) {
    return { test, foo: val, } as any;
  }
  return { test, bar: val } as any;
}
在TypeScript中,歧视联合是为数不多的几个地方之一,它实际上试图跟踪相关性。但它们只在特定情况下起作用,其中判别式是对象的属性(而不是单独的变量),并且是“单例”或文本类型(而不是像
string
number
这样的宽类型)。因此,这就是我在这里建议的重构


最后,我想说的是,正如语言设计者所希望的那样,“函数定义中条件类型的目的”不是用来区分像
布尔
这样的联合类型的
测试。当你通过一个联盟时,联盟就出来了

如果编译器通过控制流分析知道第二个参数为
true
false
,则目的是缩小结果类型:

if (test) {
  const val = wrap(21, test);
  console.log(val.foo);
} else {
  const val = wrap(21, test);
  console.log(val.bar);
}
wrap(21, true).foo; // okay
wrap(21, false).bar; // okay
test ? wrap(21, test).foo : wrap(21, test).bar; // okay
最后一行之所以有效,是因为编译器可以在三元表达式的最后两个操作数中分别将
test
缩小到
true
false
。但是如果在
test
仅为
boolean
的上下文中调用
wrap(21,test)
,那么编译器将丢失任何关联



我认为这是TypeScript缺乏对我所说的“相关联合类型”的支持的一个例子。请参阅以进行讨论

编译器将
test
的类型视为
true | false
(显示为
boolean
,但被视为两个布尔文本类型的并集),将
val
视为类型
{bar:number}{foo:number}

const test = Math.random() < 0.5; // boolean
const val = wrap(21, test); // {bar: number} | {foo: number}
另一个通用解决方案涉及重复,通过控制流分析迫使编译器遍历不同的可能性:

if (test) {
  const val = wrap(21, test);
  console.log(val.foo);
} else {
  const val = wrap(21, test);
  console.log(val.bar);
}
wrap(21, true).foo; // okay
wrap(21, false).bar; // okay
test ? wrap(21, test).foo : wrap(21, test).bar; // okay

这两个都不令人满意。。。重构无疑会更好,这样编译器就不会跟踪
test
val
之间的关系。喜欢你的版本:

if ("foo" in val) {
  console.log(val.foo);
} else {
  console.log(val.bar);
}
同样,您可以重构
wrap()
,使其输出为true,其中
test
实际存储在输出中:

const discrimWrap = <T extends boolean>(val: number, test: T): (
  T extends true ? { test: T, foo: number } : { test: T, bar: number }) => {
  if (test) {
    return { test, foo: val, } as any;
  }
  return { test, bar: val } as any;
}
在TypeScript中,歧视联合是为数不多的几个地方之一,它实际上试图跟踪相关性。但它们只在特定情况下起作用,其中判别式是对象的属性(而不是单独的变量),并且是“单例”或文本类型(而不是像
string
number
这样的宽类型)。因此,这就是我在这里建议的重构


最后,我想说的是,正如语言设计者所希望的那样,“函数定义中条件类型的目的”不是用来区分像
布尔
这样的联合类型的
测试。当你通过一个联盟时,联盟就出来了

如果编译器通过控制流分析知道第二个参数为
true
false
,则目的是缩小结果类型:

if (test) {
  const val = wrap(21, test);
  console.log(val.foo);
} else {
  const val = wrap(21, test);
  console.log(val.bar);
}
wrap(21, true).foo; // okay
wrap(21, false).bar; // okay
test ? wrap(21, test).foo : wrap(21, test).bar; // okay
最后一行之所以有效,是因为编译器可以在三元表达式的最后两个操作数中分别将
test
缩小到
true
false
。但是如果在
test
仅为
boolean
的上下文中调用
wrap(21,test)
,那么编译器将丢失任何关联



我没有一个具体的答案,但是在您的条件返回类型中,扩展原语(true)是否导致了问题?在我看到的大多数示例中,这类条件在两种不同类型之间使用,即T扩展数字或字符串,而不是粒子的实际值