DRY 原则(Don't repeat yourself)是软件开发中最重要的原则之一,即不要重复自己。应该避免在代码中的两个或多个地方存在重复的业务逻辑。
在 TypeScript 中,映射类型可以帮助我们避免编写重复的代码,它可以根据现有类型和定义的一些规则来创建新类型。下面就来看一下什么是映射类型以及如何构建自己的映射类型。
在介绍映射类型之前,先来看一些前置知识。
在 TypeScript 中,我们可以通过按名称查找属性来访问它的类型:
type AppConfig = {
username: string;
layout: string;
};
type Username = AppConfig["username"];
在这个例子中,通过 AppConfig 类型的索引 username 获取到其类型 string,类似于在 JavaScript 中通过索引来获取对象的属性值。
当类型属性的实际名称是未知的,但它们将引用的数据类型已知时,索引签名就很方便。
type User = {
name: string;
preferences: {
[key: string]: string;
}
};
const currentUser: User = {
name: 'Foo Bar',
preferences: {
lang: 'en',
},
};
const currentLang = currentUser.preferences.lang;
在上面的例子中,currentLang 的类型是 string 而不是 any。此功能与 keyof 运算符一起搭配使用是使映射类型成为可能的核心之一。
联合类型是两种或多种类型的组合。它表明值的类型可以是联合中包含的任何一种类型。
type StringOrNumberUnion = string | number;
let value: StringOrNumberUnion = 'hello, world!';
value = 100;
下面是一个更复杂的例子,编译器可以为联合类型提供一些高级保护:
type Animal = {
name: string;
species: string;
};
type Person = {
name: string;
age: number;
};
type AnimalOrPerson = Animal | Person;
const value: AnimalOrPerson = loadFromSomewhereElse();
console.log(value.name); // :white_check_mark:
console.log(value.age); // :x:
if ('age' in value) {
console.log(value.age); // :white_check_mark:
}
在这个例子中,因为 Animal 和 Person 都有 name 属性,所以第 15 行的 value.name 可以正常输出,没有错误。而第 16 行的 value.age 会编译错误,因为如果 value 是 Animal 类型,则 value 是没有 age 属性的。在第 19 行的 if 块中,因为只有 value 存在 age 属性才能进入这个代码块。所以,在这个 if 块中,value 一定是 Person,TS 可以知道 value 一定是具有 age 属性的,所以编译正确。
keyof 类型运算符返回传递给它的类型的 key 的联合。
type AppConfig = {
username: string;
layout: string;
};
type AppConfigKey = keyof AppConfig;
在这个例子中,AppConfigKey 类型会被解析为"username" | "layout"。它可以与索引签名一起使用:
type User = {
name: string;
preferences: {
[key: string]: string;
}
};
type UserPreferenceKey = keyof User["preferences"];
这里,UserPreferenceKey 类型被解析为 string | number。
元组是一种特殊的数组类型,其中数组的元素可能是特定索引处的特定类型。它们允许 TypeScript 编译器围绕值数组提供更高的安全性,尤其是当这些值属于不同类型时。
例如,TypeScript 编译器能够为元组的各种元素提供类型安全:
type Currency = [number, string];
const amount: Currency = [100, 'USD'];
function add(values: number[]) {
return values.reduce((a, b) => a + b);
}
add(amount);
// Error: Argument of type 'Currency' is not assignable to parameter of type 'number[]'.
// Type 'string' is not assignable to type 'number'.
上面的代码中会报错,Currency 类型的参数不能分配给“number[]”类型的参数,string 类型不能分配给 number 类型。
当访问超出元组定义类型的索引处的元素时,TypeScript 能够进行提示:
type LatLong = [number, number];
const loc: LatLong = [48.858370, 2.294481];
console.log(loc[2]);
// Error: Tuple type 'LatLong' of length '2' has no element at index '2'.
这里,元组类型 LatLong 只有两个元素,当试图访问第三个元素时,就会报错。
条件类型是一个表达式,类似于 JavaScript 中的三元表达式,其语法如下:
T extends U ? X : Y
来看一个实际的例子:
type ConditionalType = string extends boolean ? string : boolean;
在上面的示例中,ConditionalType 的类型将是 boolean,因为条件string extends boolean 是始终为 false。
在 TypeScript 中,当需要从另一种类型派生(并保持同步)另一种类型时,使用映射类型会特别有用。
// 用户的配置值
type AppConfig = {
username: string;
layout: string;
};
// 用户是否有权更改配置值
type AppPermissions = {
changeUsername: boolean;
changeLayout: boolean;
};
在上面的代码中,AppConfig 和 AppPermissions 之间是存在隐式关系的,每当向 AppConfig 添加新的配置值时,AppPermissions 中也必须有相应的布尔值。
这里可以使用映射类型来管理两者之间的关系:
type AppConfig = {
username: string;
layout: string;
};
type AppPermissions = {
[Property in keyof AppConfig as `change${Capitalize<Property>}`]: boolean
};
在上面的代码中,只要 AppConfig 中的类型发生变化,AppPermissions 就会随之变化。实现了两者之间的映射关系。
在 TypeScript 和 JavaScript 中,最常见的映射就是 Array.prototype.map():
[1, 2, 3].map(value => value.toString()); // ["1", "2", "3"]
这里,我们将数组中的数字映射到其字符串的表示形式。因此,TypeScript 中的映射类型意味着将一种类型转换为另一种类型,方法就是对其每个属性进行转换。
下面来通过一个例子来深入理解一下映射类型。对设备定义以下类型,其包含制造商和价格属性:
type Device = {
manufacturer: string;
price: number;
};
为了让用户更容易理解设备信息,因此为对象添加一个新类型,该对象可以使用适当的格式来格式化设备的每个属性:
type DeviceFormatter = {
[Key in keyof Device as `format${Capitalize<Key>}`]: (value: Device[Key]) => string;
};
我们来拆解一下上面的代码。Key in keyof Device 使用 keyof 类型运算符生成 Device 中所有键的并集。将它放在索引签名中实际上是遍历 Device 的所有属性并将它们映射到 DeviceFormatter 的属性。
format${Capitalize<Key>} 是映射的转换部分,它使用 key 重映射和模板文字类型将属性名称从 x 更改为 formatX。
(value: Device[Key]) => string; 利用索引访问类型 Device[Key] 来指示格式化函数的 value 参数是格式化的属性的类型。因此,formatManufacturer 接受一个 string(制造商),而 formatPrice 接受一个number(价格)。
下面是 DeviceFormatter 类型的样子:
type DeviceFormatter = {
formatManufacturer: (value: string) => string;
formatPrice: (value: number) => string;
};
现在,假设将第三个属性 releaseYear 添加到 Device 类型中:
type Device = {
manufacturer: string;
price: number;
releaseYear: number;
}
由于映射类型的强大功能,DeviceFormatter 类型会自动扩展为如下类型,无需进行任何额外的工作:
type DeviceFormatter = {
formatManufacturer: (value: string) => string;
formatPrice: (value: number) => string;
formatReleaseYear: (value: number) => string;
};
TypeScript 附带了许多用作实用程序的映射类型,最常见的包括 Omit、Partial、Readonly、Readonly、Exclude、Extract、NonNullable、ReturnType 等。下面来看看其中的两个是如何构建的。
Partial 是一种映射类型,可以将已有的类型属性转换为可选类型,并通过使用与 undefined 的联合使类型可以为空。
interface Point3D {
x: number;
y: number;
z: number;
}
type PartialPoint3D = Partial<Point3D>;
这里的 PartialPoint3D 类型实际是这样的:
type PartialPoint3D = {
x?: number;
y?: number;
z?: number;
}
当我们鼠标悬浮在 Partial 上时,就会看到它的定义:把它拿出来:
type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
下面来拆解一下这行代码:
Exclude 是一种映射类型,可让有选择地从类型中删除属性。其定义如下:
type Exclude<T, U> = T extends U ? never : T
它通过使用条件类型从 T 中排除那些可分配给 U 的类型,并且在排除的属性上返回 nerver。
type animals = 'bird' | 'cat' | 'crocodile';
type mamals = Exclude<animals, 'crocodile'>; // 'bird' | 'cat'
通过上面的对 TypeScript 内置实用程序类型的原理解释,对映射类型有了更深的理解。最后,我们来构建一个自己的映射类型:Optional,它可以将原类型中指定 key 的类型置为可选的并且可以为空。
我们可以这样做:
实现代码及测试用例如下:
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
type Person = {
name: string;
surname: string;
email: string;
}
type User = Optional<Person, 'email'>;
// 现在 email 属性是可选的
type AnonymousUser = Optional<Person, 'name' | 'surname'>;
// 现在 email 和 surname 属性是可选的
注意,这里使用 K extends keyof T 来确保只能传递属于类型/接口的属性。否则,TypeScript 将在编译时抛出错误。
映射类型的一大优点就是它们的可组合性:可以组合它们来创建新的映射类型。
上面使用了已有的实用程序类型实现了我们想要的 Optional。当然,我们也可以在不使用任何其他映射类型的情况下重新创建 Optional 映射类型实用程序:
type Optional<T, K extends keyof T> =
{ [P in K]?: T[P] }
&
{ [P in Exclude<keyof T, K>]: T[P] };
上面的代码结合了两种类型:
来源: 前端充电宝
在JavaScript中存在这样两种原始类型:Null与Undefined。这两种类型常常会使JavaScript的开发人员产生疑惑,在什么时候是Null,什么时候又是Undefined?Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。
主要介绍了JS中检测数据类型的几种方式,typeof运算符用于判断对象的类型,但是对于一些创建的对象,它们都会返回\'object\',有时我们需要判断该实例是否为某个对象的实例,那么这个时候需要用到instanceof运算符
对于object和number、string、boolean之间的转换关系,ToPrimitive是指转换为js内部的原始值,如果是非原始值则转为原始值,调用valueOf()和toString()来实现。
Undefined类型表示未定义,它的类型只有一个值为undefined。undefined和null有一定的表意差别。非整数的Number类型无法使用 == 或 === 来比较,因为 JS 是弱类型语言,所以类型转换发生非常频繁
近在做项目代码重构,其中有一个要求是为代码添加智能提示和类型检查。智能提示,英文为 IntelliSense,能为开发者提供代码智能补全、悬浮提示、跳转定义等功能,帮助其正确并且快速完成编码。
基本类型:按值访问,可以操作保存在变量中实际的值;引用类型数据存在堆内存,而引用存在栈区,也就是说引用类型同时保存在栈区和堆区,关于==的执行机制,ECMASript有规范,因为==前后的值交换顺序,返回的值也是一样的,所以在此对规范做出如下总结
JavaScript 是一种弱类型或者说动态类型语言。所以你不用提前声明变量的类型,在程序运行时,类型会被自动确定,你也可以使用同一个变量保存不同类型的数据。
js的值传递和引用(地址)传递:js的5种基本数据类型 number,string,null,undefined,boolean 在赋值传递时是值传递,js的引用数据类型(object,array,function)进行引用传递,其实底层都是对象。
JS中所有的值都可以转换成布尔类型 使用Boolean()或者 !!(两个感叹号),JS中所有的值都可以转换成数字类型,使用Number()或+。数字类型转换场景目的只有一个,用于计算,将后台传递的数据,从字符串转换为数字并参与计算
众所周知,JS在很多情况下会进行强制类型转换,其中,最常见两种是:1.使用非严格相等进行比较,对==左边的值进行类型转换2.在if判断时,括号内的值进行类型转换,转化为布尔值
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!