- 原文地址:How to Make Certain Properties Optional in TypeScript
- 原文作者:Dawid Witulski
- 译文出自:掘金翻译计划
接口可选属性的声明,你可以在属性名旁边加上一个问号 ? 来实现。
interface Person {
uuid: string;
name: string;
surname: string;
sex: string;
height: number;
age: number;
pet?: Animal;
}
interface Animal {
name: string;
age: number;
}
上面例子中,pet 是一个可选属性。它很有用,每次你想对 Person 的 pet 属性执行一些操作时,你需要检查它是否声明。 pet?: Animal; 写法就是 pet: Animal | undefined; 的简写。
小案例调研
我们假设,你想实现一个函数,该函数返回名字的首字母和姓,比如 J. Doe.。
function personShortName(name: string, surname: string): string {
return `${name[0]} ${surname}`;
}
const person1: Person = {
/* Person properties */
};
personShortName(person1.name, person1.surname);
而不像上述代码,每次调用 personShortName() 去访问 name 和 surname ,我们可以使用 Person 类型的对象作为函数参数。
function personShortName(person: Person): string {
return `${person.name[0]} ${person.surname}`;
}
当然,我们也可以使用解构来简化这个函数,如下所示:
function personShortName({ name, surname }: Person): string {
return `${name[0]} ${surname}`;
}
看起来不错,但有限制。你需要一个 Person 类型的对象来调用一个简单的函数。现在,如果你想在其他地方调用它,你需要创建一个新的 Person 。
❌ personShortName({ name: 'Robert', surname: 'Doe' });
上面的示例无法执行,因为 { name: string, surname: string } 与 Person 类型不匹配。
那么如何实现双赢呢?
Typescript 有一个 Partial<T> 实用工具类型,这使得 T 的所有属性都是可选的。如果在 personShortName() 中使用 Partial<Person> 而不是 Person 作为参数类型,那么前面的示例就可执行了:
function personShortName({name, surname}: Partial<Person>): string {
return `${name[0]} ${surname}`;
}
personShortName({ name: 'Robert', surname: 'Doe' });
但是我们这里有一个副作用,{ name: string; surname: string } 会变为 { name: string | undefined; surname: string | undefined; }。
然后,为了防止一些奇怪的结果,比如 “u. undefined”,我们需要在函数内部执行一些类型检查,确保每个需要用到的变量都是被定义了。
function personShortName({ name, surname }: Partial<Person>): string | null {
return name && surname ? `${name[0]} ${surname}` : null;
}
现在我们已经实现了另一个副作用,函数可能返回null,这可能强制在使用它的地方进行一些额外的检查。
但是别担心,这里有个更好的办法。TypeScript 提供了另一个实用工具类型—— Pick<T, Keys> ,从 T 中选择一些属性 Keys 。
function personShortName({
name,
surname,
}: Pick<Person, 'name' | 'surname'>): string {
return `${name[0]} ${surname}`;
}
通过这种方式我们实现了我们的目标——我们允许通过两种方式调用这个函数,传递一个有效的 Person 对象或仅仅需要的变量作为参数。
personShortName({ name: 'John', surname: 'Doe' });
const someone: Person = { name: 'John', ... }
personShortName(someone);
延伸学习
上述解决方案中,你无法访问 personShortName 函数中的其余 Person 属性。让我们假设你希望实现一个只在最简单变量中使用 name 和 surname ,但你也希望访问其他 Person 属性。
function personShortName(person: Person): string {
const { name, surname, uuid } = person;
if (uuid) {
const userRole = getUserRole(uuid);
return `${name[0]} ${surname}, ${userRole}`;
}
/* some other operations on persons params */
return `${name[0]} ${surname}`;
}
这种实现不允许你直接使用带有 name 和 surname 的简单调用。但是,当没有 uuid 时,我们不需要所有的 Person 属性只是为了返回简短的名称。我们需要访问所有 Person 属性,但只需要 name 和 surname 。也许我们让 person 定义为 Person | Pick<Person, 'name' | 'surname'>。
function personShortName(person: Person | Pick<Person, 'name' | 'surname'>)): string {
❌ const { name, surname, uuid } = person;
if (uuid) {
const userRole = getUserRole(uuid);
return `${name[0]} ${surname}, ${userRole}`;
}
/* some other operations on persons params */
return `${name[0]} ${surname}`;
}
这是一个好主意,但是还是行不通。编译器将报错:属性uuid 在类型{ name: string, surname: string } 上不存在。
解决方案就是组合
这种方法是一个很好的方向,因为我们生命需要一个完整的 Person 或 { name, surname }。但事实上,我们不想要整个 Person 或 { name, surname }。我们想要完成的事情是让所有的 Person 属性都是可选的,并且 name, surname 是必需的。我们可以通过组合 Partial<T> 和 Pick<T, Keys> 使用工具类型来实现这一点。
function personShortName(person: Partial<Person> & Pick<Person, 'name' | 'surname'>)): string {
const { name, surname, uuid } = person;
if (uuid) {
const userRole = getUserRole(uuid);
userRole = getUserRole(uuid);
return `${name[0]} ${surname}, ${userRole}`;
}
/* some other operations on persons params */
return `${name[0]} ${surname}`;
}
上面的语法,Partial<Person> & Pick<Person, 'name' | 'surname'> 给了我们这样的类型:
person: {
uuid?: string;
name: string; <- required
surname: string; <- required
sex?: string;
height?: number;
age?: number;
pet?: Animal;
}
通过这种方式,我们可以定义一些必需的属性,而其他属性则成为可选的。希望你会发现它很有用。