简体   繁体   English

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

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

I'm wondering if there is a way to specify an object literal's type.我想知道是否有办法指定对象文字的类型。

For example, how would one resolve this code and assign a B literal to an A variable:例如,如何解析此代码并将B文字分配给A变量:

interface A {
    a: string;
}

interface B extends A {
    b: string
}

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

B is an A , so I expect to be able to assign a B where an A is expected. BA ,所以我希望能够在需要A地方分配B However, the compiler does not have enough context to figure out that I am passing a B as opposed to an illegal A , so I need to specify it.但是,编译器没有足够的上下文来确定我传递的是B而不是非法的A ,因此我需要指定它。

I don't want to just make the above code compile, I specifically want my intent of "I am assigning a B to an A " to be conveyed to the compiler.我不想只编译上面的代码,我特别希望将“我将B分配给A ”的意图传达给编译器。 I want the type safety of having a B object literal, so if B gets additional properties for example, this should fail to compile again.我想要具有B对象文字的类型安全性,因此如果B获得其他属性,例如,这应该无法再次编译。

I stumbled upon this post when searching for this.我在搜索这个时偶然发现了这篇文章 One option is to define an identity function as such:一种选择是定义一个恒等函数:

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

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

This is not ideal as it will run code for the sake of something that is irrelevant after transpiling.这并不理想,因为它会为了一些在转译后无关紧要的东西而运行代码。 Another option is to use a second variable as such:另一种选择是使用第二个变量:

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

const a: A = b;

Also not ideal for the same reason, it's code for the sake of a TypeScript check.出于同样的原因也不理想,它是为了 TypeScript 检查而编写的代码。

@jcalz's proposals: @jcalz 的建议:

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

This allows any excess properties to pass, when I just want A and its subtypes to pass.当我只想通过A及其子类型时,这允许通过任何多余的属性。 A 's closedness is not the problem, I'm just unable to tell the compiler that I am passing a B where an A is expected. A的封闭性不是问题,我只是无法告诉编译器我在A预期的地方传递了B

Turn off --suppressExcessPropertyErrors compiler option

I want to suppress excess properties, the problem is that I'm unable to tell the compiler that these aren't excess properties, they are the properties of a proper subtype.我想抑制多余的属性,问题是我无法告诉编译器这些不是多余的属性,它们是正确子类型的属性。

Is there an equivalent to type declarations for literals?是否有等价于文字的类型声明? Is there a fundamental design of TypeScript that prevents such a construct from existing?是否有 TypeScript 的基本设计可以阻止这种构造的存在?

In a language such as Kotlin, this is trivial:在 Kotlin 等语言中,这是微不足道的:

val a: A = B()

You can separately declare the type of the variable and the type of the runtime object (must be the same type or a subtype).可以分别声明变量的类型和运行时对象的类型(必须是相同类型或子类型)。

In case this is an XY problem , the problem I was originally trying to solve looks like this:如果这是一个XY 问题,我最初试图解决的问题如下所示:

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

This does not compile because the first return is not explicitly an A , it is intended to be a B which is an A , but I have no way of specifying it as a B without resorting to one of the workarounds I mentioned above.这不会编译,因为第一个return不是明确的A ,它旨在成为BA ,但我无法在不诉诸我上面提到的解决方法之一的情况下将其指定为B I want something that looks like我想要看起来像的东西

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

// or

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

// or

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

It goes without saying that as B type assertions are not what I'm looking for here.不用说, as B类型的断言不是我在这里寻找的。 I don't want to bypass the type system, I just want to help the compiler check my object literal as a specified type.我不想绕过类型系统,我只想帮助编译器检查我的对象文字是否为指定类型。 This problem also applies to parameters:这个问题也适用于参数:

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

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

This fails, because I have no way of telling the compiler that I am passing a B , it thinks I am using excess properties.这失败了,因为我无法告诉编译器我正在传递B ,它认为我使用了多余的属性。 It only works if I use an extra variable like so:它仅在我使用像这样的额外变量时才有效:

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

foo(b)

Update: the main issue here seems to be that TypeScript does not have a "widening only assertion operator" or an "inline type annotation operator" (eg, return {a: 123} op A should fail but return {a: "", b: ""} op A should succeed).更新:这里的主要问题似乎是 TypeScript 没有“仅扩展断言运算符”或“内联类型注释运算符”(例如, return {a: 123} op A应该失败但return {a: "", b: ""} op A应该成功)。 There is an open suggestion for one, see microsoft/TypeScript#7481 but there's no real movement there.有一个开放的建议,请参阅 microsoft/TypeScript#7481,但那里没有真正的动向。 If you want to see this happen you might want to go to that issue and give it a 👍 and/or describe your use case if it's more compelling than what's already there.如果您想看到这种情况发生,您可能想要转到那个问题并给它一个 👍 和/或描述您的用例,如果它比现有的更引人注目。 In the absence of an explicit type operator for this, there are strategies and workarounds available.在没有明确的类型运算符的情况下,有可用的策略和解决方法。


It sounds like you are already aware of the most common options for dealing with this, but you don't like any of them.听起来您已经知道处理此问题的最常见选项,但您不喜欢其中任何一个。 I have a few other things you can try, but if your restrictions are:我还有一些其他的事情你可以尝试,但如果你的限制是:

  • no changes to your type definitions or annotations没有更改您的类型定义或注释
  • no changes to the emitted runtime code对发出的运行时代码没有更改
  • no type assertions没有类型断言

then your options are limited indeed and you might not find any answer satisfying.那么您的选择确实有限,您可能找不到任何令人满意的答案。


Background on what's happening:正在发生的事情的背景:

Object literals are an interesting exception to TypeScript's normal extendible object types.对象字面量是 TypeScript 的正常可扩展对象类型的一个有趣的例外。 In general, object types in TypeScript are not exact .通常,TypeScript 中的对象类型并不精确 A type definition like {a: string} only requires that a value must have a set of known properties.{a: string}这样的类型定义只要求一个值必须有一组已知的属性。 It does not require that a value must lack unknown properties .它不要求值必须缺少未知属性 So an object {a: "", b: 123, c: true} is a perfectly valid value of type {a: string} , because it has an a property whose value is assignable to string .所以一个对象{a: "", b: 123, c: true}是一个完全有效的{a: string}类型的值,因为它有a属性,其值可以赋值给string So it seems that there should be nothing with your code;所以看起来你的代码应该没有什么; your a is indeed a valid A , with or without context.您的a确实是有效的A ,无论是否有上下文。

But of course the compiler complains.但当然编译器会抱怨。 That's because you've assigned a fresh object literal to a variable annotated with a type with fewer known properties than the object literal has.那是因为您已经将一个新的对象字面量分配给一个变量,该变量的类型注释的已知属性比对象字面量少。 And thus it runs afoul of excess property checking , where object types are treated as exact.因此它与过度属性检查相冲突,其中对象类型被视为精确。 You can read the pull request implementing this feature and the rationale for doing this .您可以阅读实现此功能的拉取请求以及执行此操作基本原理 Those issues also detail the suggested workarounds for situations in which the excess property checking is undesired.这些问题还详细说明了针对不需要进行过多属性检查的情况的建议解决方法。


You've already mentioned making a helper function (eg, return is<A>({...}) ), assigning to an intermediate variable (eg, const ret = {...}; return ret; ), and using a type assertion (eg, return {...} as A ).您已经提到创建一个辅助函数(例如, return is<A>({...}) ),分配给一个中间变量(例如, const ret = {...}; return ret; ),并使用类型断言(例如, return {...} as A )。 Here are the other options as I see them:以下是我看到的其他选项:

  • Add an index signature you your A type so that it is explicitly open:为您的A类型添加索引签名,以便它显式打开:

     interface A { a: string; [k: string]: unknown; // any other property is fine } const a: A = { a: "", b: "" }; // okay now
  • Turn off excess property checking entirely with the --suppressExcessPropertyErrors compiler option (I don't really recommend this because it is such a wide-ranging change)使用--suppressExcessPropertyErrors编译器选项完全关闭多余的属性检查(我真的不推荐这样做,因为它是一个范围广泛的变化)

  • Use a union type like A | B使用联合类型,A | B A | B instead of A when annotating the type corresponding to the object literal:注释对象字面量对应的类型时,用A | B代替A

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

    Since B is a subtype of A , the type A | B由于BA的子类型,因此类型A | B A | B is structurally equivalent to A , so you can use it in place of A just about anywhere: A | B在结构上等同于A ,因此您几乎可以在任何地方使用它代替A

     const foo = (): A | B => { ... } // your impl here function onlyAcceptA(a: A) { } onlyAcceptA(foo()); // okay
  • use a type assertion just on the extra property, which requires using a computed property name :仅在额外属性上使用类型断言,这需要使用计算属性名称

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

    That really isn't any less type safe, but it does end up generating slightly different JavaScript ( {a: "", ["b"]: ""} ), assuming you are targeting a version of JavaScript that supports computed property names.这确实没有任何类型安全性降低,但它最终会生成略有不同的 JavaScript( {a: "", ["b"]: ""} ),假设您的目标是支持计算属性名称的 JavaScript 版本.

Everything else I can think of just changes your runtime code more, so I'll stop there.我能想到的其他一切只是对您的运行时代码进行了更多更改,因此我将就此打住。


Anyway, hope that gives you some ideas.无论如何,希望能给你一些想法。 Good luck!祝你好运!

Link to code 代码链接

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

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