课程大纲
- 搭建 TypeScript 开发环境。
- 掌握 TypeScript 的基础类型,联合类型和交叉类型。
- 详细类型断言的作用和用法。
- 掌握 TypeScript 中函数、类的类型声明方式。
- 掌握类型别名、接口的作用和定义。
- 掌握泛型的应用场景,熟练应用泛型。
- 灵活运用条件类型、映射类型与内置类型。
- 创建和使用自定义类型。
- 理解命名空间、模块的概念已经使用场景。
- 详细 TS 中的类型保护、装饰器。
- 巧妙运用类型推导和化简代码。
- 深入理解 TypeScript 类型层级系统。
- 详细函数的变与逆变。
- 深入研究 infer 的用法与技巧。
- 详细探究符号类型。
- 灵活编写与运用类型声明文件,扩展 TypeScript 的类型系统。
- 掌握 TS 中类型文件查找规则。
- 深刻使用装饰器,运用反射元数据扩展装饰器的功能,实现控制反转、依赖注入。
- 深度解析 TSConfig 配置文件。
- TS 类型体操
ts基础
目前大部分企业的中大型前端项目都采用了 Typescript,那么为什么我们需要它?
JavaScript 的核心特点就是灵活,但随着项目规模的增大,灵活反而增加开发者的心智负担。例如在代码中一个变量可以被赋予字符串、布尔、数字,甚至是函数,这样就充满了不确定性。而且这些不确定性可能需要在代码运行的时候才能被发现,所以我们需要类型的约束。
当然不可否认的是有了类型的加持多少会影响开发效率,但是可以让大型项目更加健壮
- Typescript 更像后端 JAVA,让 JS 可以开发大型企业应用;
- TS 提供的类型系统可以帮助我们在写代码时提供丰富的语法提示;
- 在编写代码时会对代码进行类型检查从而避免很多线上错误;
越来越多的项目开始拥抱 TS 了,典型的 Vue3、Pinia、第三方工具库,后端 NodeJS 等。我们也经常为以上编程拥有了更好的支持去编写 .d.ts 文件。
1. ts是一门语言
- ts 是 js 的一个超集,扩展了语法添加了静态类型支持以及其他一些新特性
环境配置
全局安装
npm install typescript -g
# 我们可以用tsc来把ts转成js
# 我们要把什么转换成什么,所以我们要先生成一个配置
# 在当前项目下创建一个配置文件tsc --init
# --watch
tsc --init
# 插件code runner这个插件 需要额外安装一个ts-node的包
npm init -y
npm install typescript rollup -D
npm install rollup-plugin-typescript -D
npm i @rollup/plugin-node-resolve rollup-plugin-serve -D
# rollup.config.js
# error lens 插件
学习ts就是学习类型,ts的类型分类(DOM,Promise,原始方法)基础类型,
ts中:后面的都是类型 = 后面的都是值
ts 一切从安全的角度出发,看能不能赋值,就看安全不安全
ts 还有自动的类型推导,不用见到变量就写类型,而是推断的不正确,我们才需要自己编写
基础类型
let abc: string = 'Hello World';
console.log(abc);
let name = 'John Doe'; // 当前模块中,作用域隔离
let age = 20;
let handsome: boolean = true;
// 原始类似标识都是小写的,而类名都是大写的它描述的实例(都是对象)
// 类型注解:告诉TS变量的类型
// 类型推断:TS会自动尝试分析变量的类型
// 类型注解优先级高于类型推断
// 类型注解:变量名: 类型 = 值
// 类型推断:变量名 = 值
let s1: string = 'Hello World';
let s2: String = new String('Hello World');
let s3: String = 'Hello World'; // 会自动转换为对象
// 数组声明
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
// 元组:固定长度的数组
let tuple: [string, number] = ['Hello', 123];
// 元组的越界问题
// tuple[2] = 'World'; // 会报错
let tuple2: readonly [string, number, boolean] = ['Hello', 123, true];
// tuple2[0] = 'World'; // 会报错
export { name };
枚举(自带类型的对象)
let abc: string = 'Hello World';
console.log(abc);
let name = 'John Doe'; // 当前模块中,作用域隔离
let age = 20;
let handsome: boolean = true;
// 原始类似标识都是小写的,而类名都是大写的它描述的实例(都是对象)
// 类型注解:告诉TS变量的类型
// 类型推断:TS会自动尝试分析变量的类型
// 类型注解优先级高于类型推断
// 类型注解:变量名: 类型 = 值
// 类型推断:变量名 = 值
let s1: string = 'Hello World';
let s2: String = new String('Hello World');
let s3: String = 'Hello World'; // 会自动转换为对象
// 数组声明
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
// 元组:固定长度的数组
let tuple: [string, number] = ['Hello', 123];
// 元组的越界问题
// tuple[2] = 'World'; // 会报错
let tuple2: readonly [string, number, boolean] = ['Hello', 123, true];
// tuple2[0] = 'World'; // 会报错
// 枚举 自带类型的对象,自动增长
enum USER_ROLE {
USER,
ADMIN = 6,
MANAGER,
OTHER = 'other', // 异构枚举
}
/*
USER = 0,
ADMIN = 6,
MANAGER = 7,
*/
console.log(USER_ROLE.USER); // 0
// 如果不需要对象可以直接采用常量枚举
const enum USER_ROLE2 {
USER,
ADMIN,
MANAGER,
}
console.log(USER_ROLE2.USER); // 0
export { name };
null 和 undefined
// 严格模式下,不允许使用 any 类型
// nul 和 undefined 是任何类型的子类型
let x: number | null | undefined = 1;
// let y: number = undefined; // 会报错
// let z: number = null; // 会报错
// strict:false 可以使用 any 类型
never类型
永远不
// never 类型 它是任何类型的子类型,也可以赋值给任何类型
// never 类型的变量只能被赋值为 never 类型
function fn1(): never {
throw new Error('报错了');
}
// 类型保护,保障程序的不缺失
// never 是永远到达不了的终点
function fn2(): never {
while (true) {}
}
function validate(x: never) {
console.log(x);
}
// 针对不同的类型做不同的处理
function fn3(x: number | string | boolean) {
// 类似有收敛的作用
if (typeof x === 'number') {
console.log(x.toFixed(2));
return;
}
if (typeof x === 'string') {
console.log(x.trim());
return;
}
if (typeof x === 'boolean') {
console.log(x.valueOf());
return;
}
//守卫语句
validate(x); // 如果类型不匹配,会报错 这个逻辑应该是永远不会执行的所以上在还是有没覆盖到的逻辑(要添加boolean类型的判断)
}
object的类型
// object 类型 object {} Object
let obj: object = { name: 'John Doe' };
// obj.name = 'Jane Doe'; // 会报错
// 小写的 object 是对象类型,大写的 Object 是对象的构造函数
!
号非空断言
联合类型
// 一般我们会基于额外的类型来扩展定义类型 有点类似枚举
type Direction = "up" | "down" | "right" | "left"
let direction :Direction = "left"
// type和interface的区别
type women =
| {
wealthy: true;
waste: string;
}
| {
wealthy: false;
norality: string;
};
// 是富人一定不是节俭的人,是节俭的人一定不是富人
可以利用联合类型来做到属性之间的互斥
断言
断言有可能出问题,出问题后果自负
type women =
| {
wealthy: true;
waste: string;
}
| {
wealthy: false;
norality: string;
};
// 是富人一定不是节俭的人,是节俭的人一定不是富人(类比约定,不严谨)
// 断言 把某个类型断言为为已经存在的一种类型
let ele = document.getElementById('app');
ele!.innerHTML = 'Hello World'; // 绕过TS的类型检查 ele?.innerHTML = 'Hello World'; // 可选链 操作符(取值)
// false || true = true
// false && true = false
// null ?? 'Hello World' = 'Hello World' // 空值合并操作符
// as 类型断言 可以强制把某个类型断言为另外一个类型 as 类型
// 双重断言,我们可以把一个值断言成为 any 类型,然后再断言成为另外一个类型
// 类型断言不是类型转换,只是告诉TS编译器,这个值是什么类型
// 类型断言只在编译阶段起作用,不会影响真实的值
函数
// 函数类型
// 函数声明 function定义 函数表达式
function sum(a: number, b: number): number {
return a + b;
}
let sum2 = function (a: number, b: number): number {
return a + b;
};
let sum3: (a: number, b: number) => number = function (a, b) {
return a + b;
};
// 类型比较长可以写成下面
type Sum = (a: number, b: number) => number;
let sum4: Sum = function (a, b) {
return a + b;
};
// 会根据上下文推导赋予值的类型
let sum5: Sum = (a, b) => a + b;
// 常见的类型推导的方式
// 从右向左 根据赋值进行推导
let name = "jianz"
let age = 20
// 根据返回值进行类型推导
// void 表示不关心返回的具体类型(不校验)
// 可选参数(?)
let sum6 = (a: string = 'a', b?: string): string => {
return a + b;
};
// 对象类型
let person = {
name: 'John Doe',
age: 20,
};
// ts中的this类型需要手动指定,默认是函数的第一个参数
function getVal(this: typeof person, key: keyof typeof person) {
return this[key]; // this指向调用者,必须要有类型断言才能使用{
}
let r = getVal.call(person, 'name');
console.log(r);
重载
// 重载 (一般是有限操作), 它只是一个伪重载,只是一个类型的重载
function toArray(value: number): number[];
function toArray(value: string): string[];
function toArray(value: number | string) {
if (typeof value === 'string') {
return value.split('');
} else {
return value
.toString()
.split('')
.map((item) => parseInt(item));
}
}
let ar = toArray(123);
console.log(ar);
let ar1 = toArray('123');
console.log(ar1);
类(本身就可以充当类型)
它可以描述实例(类类型)
// 类
class Circle {
// 类的属性 ts中所有的属性都要先声明再使用
PI = 3.14;
// 类的构造函数
constructor(public radius: number) {
this.radius = radius;
}
// 类的方法
area() {
return this.PI * this.radius ** 2;
}
}
// 还有一种写法
class Animals {
constructor(public name: string) {
// this.name = name; // 赋值也可以省去
}
eat() {
console.log(this.name + ' is eating');
}
}
const a1 = new Animals('dog');
a1.eat();
// 访问修饰
// public 公共的 默认的
// private 私有的 只能在类的内部访问
// protected 受保护的 只能在类的内部和子类中访问
// readonly 只读的 初始化之后不能修改
// static 静态的
// abstract 抽象的
// #号开头的属性是私有属性 只能在类的内部访问 不能在类的外部访问(这个是一个js语法)
// 不能new
class Singleton {
private static instance: Singleton;
private constructor() {}
static getInstance() {
if (!this.instance) {
this.instance = new Singleton();
}
return this.instance;
}
}
let sg1 = Singleton.getInstance();
// 抽象类不能new
// 不能实例化,只能被继承
// 抽象类中可以拥有具体宾实现
abstract class Animal {
constructor(public name: string) {}
abstract eat(): void; // 原型上的方法 默认采用这种方法更好些
abstract play:()=>void; // 实例上的方法 ts中不做区分,但是一般的是实例方法
}
接口
接口可以理解为对行为的抽象(没有具体的实现) ,可以用于描述 对象,函数,类,混合类型
// 接口是对行为的抽象,而抽象类是对类的抽象
// 用接口描述函数
// interface 描述的是形状或结构
interface IFullname {
firstname: string;
lastname: string;
}
type IFn = (obj: IFullname) => string;
let fn: IFn = ({ firstname, lastname }: IFullname): string => {
return firstname + lastname;
};
fn({ firstname: 'John', lastname: 'Doe' });
// 1. 如果只是用来描述结构我们采用interface
// 2. 如果涉及到联合类型,则只能使用type
// 3. type不能扩展,interface可以扩展
// 4. type不能重名,interface可以重名
// 5. type可以使用typeof获取实例的类型,interface不行
// 6. type可以使用keyof获取key的类型,interface不行
// 7. type可以使用infer获取函数返回值的类型,interface不行
// 8. type可以使用extends实现交叉类型,interface不行
// 9. type可以使用条件类型,interface不行
// 10. type可以使用映射类型,interface不行
// 11. type可以使用索引访问操作符,interface不行
// 可以用接口描述混合类型
interface IFn1{
(a:number,b:number):number;
prop1:string;
prop2:number;
}
// let fn11:IFn1 = (a,b)=>a+b; // 这样就报错了,因为fn11没有prop1和prop2属性且let定义可以重新被赋值
const fn11:IFn1 = (a,b)=>a+b;
fn11.prop1 = 'Hello';
fn11.prop2 = 123;
// 一般情况下我们使用interface来描述对象,如果需要使用联合类型,交叉类型,元组,只能使用type
interface IVeg {
readonly color: string;
size: number;
taste?: 'sweet' | 'sour' | 'bitter'; // 可选属性
}
let veg: IVeg = {
color: 'red',
size: 20,
};
// veg.color = 'green'; // 会报错
如果对象中的属性,多于接口定义的,
- 可以采用断言来赋值
- 可以基于接口的特性写一个同名的接口
- 产生新类型,通过继承原有属性的方式
- 类型兼容
[key:string]:any
任意类型扩展
interface ICar {
color: string;
a: 1;
b: 2;
}
type ValueOf = ICar[keyof ICar]; // 1 | 2 | string
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://joyjs.cn/archives/4409