Typescript是否支持“子集类型”?

时间:2016-04-26 16:49:33

标签: interface typescript

假设我有一个界面:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <t:RequestServerVersion Version="Exchange2013_SP1" />
  </soap:Header>
  <soap:Body>
    <m:UpdateItem MessageDisposition="SaveOnly" ConflictResolution="AlwaysOverwrite">
      <m:ItemChanges>
        <t:ItemChange>
          <t:ItemId Id="AAMkAGVlOTZjNTM3LWVjNjgtNGZlNi04MTBkLWIyNjNjNWEyY2VlNABGAAAAAABpsgv3HB+wQJRg4K+r7AmBBwBJi9ckXu/REb74AIBfn0G8AAAUrOs1AACN8cPrPdSYR5RdhR69ULJ0AAACOkAqAAA=" ChangeKey="CQAAABYAAACN8cPrPdSYR5RdhR69ULJ0AAACR0YO" />
          <t:Updates>
            <t:SetItemField>
              <t:FieldURI FieldURI="item:Categories" />
              <t:Message>
                <t:Categories>
                  <t:String>Muktader</t:String>
                </t:Categories>
              </t:Message>
            </t:SetItemField>
          </t:Updates>
        </t:ItemChange>
      </m:ItemChanges>
    </m:UpdateItem>
  </soap:Body>
</soap:Envelope>

然后我有一个期望该类型的子集(或完全匹配)的函数。也许它将传递整个对象,使它只会传入interface IUser { email: string; id: number; phone: string; }; 。我希望类型检查器允许两者。

示例:

{email: "t@g.com"}

Typescript是否支持这种行为呢?我觉得它非常有用,特别是像Redux这样的范例。

澄清一下,目标是:

  1. 避免重写接口并手动将所有属性设置为可选。
  2. 避免分配意外的属性(例如拼写错误)。
  3. 避免使用function updateUser(user: IUser) { // Update a "subset" of user attributes: $http.put("/users/update", user); } 语句等命令式逻辑,这会失去编译时类型检查的好处。
  4. 更新:Typescript宣布支持mapped types,一旦发布就应该解决这个问题。

7 个答案:

答案 0 :(得分:25)

Typescript现在支持部分类型。

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

type PartialUser = Partial<IUser>;

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:2)

这是你想要的

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

这可以确保U是T的子集,并将U作为新类型返回。例如:

interface Foo {
 name: string;
 age: number;
}

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

您不能将不属于Foo的新属性添加到Bar中,也不能以不兼容的方式更改类型。这也可以递归地作用于嵌套对象。

答案 4 :(得分:1)

您可以将其分隔到不同的界面:

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) {

    }
}

答案 5 :(得分:1)

如果我正确理解了这个问题,你需要像Flow $Shape

这样的东西

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

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

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

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

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

现在,我不认为这可以在Typescript中实现。事情可能会在2.0中发生变化,但我认为我们甚至还没有接近过类似的东西。

你可以看一下预编译器,它会在打字稿之前为你生成这样的代码,但这听起来不是一件容易的事情。

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

答案 6 :(得分:0)

值得注意的是,Partial<T>如接受的答案所示,使 all 字段为可选字段,不一定是您需要的字段。

如果您想将某些字段设为必填字段(例如idemail),则需要将其与Pick结合使用:

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

一些解释:

Pick的作用是让您简洁地指定接口的子集(如其他答案所建议的那样,无需创建重复字段类型的全新接口),然后让您使用它们,而< em>仅那些字段。

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

现在,这不是我们真正想要的,因为我们想要允许,但不是 require 电话。为此,我们通过创建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