繁体   English   中英

你能在 TypeScript 中指定一个对象字面量的类型吗?

[英]Can you specify an object literal's type in TypeScript?

我想知道是否有办法指定对象文字的类型。

例如,如何解析此代码并将B文字分配给A变量:

interface A {
    a: string;
}

interface B extends A {
    b: string
}

const a: A = {
    a: "",
    b: ""
};

BA ,所以我希望能够在需要A地方分配B 但是,编译器没有足够的上下文来确定我传递的是B而不是非法的A ,因此我需要指定它。

我不想只编译上面的代码,我特别希望将“我将B分配给A ”的意图传达给编译器。 我想要具有B对象文字的类型安全性,因此如果B获得其他属性,例如,这应该无法再次编译。

我在搜索这个时偶然发现了这篇文章 一种选择是定义一个恒等函数:

const is = <T>(x: T): T => x;

const a: A = is<B>({
    a: "",
    b: ""
});

这并不理想,因为它会为了一些在转译后无关紧要的东西而运行代码。 另一种选择是使用第二个变量:

const b: B = {
    a: "",
    b: ""
};

const a: A = b;

出于同样的原因也不理想,它是为了 TypeScript 检查而编写的代码。

@jcalz 的建议:

interface A {
  a: string;
  [k: string]: unknown; // any other property is fine
}

当我只想通过A及其子类型时,这允许通过任何多余的属性。 A的封闭性不是问题,我只是无法告诉编译器我在A预期的地方传递了B

Turn off --suppressExcessPropertyErrors compiler option

我想抑制多余的属性,问题是我无法告诉编译器这些不是多余的属性,它们是正确子类型的属性。

是否有等价于文字的类型声明? 是否有 TypeScript 的基本设计可以阻止这种构造的存在?

在 Kotlin 等语言中,这是微不足道的:

val a: A = B()

可以分别声明变量的类型和运行时对象的类型(必须是相同类型或子类型)。

如果这是一个XY 问题,我最初试图解决的问题如下所示:

const foo = (): A => {
    if (/* condition */) {
        /* some setup.. */
        return {
            a: "",
            b: ""
        }
    } else {
        return {
            a: ""
        }
    }
}

这不会编译,因为第一个return不是明确的A ,它旨在成为BA ,但我无法在不诉诸我上面提到的解决方法之一的情况下将其指定为B 我想要看起来像的东西

return {
    a: "",
    b: ""
}: B

// or

return: B {
    a: "",
    b: ""
}

// or

return {
    a: "",
    b: ""
} is B

不用说, as B类型的断言不是我在这里寻找的。 我不想绕过类型系统,我只想帮助编译器检查我的对象文字是否为指定类型。 这个问题也适用于参数:

const foo = (a: A) => {};

foo({ a: "", b: "" })

这失败了,因为我无法告诉编译器我正在传递B ,它认为我使用了多余的属性。 它仅在我使用像这样的额外变量时才有效:

const b: B = { a: "", b: "" };

foo(b)

更新:这里的主要问题似乎是 TypeScript 没有“仅扩展断言运算符”或“内联类型注释运算符”(例如, return {a: 123} op A应该失败但return {a: "", b: ""} op A应该成功)。 有一个开放的建议,请参阅 microsoft/TypeScript#7481,但那里没有真正的动向。 如果您想看到这种情况发生,您可能想要转到那个问题并给它一个 👍 和/或描述您的用例,如果它比现有的更引人注目。 在没有明确的类型运算符的情况下,有可用的策略和解决方法。


听起来您已经知道处理此问题的最常见选项,但您不喜欢其中任何一个。 我还有一些其他的事情你可以尝试,但如果你的限制是:

  • 没有更改您的类型定义或注释
  • 对发出的运行时代码没有更改
  • 没有类型断言

那么您的选择确实有限,您可能找不到任何令人满意的答案。


正在发生的事情的背景:

对象字面量是 TypeScript 的正常可扩展对象类型的一个有趣的例外。 通常,TypeScript 中的对象类型并不精确 {a: string}这样的类型定义只要求一个值必须有一组已知的属性。 它不要求值必须缺少未知属性 所以一个对象{a: "", b: 123, c: true}是一个完全有效的{a: string}类型的值,因为它有a属性,其值可以赋值给string 所以看起来你的代码应该没有什么; 您的a确实是有效的A ,无论是否有上下文。

但当然编译器会抱怨。 那是因为您已经将一个新的对象字面量分配给一个变量,该变量的类型注释的已知属性比对象字面量少。 因此它与过度属性检查相冲突,其中对象类型被视为精确。 您可以阅读实现此功能的拉取请求以及执行此操作基本原理 这些问题还详细说明了针对不需要进行过多属性检查的情况的建议解决方法。


您已经提到创建一个辅助函数(例如, return is<A>({...}) ),分配给一个中间变量(例如, const ret = {...}; return ret; ),并使用类型断言(例如, return {...} as A )。 以下是我看到的其他选项:

  • 为您的A类型添加索引签名,以便它显式打开:

     interface A { a: string; [k: string]: unknown; // any other property is fine } const a: A = { a: "", b: "" }; // okay now
  • 使用--suppressExcessPropertyErrors编译器选项完全关闭多余的属性检查(我真的不推荐这样做,因为它是一个范围广泛的变化)

  • 使用联合类型,A | B 注释对象字面量对应的类型时,用A | B代替A

     const a: A | B = { a: "", b: "" }; // okay now

    由于BA的子类型,因此类型A | B A | B在结构上等同于A ,因此您几乎可以在任何地方使用它代替A

     const foo = (): A | B => { ... } // your impl here function onlyAcceptA(a: A) { } onlyAcceptA(foo()); // okay
  • 仅在额外属性上使用类型断言,这需要使用计算属性名称

     const a: A = { a: "", ["b" as string]: "" }; // okay

    这确实没有任何类型安全性降低,但它最终会生成略有不同的 JavaScript( {a: "", ["b"]: ""} ),假设您的目标是支持计算属性名称的 JavaScript 版本.

我能想到的其他一切只是对您的运行时代码进行了更多更改,因此我将就此打住。


无论如何,希望能给你一些想法。 祝你好运!

代码链接

暂无
暂无

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

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