0
点赞
收藏
分享

微信扫一扫

TypeScript语法总结看这篇就够

1.Typescript概述

融合了后端面向对象思想的超级版的JavaScript语言。

TypeScript是JavaScript的超集,扩展了JavaScript的语法。

特点:

(1) TypeScript: 【静态类型检查器】 可在编译时检查错误

(2) TypeScript为JS的超集

(3) TS === JS + 【类型】 js有的ts都有

(4) TS 规范了 JS类型声明 使之更加严谨

TypeScript本质就是带有类型检查的JavaScript。

好处:

javascript,灵活,但会使程序出现不可预知的错误,运行时才检测。

typescript,编译时发现错误,更好的代码提示。规范代码,提升代码可读性,提高开发效率。

2.环境准备

基于node环境,vscode工具+Typescript插件。

npm i typescript -g  // 全局安装Typescript 

tsc -v // 查看ts版本

新建一个项目,在其中新建一个ts文件demo.ts,文件内容如下

console.log("hi Typescript") //先使用js的代码内容

由于ts文件是不能够在浏览器中解析的,ts文件首先需通过编译,编译后才能被浏览器解析,用命令

// tsc + 文件名
tsc demo.ts // 将ts转为js浏览器可识别的代码

// node + 文件名
node demo.js

由于每次ts修改都需使用tsc命令进行编译成js,比较麻烦,可使用以下命令进行侦听,当文件发生变化后,会自动编译ts。

tsc demo.ts -w

此时该终端窗口不关,新开一个窗口 直接执行node命令

node demo.js

这里只是对单个文件进行监听,若项目中有很多的ts文件,如何对所有文件进行侦听呢。

先在项目下新建一个tsconfig配置文件,也可执行以下命令生成:

tsc --init  // 生成tsconfig配置文件


此时再在项目中执行以下命令则可以对整个项目的ts文件进行监听了。

tsc -w

还有更简洁的方式(直接运行ts文件方法),需安装以下包

安装ts-node

ts-node 是一个TypeScript执行引擎,能让我们在 Node.js 环境下直接运行 TypeScript 代码。

npm i ts-node -g

npm init -y // 初始化一个package.json

安装声明文件

npm i @types/node -D


@types/node是TypeScript的一个声明文件包,用于描述Node.js核心模块和常用的第三方库的类型信息。

这些声明文件增加了对ts在Node.js环境下的支持,并提供了更好的类型安全和编辑器智能提醒。

@types/node提供了更好的Node.js环境下TypeScript的类型声明支持,能够获得更好的类型提示及错误提示等。

安装完毕后,可直接用命令执行ts文件!

ts-node index.ts

此时可直接用命令执行ts文件!

3.数据类型

3.1 基本类型(原始类型)

原始类型(string, number, boolean, undefined, null)

特点:简单,这些类型,完全按照 JS 中类型的名称来书写。

//string类型
let str: string = 'abcdefg'; //:string   表示【类型注解】,声明变量的时候就进行类型约束
console.log("🚀 ~ str:", str)
// str = 12; // 报错,输入与预先定义的类型不同的值的时候,就会报错,

// number类型
let num:number = 10;
console.log("🚀 ~ num:", num)

// boolean类型
let bool: boolean = true;
console.log("🚀 ~ bool:", bool)

//类型推导:  变量未指定类型时,ts会依照上下文为其指定类型。
let age = 19
console.log("🚀 ~ file: 基本类型.ts:15 ~ age:", age)
let level; // 未赋初值,类型为any
console.log("🚀 ~ level:", level)

// undefined类型
let un: undefined = undefined;
console.log("typeof un:", typeof un)
// 声明一个变量,但是没有赋值,该变量值为undefined
let a;
console.log("🚀 ~ a:", a)

// null类型
// null表示什么都没有,表示一个空对象引用
let nu: null = null;
console.log("🚀 ~ typeof nu:", typeof nu ) // object

这样做好处就是当输入与预先定义的类型不同的值的时候,就会报错。

3.2 基本类型(对象类型)

对象类型:object(包括数组、对象、函数等对象)。

3.2.1 Object与object区别

// 一、Object与object区别:
// Object与原型链有关,因为原型链的顶端就是Object或者function,在TS中他就代表了所有的类型。
let a1: Object = 2;
let a2: Object = "2";
let a3: Object = () => 12;
// 除了null,undefined  不能赋值给它,其他的都可以

// object表示非原始类型,也就是除number,string,boolean等之外的类型。它支持引用类型,比如数组,对象,函数等等。
let x: object = [];
let x1: object = {};
let x2: object = () => 2;

3.2.2 数组类型(Array)

// 二、数组类型(Array)
// 两种方式定义数组
// 1种是元素后接[]
let arr1: number[] = [1,2,3]
// 2种是使用数组泛型,Array<元素类型>,泛型后面讲
let arr2:Array<number>= [3,4,5];
let arr0 =[4, 5, 6];//另外也可省略掉指定类型,编译器会自动推断

// 二维数组定义方式
let arr3:number[][] = [[1], [2], [3]];
let arr4: Array<Array<number>>= [[1],[2], [3]];

3.2.3 元组tuple

// 三、元组tuple
// 元组是ts中新增类型,固定长度且每个元素都是特定类型的数组就是元组,元组一般用于键值对。
let user: [number, string] = [1, 'zhaogu']

3.2.4 函数和函数类型

//四、函数和函数类型
// 函数---指定参数和返回值类型
function add(x: number,y: number): number {
    return x + y;
}
// 通常也可【省略】返回值类型,TS能根据返回语句自动进行类型推断。
function add2(x: number,y: number) {
    return x + y;
}
// 返回值类型void
function testFunc(name: string): void {
    console.log(name);
}
// 有时需给某参数传值,有时不需要
// 第一种方法:参数加? 表示可选,可选参数必须放在必须参数后面。

function getName(name: string, nickName?: string) {
    let resName: string = name + ' ' + nickName;
    console.log("🚀 ~ getName ~ resName:", resName)
    return resName;
}
getName('mary')
getName('mary', 'coco')
// 第二种方法:参数设置默认值
function getName2(name: string, nickName = 'cc') {
    let resName2: string = name + ' ' + nickName;
    console.log("🚀 ~ getName2 ~ resName2:", resName2)
    return resName2;
}
getName2('nana')
getName2('nana', 'kiki')

// 带默认值的参数不需要一定在必须参数后,若在必须参数之前,用户必须明确传入undefined值获得默认值
function getName3(nickName = 'uu', name: string) {
    let resName3: string = name + ' ' + nickName;
    console.log("🚀 ~ getName3 ~ resName3:", resName3)
    return resName3;
}
// getName3('nana') // 报错
getName3(undefined, 'nana')
getName3('vv', 'nana')

// 函数表达式类型写法,会根据右边函数推到并且返回给变量info
const info = (name:string, age:number): number => {
    return age
}
// 为函数定义类型
const info2: (name:string, age:number) => number = (name, age) => {
    return age;
}
// 上面写法较繁琐,可用type将类型抽离。type后面详讲。
type InfoType = (name:string, age:number) => number
// interface InfoType {
//     (name:string, age:number): number
// }
const info3: InfoType = (name, age) => {
    return age;
}

3.2.5 对象类型

// 四、对象
const employeeObj: {
    readonly id: number;
    name: string;
    age?: number; // 可选属性
    retire: (date: Date) => void
} = {
    id: 1,
    name: 'kele',
    age: 18,
    retire: (date: Date) => {
        console.log("🚀 ~ date:", date)
    }
}

3.3 基本类型(枚举类型)

枚举enum:是各个有关联的常数的列表。在ts中,用首字母大写方式(Pascal命名法)

为什么使用?:解决多次if/switch判断值的语义化问题,语义更清晰,增强可读性。

// 如定义T恤的尺寸
enum Size {
    Small,
    Medium,
    Large
}
// 默认情况下,第一个常数sma1l值为0,第二个Medium为1,以此递增

// a.数字枚举
// 也可设置开始值,如设置Sma11值为1,第二个Medium为2,以此递增
enum Size1 {
    Small=1,
    Medium, // 2
    Large // 3
}
// 取值方式
console.log("🚀 ~ enum-Size1.Small:", Size1.Small)
console.log("🚀 ~ enum-Size1:", Size1['Small']) // 以key取值
console.log("🚀 ~ enum-Size1:", Size1[2]) // 以值取key

// b.字符串枚举
// 枚举值还可设置string,这样需全部手动赋值。
enum Size2 {
    Small = 's',
    Medium = 'm',
    Large = 'l'
}
console.log("🚀 ~ enum-Size2.Large:", Size2.Small)

3.4 基本类型(其他特殊类型)

其他特殊类型(void, any, unknown, never)

// 一、void类型 表示没有任何类型
let vo: void = undefined;
let vo2: null = null;
// 声明一个void类型的变量没什么大用,因为只能为它赋予undefined和null。
// 当一个函数没有返回值时,会见到其返回值类型是 void
function warnUser(): void {
    console.log("This is my warning message");
}

// 二、any类型
// any是假设变量可为任何类型,若使用any,相当于丢掉ts的优点,应当避免使用any类型。
// 但实际开发中在编程阶段还不清楚变量的类型,此时可用any。
let ss: any = "";
ss= 1;
ss= true;
ss = [1, 2, 3];

function test(param: any) {
    console.log("🚀 ~ test ~ param:", param)
}

// 三、unknown类型 尽量用unkown而避免使用any,因为前者促使做一些类型检查,保证目标对象确实存在调用的方法或属性
function check(oo: any) {
    oo.fly(); // any类型我们可调用任何方法或属性,如果实际不存在,应用程序会崩渍,所以避免使用any
}
function check2(oo: unknown) {
    if (typeof oo === 'string') {
        oo.toLowerCase();
    }
    // 尽量用unkown而避免使用any,因为前者促使做一些类型检查,保证目标对象确实存在调用的方法或属性
    
    oo.fly(); // 使用unknown及早看到编译错误,不然使用any运行时才崩溃暴露问题。
}
// any和unknown相同点:
// any和unknown可以是任何类的父类,所以任何类型都可赋值给any类型或者unknown类型的变量
// 不同点:
// 1.any可以是任何类型的子类,但unknown不可以,所以any类型的变量可以赋值给其他类型的变量。
// 2.不能拿unknown类型的变量来获取任何属性和方法,但any类型的变量可获取任意名称的属性和方法。

// 四、never类型.表示该值永不会发生,never类型用得很少,不常用。
// 1.返回never的函数必须存在无法达到的终点 //死循环
function foo(): never {
    while(true) {
    }
}
foo();
console.log(1111) // 永不会被执行,因为函数中有死循环,指示该函数为never。

// 2.总是会抛出错误的函数
function bar(msg: string): never {
    throw new Error(msg)
}
// never类型是任何类型的子类型,也可赋值给任何类型,但never除了其自身外,没有类型可赋值给never类型,any也不可以。

3.5 高级类型
3.5.1 类型别名(type)

// 一、类型别名 type
// 用type关键字自定义一个类型,类型名首字母大写(Pascal命名法)
// type可定义任何类型。

// 定义基础类型
type nn = number;
// 定义对象类型
type Employee = {
    readonly id: number;
    name: string
}
let employee: Employee = {
    id: 1,
    name: 'vv'
}
// 定义数组类型
type CustomArr = Array<number>;
let customArr: CustomArr = [1,2,3]

3.5.2 接口类型(interface)

// 二、接口类型 interface
// 接口类型:另一种定义对象类型的接口
// interface 来声明接口,声明后,直接使用接口为变量的类型

// 接口定义对象类型
interface IPeople {
    username: string;
    age: number;
}
let peo: IPeople = {
    username: "uu",
    age: 20,
}
// 可索引签名:动态生成属性
interface IFruit {
    id: string;
    type: string;
    // 类型不明确时可写成any,未知属性可索引签名
    [prop:string]: any;
}
const fruit: IFruit = {
    id: "22",
    type:"apple",
    // 以下属性类型都符合
    color : "red",
    count: 5,
    isCheck: true
}
// 接口重名合并
interface ITool {
    id: number;
}
interface ITool {
    name: string;
    desc?: string;
}
const tool: ITool= {
    id: 1,
    name: '工具1',
    desc: '说明'
}
// 接口继承
interface ITab {
    tabId: string;
    tabName:string;
}
interface ISubTab extends ITab {
    tabSize: string;
}
const tab: ISubTab= {
    tabId: '001',
    tabName: '标签1',
    tabSize: 'small'
}
// 接口应用场景:
// 1.一些第三方包或者框架底层源码中有大量的接口类型
// 2.提供方法的对象类型的参数时使用
// 3.为多个同类别的类提供统一的方法和属性声明

// type与interface相同点:都可给对象指定类型
// type与interface区别:
// interface接口可重名(同名接口会合并为一个接口),type不可重名
// interface用于定义对象类型,可描述对象属性和方法,也可实现继承。type可定义任何类型。

3.5.3 联合类型

// 三、联合类型
// 变量或函数的参数不是确定的类型,可能是某些类型的一种,可使用联合类型,竖线分割。
const ak:string | number = 8;
let salary: number| string | null = "25k";
salary= null;
let arrType: number[] | string[] = [1, 2, 3];
// 还可,
interface Foo {
    foo: string;
    name: string;
}
interface Bar {
    bar: string;
    name: string;
}
const sayHi = (obj: Foo | Bar) => {
    console.log(obj);
}

3.5.4 交叉类型

// 四、交叉类型
// 联合类型是参数或变量的一种类型,而交叉类型则表示【同时】属于两个类型
type UserType1= { userName: string };
type UserType2= { age: number };
const userObj: UserType1 & UserType2 = {
    userName: 'wangwu',
    age: 22
}

3.5.5 字面量类型

字面量类型: 限制只允许某些值。

五、字面量类型--限制只允许某些值
type Flag = "success" | "error"| true | false;
// const flag:Flag = 99; //报错.只能传规定的字面量值
const flag2: Flag = true;

3.5.6 可选链接

// 六、可选链接
// 判定是否为空不为空才作进一步处理,使用可选链接(?.)
type Customer = {
    birthday: Date;
}
function getCustomer(id:number): Customer | null | undefined {
    return id === 0 ? null : { birthday: new Date()};
}
const customer = getCustomer(1);
console.log("🚀 ~ customer?.birthday:", customer?.birthday)
// 不用可选链接,则需要判断对象是否有效
// 使用数组时,有时需判断数组是否为空,也可用可选链接 dogs?.[0]

3.5.7 空值合并运算符(??)

// 七、空值合并运算符(??)
// 空值合并操作符(??) 是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
const speed: number | null = null;
const ride = {
    // 此处不能使用三元运算符,因为当speed为8时也被认为是fa1se而返回30
    // speed: speed ? speed : 30
    speed: speed ?? 30
    // 两个??就是空值合并操作,而??只检测是否是null或undefined
}

3.5.8 类型断言

// 八、类型断言
// 有时我们比TypeScript更知道对象类型,可使用类型断言,关键字是as
// 可理解成强制转换
const phone = document.getElementById("phone") as HTMLInputElement;
// getElementById 返回的类型是HTMLElement 没有我们想要的value属性
// console.log(phone.value);
// 或
const phone2 = <HTMLInputElement>document.getElementById("phone");

interface IFoo {
    foo: string;
    name: string;
}
interface IBar {
    bar: string;
    name: string;
}
const sayHello = (obj: IFoo | IBar) => {
    console.log(obj.name);
    // console.log(obj.foo);//报错,因为还不确定传过来的对象类型
    console.log((obj as IFoo).foo);//确定对象类型,使用类型断言
}

4.类与接口

4.1 面向对象

// 一、对象
// 万物皆对象,都有自己的属性和行为。
// 程序中对象由属性和方法组成,对象中变量叫属性,对象中函数称为方法。
// 面向对象:一种思想,只关心怎么调用,不关心怎么实现的。

4.2 类

类是创建对象的蓝图,是对对象的抽象。类就是拥有相同属性和方法的一系列对象的集合。

// 二、创建类
// 类是创建对象的蓝图,是对对象的抽象。类就是拥有相同属性和方法的一系列对象的集合。
// 通过类可以创建多个对象,每个对象都有自己独立的属性和方法。
// 创建一个动物Animal类,类名首字母大写。
class Animal {
    name: string;
    age: number;

    // 构造函数初始化属性
    // 用构造函数constructor来初始化属性。构造函数不能有返回值,因为它总是返回一个叫Animal的实例。
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }

    sayHi():void {
        console.log("发出动物叫声");
    }
}

4.3 创建对象

// 三、创建对象
// 使用new关键字创建对象,类似调用函数,我们提供初始属性值。
const animal = new Animal("小蛇", 4); //实例化,提供初始属性值
animal.sayHi(); //调用方法
console.log("animal:", animal);
// 用【instanceof】来判断是否是类的实例
console.log(animal instanceof Animal); //true

4.4 类的继承(extends)

// 四、类的继承(extends)
// 上面Anima1是一个广泛的基类,想要不同动物,各自实现自己的功能但又基于Animal。
// extends 继承
class Dog extends Animal{ 
    dogId: string; // 自己的属性
    constructor(name: string, age: number, dogId: string){
        super(name, age); // super()表示调用父类构造方法 (super.属性/方法可用来调用父类的属性或方法)
        this.dogId = dogId;
    }

    // 自己的方法
    walk() {
        console.log("walking");
    }
}
const dog = new Dog("二狗", 2, "001");
console.log("🚀 ~ dog.dogId:", dog.dogId)
dog.sayHi(); //调用的是父类的方法
dog.walk(); // 调用自身方法

// 方法【重写】:有时候想要修改父类的一些实现
class Cat extends Animal {
    catId: string;
    constructor(name: string, age: number, catId: string){
        super(name, age); // 调用父类构造方法
        this.catId = catId;
    }

    // 重写方法,覆盖父类此方法
    sayHi(): void{
        console.log("发出喵~叫声");
    }
}
const cat = new Cat("小喵", 2, "002");
console.log("🚀 ~ cat:", cat)
cat.sayHi();

4.5 修饰符

public private protected 默认情况下属性和方法都是public,外部均可访问。

// 五、修饰符
// public private protected 默认情况下属性和方法都是public,外部均可访问。
// 5.1 private修饰符
// private可加在类属性或方法前,表示只能在类的内部被调用,外部不能访问
class BankAccount {
    id:number;
    owner:string;
    // 属性【私有化】
    private _balance: number;//账户余额

    constructor(id:number, owner:string,balance: number){
        this.id = id;
        this.owner = owner;
        this._balance = balance;
    }

    // 存款方法
    deposit(amount: number){
        if (amount <= 0) {
            throw new Error("Invalid amount");
        }
        // 记录操作记录
        this._balance = amount;
    }

    //方法也可【私有化】
    private calculateTotal(){
        console.log("方法私有化,外部不能访问");
    }

    // 外部不能访问 _balance,所以添加一个方法getBalance用于获取账户
    getBalance(): number{
        return this._balance;
    }
    protected getIncomeDetail() {
        console.log("获取收入明细");
    }
}

const bankAccount = new BankAccount(2,"jj", 0);
console.log("🚀 ~ bankAccount:", bankAccount)
bankAccount.deposit(100); // 存100
// console.log(bankAccount._balance); //报错,私有属性,只能在类中访问
// bankAccount.calculateTotal(); //报错,私有方法,只能在类中访问
console.log("🚀 ~ bankAccount.getBalance():", bankAccount.getBalance())
// bankAccount.getIncomeDetail(); // 受protected保护,只能在类BankAccount及子类中访问

// 5.2 protected修饰符
// 外部不能访问protected成员和private成员。
// 但protected成员是【可继承】的,也就是子类中能看到父类的protected成员,而private不可以。
class subAccount extends BankAccount {
    getIncomeInfo(){
        super.getIncomeDetail();// 访间父类方法
        // super.calculateTotal(); //访问不了,已private私有化
    }
}

4.6 get和set

用来获取和修改类属性的方法。

// 六、get和set
// 用来获取和修改类属性的方法。
// 上面我们用方法getBalanced()在外部获取私有属性_balance,实际有更好的方法:get。
class BankAccount2 {
    id:number;
    owner:string;
    // 属性【私有化】
    private _balance: number;//账户余额

    constructor(id:number, owner:string,balance: number){
        this.id = id;
        this.owner = owner;
        this._balance = balance;
    }

    // 存款方法
    deposit(amount: number){
        if (amount <= 0) {
            throw new Error("Invalid amount");
        }
        this._balance = amount;
    }

    // get:获取属性值
    get balance(): number {
        return this._balance;
    }

    // set:设置属性值。set的作用之一就是在设置属性时进行验证和逻辑操作。
    set balance(value: number){
        if (value <= 0) {
            throw new Error("Invalid amount");
        }
        this._balance = value;
    }
}
const bankAccount2 = new BankAccount2(2, "nana", 200);
console.log("🚀 ~ bankAccount2:", bankAccount2)
console.log("🚀 ~ bankAccount2.balance:", bankAccount2.balance);
bankAccount2.balance = 18888; // 设置值
console.log("🚀 ~ bankAccount2.balance:", bankAccount2.balance);

4.7 静态成员static

// 七、静态成员 static
// 上面讨论的都是类的实例成员,那些仅当被实例化的时候才会被初始化的属性。
// 静态成员存在于【类本身】上,而不是类的实例上。用关键字static定义。
// 静态成员【静态属性+静态方法】
// 调用方式:(类名.方法名)或(类名.属性)

// 静态成员应用场景--工具类
// 工具函数一般都是固定死的,所以工具一般定义为静态方法直接使用
class DateUtils {

    //日期转字符串格式
    static formatDate(date: Date){
        var year = date.getFullYear();//年
        var month = date.getMonth();//月
        var day = date.getDate();//日
        var hours = date.getHours();//时
        var min = date.getMinutes();//分
        var second = date.getSeconds();//秒
        return year + "-" +
            ((month + 1) > 9 ? (month + 1) : "0" + (month + 1)) + "-" +
            (day > 9 ? day : ("0" + day)) + " " +
            (hours > 9 ? hours : ("0" + hours)) + ":" +
            (min > 9 ? min : ("0" + min)) + ":" +
            (second > 9 ? second : ("0" + second));
    }

    //字符串转日期
    static StrToDate(datestr: string) {
        return new Date(datestr);
    }
}
// 调用方式(类名.方法名)
DateUtils.StrToDate('2024-01-03')
console.log("DateUtils.StrToDate:", DateUtils.StrToDate('2024-01-03'))
console.log('DateUtils.formatDate:', DateUtils.formatDate(new Date()));

4.8 抽象类和抽象方法

// 八、抽象类和抽象方法
// 在class前加上abstract关键字,该类就变成了抽象类。
// 抽象类作为其他派生类的基类使用,就是为被别的类继承的。
// 简单讲就是无法进行实例化的类。
// 假设要在画布上画些图形,各个形状有共同属性color和共同方法render,可以创建类Shape
abstract class Shape{
    color: string;
    constructor(color: string){
        this.color = color;
    }
    // 抽象方法不能有实现,即不能有大括号{}
    // 抽象方法也只能存在于抽象类中。
    abstract render(): void;

    foo() {
        console.log("非抽象方法");
    }
}
class Circle extends Shape {
    radius: number;
    constructor(radius: number, color: string){
        super(color);
        this.radius = radius;
    }
    // 抽象类中的抽象方法必须在派生类中实现
    render(): void {
        console.log("rendering a circle");
    }
}
// const shape = new Shape("red") // 抽象类不能实例化
const circle = new Circle(5, "blue");
console.log("🚀 ~ circle:", circle);
circle.render();
circle.foo();
// 抽象类是从子类中发现公共东西,泛化出父类,子类继承父类,考虑把实例化没有任何意义的父类定义为抽象类。
// 要画出什么形状通过子类才知道,子类实现。

4.9 接口

// 九、接口
// 前面提到接口类型interface定义对象结构。
// 在类中也可用interface进行约束,类使用implements关键字进行实现接口。

// 例1:类实现接口
interface IClock {
    currentTime: Date;
    setTime(d:Date): void; // 声明方法
}

class Clock implements IClock {
    currentTime: Date;

    constructor(d: Date){
        this.currentTime =d;
    }

    setTime(d:Date) {
        this.currentTime =d;
    }
}
const clock = new Clock(new Date());
console.log("🚀 ~ clock:", clock)
clock.setTime(new Date("2024-01-01"))
console.log(" clock.currentTime:", clock.currentTime)

// 例2:类继承父类并实现接口
interface PigIn {
    sex: string;
    eat(): void;
}
class Pig extends Animal implements PigIn {
    sex: string;

    constructor(sex: string,name: string,age: number){
        super(name, age);
        this.sex = sex;
    }

    // 实现接口方法
    eat(){
        console.log("吃吃吃,果然是猪...");
    }
}
const pig = new Pig("公","猪猪", 2);
console.log("🚀 ~ pig:", pig)
pig.eat();

// 例3:接口继承及类实现接口
interface Calendar {
    name: string;
    add(): void;
    remove(): void;
}
// 接口也可继承,接口CloudCalendar继承Calendar并新增sync()方法
interface CloudCalendar extends Calendar {
    sync():void;
}
// 实现接口用implements关键字
class GoogleCalendar implements CloudCalendar  {
    name: string;
    constructor(name:string){
        this.name = name;
    }

    add(): void {
        console.log("🚀 ~ GoogleCalendar ~ add ~ add")
    }
    remove(): void {
        console.log("🚀 ~ GoogleCalendar ~ remove ~ remove")
    }
    sync(): void {
        console.log("🚀 ~ GoogleCalendar ~ sync ~ sync")
    }
}

const googleCalendar = new GoogleCalendar('vv日历');
console.log("🚀 ~ googleCalendar:", googleCalendar)
googleCalendar.sync();

4.10 抽象类与接口区别

// 抽象类与接口interface

// 抽象类:是从子类中发现公共东西,泛化出父类,子类继承父类,考虑把实例化没有任何意义的父类定义为抽象类。
// 接口:是把隐式的公共方法和属性组合起来,封装成特定功能的一个集合。

// 类是对对象的抽象,抽象类是对类的抽象,接口是对行为的抽象。
// 接口用于规范,抽象类用于共性。

// 区别:
// 1.抽象类是类,只能单继承,接口能被多实现。
// 2.抽象类中可做方法声明(抽象方法),也可做方法实现(非抽象方法),接口只能做方法声明。
// 3.往抽象类中添加新方法,可提供默认实现,因此不会影响现有代码,接口中添加新方法,所有实现该接口的类都必须修改实现该方法。

// 疑问:到底用abstract抽象类还是interface接口?
// 看具体情况,如果基类没有任何逻辑,仅定义了结构,应该使用接口,代码更简洁。
// 基类有一些逻辑需要共享给子类的话,就该使用抽象类。

5.泛型

5.1 什么是泛型

// 一、泛型:通用类型
// 使用泛型创建可重用的组件,该组件可支持多种数据类型。
// 用<T>代表传递的类型,不一定是T,可以说任何字母,T只是Template的缩写

5.2 泛型函数

// 二、泛型函数
function fn<T>(args: T) : T {
    console.log("🚀 ~ args:", args)
    return args;
}
fn("hello");
fn(888);
fn([1,2,3]);

5.3 泛型类

// 三、泛型类
class KeyValuePair<T> {
    key:  T;
    value: string;
    constructor(key: T, value: string){
        this.key = key;
        this.value = value;
    }
}
// 用<string>给<T>,就可使用类型为string的Key了,还可传递其他任何类型
const keyValuePair = new KeyValuePair<string>("type", "typescript");
// 不指定类型,TS会自动进行类型推断
const keyValuePairNum = new KeyValuePair(1, "typescript");
console.log("🚀 ~ keyValuePairNum:", keyValuePairNum);

// 进一步,将Key和Value都改成泛型
class KeyValuePair2<K, V>{
    key: K;
    value:V;
    constructor(key: K, value: V){
        this.key = key;
        this.value = value;
    }
}
const keyValuePair2 = new KeyValuePair2<string, boolean>("boo", true);
const keyValuePair22 = new KeyValuePair2("boo", 1);//不指定类型,TS会自动进行类型推断
const kv = new KeyValuePair2('user', { name: "dan" });
console.log("🚀 ~ kv:", kv)
// 使用泛型类时,可指定类型,也可不必指定编译器自动感知。绝大多数【无需指定】,由编译器自动识别就好。

5.4 泛型接口

// 四、泛型接口
interface Result<T> {
    data: T[] | null;
    error: string | null;
}
interface User {
    userId: string,
    userName: string
}
interface Product {
    productId: string;
    productName: string;
}
// 因为返回值是泛型,所以函数也需指明是泛型
function fetch<T>(url: string): Result<T> {
    return { data: null, error: null};
}
const userResult = fetch<User>("user-url");
const productResult = fetch<Product>("product-ur1");
// fetch函数返回的接口Resu1t使用了泛型,调用函数时,传什么类型就返回对应类型列表数据,很灵活。

interface TestFoo<T>{
    func(val:T):T;
}
const o1: TestFoo<number> = {
    func: (num) => num
}
const o2: TestFoo<string> = {
    func: (string) => string
}
const n= o1.func(11);
console.log("🚀 ~ o1.func:", n)

5.5 泛型约束

// 五、泛型约束
// 看以下函数,T代表任何类型,导致无法访问任何属性,有时需要类型约束。
// 这里extends关键字在此处理解为“约束于”
function echo<T>(value: T){
    return value;
}
// 如需要约束只能是number或string类型,可使用【 T extends number | string 】
function echo2<T extends number | string>(value: T){
    return value;
}
// const ss = echo2(true); // 报错
const ss2 = echo2(5);

//也可约束为某【对象类型】
type oo ={
    name: string;
    age: number
}
function echo3<T extends oo>(value: T){
    return value.name;
}
const ss3= echo3({ name: 'mary', age: 22 });
console.log("🚀 ~ ss3:", ss3);

// 约束传入的类型必须有1ength这个属性
interface ILength {
    length: number;
}
function identity<T extends ILength>(arg:T){
    console.log(arg.length);
    return arg;
}
// identity(3); //报错.没有1ength属性
identity("qwert");
identity({ length: 6, value: "hhhhhh" });

// 还可约束为某个【类】
class PersonTest{
    name:string;
    constructor(name: string){
        this.name = name;
    }
}
// 约束为PersonTest类
function echoFunc<T extends PersonTest>(value: T){
    console.log("🚀 ~ value:", value)
    return value;
}
echoFunc(new PersonTest("juju"));

5.6 关键字keyof

// 六、关键字keyof
// keyof 可获取对象中的key值
const obj = {
    name: "困困",
    sex: "男"
}
type Key = keyof typeof obj;
// 这里获取Key的类型为联合类型,为"name" ! "sex",这里面type将其推断为联合类型。’

// 使用场景:需要获取一个对象中的某个值
interface IData {
    name: string;
    age: number;
    sex: string;
}
function ob<T extends IData, K extends keyof T>(obj: T, key: K){
    return obj[key];
}
// 使用【keyof】确保key参数是泛型类的属性,不然obj[key]会报错

// 首先第一个参数将T约束为IData类型,第二个参数再将key值約束为传递的T中的K值。
const uuObj: IData = {
    name: '章鱼',
    age: 19,
    sex: '女'
}
const result = ob(uuObj, "name")
console.log("🚀 ~ result---->:", result)

5.7 类型映射

// 七、类型映射
// 有时需要基于某个类型构建另一个类型。
// 比如,IPro接口有name,price两个属性,现在要创建另一个接口Read0nlyPro,需要将name和price改成只读。
// 如果再手动创建ReadonlyPro,明显代码是有重复的,可用类型映射来解决。
// 类型映射结合了索引签名和keyof来实现。
interface IPro {
    name: string;
    price: number
}
type ReadOnlyPro = {
    readonly [property in keyof IPro]: IPro[property]
}
const pp: ReadOnlyPro = {
    name: '产品A',
    price: 16
}
// pp.price = 9; // 编译报错,只读属性,不可设置值

// 我们显然不满足仅将IPro改成Read0nlyPro,我们希望是通用的,可将任何【其他接口】的属性改成只读的。
// 我们将IPro改成泛型<T>,同时为了简洁,property改成字母K。
type ReadOnly<T> = {
    readonly [K in keyof T]: T[K]
}
const pp2: ReadOnly<IPro> = {
    name: '产品A',
    price: 16 // 两属性,缺一不可
}

// 同样道理,我们也可映射成0ptiona1可能性是无限的
type Optional<T> = {
    [K in keyof T]?: T[K]
}
const pp3: Optional<IPro> = {
    name: '产品B',
}
pp3.price = 12
// 正因为类型映射好处太多,Typescript已内置了一些类型。
// 内置工具类型里面的Partial<Type>就是我们的0ptional<T>,Readonly<T>对应我们的Readonly<T>

5.8 内置工具类型
5.8.1 Partial

// 8.1 Partial
// 用途: Partia1工具类型将泛型类型的所有属性变为可选属性
interface Bag {
    name: string; 
    size: string;
    color: string
}
type PartialBag = Partial<Bag>;

5.8.2 Readonly

// 8.2 Readonly
// 用途: Readonly工具类型将泛型类型的所有属性变为只读属性。
type ReadonlyBag = Readonly<Bag>;

5.8.3 Pick

// 8.3.Pick
// Pick<T,K>用途: Pick工具类型用于从泛型类型中【选取】指定属性键集合Keys对应的属性。
type PickBag = Pick<Bag, "name">;
type PickBag2 = Pick<Bag, "name" | "size">;
// Pick 挑选,从对象中挑选一部分作为新的类型。

5.8.4 0mit

// 8.4 0mit
// 0mit<T,K>用途: 0mit工具类型用于从泛型类型中【排除】指定属性键集合Keys对应的属性。
type OmitBag = Omit<Bag, "color">;
type OmitBag2 = Omit<Bag, "color" | "size">;
// 常用于,需要排除一些属性,利用大部分属性,不想要重新在写,可用此工具类型。

5.8.5 Record

// 8.5.Record
// Record<K extends keyof any, T>
// 用途: Record工具类型用于创建一个对象类型,其中键为Key类型的值,值为Type类型的值。
interface E {
    title: string;
    i: string
}
type Article = "home"| "other" | "about";
type Ded = Record<Article, E>;

举报

相关推荐

0 条评论