在 TypeScript 中将单个属性设为可选

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43159887/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:24:42  来源:igfitidea点击:

Make a single property optional in TypeScript

typescript

提问by DallonF

In TypeScript, 2.2...

在 TypeScript 中,2.2...

Let's say I have a Person type:

假设我有一个 Person 类型:

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

And I'd like to create a function that returns a Person, but doesn't require a nickname:

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

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

What should be the type of input? I'm looking for a dynamic way to specify a type that is identical to Personexcept that nicknameis optional (nickname?: string | undefined). The closest thing I've figured out so far is this:

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

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

but that's not quite what I'm looking for, since I have to specify all the types that are requiredinstead of the ones that are optional.

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

回答by piecyk

You can also do something like this, partial only some of the keys.

你也可以做这样的事情,只是部分的一些键。

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'>

回答by DallonF

Update:

更新:

As of TypeScript 2.8, this is supported much more concisely by Conditional Types! So far, this also seems to be more reliable than previous implementations.

从 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};
}

As before, MakePersonInputis equivalent to:

和以前一样,MakePersonInput相当于:

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

Outdated:

过时:

As of TypeScript 2.4.1, it looks like there's another option available, as proposed by GitHub user ahejlsberg in a thread on type subtraction: https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458

从 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};
}

According to Intellisense, MakePersonInputis equivalent to:

根据 Intellisense,MakePersonInput相当于:

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

which looks a little funny but absolutely gets the job done.

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

On the downside, I'm gonna need to stare at that Difftype for a while before I start to understand how it works.

不利的一面是,Diff在我开始了解它的工作原理之前,我需要盯着这种类型看一会儿。

回答by Philip Bulley

For a plug and play solution, consider using the brilliant utility-typespackage:

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

npm i utility-types --save

Then simply make use of Optional<T, K>:

然后只需使用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;
// }

回答by Brian Gorman

Ok well what you are really describing is two different "Types" of people (i.e. Person types) .. A normal person and a nick named person.

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

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

interface NicknamedPerson extends Person {
    nickname: string;
}

Then in the case where you don't really want a nicknamed person but just a person you just implement the Person interface.

然后,如果您真的不想要昵称的人,而只是想要一个人,您只需实现 Person 接口。

An alternative way to do this if you wanted to hang on to just one Person interface is having a different implementation for a non nicknamed 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);
     }
}

This enables you to still assign a nickname (which is just the persons name in case of an absence of a nickname) and still uphold the Person interface's contract. It really has more to do with how you intend on using the interface. Does the code care about the person having a nickname? If not, then the first proposal is probably better.

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

回答by DallonF

After a lot of digging, I think what I'm trying to do just isn't possible in TypeScript... yet. When spread/rest typesland, I think it will be, though, with syntax something along the lines of { ...Person, nickname?: string }.

经过大量挖掘后,我认为我正在尝试做的事情在 TypeScript 中是不可能的......。当传播/休息类型登陆时,我认为它的语法将类似于{ ...Person, nickname?: string }.

For now, I've gone with a more verbose approach, declaring the properties that arerequired:

现在,我已经有一个更详细的办法,宣布该属性必需的:

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

This unfortunately requires me to update MakePersonInputwhenever I add more required properties to Person, but it's impossible to forget to do this, because it will cause a type error in makePerson.

这不幸的是我需要更新MakePersonInput,每当我添加更多需要性能Person,但它不可能会忘记这样做,因为这将导致在一个类型的错误makePerson

回答by Tim Krins

Here is my Typescript 3.5+ Optional utility type

这是我的 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'>