TS 基础学习(四)
交叉类型
交叉类型(&): 功能类似于接口继承(extends),用于组合多个类型为一个类型(常用与对象类型)
交叉类型(&)和接口继承(extends)的对比:
1、相同点: 都可以实现对象类型的组合
2、不同点: 两种方式实现类型组合时, 对于同名属性之间,处理类型冲突的方式不同
泛型和keyof
泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。
泛型函数
// 定义一个泛型函数
function id<Type>(value: Type): Type { return value}
1、语法: 在函数名称后面添加<>,尖括号中添加类型变量,比如此处的Type
2、类型变量Type, 是一种特殊类型的变量,它处理类型而不是值
3、该类型变量相当于一个类型容易,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
4、因为Type是类型,因此可以将其作为函数参数和返回值类型,表示参数和返回值具有相同的类型
5、类型变量Type,可以是任意合法的变量名称
// 调用泛型函数
const num = id<number>(10)
const str = id<string>('str')
6、语法: 在函数名称后面添加<>,尖括号中指定具体的类型,比如此处的number
7、当传入类型number后,这个类型就会被函数声明时指定的类型变量Type捕获到。
8、此时,Type的类型就是number,所以,函数id参数和返回值类型也是number。
9、同样,如果传入类型string, 函数id参数和返回值类型都是string。
这样通过 泛型就做到了让id函数与多种不同的类型一起工作了,实现了复用的同时保证了类型安全。
10、在调用泛型函数时,可以省略<类型>来监护泛型函数的调用
11、此时,TS内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动判断出类型变量Type的类型。
12、推荐: 使用这种简化的方式调用泛型函数,使代码更短、更已于阅读。
13、说明: 当编译器无法推断类型或者推断的类型不准确时,就需要显示传入类型参数。
泛型约束
泛型约束:默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性。
Type可以代表任意类型, 无法保证一定存在length属性,比如number类型就没有length。此时就需要为泛型添加约束来收缩类型(缩窄类型取值范围)。
添加泛型约束收缩类型,主要有以下两种方式: 1.指定更加具体的类型; 2. 添加约束
添加约束:
// 定义一个接口 接口中有一个属性length
interface ILength { length: number}
function id<Type extends ILength>(value: Type): Type {
console.log(value.length);
return value;
}
1、创建描述约束的接口ILength,该接口要求提供length属性
2、通过extends关键字使用该接口,为泛型(类型变量)添加约束
3、该约束表示:传入的类型必须具有length属性
注意: 传入的实参(比如,数组)只要有length属性即可,这也符合前面讲到的接口的兼容性
注意:此处的extends不是继承的意思,是Type要满足ILength的接口约束(这里传入的参数必须满足具有一个叫length属性的约束)
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)。比如,创建一个函数来回去对象属性的值:
1、添加了第二个类型变量Key,两个类型变量之间使用(,)逗号分隔
2、keyof关键字接受一个对象类型,生成其名称(可能是字符串或数字)的联合类型
3、本示例中keyof Type实际上获取的是person对象所有的联合类型,也就是name | age | gender
4、类型变量Key受Type约束,可以理解为: Key只能是Type所有键中的任意一个,或者说只能访问对象中存在的属性
泛型接口
泛型接口: 接口也可以配合泛型来使用, 以增加其灵活性,增强其复用性。
// 定义一个泛型接口
interface IdFunc<Type> {
id: (value: Type) => Type
ids: () => Type[]
}
let obj: IdFunc<number> = {
id(value) {
return value
},
ids() {
return [1, 3, 5]
},
}
1、在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口
2、接口的类型变量,对接口中所有其他成员可见, 也就是接口中所有成员都可以使用类型变量。
3、使用泛型接口,需要显示指定具体的类型(比如,此处的idFunc)
4、此时,id方法的参数和返回值类型都是number;ids方法的返回值类型是number[].
当我们在使用数组时,TS会根据数组的不同类型,来自动将类型变量设置为相应的类型
泛型类
泛型类:class也可以配合泛型来使用。比如React的class组件的基类Component就是泛型类型
React.Component泛型类两个类型变量,分别指定props和state类型
class GenericNumber<NumType> {
defaultValue: NumType
add: (x: NumType, y: NumType) => NumType
}
1、类似于泛型接口,在class名称后面添加<类型变量>这个类就变成了反省类。
2、此处的add方法,采用的是箭头函数形式的类型书写方式。
const myNum = new GenericNumber<number>()
myNum.defaultValue = 0
泛型工具类
泛型工具类:TS内置了一些常用的工具类型,来简化TS中的一些常规操作
1、Partial<Type>
作用:Partial接收一个泛型类型Type,并将Type所有属性都设置为可选的,返回构造的新类型。
ts复制代码interface User {
name: string
age: number
address: string
}
function updateUser(user: User, fieldsToUpdate: Partial<User>) {
return { ...user, ...fieldsToUpdate }
}
const user: User = { name: 'xiaoming', age: 30, address: '上海' }
const newUser = updateUser(user, { address: '北京' })
2、Required<Type>
作用:Required接收一个泛型类型Type,并将Type所有属性都设置为必选的,返回构造的新类型(Required的作用与Partial相反)。
ts复制代码interface Props {
name?: string
age?: number
}
function printProps(props: Required<Props>) {
console.log(`${props.name} is ${props.age} years old.`)
}
printProps({ name: 'xiaoming'}) // error ❗️
3、Readonly<Type>
作用:Readonly接收一个泛型类型Type,并将Type所有属性都设置为只读的,返回构造的新类型, 新类型的属性不可再进行分配。
ts复制代码interface User {
name: string
age: number
}
const user: Readonly<User> = { name: 'xiaoming', age: 30 }
user.name = 'zhangsan' // error ❗️
4、Record<Keys, Type>
作用:构造一个对象类型,其属性键为Keys,属性值为Type。
ts复制代码interface User {
name: string
age: number
}
type UserName = 'xiaoming' | 'xiaohong' | 'xiaohuang'
const users: Record<UserName, User> = {
xiaoming: { name: 'ming', age: 23 },
xiaohong: { name: 'hong', age: 24 },
xiaohuang: { name: 'huang', age: 25 }
}
5、Pick<Type, Keys>
作用: 从类型Type中选择一组属性Keys来创建类型。
复制代码interface User {
name: string;
age: number;
address: string
}
type NameAndAgeOnly = Pick<User, 'name' | 'age'>;
const nameAndAgeOnly: NameAndAgeOnly = { name: 'xiaoming', age: 26 };
扩展:
结合pick函数使用
ts复制代码const user = {name: 'xiaoming', age: 26, address: 'shanghai'}
declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;
const nameAndAgeOnly = pick(user, "name", "age"); // { name: string, age: number }
6、xclude<UnionType, ExcludedMembers>
作用: 从联合类型UnionType中排除ExcludedMembers类型然后返回一个新类型。
ts复制代码interface User {
name: string;
age: number;
address: string
}
type UserExcludeAddress = Exclude<keyof User, 'address'> // "name" | "age"
扩展:
ts复制代码const user1 = { name: 'ming', age: 23, address: 'shanghai' } as const
const user2 = { name: 'hong', age: 24, address: 'beijing' } as const
const user3 = { name: 'huang', age: 25, address: 'shenzhen' } as const
type Users = typeof user1 | typeof user2 | typeof user3
type UsersExcludeMing = Exclude<Users, { name: 'ming' }>
7、Extract<Type, Union>
作用: 从联合类型Type中提取Union类型然后返回一个新类型。
ts复制代码interface User {
name: string;
age: number;
address: string
}
type UserAddress = Extract<keyof User, 'address'> // address
type Person = {
name: string;
age: string;
}
const user: Extract<keyof User, keyof Person> = 'name' || 'age';
type SuccessCode = Extract<200 | 404, 200>; // 200
扩展:
如果泛型Type中没有Union, 返回never。
ts复制代码type SuccessCode = Extract<200 | 404, 204>; // never
8、Omit<Type, Keys>
作用: 与Pick相反,Omit是从Type中选取所有Keys属性然后删除构造一个新类型。
ts复制代码interface User {
name: string;
age: number;
address: string
}
type UserOmitAge = Omit<User, 'address'>;
const userOmitAge: UserOmitAge = { name: 'xiaoming', age: 30 };
扩展:
从源码可以看出来Omit的实现其实就是Pick和Exclude的组合。因为Omit的使用场景比较多,所以Typescript应使用者要求新增了Omit工具类型。
9、NonNullable<Type>
作用: 通过从Type中排除null和undefined来构造一个类型。
ts复制代码type PortNumber = string | number | null;
type ServerPortNum = NonNullable<PortNumber>
10、Parameters<Type>
作用: 接受一个函数类型, 将函数的参数处理成一个元组类型
ts复制代码function createStudent(sno: string, name: string, age: number) {
return { sno, name, age }
}
type CreateStudentParams = Parameters<typeof createStudent>
const createStuParams: CreateStudentParams = ['112899022', 'ming', 30]
const stu1 = createStudent(...createStuParams)
扩展:
Parameters接收的泛型必须是一个函数类型,但是作为顶级类型any和底层类型never, Typescript对其有特殊的处理。
ts复制代码type T1 = Parameters<any>; // unknown[]
type T2 = Parameters<never>; // never
Typescript中定义的Function也不能被parameters接收,看Function 的实现就知道了。
复制代码type T7 = Parameters<Function>; // error
Function的实现:
ts复制代码interface Function {
readonly name: string;
}
如果函数是重载函数,那么返回的是最后一个函数的参数组成的元组类型
ts复制代码declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;
type T1 = Parameters<typeof stringOrNum>; // [x: string | number]
11、ConstructorParameters<Type>
作用: 接受一个具有构造函数的类型, 将构造函数的参数处理成一个元组类型。
ts复制代码class Test { constructor(a: number, b: string) {}}
type T1 = ConstructorParameters<typeof Test>; // [a: number, b: string]
type T2 = ConstructorParameters<new (x: string, y: number) => any> // [x: string, y: number]
12、ReturnType<Type>
作用: 获取函数类型的返回值类型。
复制代码function getUser() {
return { name: 'ming', age: 30 }
}
type User = ReturnType<typeof getUser>
const user: User = { name: 'hong', age: 26 }
扩展:
同Parameters类型一样,接收的泛型必须是一个函数类型
泛型是any和never的情况
ts复制代码type T1 = ReturnType<any>; // any
type T2 = ReturnType<never>; // never
Function不能被ReturnType接收
如果函数是重载函数,那么返回的是最后一个函数的返回类型。
13、InstanceType<Type>
作用: 获取构造函数类型的返回类型(构造函数返回什么什么类型,InstanceType获取的就是什么类型)。
ts复制代码class Person {
constructor(public name: string) {}
}
type PersonInstance = InstanceType<typeof Person>
const person: PersonInstance = new Person('Alice')
interface User {
new (name: string): Object
}
type UserInstance = InstanceType<User> // Object
扩展:
InstanceType也常用于获取内置构造方法或第三方库的参数类型
ts复制代码type T3 = InstanceType<ErrorConstructor> // Error
type T4 = InstanceType<FunctionConstructor> // Function
type T5 = InstanceType<RegExpConstructor> // RegExp
对any和never的特殊处理
ts复制代码type T7 = InstanceType<any>;
type T8 = InstanceType<never>;
一个vue的例子
ts复制代码<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
defineExpose({
open
})
</script>
// 为了获得MyModal的实例类型,使用InstanceType实用程序提取其实例类型
<!-- Other.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const modal = ref<InstanceType<typeof MyModal> | null>(null)
const openModal = () => {
modal.value?.open()
}
</script>
14、Awaited<Type>
作用: 获取Promise中的类型(如await、then方法返回的被Promise包裹的数据的类型)。适合处理异步操作并确保解析值的类型安全。
ts复制代码type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
// 假如这是一个第三方库,User没有导出,fetchUser函数导出了
interface User {
name: string
age: number
}
export async function fetchUser(): Promise<User> {
const data = await fetch('https://www.example.com/user').then(res => {
return res.json()
})
return data
}
// 我们开发中在获取到了User类型
type UserFetch = Awaited<ReturnType<typeof fetchUser>>
async function getUserInfo() {
let user: UserFetch = { name: 'ming', age: 30 }
return user
}
扩展:
Awaited是可以递归解Promise
type T1 = Awaited<Promise<Promise<Promise<Promise<Promise<number>>>>>> // number
如果传入Awaited的泛型不是Promise,那么会原样返回
ts
复制代码type T2 = Awaited<string> // string
15、ThisParameterType<Type>
作用: 提取函数类型的this参数的类型, 如果函数类型没有this参数, 返回unknown。
ts复制代码function toHex(this: Number) {
return this.toString(16);
}
function numberToString(n: ThisParameterType<typeof toHex>) {
return toHex.apply(n);
}
16、OmitThisParameter<Type>
作用: 与ThisParameterType相反, 排除函数类型的this参数
ts复制代码function toHex(this: Number) {
return this.toString(16);
}
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);
console.log(fiveToHex());
17、ThisType<Type>
作用: 控制字面量对象中this所表示的类型。 只在--noImplicitThis下有用
ts复制代码// 正常情况推导出来的this
type Point = {
x: number;
y: number;
moveBy(dx: number, dy: number): void;
}
let p: Point = {
x: 10,
y: 20,
moveBy(dx, dy) {
this.x += dx; // this has type Point
this.y += dy; // this has type Point
}
}
18、 ReadonlyArray<Type>
作用: 描述只能读的数组, 不可进行添加、删除、替换操作。
ts复制代码function foo(arr: ReadonlyArray<string>) {
arr.slice(); // okay
arr.push("hello!"); // Type error
arr.pop(); // Type error
arr.splice(1, 1); // Type error
arr[0] = 'aa' // Type error
}
foo(['a','b','c'])
ReadonlyArray<string>`的简写:`readonly string[]
19、Uppercase<StringType>
作用: 将字符串中的每个字符转换为对应的大写。
ts复制代码type Greeting = 'Hello, world'
type ShoutyGreeting = Uppercase<Greeting> // "HELLO, WORLD"
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<'my_app'> // "ID-MY_APP"
20、Lowercase<StringType>
作用: 将字符串中的每个字符转换为对应的小写。
ts复制代码type Greeting = 'Hello, world'
type QuietGreeting = Lowercase<Greeting> // "hello, world"
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<'MY_APP'> // "id-my_app"
21、Capitalize<StringType>
作用: 将字符串中的第一个字符转换为大写字母。
ts复制代码/**
* Convert first character of string literal type to lowercase
*/
type Uncapitalize<S extends string> = intrinsic;
22、Uncapitalize<StringType>
作用: 将字符串中的第一个字符转换为小写字母。
ts复制代码type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
优秀的类型库
utility-types
type-festts-toolbelt
ts-toolbelt