类型是 TypeScript 的灵魂,很多时候我们需要种种新的类型。工具类型是 TypeScript 的一种特殊类型,为了解决某一特定的类型问题,得到一种新的类型。有的就是一种通用类型;有的则可以对现有类型进行一定的转换,从而得到一种新的类型。
TypeScript 内置了很多工具类型,熟练运用它们,可以让我们开发工作事半功倍。
TypeScript 的工具类型文档:https://www.typescriptlang.org/docs/handbook/utility-types.html
本文出自:https://fjolt.com/series/typescript-utility-types,在此表示感谢
下面我们将逐一介绍这些内置工具类型。
Record是一种基于键值对的类型,可以创建固定格式的数据结构,用于组合复杂的数据类型。
假设有一个数据集如下:
const myData = {
"123-123-123" : { firstName: "John", lastName: "Doe" },
"124-124-124" : { firstName: "Sarah", lastName: "Doe" },
"125-125-125" : { firstName: "Jane", lastName: "Smith" }
}
这个数据集有一个string类型的 ID 作为键,所有值类型都含有string类型的firstName和string类型的lastName两个字段。
对于这种数据类型,Record是最适合的。我们可以这么定义类型:
type User = {
firstName: string,
lastName: string
}
const myData:Record<string, User> = {
"123-123-123" : { firstName: "John", lastName: "Doe" },
"124-124-124" : { firstName: "Sarah", lastName: "Doe" },
"125-125-125" : { firstName: "Jane", lastName: "Smith" }
}
Record类型格式是Record<K, T>,其中,K是键的类型,T是值的类型。
上面代码中,我们定义了一种新的类型User,用于描述值类型,将键的类型指定为string。
有时候,我们的键值只是某些可选值的集合。例如:
const myData = {
"uk" : { firstName: "John", lastName: "Doe" },
"france" : { firstName: "Sarah", lastName: "Doe" },
"india" : { firstName: "Jane", lastName: "Smith" }
}
我们假设数据集如上,所以键值只允许是uk、france和india之一。这样的仅有有限值组成的集合类型叫做联合 union。
在这个例子中,我们可以定义User类型以及一个联合类型作为键:
type User = {
firstName: string,
lastName: string
}
type Country = "uk" | "france" | "india";
const myData:Record<Country, User> = {
"uk" : { firstName: "John", lastName: "Doe" },
"france" : { firstName: "Sarah", lastName: "Doe" },
"india" : { firstName: "Jane", lastName: "Smith" }
}
利用联合类型,我们就可以确保Record的键为三个可选值之一。
有时我们需要确保对象有一些属性是必须的,甚至这些属性是可选的,也必须给值。为了达到这一目的,TypeScript 提供了一个工具类型Required。
默认情况下,我们在 TypeScript 中定义的新类型,所有属性都自动成为必须的:
type User = {
firstName: string,
lastName: string
}
let firstUser:User = {
firstName: "John"
}
上面代码中,firstUser只有一个firstName属性,没有lastName,那么,TypeScript 会报错:
Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'User'.
如果我们希望这个属性是可选的,那么,我们需要在类型定义中添加?标注:
type User = {
firstName: string,
lastName?: string
}
let firstUser:User = {
firstName: "John"
}
例如上面的代码,我们把lastName改成lastName?,此时,lastName就成为可选的,firstUser也就能编译通过了。
至此,一切都很好。但是,现在又有一个问题:虽然在类型定义中,lastName是可选的,但在某些情况下,我们需要User类型必须提供lastName,才能进行接下来的操作。为达到这一目的,我们可以使用下面的代码:
type User = {
firstName: string,
lastName?: string
}
let firstUser:User = {
firstName: "John",
}
let secondUser:Required<User> = {
firstName: "John"
}
在这个例子中,secondUser会报错:
Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'Required'.
所以,当我们使用了Required类型的时候,我们必须添加lastName属性,这样就没有错误了:
type User = {
firstName: string,
lastName?: string
}
let secondUser:Required<User> = {
firstName: "John",
lastName: "Doe"
}
这种机制看似多此一举,但带给我们更多的灵活性:我们可以针对系统中的某些函数进行特殊的建模,强制某些属性仅在某些场景是必须的。与其它工具类型一样,Required可以针对interface或对象类型使用,因为它是针对类型的,但不能对变量使用,不过这也没多大关系,因为对象不可能是空值(undefined毕竟也是一种类型)。
Partial与Required正好相反,它的目的是把一种类型的所有属性都变成可选的。
我们还是使用上面的例子:
type User = {
firstName: string,
lastName: string
}
let firstUser:User = {
firstName: "John"
}
这段代码会报错:
Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'User'.
因为firstUser缺少了必须的属性lastName。但是,如果在某些场景中,lastName就是缺失的呢?我们就可以使用Partial类型:
type User = {
firstName: string,
lastName: string
}
let firstUser:Partial<User> = {
firstName: "John"
}
Partial实际是把User类型转换成了:
type User = {
firstName?: string,
lastName?: string
}
与创建一个这样的类型不同,使用Partial类型,我们可以同时拥有普通的User类型以及完全可选的Partial<User>类型。
顾名思义,Readonly类型将一个类型变成只读的。
例如,在下面的代码中,我们不想任何人修改firstUser对象的值,就可以将firstUser类型设置为Readonly<User>:
type User = {
firstName: string,
lastName: string
}
let firstUser:Readonly<User> = {
firstName: "John",
lastName: "Doe"
}
这样,如果你要修改firstUser.firstName或者firstUser.lastName,就会直接报错:
Cannot assign to 'firstName' because it is a read-only property.
需要注意的是,Readonly只针对于interface或对象类型。一个变量的类型是Readonly,只是这个对象的属性值是只读的,并不意味着这个对象是不可变的。例如:
let myVariable:Readonly<string> = "Hello World";
myVariable = "Goodbye World";
console.log(myVariable); // 输出 "Goodbye World"
虽然myVariable类型是Readonly,但我们仍旧可以给myVariable赋一个新值。为了将myVariable的引用本身设置为只读,我们需要使用const关键字:
const myVariable:string = "Hello World";
// 错误: Cannot assign to 'myVariable' because it is a constant.
myVariable = "Goodbye World";
前面我们说过,联合就是有限值的集合。我们可以直接定义一个联合:
type MyUnionType = "A" | "B" | "C" | "D"
上面的例子中,我们定义了一个联合类型MyUnionType,其可选值只有四个:A、B、C、D。我们可以使用这种类型:
type MyUnionType = "A" | "B" | "C" | "D"
// 可以这么实用
let firstString:MyUnionType = "A"
// 错误:Type '"some-string"' is not assignable to type 'MyUnionType'.
let secondString:MyUnionType = "some-string"
理解了联合类型,我们就可以看看Exclude了。
假设我们有一个MyUnionType类型,它包含四个可选值:A、B、C、D。但是,在某些场景中,我们不希望值A出现,那么就可以使用Exclude类型。Exclude语法如下:
Exclude<UnionType, ExcludedMembers>
第一个泛型参数是一个普通的联合类型,第二个泛型参数是需要排除的值。例如:
type MyUnionType = "A" | "B" | "C" | "D"
// 可以这么使用
let firstString:MyUnionType = "A"
// 错误:Type '"A"' is not assignable to type '"B" | "C" | "D"'.
let secondString:Exclude<MyUnionType, "A"> = "A"
注意上面的secondString变量,其类型是排除了A之后的MyUnionType,因此,我们不能将A赋值给它。
如果需要排除多个值,可以使用|运算符,例如:
type MyUnionType = "A" | "B" | "C" | "D"
// 可以这么使用
let firstString:MyUnionType = "A"
let secondString:Exclude<MyUnionType, "A"> = "D"
// ^
// └ - - 类型是 "B" | "C" | "D"
let thirdString:Exclude<MyUnionType, "A" | "B"> = "D";
// ^
// └ - - 类型是 "C" | "D"
let forthString:Exclude<MyUnionType, "A" | "B" | "C"> = "D";
// ^
// └ - - 类型是 "D"
let lastString:MyUnionType = "A"
// ^
// └ - - 类型是 "A" | "B" | "C" | "D"
Exclude类型并不会改变原始的联合类型,这带给我们一种灵活性,即在某些场景中可以排除掉联合类型的某些值,但在另外的场景又可以使用完整的联合类型。
与Exclude类似,Extract类型同样适用于联合。Exclude是排除联合中的某些值,Extract则是选取其中的某些值。Extract语法如下:
Extract<Type, Union>
我们来看一个例子:
type MyUnionType = "A" | "B" | "C" | "D"
let firstString:Extract<MyUnionType, "A" | "B"> = "A"
// ^
// └ - - 类型是 "A" | "B"
当我们使用Extract类型时,Extract会根据检查MyUnionType,看看其中是不是包含有"A" | "B",如果存在,则返回一个新的联合类型。如果Extract给的值不存在,则新的值直接被忽略:
type MyUnionType = "A" | "B" | "C" | "D"
let firstString:Extract<MyUnionType, "A" | "B" | "X"> = "A"
// ^
// └ - - 类型是 "A" | "B",由于 "X" 不存在于 MyUnionType,直接被忽略
与Exclude类似,Extract也不会改变原始的联合类型。
Omit类型用于定制化已有类型。
以User类型为例:
type User = {
firstName: string;
lastName: string;
age: number;
lastActive: number;
}
User包含四个属性:firstName、lastName、age以及lastActive。但我们不能保证User类型一直能够满足我们的需求:有些时候我们希望有一个新的类型,这个类型同User大致相同,只是少了age和lastActive属性。那么,我们必须定义一个新的类型吗?不是。Omit就是为了满足这种需要。
Omit类型语法如下:
Omit<Type, Omissions>
其中,第一个泛型参数是Omit作用的类型,第二个参数是联合类型,表示需要忽略的属性。
例如:
type User = {
firstName: string;
lastName: string;
age: number;
lastActive: number;
}
type UserNameOnly = Omit<User, "age" | "lastActive">
上面代码中,UserNameOnly只包含了两个属性:firstName和lastName,age和lastActive则被忽略。这样,我们就获得了可以使用的新类型。比如下面的代码:
type User = {
firstName: string;
lastName: string;
age: number;
lastActive: number;
}
type UserNameOnly = Omit<User, "age" | "lastActive">
type UserNameAndActive = Omit<User, "age">
const userByName:UserNameOnly = {
firstName: "John",
lastName: "Doe",
};
const userWithoutAge:UserNameAndActive = {
firstName: "John",
lastName: "Doe",
lastActive: -16302124725
}
Pick类型与Omit相反:Omit会忽略已有类型的某些属性,Pick则是选取已有类型的某些属性。
来看下面的例子:
type User = {
firstName: string;
lastName: string;
age: number;
}
type UserName = Pick<User, "firstName" | "lastName">
let user:UserName = {
firstName: "John",
lastName: "Doe"
}
可以看到,Pick从已有的User类型生成了一个新的类型,我们可以直接使用新的类型。
NonNullable类型会将已有类型的null和undefined类型移除。
例如,我们有这样的类型:
type MyType = string | number | null | undefined
但是在某些场景中,我们不需要MyType类型中的null和undefined,那么,我们就可以使用NonNullable类型:
type MyType = string | number | null | undefined
type NoNulls = NonNullable<MyType>
// ^
// └ - - 类型是 string | number
Parameters用于根据函数的参数生成一个新的类型。
假设我们有一个函数:
const myFunction = (a: string, b: string) => {
return a + b;
}
我们想要调用这个函数,方法有很多。其中一种是创建一个元组(tuple),使用展开运算符(...)去调用:
const myFunction = (a: string, b: string) => {
return a + b;
}
let passArray:[string, string] = [ 'hello ', 'world' ]
// Returns 'hello world'
myFunction(...passArray);
这里,我们定义了一个元组[string, string],然后给它赋值,最后使用展开运算符运行函数。
到目前为止,一切都很顺利。但是,如果myFunction的参数变了呢?我们定义的[string, string]都需要修改。Parameters类型就是为了解决这一问题:
const myFunction = (a: string, b: string) => {
return a + b;
}
type MyType = Parameters<typeof myFunction>
let myArray:MyType = [ 'hello ', 'world' ];
myFunction(...myArray)
Parameters类型实际简化了根据函数参数构建元组的方法。
既然是元组,我们就可以按照元组的方式去使用Parameters的返回值:
const myFunction = (a: string, b: string) => {
return a + b;
}
type AType = Parameters<typeof myFunction>[0]
type BType = Parameters<typeof myFunction>[1]
let a:AType = 'hello '
let b:BType = 'world'
myFunction(a, b)
Parameters也可以直接使用函数作为参数,例如:
type AnotherType = Parameters<(a: string, b: number) => void>
只不过这种直接使用匿名函数的方法并不怎么实用(因为仅仅是使用了匿名函数的声明,并没有定义)。
如果泛型参数不是一个函数,Parameters会直接返回never类型。
ConstructorParameters与Parameters类型,区别在于,后者按照返回函数参数列表返回一个元组,而前者按照类型构造函数参数返回。
例如,ErrorConstructor声明如下:
new ErrorConstructor(message?: string): Error
那么,
type ErrorType = ConstructorParameters<ErrorConstructor>;
// ^
// └ - - 类型是 [message?: string]
ReturnType与Parameters类似,只不过ReturnType基于函数的返回值构建一种新的类型。
我们看一个例子:
function sendData(a: number, b: number) {
return {
a: `${a}`,
b: `${b}`
}
}
type Data = ReturnType<typeof sendData>
// The same as writing:
// type Data = {
// a: string,
// b: string
// }
由于sendData()函数返回值类型是{ a: string, b: string },Data就是这个类型。这意味着我们不需要维护同一类型的两份拷贝,我们只有在函数中实际返回的那个类型。这无疑简化了我们的代码。
在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判断时,括号内的值进行类型转换,转化为布尔值
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!