在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;
}
但这并不是我想要的,因为我必须指定所需的所有类型而不是可选的类型。
答案 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接口的合同。它实际上与你打算如何使用界面有关。代码是否关心有昵称的人?如果没有,那么第一个提案可能更好。