在TypeScript中使单个属性可选

时间:2017-04-01 17:24:07

标签: typescript

在TypeScript中,2.2 ......

我们说我有一个人类型:

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}

我想创建一个返回Person的函数,但不需要昵称:

function makePerson(input: ???): Person {
  return {...input, nickname: input.nickname || input.name};
}

input的类型应该是什么?我正在寻找一种动态方式来指定与Person相同的类型,但nickname是可选的(nickname?: string | undefined)。到目前为止,我最接近的是:

type MakePersonInput = Partial<Person> & {
  name: string;
  hometown: string;
}

但这并不是我想要的,因为我必须指定所需的所有类型而不是可选的类型。

6 个答案:

答案 0 :(得分:16)

您还可以执行类似的操作,只对某些键进行部分操作。

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}

type MakePersonInput = PartialBy<Person, 'nickname'>

答案 1 :(得分:15)

这是我的Typescript 3.5+可选实用程序类型

type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;

// and your use case
type MakePersonInput = Optional<Person, 'nickname'>

// and if you wanted to make the hometown optional as well
type MakePersonInput = Optional<Person, 'hometown' | 'nickname'>

答案 2 :(得分:13)

有关即插即用解决方案,请考虑使用出色的utility-types软件包:

npm i utility-types --save

然后只需使用Optional<T, K>

import { Optional } from 'utility-types';

type Person = {
  name: string;
  hometown: string;
  nickname: string;
}

type PersonWithOptionalNickname = Optional<Person, 'nickname'>;

// Expect:
//
// type PersonWithOptionalNickname {
//   name: string;
//   hometown: string;
//   nickname?: string;
// }

答案 3 :(得分:9)

<强>更新

从TypeScript 2.8开始,条件类型更加简洁地支持了这一点!到目前为止,这似乎比以前的实现更可靠。

type Overwrite<T1, T2> = {
    [P in Exclude<keyof T1, keyof T2>]: T1[P]
} & T2;

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}

type MakePersonInput = Overwrite<Person, {
  nickname?: string;
}>

function makePerson(input: MakePersonInput): Person {
  return {...input, nickname: input.nickname || input.name};
}

和以前一样,MakePersonInput相当于:

type MakePersonInput = {
    name: string;
    hometown: string;
} & {
    nickname?: string;
}

<强>过时:

从TypeScript 2.4.1开始,看起来还有其他选项可供选择,正如GitHub用户ahejlsberg在类型减法的线程中提出的那样:https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}
type MakePersonInput = Overwrite<Person, {
  nickname?: string
}>
function makePerson(input: MakePersonInput): Person {
  return {...input, nickname: input.nickname || input.name};
}

根据Intellisense,MakePersonInput相当于:

type MakePersonInput = {
    name: string;
    hometown: string;
} & {
    nickname?: string;
}

看起来有点搞笑,但绝对可以完成工作。

不利的一面是,在我开始了解它是如何工作之前,我需要盯着Diff类型一段时间。

答案 4 :(得分:1)

经过大量的挖掘后,我认为我想要做的只是在TypeScript中不可能...... 还是。当spread/rest types落地时,我认为它的语法与{ ...Person, nickname?: string }一样。

目前,我已经采用了更详细的方法,声明了 所需的属性:

type MakePersonInput = Partial<Person> & {
  name: string;
  hometown: string;
};
function makePerson(input: MakePersonInput): Person {
  return {...input, nickname: input.nickname || input.name};
}

不幸的是,每当我向MakePersonInput添加更多必需属性时,我都需要更新Person,但是不可能忘记这样做,因为它会导致makePerson中的类型错误。

答案 5 :(得分:0)

好的,你真正描述的是两种不同的“类型”的人(即人物类型)。一个普通人和一个名叫人的昵称。

interface Person {
    name: string;
    hometown: string;
}

interface NicknamedPerson extends Person {
    nickname: string;
}

然后,如果您不想要一个昵称的人,而只是一个人,那么您只需要实现Person界面。

如果你想只挂在一个Person界面上,另一种方法就是为一个非昵称的人提供不同的实现:

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}
class NicknamedPerson implements Person {
    constructor(public name: string, public hometown: string, public nickname: string) {}
}

class RegularPerson implements Person {
    nickname: string;
    constructor(public name: string, public hometown: string) {
        this.nickname = name;
    }
}

makePerson(input): Person {
     if(input.nickname != null) {
       return new NicknamedPerson(input.name, input.hometown, input.nickname);
     } else {
       return new RegularPerson(input.name, input.hometown);
     }
}

这使您仍然可以指定一个昵称(在没有昵称的情况下只是人名),并且仍然支持Person接口的合同。它实际上与你打算如何使用界面有关。代码是否关心有昵称的人?如果没有,那么第一个提案可能更好。