哪里做网站好,网站怎么做sem优化,做网站昆山,天津企业网站建设价格推荐一下小册 TypeScript 全面进阶指南#xff0c;此篇笔记来源于此#xff0c;记录总结#xff0c;加深印象#xff01;
另外#xff0c;如果想了解更多ts相关知识#xff0c;可以参考我的其他笔记#xff1a;
vue3ts开发干货笔记TSConfig 配置#xff08;tsconfig.…推荐一下小册 TypeScript 全面进阶指南此篇笔记来源于此记录总结加深印象
另外如果想了解更多ts相关知识可以参考我的其他笔记
vue3ts开发干货笔记TSConfig 配置tsconfig.json)ts相关笔记Partial、Required、Readonly、Record、Exclude、Extractts相关笔记类型层级ts相关笔记extends、infer、Pick、Omit
原始类型和对象类型
原始类型
除了最常见的 number / string / boolean / null / undefined ES6、ES11)又分别引入了 2 个新的原始类型symbol 与 bigint
const name: string ts;
const age: number 18;
const male: boolean false;
const undef: undefined undefined;
const nul: null null;
const obj: object { name, age, male };
const bigintVar1: bigint 90071992547409212n;
const bigintVar2: bigint BigInt(9007199254740991);
const symbolVar: symbol Symbol(unique);null和undefined
在js里null表示一个空值undefined表示没有值在ts里null 与 undefined 类型都是有具体意义的类型
在没有开启strictNullChecks 检查的情况下这两种类型会被视作其他类型的子类型比如 string 类型会被认为包含了 null 与 undefined 类型
const tmp1: null null;
const tmp2: undefined undefined;const tmp3: string null; // 仅在关闭 strictNullChecks 时成立下同
const tmp4: string undefined;void
用于描述一个内部没有 return 语句或者没有显式 return 一个值的函数的返回值如
function func1() {}
function func2() {return;
}
function func3() {return undefined;
}数组类型标注
const arr1: string[] []; // 更推荐此写法const arr2: Arraystring [];元组Tuple
元组就是类型和数据的个数一开始就已经限定好了某些情况下使用元组代替数组要更加妥当。 下面是一些应用 提供越界提醒 const arr3: string[] [aaa, bbb, ccc]
const arr4: [string,string,string] [aaa, bbb, ccc]
console.log(arr4[4])可选 const arr6: [string, number?, boolean?] [aaa];
// 下面这么写也可以
// const arr6: [string, number?, boolean?] [aaa, , ,];具名元组 在 TypeScript 4.0 中有了具名元组Labeled Tuple Elements的支持使得我们可以为元组中的元素打上类似属性的标记 const arr7: [name: string, age: number, male?: boolean] [aaa, 18, true];对象类型标注
使用interface接口
interface IDescription {name: string;age: number;male: boolean;
}const obj1: IDescription {name: aaa,age: 599,male: true,
};可选 ?
interface IDescription {name: string;age: number;male?: boolean;func?: Function;
}const obj2: IDescription {name: aaa,age: 599,male: true,// 无需实现 func 也是合法的
};只读
interface IDescription {readonly name: string;age: number;
}const obj3: IDescription {name: aaa,age: 599,
};// 无法分配到 name 因为它是只读属性
obj3.name AAA;使用type 类型别名
type User {name: stringage: number
};很多人更喜欢用 typeType Alias类型别名来代替接口结构描述对象 而更推荐的方式是
interface 用来描述对象、类的结构type类型别名用来将一个函数签名、一组联合类型、一个工具类型等等抽离成一个完整独立的类型。
但大部分场景下接口结构都可以被类型别名所取代取决于个人喜好或者团队的一些规范吧。
object、Object 以及 { }
Object
js中原型链的顶端是 Object 以及 Function这也就意味着所有的原始类型与对象类型最终都指向 Object在 TypeScript 中就表现为 Object 包含了所有的类型
// 对于 undefined、null、void 0 需要关闭 strictNullChecks
const tmp1: Object undefined;
const tmp2: Object null;
const tmp3: Object void 0;const tmp4: Object aaa;
const tmp5: Object 599;
const tmp6: Object { name: aaa };
const tmp7: Object () {};
const tmp8: Object [];和 Object 类似的还有 Boolean、Number、String、Symbol这几个装箱类型Boxed Types 同样包含了一些超出预期的类型。 以 String 为例它同样包括 undefined、null、void以及代表的 拆箱类型Unboxed Types — string但并不包括其他装箱类型对应的拆箱类型如 boolean 与 基本对象类型我们看以下的代码
const tmp9: String undefined;
const tmp10: String null;
const tmp11: String void 0;
const tmp12: String aaa;// !!! 以下不成立因为不是字符串类型的拆箱类型
const tmp13: String 599;
const tmp14: String { name: aaa };
const tmp15: String () {};
const tmp16: String [];
object object 的引入就是为了解决对 Object 类型的错误使用它代表所有非原始类型的类型即数组、对象与函数类型这些
const tmp17: object undefined;
const tmp18: object null;
const tmp19: object void 0;const tmp20: object aaa; // X 不成立值为原始类型
const tmp21: object 599; // X 不成立值为原始类型const tmp22: object { name: aaa };
const tmp23: object () {};
const tmp24: object [];{ } 可以认为使用{ }作为类型签名就是一个合法的但内部无属性定义的空对象 这类似于 Object想想 new Object()它意味着任何非 null / undefined 的值
const tmp25: {} undefined; // 仅在关闭 strictNullChecks 时成立下同
const tmp26: {} null;
const tmp27: {} void 0; // void 0 等价于 undefinedconst tmp28: {} aaa;
const tmp29: {} 599;
const tmp30: {} { name: aaa };
const tmp31: {} () {};
const tmp32: {} [];虽然能够将其作为变量的类型但你实际上无法对这个变量进行任何赋值操作
const tmp30: {} { name: aaa };tmp30.age 18; // X 类型“{}”上不存在属性“age”。总结 在任何时候都不要不要不要使用 Object 以及类似的装箱类型。 当你不确定某个变量的具体类型但能确定它不是原始类型可以使用 object。但更推荐进一步区分也就是使用 Recordstring, unknown 或 Recordstring, any 表示对象 unknown[] 或 any[] 表示数组 (…args: any[]) any表示函数 这样。 我们同样要避免使用{}。{}意味着任何非 null / undefined 的值从这个层面上看使用它和使用 any 一样恶劣。
字面量类型和枚举
字面量类型
看一个例子我们开发中定义的请求 接口返回 结果可能如下
interface Res {code: 10000 | 10001 | 50000;status: success | failure;data: any;
}上面success 或者 “failure” 不是一个值吗为什么它也可以作为类型
在 TypeScript 中这叫做字面量类型Literal Types它代表着比原始类型更精确的类型同时也是原始类型的子类型
字面量类型主要包括字符串字面量类型、数字字面量类型、布尔字面量类型和对象字面量类型它们可以直接作为类型标注
const str: aaa aaa;
const num: 599 599;
const bool: true true
// 报错Type aaa123 is not assignable to type aaa
const str1: aaa aaa123;
单独使用字面量类型比较少见因为单个字面量类型并没有什么实际意义。 它通常和联合类型即这里的 |一起使用表达一组字面量类型
interface Tmp {bool: true | false;num: 1 | 2 | 3;str: aa | bb | cc
}补充联合类型
联合类型你可以理解为它代表了一组类型的可用集合只要最终赋值的类型属于联合类型的成员之一就可以认为符合这个联合类型。 联合类型对其成员并没有任何限制除了上面这样对同一类型字面量的联合我们还可以将各种类型混合到一起
interface Tmp {mixed: true | string | 599 | {} | (() {}) | (1 | 2)
}联合类型的常用场景之一是通过多个对象类型的联合来实现手动的互斥属性即这一属性如果有字段1那就没有字段2
interface Tmp {user:| {vip: true;expires: string;}| {vip: false;promotion: string;};
}declare var tmp: Tmp;if (tmp.user.vip) {console.log(tmp.user.expires);
}枚举
// js中定义一些常量
export const PageUrl {Home_Page_Url: url1,Setting_Page_Url: url2,Share_Page_Url: url3,
}使用ts的枚举 enum
enum PageUrl {Home_Page_Url url1,Setting_Page_Url url2,Share_Page_Url url3,
}
// 使用
const home PageUrl.Home_Page_Url;如果你没有声明枚举的值它会默认使用数字枚举并且从 0 开始以 1 递增
enum Items {Foo,Bar,Baz
}
// Items.Foo , Items.Bar , Items.Baz的值依次是 012 如果你只为某一个成员指定了枚举值那么之前未赋值成员仍然会使用从 0 递增的方式之后的成员则会开始从枚举值递增。
enum Items {Foo, // 0 Bar 599,Baz // 600
}枚举和对象的重要差异在于对象是单向映射的我们只能从键映射到键值。 而枚举是双向映射的即你可以从枚举成员映射到枚举值也可以从枚举值映射到枚举成员
enum Items {Foo,Bar,Baz
}const fooValue Items.Foo; // 0
const fooKey Items[0]; // Foo函数和class中的类型
函数
// 函数声明
function foo(name: string): number {return name.length;
}// 函数表达式const foo function (name: string): number {return name.length
}
const foo: (name: string) number function (name) {return name.length
}// 箭头函数
// 方式一
const foo (name: string): number {return name.length
}// 方式二 代码的可读性会非常差不推荐
const foo: (name: string) number (name) {return name.length
}要么直接在函数中进行参数和返回值的类型声明要么使用类型别名将函数声明抽离出来如下
type FuncFoo (name: string) numberconst foo: FuncFoo (name) {return name.length
}如果只是为了描述这个函数的类型结构我们甚至可以使用 interface 来进行函数声明
interface FuncFooStruct {(name: string): number
}void 类型
// 没有调用 return 语句
function foo(): void { }// 调用了 return 语句但没有返回值
function bar(): void {return;
}可选参数与 rest 参数
使用 ? 描述一个可选参数 注意可选参数必须位于必选参数之后
// 在函数逻辑中注入可选参数默认值
function foo1(name: string, age?: number): number {const inputAge age || 18; // 或使用 age ?? 18return name.length inputAge
}// 直接为可选参数声明默认值
function foo2(name: string, age: number 18): number {const inputAge age;return name.length inputAge
}对于 rest 参数的类型标注也比较简单由于其实际上是一个数组这里我们也应当使用数组类型进行标注
function foo(arg1: string, ...rest: any[]) { }// 元组标注
function foo(arg1: string, ...rest: [number, boolean]) { }foo(aaa, 18, true)重载 在某些逻辑较复杂的情况下函数可能有多组入参类型和返回值类型
function func(foo: number, bar?: boolean): string | number {if (bar) {return String(foo);} else {return foo * 599;}
}要想实现与入参关联的返回值类型我们可以使用 TypeScript 提供的函数重载签名Overload Signature将以上的例子使用重载改写
function func(foo: number, bar: true): string;
function func(foo: number, bar?: false): number;
function func(foo: number, bar?: boolean): string | number {if (bar) {return String(foo);} else {return foo * 599;}
}const res1 func(599); // number
const res2 func(599, true); // string
const res3 func(599, false); // number
异步函数
async function asyncFunc(): Promisevoid {}对于异步函数即标记为 async 的函数其返回值必定为一个 Promise 类型而 Promise 内部包含的类型则通过泛型的形式书写即 Promise T
class
一个函数的主要结构即是参数、逻辑和返回值对于逻辑的类型标注其实就是对普通代码的标注所以我们只介绍了对参数以及返回值的类型标注。
而到了 Class 中其实也一样它的主要结构只有构造函数、属性、方法和访问符Accessor我们也只需要关注这三个部分即可
构造函数
class Foo {prop: string;constructor(inputProp: string) {this.prop inputProp;}print(addon: string): void {console.log(${this.prop} and ${addon})}get propA(): string {return ${this.prop}A;}set propA(value: string) {this.prop ${value}A}
} setter 方法不允许进行返回值的类型标注你可以理解为 setter 的返回值并不会被消费它是一个只关注过程的函数。 修饰符 分别有 public / private / protected / readonly readonly 属于操作性修饰符就和 interface 中的 readonly 意义一致 其它三个属于访问性修饰符
public此类成员在类、类的实例、子类中都能被访问。private此类成员仅能在类的内部被访问。protected此类成员仅能在类与子类中被访问你可以将类和类的实例当成两种概念即一旦实例化完毕出厂零件那就和类工厂没关系了即不允许再访问受保护的成员。
class Foo {private prop: string;constructor(inputProp: string) {this.prop inputProp;}protected print(addon: string): void {console.log(${this.prop} and ${addon})}public get propA(): string {return ${this.prop}A;}public set propA(value: string) {this.propA ${value}A}
}
抽象类 抽象类是对类结构与方法的抽象简单来说一个抽象类描述了一个类中应当有哪些成员属性、方法等一个抽象方法描述了这一方法在实际实现中的结构。
abstract class AbsFoo {abstract absProp: string;abstract get absGetter(): string;abstract absMethod(name: string): string
}抽象类中的成员也需要使用 abstract 关键字才能被视为抽象类成员如这里的抽象方法。我们可以实现implements一个抽象类
class Foo implements AbsFoo {absProp: string aaaget absGetter() {return aaa}absMethod(name: string) {return name}
}另外使用 interface 不仅可以声明函数结构也可以声明类的结构
interface FooStruct {absProp: string;get absGetter(): string;absMethod(input: string): string
}class Foo implements FooStruct {absProp: string aaaget absGetter() {return aaa}absMethod(name: string) {return name}
}any、unknown、never
any 类型的主要意义其实就是为了表示一个无拘无束的“任意类型”它能兼容所有类型也能够被所有类型兼容。
千万不要 anyscript !!!
unknown 类型的变量可以再次赋值为任意其它类型但只能赋值给 any 与 unknown 类型的变量
let unknownVar: unknown aaa;unknownVar false;
unknownVar aaa;
unknownVar {site: bbb
};unknownVar () { }const val1: string unknownVar; // Error
const val2: number unknownVar; // Error
const val3: () {} unknownVar; // Error
const val4: {} unknownVar; // Errorconst val5: any unknownVar;
const val6: unknown unknownVar;any 放弃了所有的类型检查而 unknown 并没有
let unknownVar: unknown;unknownVar.foo(); // 报错对象类型为 unknown// 类型断言
let unknownVar: unknown;(unknownVar as { foo: () {} }).foo();类型断言: 虽然这是一个未知的类型但我跟你保证它在这里就是这个类型
never 类型被称为 Bottom Type是整个类型系统层级中最底层的类型
never 类型不携带任何的类型信息因此会在联合类型中被直接移除
泛型
可以理解为一个接受参数的函数
类型别名type中的泛型
type FactoryT T | number | string;type中的泛型大多是用来进行工具类型封装比如
// 把一个对象中的所有属性变成 string类型
type StringifyT {[K in keyof T]: string;
};// 复制对象中的所有属性类型
type CloneT {[K in keyof T]: T[K];
};看一个例子
type PartialT {[P in keyof T]?: T[P];
};
interface IFoo {prop1: string;prop2: number;prop3: boolean;prop4: () void;
}type PartialIFoo PartialIFoo;// 等价于
interface PartialIFoo {prop1?: string;prop2?: number;prop3?: boolean;prop4?: () void;
}泛型还可以作为条件类型中的判断条件
type IsEqualT T extends true ? 1 : 2;type A IsEqualtrue; // 1
type B IsEqualfalse; // 2
type C IsEquallinbudu; // 2可以设置默认值
List item
type FactoryT boolean T | number | string;// 调用时就可以不带任何参数了默认会使用我们声明的默认值来填充
const foo: Factory false;可以使用 extends 关键字来约束传入的泛型参数必须符合要求 比如
A extends B 意味着 A 是 B 的子类型‘aaa’ extends string18 extends number 成立。联合类型子集均为联合类型的子类型即 1、 1 | 2 是 1 | 2 | 3 | 4 的子类型{ name: string } 是 {} 的子类型因为在 {} 的基础上增加了额外的类型
看下面例子根据传入的请求码判断请求是否成功
type ResStatusResCode extends number ResCode extends 10000 | 10001 | 10002? success: failure;type Res1 ResStatus10000; // success
type Res2 ResStatus20000; // failuretype Res3 ResStatus10000; // 类型“string”不满足约束“number”。type ResStatusResCode extends number 10000 ResCode extends 10000 | 10001 | 10002? success: failure;type Res4 ResStatus; // success多泛型关联 不仅可以同时传入多个泛型参数还可以让这几个泛型参数之间也存在联系类似于传入多个参数
type ConditionalType, Condition, TruthyResult, FalsyResult Type extends Condition ? TruthyResult : FalsyResult;// passed!
type Result1 Conditionalaaa, string, passed!, rejected!;// rejected!
type Result2 Conditionalbbb, boolean, passed!, rejected!;接口interface中的泛型
常见的一个例子应该还是响应类型结构的泛型处理
interface IResTData unknown {code: number;error?: string;data: TData;
}这个接口描述了一个通用的响应类型结构预留出了实际响应数据的泛型坑位然后在你的请求函数中就可以传入特定的响应类型了
interface IUserProfileRes {name: string;homepage: string;avatar: string;
}function fetchUserProfile(): PromiseIResIUserProfileRes {}type StatusSucceed boolean;
function handleOperation(): PromiseIResStatusSucceed {}函数中的泛型
函数中的泛型是很常用的主要是做类型的自动提取
function handleT(input: T): T {}我们为函数声明了一个泛型参数 T并将参数的类型与返回值类型指向这个泛型参数。这样在这个函数接收到参数时T 会自动地被填充为这个参数的类型。
例子
function handleT(input: T): T {}const author aaa; // 使用 const 声明被推导为 aaalet authorAge 18; // 使用 let 声明被推导为 numberhandle(author); // 填充为字面量类型 aaa
handle(authorAge); // 填充为基础类型 number在基于参数类型进行填充泛型时其类型信息会被推断到尽可能精确的程度如这里会推导到字面量类型而不是基础类型。 这是因为在直接传入一个值时这个值是不会再被修改的因此可以推导到最精确的程度。而如果你使用一个变量作为参数那么只会使用这个变量标注的类型在没有标注时会使用推导出的类型。
例子
function swapT, U([start, end]: [T, U]): [U, T] {return [end, start];
}const swapped1 swap([aaa, 18]); // const swapped1: [number, string]
const swapped2 swap([null, 18]); // const swapped2: [number, null]
const swapped3 swap([{ name: aaa }, {}]); // const swapped3: [{}, { name: string;}]函数中的泛型同样存在约束与默认值
// 不再处理对象类型的情况了
function handleT extends string | number(input: T): T {}// 只想处理数字元组的情况
function swapT extends number, U extends number([start, end]: [T, U]): [U, T] {return [end, start];
}函数的泛型参数也会被内部的逻辑消费如
function handleT(payload: T): Promise[T] {return new Promise[T]((res, rej) {res([payload]);});对于箭头函数的泛型其书写方式是这样的
const handle T(input: T): T {}在 tsx 文件中泛型的尖括号可能会造成报错编译器无法识别这是一个组件还是一个泛型此时你可以让它长得更像泛型一些
const handle T extends any(input: T): T {}需要注意的是不要为了用泛型而用泛型就像这样 没有意义
function handleT(arg: T): void {console.log(arg);
};Class 中的泛型
Class 中的泛型和函数中的泛型非常类似只不过函数中泛型参数的消费方是参数和返回值类型 Class 中的泛型消费方则是属性、方法、乃至装饰器等。 同时 Class 内的方法还可以再声明自己独有的泛型参数。我们直接来看完整的示例
class QueueTElementType {private _list: TElementType[];constructor(initial: TElementType[]) {this._list initial;}// 入队一个队列泛型子类型的元素enqueueTType extends TElementType(ele: TType): TElementType[] {this._list.push(ele);return this._list;}// 入队一个任意类型元素无需为队列泛型子类型enqueueWithUnknownTypeTType(element: TType): (TElementType | TType)[] {return [...this._list, element];}// 出队dequeue(): TElementType[] {this._list.shift();return this._list;}
}