简体   繁体   English

如何在TypeScript中以变量作为键返回对象接口? 类型不可分配给类型错误

[英]How to return object interface with variable as a key in TypeScript? Type is not assignable to type error

I have the following type and interfaces.我有以下类型和接口。

type ColVal = [{
    col: string
}, {
    val: string
}]

interface IEquals {
    eq: ColVal,
}

interface INotEquals {
    ne: ColVal,
}

And I have the following function:我有以下功能:

const getOperation = (col: string, val: string, operation: string): IEquals | INotEquals => {
    let op: 'eq' | 'ne' = operation === 'Equals' ? 'eq' : 'ne';
    return {
        [op]: [{
            col,
        }, {
            val,
        }]
    };
};

But I get the error Type '{ [x: string]: ({ col: string } | { val: string; })[]; }' is not assignable to 'IEquals | INotEquals'.但我收到错误Type '{ [x: string]: ({ col: string } | { val: string; })[]; }' is not assignable to 'IEquals | INotEquals'. Type '{ [x: string]: ({ col: string } | { val: string; })[]; }' is not assignable to 'IEquals | INotEquals'.

If I change [op] to ['eq'] or ['ne'] , the error goes away.如果我将[op]更改为['eq']['ne'] ,错误就会消失。 Does anybody know how to fix this?有谁知道如何解决这个问题?

Here is the TypeScript playground for you guys to see the issue: Playground .这里是 TypeScript 游乐场,供你们查看问题: Playground

It's currently a design limitation of TypeScript that computed properties of union types have their key widened all the way to string by the compiler.目前 TypeScript 的一个设计限制是联合类型的计算属性的键被编译器一直扩展到string So an object literal like {[Math.random()<0.5 ? "a" : "b"]: 123}所以像{[Math.random()<0.5 ? "a" : "b"]: 123}这样的对象字面量{[Math.random()<0.5 ? "a" : "b"]: 123} {[Math.random()<0.5 ? "a" : "b"]: 123} is inferred by the compiler to be of the type {[k: string]:number} instead of the more specific {a: number} | {b: number} {[Math.random()<0.5 ? "a" : "b"]: 123}被编译器推断为{[k: string]:number}而不是更具体的{a: number} | {b: number} {a: number} | {b: number} . {a: number} | {b: number}

Both microsoft/TypeScript#13948 and microsoft/TypeScript#21030 concern this issue. microsoft/TypeScript#13948microsoft/TypeScript#21030 都关注这个问题。 It looks like there was some attempt to address it at one point, microsoft/TypeScript#21070 ) but it fizzled out.看起来曾经有人试图解决它, microsoft/TypeScript#21070 ) 但它失败了。

I don't know if it will ever be addressed, but for now you'll have to work around it.我不知道它是否会得到解决,但现在你必须解决它。


The least disruptive (and least type safe) workaround this is just to assert that the return value is of the appropriate type.破坏性最小(且类型最不安全)的解决方法是断言返回值是适当的类型。 In this case the compiler sees the return value is of a type that's so wide that it doesn't even see it as related to IEquals | INotEquals在这种情况下,编译器看到返回值的类型太宽,以至于它甚至不认为它与IEquals | INotEquals相关IEquals | INotEquals IEquals | INotEquals . IEquals | INotEquals So you'll have to assert through some intermediate type... might as well just use any , the ultimate "just make it work" type:所以你必须通过一些中间类型来断言......还不如使用any ,最终的“让它工作”类型:

const getOperationAssert = (col: string, val: string, operation: string): IEquals | INotEquals => {
    let op: 'eq' | 'ne' = operation === 'Equals' ? 'eq' : 'ne';
    return {
        [op]: [{
            col,
        }, {
            val,
        }]
    } as any; // 🤓 I'm smarter than the compiler 
};

Another idea is to manually implement a helper function that behaves the way computed properties "should" behave.另一个想法是手动实现一个辅助函数,该函数的行为方式与计算属性“应该”的行为方式相同。 Like this:像这样:

function computedProp<K extends PropertyKey, V>(key: K, val: V): { [P in K]: { [Q in P]: V } }[K];
function computedProp(key: PropertyKey, val: any) {
    return { [key]: val };
}

So if you call computedProp(Math.random()<0.5 ? "a" : "b", 123) , the implementation just makes an object with a computed property, but the typing is such that it returns {a: number} | {b: number}因此,如果您调用computedProp(Math.random()<0.5 ? "a" : "b", 123) ,该实现只会创建一个具有计算属性的对象,但类型是这样的,它返回{a: number} | {b: number} {a: number} | {b: number} . {a: number} | {b: number} Then your getOperation() becomes:然后你的getOperation()变成:

const getOperationHelper = (col: string, val: string, operation: string): IEquals | INotEquals => {
    let op: 'eq' | 'ne' = operation === 'Equals' ? 'eq' : 'ne';
    return computedProp(op, [{
        col,
    }, {
        val,
    }]);
};

Finally, if you're willing to refactor, you could consider not using computed properties at all.最后,如果您愿意重构,您可以考虑根本不使用计算属性。 Store your ColVal value in a variable, and then return it as the property of either an object literal with a literal eq key or one with a literal ne key.ColVal值存储在一个变量中,然后将其作为带有文本eq键的对象文本或带有文本ne键的对象文本的属性返回。 The compiler can follow that flow much more accurately and can verify it as safe:编译器可以更准确地遵循该流程,并可以验证它是否安全:

const getOperationRefactor = (col: string, val: string, operation: string): IEquals | INotEquals => {
    let op: 'eq' | 'ne' = operation === 'Equals' ? 'eq' : 'ne';
    const colVal: ColVal = [{ col }, { val }];
    return (operation === 'Equals') ? { eq: colVal } : { ne: colVal };
};

Hopefully one of those works for you.希望其中之一对您有用。

Playground link to code Playground 链接到代码

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

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