首页 > 解决方案 > 如何在 TypeScript 中以变量作为键返回对象接口?类型不可分配给类型错误

问题描述

我有以下类型和接口。

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

interface IEquals {
    eq: ColVal,
}

interface INotEquals {
    ne: ColVal,
}

我有以下功能:

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

但我得到了错误Type '{ [x: string]: ({ col: string } | { val: string; })[]; }' is not assignable to 'IEquals | INotEquals'.

如果我更改[op]['eq']or ['ne'],错误就会消失。有人知道如何解决这个问题吗?

这是 TypeScript 游乐场,供大家查看问题:Playground

标签: javascripttypescript

解决方案


目前,TypeScript 的一个设计限制是联合类型的计算属性的键一直string被编译器加宽。因此,编译器将类似的对象字面{[Math.random()<0.5 ? "a" : "b"]: 123}量推断为类型{[k: string]:number}而不是更具体的{a: number} | {b: number}.

microsoft/TypeScript#13948和microsoft /TypeScript #21030 都涉及此问题。似乎有一次尝试解决它,microsoft/TypeScript#21070)但它失败了。

我不知道它是否会得到解决,但现在你必须解决它。


破坏性最小(并且类型安全性最低)的解决方法就是断言返回值是适当的类型。在这种情况下,编译器看到返回值的类型非常广泛,以至于它甚至不认为它与IEquals | INotEquals. 所以你必须通过一些中间类型来断言......不妨只使用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 
};

另一个想法是手动实现一个辅助函数,该函数的行为方式与计算属性“应该”的行为方式相同。像这样:

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 };
}

因此,如果您调用computedProp(Math.random()<0.5 ? "a" : "b", 123),该实现只会创建一个具有计算属性的对象,但类型是这样的,它会返回{a: number} | {b: number}。然后你的getOperation()变成:

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

最后,如果你愿意重构,你可以考虑完全不使用计算属性。将您的ColVal值存储在一个变量中,然后将其作为带有文字键的对象文字eq或带有文字键的对象文字的属性返回ne。编译器可以更准确地遵循该流程,并可以验证它是否安全:

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 };
};

希望其中之一对您有用。

Playground 代码链接


推荐阅读