简而言之,我需要一个返回either类型"Person"或类型"PersonWithSurname"的函数.这与条件类型一起工作得很好.然而,我正在努力在没有错误的情况下构建结果.

interface Person {
  name: string;
}

type PersonWithSurname = Person & { 
  surname : string 
};

type PersonNameConditional<T> = T extends true 
  ? PersonWithSurname 
  : Person; 

function getPerson<T extends boolean>(returnSurname: withSurname
  ): PersonNameConditional<T> {
    if (returnSurname) {
      return { name: "John", surname: "Wic-k" } as PersonNameConditional<withSurname>
    } else {
      return { name: "John" } as PersonNameConditional<withSurname>
    }
}

// All good here ✅

上面的方法运行得很好,甚至IntelliSense也会根据收到的参数向我显示返回的类型,如下所示.

const result = getPerson(true) // { name: "John", surname: "Wic-k" }: PersonWithSurname
const result = getPerson(false) // { name: "John" }: Person

// All good here ✅

然而,在这里我遇到了一个问题,TS无法从响应中识别surname.

const requestedSurname: boolean = req.query.withSurname

let result = getPerson(requestedSurname)

if ("surname" in result)
  // All good here  ✅
  result.surname = result.surname.replace(/-/g, '')

if (result.surname)
  // ❌ Property "surname" does not exist on type { name : string } | PersonWithSurname
  result.surname = result.surname.replace(/-/g, '')

// the ideal usage
if (requestedSurname) 
  // ❌ Property "surname" does not exist on type { name : string } | PersonWithSurname
  result.surname = result.surname.replace(/-/g, '')

return result;

难道不能使用条件变量吗?

推荐答案

不幸的是,这是不可能的.在非常特定的有限场景中,TypeScrip只支持control flow analysis,而您的场景不在其中.参见microsoft/TypeScript#37223microsoft/TypeScript#54041microsoft/TypeScript#54252中的例子,人们期望这种事情会发生,而答案是"对不起,这不是TS的工作方式".


TypeScrip不会将控制流分析"分发"到它遇到的每个union-typed个值上.我的意思是,当查看如下代码时

declare const requestedSurname: boolean;
let result = getPerson(requestedSurname);
// let result: Person | PersonWithSurname
if (requestedSurname) {
    result.surname
} 

编译器does not模拟requestedSurname的每一种可能的缩小,并说:"requestedSurnametrue | false,所以我最好将可能的世界分成truefalse,看看会发生什么.在true的世界中,result将是PersonWithSurname,result.surname块将是有效的.在false世界中,result将是Person,result.surname块将永远不会被执行.无论哪种方式result.surname都是有效的,只要它可以到达,结果就是string."

这看起来似乎是一种合理的方法,但前提是你大部分时间都可以 Select not.想象一下这样的代码:

declare const twoHundredBooleans: boolean[]
const twoHundredPeople = twoHundredBooleans.map(getPerson);
if (twoHundredBooleans.every((b): b is true => b)) {
    twoHundredPeople.map(p => p.surname);
}

(您可以想象我实际上写出了对应于200个boolean元素的tuple type).编译器不是一个聪明人,所以它不能只看到"大局".任何分割成每个联盟成员的多遍的分析将最终需要2次200迭代,并且在人类存在的末日之前不会完成.

我一度建议为这类分析建立一个 Select 加入机制,比例为microsoft/TypeScript#25051,但被拒绝了.因此,开发人员无法使用句柄来获取此行为的一部分.这不是编译器的工作方式.


所以事情并不是这样的.实际发生的情况是,编译器只将result的类型和requestedSurname的类型视为independentuncorrelated联合类型.因此,如果你关心result的类型,你将需要直接判断result,因为还没有跟踪到requestedSurname的关系.

最简单的方法是

if ("surname" in result) { result.surname }

它使用in-operator narrowing根据已知属性键的存在或不存在来(不合理但方便地)区分联合.

Playground link to code

Typescript相关问答推荐

VS代码1.88.0中未出现自动导入建议

属性的类型,该属性是类的键,其值是所需类型

类型{...}的TypeScript参数不能赋给类型为never的参数''

Angular 信号:当输入信号改变值时,S触发取数的正确方式是什么?

如何使用一个字段输入对象,其中每个键只能是数组中对象的id属性,而数组是另一个字段?

TypeScrip:逐个 case Select 退出noUncheck kedIndexedAccess

如何调整对象 struct 复杂的脚本函数的泛型类型?

是否可以从函数类型中创建简化的类型,go 掉参数中任何看起来像回调的内容,而保留其余的内容?

使用数组作为类型中允许的键的列表

如何在ANGLE中注册自定义验证器

从对象类型描述生成类型

如何编写在返回函数上分配了属性的类型安全泛型闭包

如何将TS泛型用于react-hook-form接口?

Angular material 无法显示通过API获取的数据

try 使Angular依赖注入工作

如何键入并类型的函数&S各属性

有没有更干净的方法来更新**key of T**的值?

将对象数组传递给 Typescript 中的导入函数

我是否应该在 Next.js 中的服务器组件中获取秘密数据的所有函数中使用使用服务器?

req.files = 未定义(Multer、Express、Typescript)