【问题标题】:Does Typescript support "subset types"?Typescript 是否支持“子集类型”?
【发布时间】:2016-08-20 15:26:39
【问题描述】:

假设我有一个接口:

interface IUser {
  email: string;
  id: number;
  phone: string;
};

然后我有一个函数需要该类型的 子集(或完全匹配)。也许它会传递整个对象,使它只会传递{email: "t@g.com"}。我希望类型检查器同时允许两者。

例子:

function updateUser(user: IUser) {
  // Update a "subset" of user attributes:
  $http.put("/users/update", user);
}

Typescript 是否支持这种行为?我会发现它非常有用,尤其是像 Redux 这样的范例。

为了澄清,目标是:

  1. 避免重写接口并手动将所有属性设置为可选。
  2. 避免分配意外属性(例如拼写错误)。
  3. 避免使用命令式逻辑,例如 if 语句,这会失去编译时类型检查的好处。

更新:Typescript 已宣布支持mapped types,一旦发布,它应该可以解决这个问题。

【问题讨论】:

  • 不是Partial 你在找什么?
  • 当我问这个问题时,它们并不存在。我后来添加了一个链接,在它们被添加到 TS 之后。

标签: interface typescript


【解决方案1】:

您可以将部分或所有字段声明为可选字段。

interface IUser {
  email: string; // not optional
  id?: number; // optional 
  phone?: string; // optional
};

【讨论】:

  • 这很接近,但不是我想要的。对于 IUser,所有字段都是必需的。只是在某些情况下,我希望它是部分匹配。
  • 要求是矛盾的,你不能让一个字段同时是可选的和必填的。 :P 我想你可以使用 2 个接口来获得它..
  • 很多时候你会想要这个并且其他语言(比如 Flow)允许它。出于 DRYness 的考虑,您希望在某些情况下允许一个子集。考虑 Backbone 模型更新,其中您已命名参数,这些参数必须是模型属性的子集。
  • 我同意@toskv。如果在某些情况下这些字段可能不存在,则这些字段不是强制性的。您可以为这些不同的场景定义不同的接口。无论如何,我不确定为什么有人对他的回答投了反对票,这是正确的。
【解决方案2】:

你可以把它分成不同的界面:

interface IUser {
    id: number;
};

interface IUserEmail extends IUser {
    email: string;
}

interface IUserPhone extends IUser {
    phone: string;
}

让您的方法接收基本的IUser 接口,然后检查您需要的字段:

function doit(user: IUser) {
    if (user.email) {

    } else if (user.phone) {

    }
}

【讨论】:

    【解决方案3】:

    如果我正确理解了这个问题,你想要 Flow 的 $Shape 之类的东西

    所以,在一个地方,你可能有一些需要类型的东西

    interface IUser {
      email: string;
      id: number;
      phone: string;
    };
    

    然后,在另一个地方,您想要一个与 IUser 具有相同类型的类型,只是所有字段现在都是可选的。

    interface IUserOptional {
      email?: string;
      id?: number;
      phone?: string;
    };
    

    您想要一种基于IUser 自动生成IUserOptional 的方法,而无需再次写出类型。

    现在,我认为这在 Typescript 中是不可能的。在 2.0 中情况可能会发生变化,但我认为我们在 Typescript 中甚至还没有接近这样的东西。

    你可以查看一个预编译器,它会在 typescript 运行之前为你生成这样的代码,但这听起来并不容易。

    考虑到这个问题,我只能建议您改用 Flow。在流程中,您只需执行$Shape<IUser> 即可以编程方式生成您想要的类型。当然,Flow 在许多大大小小的方面都与 Typescript 不同,因此请记住这一点。 Flow 不是编译器,因此您不会得到 Enums 和实现接口的类之类的东西

    【讨论】:

    • 这违背了创建接口的目的——它们应该是可重用和可组合的。这个假设只是为了必填字段而维护同一实体的两个版本。
    【解决方案4】:

    映射类型的正确解决方案:

    updateUser<K extends keyof IUser>(userData: {[P in K]: IUser[P]}) {
        ...
    }
    

    【讨论】:

    • Partial 更容易。但是您的回答消除了我对这种情况下的语法的一些疑问。谢谢!
    【解决方案5】:

    Typescript 现在支持部分类型。

    创建分部类型的正确方法是:

    type PartialUser = Partial<IUser>;
    

    【讨论】:

    • 有没有办法指定一个特定的类的部分子集?即 id 和 email 是强制性的但电话不存在的课程?
    • @James 请在下面查看我的解决方案。这就是你要的。
    • @James Pick 类型是您所需要的。如果您根本不想允许电话,请使用Pick&lt;IUser, 'id' | 'email'&gt;,如果您想允许电话但不需要它(同时需要 id 和电子邮件),请使用 Pick&lt;IUser, 'id' | 'email'&gt; &amp; Partial&lt;IUser&gt;。详情见我的回答。
    【解决方案6】:

    你想要的是这个

    type Subset<T extends U, U> = U;
    

    这确保了 U 是 T 的一个子集并返回 U 作为一个新类型。例如:

    interface Foo {
     name: string;
     age: number;
    }
    
    type Bar = Subset<Foo, {
     name: string;
    }>;
    

    您不能向 Bar 添加不属于 Foo 的新属性 - 并且您不能以不兼容的方式更改类型。这也适用于嵌套对象。

    【讨论】:

    • 哟,这很酷,我不知道你可以像这样在类型分配中进行前向引用,但我想这是有道理的。光滑的东西。
    【解决方案7】:

    值得注意的是,Partial&lt;T&gt;,正如接受的答案中所建议的那样,将 all 字段设为可选,这不一定是您需要的。

    如果您想让某些字段成为必填项(例如idemail),则需要将其与Pick 结合起来:

    type UserWithOptionalPhone = Pick<IUser, 'id' | 'email'> & Partial<IUser>
    

    一些解释:

    Pick 的作用是让您简洁地指定接口的子集(无需创建一个重复字段类型的全新接口,正如其他答案所建议的那样),然后让您使用这些接口,然后 只有那些字段。

    function hello1(user: Pick<IUser, 'id' | 'email'>) {
    }
    
    hello1({email: '@', id: 1}); //OK
    
    hello1({email: '@'}); //Not OK, id missing
    
    hello1({email: '@', id: 1, phone: '123'}); //Not OK, phone not allowed
    
    

    现在,这并不是我们所需要的,因为我们想要允许,而不是要求电话。为此,我们通过创建intersection type 来“合并”我们类型的部分版本和“挑选”版本,然后将idemail 作为必填字段,而其他所有内容都是可选的——这正是我们的方式想要它。

    function hello2(user: Pick<IUser, 'id' | 'email'> & Partial<IUser>) {
    }
    
    hello2({email: '@', id: 1}); //OK
    
    hello2({email: '@', id: 1, phone: '123'}); //OK
    
    hello2({email: '@'}); //Not OK, id missing
    

    【讨论】:

      猜你喜欢
      • 2017-06-26
      • 2012-10-04
      • 1970-01-01
      • 2014-08-26
      • 2019-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-21
      相关资源
      最近更新 更多