Class
class 的基本语法我们就不探究了,class
在 TS
中是全面支持的,我们主要关注 TS
对于类属性和方法的类型声明方式即可。
对于类属性,有两种声明类型的方式:
javascript
class Person {
name: string; // 1. 直接在顶层声明时顺便给出类型(推荐)
age: number = 18; // 2. 通过类型推断
constructor(name: string) {
this.name = name;
}
}
类属性在 TS
中默认要求有初始值,也可以通过 strictPropertyInitialization
配置项去关闭。
对于类方法,它与普通函数的类型声明方式一致。
javascript
class Person {
name = '橙某人';
constructor(name: string) {
this.name = name;
}
say(newName: string): void {
console.log(`my name is ${this.name}`);
}
}
implements
implements 表示实现,一个新类必须实现类型别名、接口或者父类的所有属性和方法。
javascript
type P = {
name: string;
age: number;
say: (newName: string) => void;
}
/* or
interface P {
name: string;
age: number;
say: (newName: string) => void;
}
*/
class Person implements P {
name;
age = 18;
constructor(name: string) {
this.name = name;
}
say(newName: string) {
console.log(`my name is ${this.name}`);
}
}
需要注意,类方法参数的类型声明不能省略,也不能改成其他类型,否则参数类型会变成 any
类型或者报错。😕
javascript
class Person implements P {
say(newName) { // any
}
}
class Person implements P {
say(newName: number) { // ❌
}
}
多重实现
类可以实现多重限制,每个限制之间使用逗号隔开。
javascript
type P1 = {
name: string;
}
interface P2 {
age: number;
}
class P3 {
say(newName: string): void {}
}
class Person implements P1, P2, P3 {
name = '橙某人';
age = 18;
say(newName: string) {
console.log(`my name is ${this.name}`);
}
}
实现多重限制时,不同限制之间不能有冲突的属性。
实例类型声明
声明类实例的类型有两种方式:
javascript
type P1 = {
name: string;
}
interface P2 {
age: number;
}
class P3 {
say(newName: string): void {}
}
class Person implements P1, P2, P3 {
name = '橙某人';
age = 18;
say(newName: string) {
console.log(`my name is ${this.name}`);
}
}
let p1: Person = new Person(); // 推荐
let p2: P1 = new Person();
p2.name // ✅
p2.age // ❌
修饰符
readonly
readonly
是 TS
新增的一个关键字,表示只读,不可能被修改。
它的出现主要是为了解决 const
声明的对象,所指堆空间对象依旧可变的不足。
列了一些使用场景:
javascript
// 对象
let obj: {
readonly name: string;
readonly age: number;
} = {
name: '橙某人',
age: 18
}
obj.name = ''; // ❌
// 函数
function fn(obj: { readonly name: string; readonly age: number; }) {
obj.name = ''; // ❌
}
// 数组
let arr: readonly number[] = [1, 2, 3];
arr.push(4); // ❌
// 元组
let tArr: readonly [number, string] = [1, 'a'];
tArr.push(4); // ❌
// type
type Obj = {
readonly name: string;
readonly age: number;
}
let o: Obj = {
name: '橙某人',
age: 18
}
o.name = ''; // ❌
// interface
interface Person {
readonly name: string;
readonly age: number;
}
let oo: Person = {
name: '橙某人',
age: 18,
}
oo.name = ''; // ❌
// class
class Student {
readonly name: string = '橙某人';
readonly age: number;
constructor() {
this.age = 18; // 这里只是初始化而已,不是修改❗
}
say() {
this.name = ''; // ❌
}
}
随便看看就得了。👻
泛型
泛型(Generics)可以理解成一个占位的未知"类型参数",具体这个类型参数是什么类型需要在使用的时候才能确定下来。
来看个例子:
javascript
function numToArray(a: number, b: number): number[] {
return [a, b];
}
function strToArray(a: string, b: string): string[] {
return [a, b];
}
let numArray = numToArray(1, 2); // number[]
let strArray = strToArray('a', 'b'); // string[]
上述两个函数,在 JS
中完全能被写成一个,但是在 TS
中却需要拆成两个函数,只因参数和返回值类型的不一致导致。
虽然也能用函数重载来解决😏,但...这不重要不重要。
而这种场景,利用泛型就能很好得到解决。
javascript
function fn<T>(a: T, b: T): T[] {
return [a, b];
}
let numArray = fn<number>(1, 2); // number[]
let strArray = fn<string>('a', 'b'); // string[]
是不是能感受到泛型的灵活和强大了?💪
当然,泛型的类型参数可以有多个,通过逗号隔开。
javascript
function fn<T, K, V>(a: T, b: K, c: V): (T | K | V)[] {
return [a, b, c];
}
let res = fn<number, string, boolean>(1, 'a', true); // (string | number | boolean)[]
T, K, V
这些参数命名与函数参数名一样,可以由你随意定义。
四种应用场景
泛型主要用在四个场景:函数、类型别名、接口、class
。
函数
javascript
function fn1<T>(args: T): T {
return args;
}
fn1<number>(1);
let fn2: <T>(args: T) => T = args => args;
fn2<number>(1);
let obj: {
fn3: <T>(args: T) => T;
} = {
fn3: args => args
};
obj.fn3<number>(1);
类型别名
javascript
type ArticleName<T> = string | number | T;
type Person<T, K> = {
name: T;
age: K;
}
let an1: ArticleName<string> = '橙某人';
let an2: ArticleName<number> = 999;
let an3: ArticleName<boolean> = true;
接口
javascript
interface Person<T, K> {
name: T;
age: K;
}
let person: Person<string, number> = {
name: '橙某人',
age: 18
}
函数的泛型接口可以有两种形式:
javascript
// 在定义时传递类型
interface Fn1<T> {
(args: T): T;
}
let fn4: Fn1<number> = args => args;
fn4(1);
// 在调用函数的时候传递类型
interface Fn2 {
<T>(args: T): T;
}
let fn5: Fn2 = args => args;
fn5<number>(1);
class
javascript
class Person<T, K> {
name: T;
constructor(name: T) {
this.name = name;
}
say(args: K): K {
return args;
}
}
let p = new Person<string, number>('橙某人');
默认值
泛型的类型参数也可以设置默认值,当我们没有传递类型时,就会使用默认值。
javascript
type ArticleName<T = string> = T;
let name1: ArticleName<number> = 1; // number
let name2: ArticleName; // string
默认值有两个注意点:
- 默认值经常会被类型推断给覆盖。
- 多个泛型的类型参数,具有默认值的类型参数必须放置到最后面。
用上面的例子来看:
javascript
function fn<T = string>(a: T, b: T): T[] {
return [a, b];
}
let numArray = fn(1, 2); // number[]
let strArray = fn('a', 'b'); // string[]
它也能简写,不传类型,让 TS
自行去进行类型推断,结果是一样的。
同时,虽然没有传递类型,但是默认值(T = string
)也没有生效,说明类型推断的优先级是高于默认值的。
如果有多个类型参数,具有默认值的类型参数必须在最后面。
javascript
function fn1<T = string, U>(){} // ❌
function fn2<U, T = string>(){} // ✅
function fn3<U, T = string, K = number>(){} // ✅
泛型约束-extends
泛型的强大和灵活相信你能有所体会,但是有利就有弊。
来看下面的例子:
从入参我们可以明确知道参数会有 length
属性,但是我们想打印它却不行❗
泛型约束 的存在就是为了解决这类问题的。
泛型约束使用 extends
关键字来完成,它能帮我们提前告诉 TS
,泛型的类型参数可能存在"某些类型"。
javascript
function fn<T extends { length }>(args: T) {
console.log(args.length); // ✅
}
fn<string>('abc');
fn<number[]>([1, 2, 3]);
fn<number>(123); // ❌
或者
javascript
interface Len{
length: number
}
function fn<T extends Len>(args: T) {
console.log(args.length); // ✅
}
fn<string>('abc');
fn<number[]>([1, 2, 3]);
fn<number>(123); // ❌
再多列几个例子体验体验:
javascript
// 1
type Name<T extends string> = T;
let name1: Name<string> = '橙某人';
let name2: Name<number> = 1; // ❌
// 2
type Obj<T extends { name: string }> = T;
let obj1: Obj<{ name: string, age: number }> = {
name: '橙某人',
age: 18,
};
let obj2: Obj<{ age: number }> = {}; // ❌
// 3
type Obj<T extends U, U> = T;
let obj3: Obj<{ name: string, age: 18 }, { name: string }> = {
name: '橙某人',
age: 18,
}
let obj4: Obj<{ name: string }, { name: string, age: 18 }> = {} // ❌
从这些例子中,能大概瞧出(T extends U
)T
必须要满足 U
才行,否则就会报错。
泛型约束在一些地方也被叫做泛型条件类型,因为它和我们熟知的三元表达式(?:
)很相像。
泛型约束这个概念不管在官网还是在网上的一些文章中,小编都没有一个很好的总结,大部分都是通过例子来说明。如您有更好的说明,希望、期待你在评论区中告诉小编,感谢感谢。😀😀😀
内置的泛型工具类型
泛型虽好,但不可贪多。
泛型虽然强大灵活,但是会加大代码的复杂性,可读性大大降低。
同时,泛型的编写难度门槛也是相当之高。
为此,TS
专门内置了一些常用的泛型工具类型,就像我们常用的 JS
工具方法一样。
Partial
Partial<T>
:将声明的对象类型所有属性变成可选的。
示例
javascript
interface Person {
name: string;
age: number;
}
type P = Partial<Person>;
// 等价于
type P = {
name?: string;
age?: number;
}
源码实现
javascript
type Partial<T> = {
[P in keyof T]?: T[P];
};
Required
Required<T>
:将声明的对象类型所有属性变成必填的。
示例
javascript
interface Person {
name?: string;
age?: number;
}
type P = Required<Person>;
// 等价于
type P = {
name: string;
age: number;
}
源码实现
javascript
type Required<T> = {
[P in keyof T]-?: T[P];
};
Readonly
Readonly<T>
:将声明的对象类型所有属性变成只读的。
示例
javascript
interface Person {
name: string;
age?: number;
}
type P = Readonly<Person>;
// 等价于
type P = {
readonly name: string;
readonly age?: number;
}
源码实现
javascript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Pick
Pick<T, K>
:从一个声明好的对象类型中,挑取部分属性出来组成一个新的对象类型。
示例
javascript
interface Person {
readonly name: string;
age?: number;
sex: boolean
}
type P = Pick<Person, 'name' | 'age'>;
// 等价于
type P = {
readonly name: string;
age?: number;
}
源码实现
javascript
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Omit
Omit<T, K>
:从一个声明好的对象类型中,去除部分属性,剩下的组成一个新的对象类型。(与 Pick
相反)
示例
javascript
interface Person {
name: string;
age?: number;
sex: boolean
}
type P = Omit<Person, 'name' | 'sex'>;
// 等价于
type P = {
age?: number;
}
源码实现
javascript
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Record
Record<K, T>
:将所有的 K
属性变成 T
类型,返回一个对象类型。
(K
属性只能是 string | number | symbol
)
示例
javascript
type Person = 'name' | 'age';
type P = Record<Person, string>;
// 等价于
type P = {
name: string;
age: string;
}
源码实现
javascript
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Exclude
Exclude<T, U>
:将 T
类型中属于 U
类型的部分移除。
示例
javascript
type Person1 = string | number | boolean;
type Person2 = string;
type P = Exclude<Person1, Person2>;
// 等价于
type P = number | boolean;
值类型
javascript
type Person1 = '橙某人' | 18 | true;
type Person2 = '橙某人';
type P = Exclude<Person1, Person2>;
// 等价于
type P = 18 | true;
源码实现
javascript
type Exclude<T, U> = T extends U ? never : T;
😮这个其实挺有意思的,从源码中完全看不出是这个效果呢。。。猜想应该是
never
的效果,还没去细细探究过,小编期待你的评论。😖
Extract
Extract<T, U>
:将 T
类型与 U
类型中相同部分提取出来,组成一个新类型。(与 Exclude
相反)
示例
javascript
type Person1 = string | number | boolean;
type Person2 = string;
type P = Extract<Person1, Person2>;
// 等价于
type P = string;
源码实现
javascript
type Extract<T, U> = T extends U ? T : never;
ReturnType
ReturnType<T>
:获取函数类型的返回值类型。
示例
javascript
type Fn = (args: number) => string;
type P = ReturnType<Fn>; // string
type P1 = ReturnType<() => string>; // string
type P2 = ReturnType<() => void>; // void
type P3 = ReturnType<<T>() => T>; // unknown
type P4 = ReturnType<any>; // any
type P5 = ReturnType<never>; // never
type P6 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
源码实现
javascript
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
Parameters
Parameters<T>
:获取函数类型的参数类型,返回一个元组。
示例
javascript
type Fn = (a: number, b: string) => void;
type P = Parameters<Fn>;
// 等价于
type P = [a: number, b: string]
源码实现
javascript
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
以上小编列举了常见的十个泛型工具类型以供参考。
更多工具类型可以上源码中查阅。传送门
一些高级的工具类型的解析可以参考这篇文章。传送门
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。