前端工程化(2)- TS
文章目录
- 前端工程化(2)- TS
- TS
- 概念
- TS声明变量方式
- TS的类
- 访问修饰符
- 静态属性
- 抽象类
- TS的数据类型
- tuple元祖
- enum枚举
- any
- void
- never
- enum枚举
- 使用例子
- 反向映射
- 枚举和常量枚举(const枚举)的区别
- 接口
- 泛型
- 高级类型
TS
概念
TypeScript 是一个强类型的 JavaScript 超集,支持ES6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。Typescript并不直接在浏览器上运行,需要编译器编译成纯Javascript来运行。
添加了静态类型,更利于构建大型应用:
因为有类型约定,减少了在写代码的时候出现错误,而且出现问题后更容易查错。
代码更清晰,某种程度可以看作文档,后面新人接手项目能更快上手。
TS声明变量方式
// 声明类型和值
let name: type = value;
// 只声明类型
let name: type;
// 只声明值(类型隐式添加上了)
let name = value;
// 声明变量无类型和值
let name;
TS的类
类是面向对象程序设计实现信息封装的基础。
JS基于原型的方式让开发者多了很多理解成本。在ES6之后,JS拥有了class关键字(本质依然是构造函数),但是JS的class依然有一些特性没有加入,比如修饰符和抽象类。TS的class支持面向对象的所有特性,比如接口等。
访问修饰符
public,表示属性或方法是公共的,可以在任何地方访问。
protected,表示属性或方法是受保护的,可以在类内部和子类中访问。
private,表示属性或方法是私有的,只能在类内部访问。
readonly,属性设置为只读,只读属性必须在声明时或构造函数里被初始化。
TypeScript 中的修饰符主要在编译阶段进行检查,而 JavaScript 运行时并不支持这些修饰符。
class Animal {
public name: string; // 默认为 public
private age: number; // 私有属性,只能在类内部访问
protected sound: string; // 受保护的属性,可以在子类中访问
constructor(name: string, age: number, sound: string) {
this.name = name;
this.age = age;
this.sound = sound;
}
public makeSound(): void {
console.log(`${this.name} says ${this.sound}`);
}
private getAge(): number {
return this.age;
}
protected showAge(): void {
console.log(`${this.name} is ${this.getAge()} years old.`);
}
}
class Dog extends Animal {
constructor(name: string, age: number) {
super(name, age, 'Woof');
}
public bark(): void {
console.log(`${this.name} barks loudly!`);
// 在子类中可以访问 protected 属性和方法
this.showAge();
}
}
const cat = new Animal('Whiskers', 3, 'Meow');
cat.makeSound(); // 可以访问 public 方法
// cat.age; // 编译错误,无法访问 private 属性
const dog = new Dog('Buddy', 2);
dog.makeSound(); // 可以访问从父类继承来的 public 方法
// dog.showAge(); // 编译错误,受保护的方法只能在子类内部访问
dog.bark(); // 可以访问子类的 public 方法
静态属性
这些属性存在于类本身上面而不是类的实例上,通过static进行定义,访问这些属性需要通过 类型.静态属性 的这种形式访问
class Square {
static width = '100px'
}
console.log(Square.width) // 100px
上述的类都能发现一个特点就是,都能够被实例化。TS中还有一种不会被实例化的抽象类。
抽象类
抽象类做为其它派生类的基类使用,它们一般不会直接被实例化,不同于接口,抽象类可以包含成员的实现细节。
// abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
// 这种类并不能被实例化,通常需要我们创建子类去继承
class Cat extends Animal {
makeSound() {
console.log('miao miao')
}
}
const cat = new Cat()
cat.makeSound() // miao miao
cat.move() // roaming the earch...
TS的数据类型
TS兼容JS,拥有JS相同的数据类型,另外在JS基础上提供了更多的类型。
在开发阶段,可以为明确的变量定义为某种类型,这样typescript就能在编译阶段进行类型检查,当类型不合符预期结果的时候则会出现错误提示。
boolean(布尔类型)number(数字类型)string(字符串类型)array(数组类型)object 对象类型 null 和 undefined 类型
tuple(元组类型)enum(枚举类型)any(任意类型)void 类型 never 类型
tuple元祖
元祖类型,允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
赋值的类型、位置、个数需要和定义(生明)的类型、位置、个数一致。
let tupleArr:[number, string, boolean];
tupleArr = [12, '34', true]; //ok
typleArr = [12, '34'] // no ok
enum枚举
下面再详细描述。
any
可以指定任何类型的值,在编程阶段还不清楚类型的变量指定一个类型,不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查,这时候可以使用any类型。
let num:any = 123;
let arrayList: any[] = [1, false, 'fine'];
void
用于标识方法返回值的类型,表示该方法没有返回值。
function hello(): void {
alert("Hello Runoob");
}
never
never是其他类型 (包括null和 undefined)的子类型,可以赋值给任何类型,代表从不会出现的值。
但是没有类型是 never 的子类型,这意味着声明 never 的变量只能被 never 类型所赋值。
never 类型一般用来指定那些总是会抛出异常、无限循环。
let a:never;
a = 123; // 错误的写法
a = (() => { // 正确的写法
throw new Error('错误');
})()
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
enum枚举
枚举主要是将一些预定义的值显示的表达出来。它的优势是更加语义化,并更好维护。
在工作中也经常用,比如定义一些后端传回的错误码,就会用枚举把数字变成更好读的枚举名称。比如规定一些key的值,在不同地方使用的时候统一用枚举,减少拼错等BUG的可能性,也和后端更好对接。
使用例子
todo 在项目里找一下很多。
反向映射
反向映射只适用于数字枚举。字符串枚举等没有反向映射。
当我们声明一个枚举类型是,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从0开始依次累加:
enum Direction {
Up, // 值默认为 0
Down, // 值默认为 1
Left, // 值默认为 2
Right // 值默认为 3
}
console.log(Direction.Up === 0); // true
正向映射( name -> value )和反向映射( value -> name )
console.log(Direction[0]); // Up
枚举和常量枚举(const枚举)的区别
枚举会被编译时会编译成一个对象,可以被当作对象使用;
const枚举会在ts编译期间被删除,避免额外的性能开销;
// 普通枚举
enum Witcher {
Ciri = 'Queen',
Geralt = 'Geralt of Rivia'
}
function getGeraltMessage(arg: {[key: string]: string}): string {
return arg.Geralt
}
getGeraltMessage(Witcher) // Geralt of Rivia
// const枚举
const enum Witcher {
Ciri = 'Queen',
Geralt = 'Geralt of Rivia'
}
const witchers: Witcher[] = [Witcher.Ciri, Witcher.Geralt]
// 编译后
// const witchers = ['Queen', 'Geralt of Rivia'
接口
接口可以用于定义对象、函数、类等的类型。
定义对象的结构:
interface Person {
firstName: string;
lastName: string;
age?: number; // 可选属性
}
// 符合接口结构的对象
let person: Person = {
firstName: "John",
lastName: "Doe"
};
定义函数类型:
interface GreetFunction {
(greeting: string, name: string): string;
}
let greet: GreetFunction = function (greeting, name) {
return `${greeting}, ${name}!`;
};
定义类的接口:
interface Shape {
area(): number;
}
class Circle implements Shape {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
area(): number {
return Math.PI * this.radius ** 2;
}
}
可扩展的对象结构:
interface Car {
brand: string;
model: string;
// 允许添加其他属性
[key: string]: any;
}
可以继承:
interface Father {
color: String
}
interface Mother {
height: Number
}
interface Son extends Father,Mother{
name: string
age: Number
}
在多人开发的时候有对象属性类型约束或者有函数输入输出类型约束,可以提高效率避免BUG。
泛型
泛型就是把类型当成变量。定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型。
在泛型中,类型参数写在左括号(<)和右括号(>)之间。
接收什么类型的参数返回什么类型的参数,即在运行时传入参数我们才能确定类型:
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("CoderBin");
let output2 = identity<number>( 117 );
定义泛型的时候,可以一次定义多个类型参数,比如我们可以同时定义泛型 T 和 泛型 U:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
接口声明
interface ReturnItemFn<T> {
(para: T): T
}
const returnItem: ReturnItemFn<number> = para => para
类声明
class Stack<T> {
private arr: T[] = []
public push(item: T) {
this.arr.push(item)
}
public pop() {
this.arr.pop()
}
}
const stack = new Stacn<number>()
泛型约束:如果泛型只能传递 string 和 number 类型,这时候就可以使用 的方式猜实现约束泛型。
type Params = string | number
class Stack {
}
高级类型
交叉类型和联合类型:
交叉类型:通过 & 将多个类型合并为一个类型,包含了所需的所有类型的特性,本质上是一种并的操作。
T & U
联合类型:表示其类型为连接的多个类型中的任意一个,本质上是一个交的关系。
T | U
类型别名:类型别名会给一个类型起个新名字,类型别名有时和接口很像,但是可以作用于原始值、联合类型、元组以及其它任何你需要手写的类型。
type some = boolean | string
const b: some = true // ok
可以是泛型
type Container = { value: T };
keyof:用于获取一个接口中Key的联合类型。
interface Button {
type: string
text: string
}
type ButtonKeys = keyof Button
// 等效于
type ButtonKeys = "type" | "text"
常用 keyof typeof obj
类型约束:通过关键字 extend 进行约束,不同于在 class 后使用 extends 的继承作用,泛型内使用的主要作用是对泛型加以约束。
type BaseType = string | number | boolean
// 这里表示 copy 的参数
// 只能是字符串、数字、布尔这几种基础类型
function copy<T extends BaseType>(arg: T): T {
return arg
}
类型约束通常和类型索引一起使用,例如我们有一个方法专门用来获取对象的值,但是这个对象并不确定,我们就可以使用 extends 和 keyof 进行约束。
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
const obj = { a: 1 }
const a = getValue(obj, 'a')
Omit 以一个类型为基础支持剔除某些属性,然后返回一个新类型。 语法如下
Omit<Type, Keys>
interface Todo {
title: string
description: string
completed: boolean
createdAt: number
}
type TodoPreview = Omit<Todo, "description">
Partial 可以快速把某个接口类型中定义的属性变成可选的
interface IUser {
name: string
age: number
department: string
}
//经过 Partial 类型转化后得到
type optional = Partial<IUser>
// optional的结果如下
type optional = {
name?: string | undefined;
age?: number | undefined;
department?: string | undefined;
}
Merge<O1, O2> 是将两个对象的属性合并。
Overwrite<T, U> 是用U的属性覆盖T的相同属性。
Exclude<T, U>的作用是将某个类型中属于另一个类型的移除掉
https://juejin.cn/post/7157149049714376735
https://juejin.cn/post/6988763249982308382
https://juejin.cn/post/7162011064819777567
https://juejin.cn/post/6999985372440559624
https://www.tslang.cn/docs/handbook/interfaces.html