繁体   English   中英

如何根据是否在 Typescript 中设置了 object 属性来定义条件返回类型

[英]How to define a conditional return type based on if an object property is set in Typescript

interface Options {
  flagA?: boolean
  flagB?: boolean
}

function abc(name, options?: Options) {}

if(options.flagA) return some type
if(options.flagB) return some some other type

我想根据是否设置了 flagA 或 flagB 有不同的返回类型。 这可能吗?

我已经做到了类似的事情

function abc<T>(name, options?: T): T extends Options ? string : number { return }
const data = abc('test', { flagA: true });

根据 T 是否实现选项返回字符串或数字,但我不确定如何更具体地检查 options.flagA 是否具有值。

具体而言,我将定义这些接口:

interface FlagARet {
    a: string;
}
interface FlagBRet {
    b: string;
}
interface NoFlagRet {
    x: string;
}

意图是如果optionsflagA设置为true ,则abc()将返回一个FlagARet值; 否则,如果optionsflagB设置为true ,则abc()将返回一个FlagBRet值; 否则abc()将返回NoFlagRet值。


这种类型的杂耍不是编译器会自动为你做的,甚至无法在编译时进行验证; 您需要向编译器准确说明您想要评估的 function 类型,并执行与类型断言等效的操作来告诉编译器您保证 function 实际上返回您声明的类型. 这很难真正保证。 可能存在边缘情况,因此这可能比它的价值更麻烦。 被警告。


下面是我尝试表示输入和 output 之间关系的尝试,其中考虑了输入类型的不确定性。 例如,如果调用abc("", {flagA: Math.random()<0.5} ,编译器应该返回类似FlagARet | NoFlagRet的内容,因为它不能确定flagA属性是true还是false

首先,我创建了If类型别名,它只执行条件类型检查,以保证它将分布在 unions中。

type If<X, Y, T, F> = X extends Y ? T : F;

现在是主要事件:

function abc<T extends Options | undefined = undefined>(name: string, options?: T):
    T extends undefined ? NoFlagRet :
    If<NonNullable<T>['flagA'], true, FlagARet,
        If<NonNullable<T>['flagB'], true, FlagBRet, NoFlagRet>
    >
function abc(name: string, options?: Options) {
    if (options?.flagA) return { a: "ay" };
    if (options?.flagB) return { b: "be" }
    return { x: "ex" };
}

这有点涉及,但我会逐步介绍。 首先,在您不传入options参数的情况下,泛型类型参数T默认undefined 如果T (或T的任何联合成员) undefined ,则 output 类型将为NoFlagRet (或NoFlagRet将通过联合添加到 output 类型)。 然后,我们检查NonNullable<T>['flagA'] NonNullable<T>类型确保我们忽略任何undefined的部分,然后['flagA']类型正在查找TflagA属性。 如果该标志(或其任何联合成员)为true ,则我们返回FlagARet (或FlagARet将通过联合添加到 output 类型。)否则我们检查NonNullable<T>['flagB'] 如果该标志(或等等等等)是true ,那么我们返回FlagBRet (或等等添加联合等等)。 否则我们返回NoFlagRet

我已将 function 编写为单调用签名重载,因此实现类型检查更宽松,我可以返回FlagARet | FlagBRet | NoFlagRet类型的值FlagARet | FlagBRet | NoFlagRet FlagARet | FlagBRet | NoFlagRet没有编译器抱怨。 希望您能看到实现与调用签名匹配。


好的,让我们测试一下:

console.log(abc("", { flagA: true }).a.toUpperCase()); // AY
console.log(abc("", { flagA: true, flagB: true }).a.toUpperCase()); // AY
console.log(abc("", { flagB: true }).b.toUpperCase()); // BE
console.log(abc("", {}).x.toUpperCase()); // EX
console.log(abc("", { flagA: true, flagB: false }).a.toUpperCase()); // AY
console.log(abc("", { flagA: false, flagB: true }).b.toUpperCase()); // BE
console.log(abc("").x.toUpperCase()); // EX
const aOrB = abc("", { flagA: Math.random() < 0.5, flagB: true });
// const aOrB FlagARet | FlagBRet
const aOrX = abc("", { flagA: Math.random() < 0.5 });
// const aOrX: FlagARet | NoFlagRet
const bOrX = abc("", { flagB: Math.random() < 0.5 });
// const bOrX: FlagBRet | NoFlagRet
const aOrBorX = abc("", { flagA: Math.random() < 0.5, flagB: Math.random() < 0.5 });
// const aOrBorX: FlagARet | FlagBRet | NoFlagRet

这一切都按我的预期工作。 没有编译器错误; 在每一步,编译器都会理解abc()的 output 类型。 特别是,您可以看到,当我们开始传入类型为boolean而不是已知的truefalse的对象时,output 类型本身就是相关的联合。


这是完美的吗? 不可以您始终可以将具有已知属性的值扩大到没有该属性的值。 例如,您可以将任何 object 类型分配给空的 object 类型{}

const flagA = {flagA: true};
let someObj: {};
someObj = flagA; // works because all objects are assignable to {}

这对我的abc()实现来说是个坏消息:

abc("", someObj).x.toUpperCase(); // no compiler error, but runtime error!
// TypeError: abc(...).x is undefined

因为不再知道someObj具有flagA: true属性,所以编译器假定它没有,因此期望 output 类型为NoFlagRet 但是在运行时会出现一个FlagARet ,而我们有一个编译器没有捕捉到的运行时错误。

可以修改abc()的定义以更加谨慎,并假设其类型没有显式flagA属性的值实际上可能在该属性处实际上具有true值或false值。 但这会使abc()的 output 类型对常见用例不太有用。 这可能会得到缓解,并且存在一些最不坏的解决方案。

但这里的重点是,这种类型的杂耍并不容易做到,编译器并没有真正帮助你保持诚实。 您应该考虑是否真的需要这种功能,如果需要,请注意潜在的陷阱。

Playground 代码链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM