TypeScript

120 阅读12分钟

TypeScript简称TS,是一门开源的编程语言。

一、TS 的特点:

  • 完全兼容Javascript,是JS的超集;
  • TS引入了数据类型,可以尽早定位错误,

二、TS环境搭建

Step1:全局安装typescript包 npm i typescript -gyarn add global typescript

【注】查看全局包的安装路径 npm root -g

Step2:安装之后,检查是否安装成功及版本号 tsc -v

Step3:typescript初始化,tsc --init TS初始化后,创建了ts的配置文件tsconfig.json

Step4:创建一个TS文件<hello.ts>

【注】ts文件不能直接运行,必须要转换为js文件才可以运行。

Step5:生成对应的js文件tsc hello.ts

class Person {
    name: string
    age: number

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

    eat() {
        console.log(this.name + '吃饭了' + this.age + '白米饭');
    }
}

let p1 = new Person('Evelyn', 22);
p1.eat();

Step6:终端使用node命令执行js文件 node hello.js

三、webpack打包TS文件

项目中可以借助webpack打包工具直接处理ts文件。

(webpack知识面匮乏,之后补充笔记)

四、TS语法

4-1 TS基础类型

//1、boolean类型
let love: boolean = true;
console.log(love ? 'hold together' : 'break up');//hold together

//2、number类型
//十进制
let num1: number = 27;
//十六进制
let num2: number = 0x6f;
//八进制
let num3 = 0o36;
//二进制
let num4 = 0b101010101;//binary

let n: number = 100;
n = '200'//报错,不能将类型“string”分配给类型“number”

//3、string类型
let str1: string = 'Evelyn-Tora-family';
console.log(str1.split('-'));//['Evelyn', 'Tora', 'family']
console.log(str1.split('-').join('^-^'));//Evelyn^-^Tora^-^family(字符串)

//4、字面量类型
// let a:100 = 200;//报错
let obj: { n: 100 } = { n: 100 };
console.log(obj);//{n: 100}

//5、any类型(很少用)
//any 类型允许变量的值为任意类型, 并且可以进行任意类型的赋值
// let x: any;
let x;
x = 100;
x = 'i love you';
console.log(x);//i love you
//如果声明的变量没有限定类型,默认为any和直接定义类型为any等同
//let x;

//6、void类型 
//void 限制值为 undefined
//可以限制变量的值,更多的时候是限制函数的返回值
let v: void;
console.log(v); //undefined

//强制要求函数的返回值是undefined/没有返回值
function fn1(): void {
    // return 100;//报错
    return;//不会报错
    //不写return语句也不会报错
}
console.log(fn1());//undefined

let fun2 = (): void =>{ 
    return;
}
console.log(fn2());//undefined
//给函数限制返回值类型时,写法:  ():类型

//【注】:void
//<a href="javascript:;"></a>
//<a href="javascript:void(0)"></a>
// <a href="javascript:函数名(实际参数)"></a>


//7、never类型(基本不用)
let a: never = true;
console.log(a);//报错,不能将类型boolean分配给类型never

function fn(): never {
    throw new Error('出错啦');
}
fn();//控制台捕获错误

8、对象

//方式一:声明时限定类型为Object
//键名键值并没有规范语法进行约束
//在获取对象的键值时会报错,因为程序检测不到
let obj : Object = {
    name:'Evelyn',
    age:22,
    sex:'女'
};

console.log(obj);//{name: 'Evelyn', age: 22, sex: '女'}
console.log(obj.name)//报错,类型Object上不存在“name”属性

//方式二
let obj1:{a:number,b:number} = {a:100,b:200};

9、数组 ts中的数组,要求[]里面所有值的数据类型统一,否则是元祖。

//方式一:
let arr1: number[] = [1, 2, 3, 4, 5, 6];

//方式二(泛型式写法)
let arr2:Array<number> = [1,2,3,4,5]
let arr3:Array<string> = ['a','b','c']

10、元祖 元祖的核心仍然为数组,只不过数组中的值的类型可以不一致。 let 变量名: [] = [];

let arr:[string,num,boolean] = ['Evelyn',27,true];
//元祖可以使用数组的API

let arr1: [Array<string>, boolean, string, number] = [['a', 'b', 'c'], true, 'a', 1];
let arr2: [string[],boolean, string, number] = [['a', 'b', 'c'], true, 'a', 1];
//两种写法等效

//二维元祖
let arr3:[boolean,number,string][] = [[true, 100, 'hello1'],[false, 200, 'hello2']]

11、枚举 限制为枚举类型的数据,更像是一种语法规范(定义常量的感jio) enum 枚举名称{}

enum gender{
    man = '男',
    woman = '女'
}

let obj:Object = {
    name:'Evelyn',
    sex:gender.woman
}

4-2 联合类型

联合类型可以一次性声明多个类型,后期设置的变量值,在这个类型范畴中即可 每一个类型中间使用"|"分割,表示"或者"

let x: number | string | boolean;
x = true;
x = 'a';
x = 10;

4-3 类型断言

类型断言和联合类型是联合使用的,类型断言是在真正调用这些类型中的方法的时候,容易起冲突的部分需要下断言。

//语法一:(变量 as 具体哪一个数据类型)(更常用)
let x1: number | string ;
x1 = 123.456;
console.log((x1 as number).toFixed(2));

//语法二:(<具体哪一个数据类型>变量)
console.log((<number>x1).toFixed(2));

4-4 类型推断

//1. 变量声明时已赋值,推断为值对应的类型
let a = 10; //等效于 let a:number = 10
a = 'a';//报错,因为a已经被推断为number,后期赋值只能是number类型的

//2.变量声明时没有赋值, 推断为 any 类型
let a;

//3.函数的返回值推断
let fun = () => {
    return 'str...';//此时函数的返回值类型已经推断为string
}

4-5 函数

//1、函数的参数的写法
let fn = (x: boolean, y: Array<number>) => {
     console.log(x)
     console.log(y)
 }
fn(true, [1, 2, 3]);

//2、函数的返回值写法
//一般在后期开发的时候会更多情况选择自动推算函数返回值类型
let fun = (): Array<number> => {
     return [1, 2, 3];
}
console.log(fun());

//3、箭头函数的完整声明
let fn: () => string = () => {
    return 'hello';
}

//4、可选参数 (参数名?:数据类型)
function fun(a: number, b: string, c?: boolean) {
     console.log(a)
     console.log(b)
     console.log(c);
 }
fun(1, 'hello');
fun(1, 'hello', true);

//5、参数的默认值
//参数的默认值在函数的形参列表的末尾
//前面先定义所有必填的参数,然后在加可选的参数或者是参数的默认值
function fun(a: number, b: string, c: string = 'hello') {
    console.log(a)
    console.log(b)
    console.log(c)
}
fun(10, 'Evelyn');
fun(10, 'Evelyn', 'world');


//6、剩余参数(ES6中的rest参数)
//rest参数:当函数调用的时候,实际参数个数不明确
//rest参数一定要在形式参数列表的结尾,并且返回的是一个纯数组
//剩余参数...args,是一个数组,所以写法:...args: number[]
function fun1(...args: number[]) {
    // console.log(args);
    let res = args.reduce((prev: number, item: number) => {
        return prev + item;
    }, 0)
    console.log(res)
}
fun1(1, 2, 3);

//7、特别注意
let fun: (a: number, b: string, c?: number) => string = (x: number, y: string, z: number = 0) => {
    return x + y + z;
}
console.log(fun(100, '200'));

4-6 类

4-6-1 类的定义

class Student {
    //提前规定类中的属性!!!
    name: string;
    age: number;
    //在实例化对象添加一个统一值的属性
    address: string = '尚硅谷'

    //构造器(构造方法/构造函数)
    //构造器无需程序员手动调用,在new一个类的时候自动执行,
    //每一个类只能有一个构造器
    constructor(name: string, age: number) {
        //在实例化对象的身上添加属性
        this.name = name;
        this.age = age;
    }

    study(): string {
        return (this.name + '爱study...在' + this.address + '学习');
    }

    eat(food?: string): string {
        return `${this.name}爱吃${food}`
    }
}

let s1 = new Student('Evelyn', 22);
let s2 = new Student('Tora', 23);

console.log(s1.study());
// s2.study();
console.log(s1.eat('火锅'));
console.log(s1.eat())

4-6-2 类的继承&成员类型限制

  • public(公开,默认的修饰的权限,加或者不加都可以访问) 当前类内可以直接访问,类外也可以直接访问,其被继承的子类也有权访问
  • private(私有) 当前类内可以直接访问,类外不允许访问,其被继承的子类也不能访问
  • protected(受保护) 当前类内可以直接访问,类外不允许访问,其被继承的子类能访问
//父类
class Father {
    name: string
    age: number
    protected asset: string
    private health: string
    constructor(name: string, age: number, asset: string, health: string) {
        this.name = name;
        this.age = age;
        this.asset = asset;
        this.health = health;
    }
    intorduction() {
        console.log(`我是${this.name},我的年龄是:${this.age}`)
    }
    showAsset() {
        console.log(`爸爸的资产是${this.asset}`)
    }

    protected showHealth() {
        console.log(`${this.name}的身体状况:${this.health}`)
    }
}

//子类
class Son extends Father {
    grade: string
    //当如果子类继承了父类,如果子类没有明确表示构造器,则调用的是父类的构造器
    //当所有子类的实例化对象的属性值是统一的值,则需要直接赋值,无需通过构造器
    //每一个实例化对象的属性值都不相同,则必须通过构造器的形式参数传递实际参数来表示
    constructor(name: string, age: number, grade: string, asset: string, life: string) {
        super(name, age, asset, life);
        this.grade = grade;
    }
    showAsset() {
        console.log(`儿子的资产是${this.asset}`)
    }
    study() {
        console.log(`${this.name}每天都得学习,现在在读${this.grade}`)
    }
    showHealthhh() {
        console.log(this.showHealth());
    }
}

let f1 = new Father('爸爸', 50, 'one billion', '延年益寿');
let s1 = new Son('好大儿', 22, '高一年级', '¥100', '茁壮成长');

//protected
// console.log(f1.asset) //报错,属性"asset"受保护,只能在类Father及其子类中访问,类外不能访问
f1.showAsset();//爸爸的资产是one billion
s1.showAsset();//儿子的资产是¥100

//private
// console.log(s1.health);//报错,属性“health”是私有属性,只能在类Father中访问
// console.log(f1.health);
// f1.showHealth();//报错,属性“showHealth”受保护,只能在类“Father”及其子类中访问
s1.showHealthhh();//好大儿的身体状况:茁壮成长(间接读取私有属性值)

//public
s1.study();//好大儿每天都得学习,现在在读高一年级
s1.intorduction();//我是好大儿,我的年龄是:22
f1.intorduction();//我是爸爸,我的年龄是:50


/*
每次new一个实例化对象时,就会调用其构造函数的constructor,
new Son('好大儿', 22, '高一年级', '¥100', '茁壮成长'),这里是实参,
constructor(name: string, age: number, asset: string, health: string),这里是形参,
如果存在继承,子类的constructor中调用super方法,将值传递给super方法,
(super方法会调用父类的构造器)
super(name, age, asset, life),这里是实参,而父类的constructor中是形参。
*/

4-7 接口

接口的作用是为了规范对象的结构(接口就是为对象服务的!)

【注】如果使用let obj:Object={a:100}的方式,由于Object中不存在属性a,所以obj.a会报错!

  • 声明一个接口:
    interface 接口名称{}
  • 在对象中使用接口:
    let obj : 接口名称 = {...}
  • 在类中使用接口:
    class Person implements 接口名称{...}

4-7-1 对象&接口

interface Person {
    //属性分为可选属性、必填属性、只读属性
    //1、必填属性:
    //如果接口中定义的是必填属性,那么对象在调用接口类型时,必填属性要写全,否则会报错
    name: string,
    //2、只读属性,不允许被修改
    readonly age: number,
    //3、可选属性
    //如果接口中定义的是可选属性,那么对象在调用接口类型时,可选属性可以不写全,不会报错
    sex?: string

    //必填方法
    eat: () => void
    //如果接口中函数返回值设置为void,不会那么严格,对象种方法有返回值也可以
    //可选方法
    speak?: () => string
}

let obj: Person = {
    age: 22,
    name: 'Evelyn',
    //必填方法
    eat: () => {
        console.log('去吃火锅吧!')
    },
    //可选方法
    speak: () => {
        return 'Do not speak loudly'
    }

}
// obj.age = 18; // 报错
console.log(obj.age)
console.log(obj.name)
console.log(obj.sex)//undefined

//方法
obj.eat();//去吃火锅吧!

//如果接口中定义的方式是可选方法时,在对象实现且调用时,
//程序认为这个方法可能存在也可能不存在
//如果存在就是接口中定义的方法值,如果不存在就是undefined
//解决方法有两个:
//1、修改tsconfig.json文件中的严格模式“strict”:false(修改了配置文件脚手架要重启)
//2、调用方法时,在调用的()前面加一个!,程序就会认为speak方法一定存在
console.log(obj.speak())//Do not speak loudly
console.log(obj.speak!())

4-7-2 类&接口

1.类中应该有什么样的属性/方法,需要使用接口来进行规范!

2.接口和类的关系是:类是实现接口中的规范(implements),每个类可以实现多个接口规范;

3.类要[全部]实现接口中规范的接口和属性

class 类名 implements 接口名称1,接口名称2...{}

  • 类——类 [继承关系] class 子类 extends 父类{}
  • 接口——接口 [继承关系] interface 子接口 extends 父接口{}
  • 类——接口 [实现关系] class 类 implements 接口{}
interface PerInterface {
    //接口中定义属性&方法的名和类型
}

//接口中可能有重复属性,所以将公共的属性单独抽离出来一个公共的接口
//所以其他的每一个接口需要来继承这个公共的接口Common
interface common {
    name: string,
    age: number
}

interface PerInterface1 extends common {
    hobby: string
}

interface PerInterface2 extends common {
    sex: string
}

class Person implements PerInterface1, PerInterface2 {
    name: string;
    age: number;
    sex: string;
    hobby: string
    //类如果已经全部实现了接口规范中的属性和方法后,仍然可以给自身添加自定义属性
    other: string

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

let p1 = new Person('Evelyn', 22, '女', '躺着', '接口之外的其他属性');
console.log(p1);
//Person {name: 'Evelyn', age: 22, sex: '女', hobby: '躺着', other: '接口之外的其他属性'}

4-8 泛型

泛型(generic)指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。“不确定”

  • 泛型变量不固定,但是基本上都是一个字母且大写;
  • 通常用的变量名是:P、I、R、U...

4-8-1 函数中的泛型变量的使用

泛型在函数中的使用,主要用于解决,在声明时不确定形参类型。

语法:function 函数名称<泛型名称> {...}

//如果不规定返回值:function fun<P, I>(x: number, y: P, c: I): (P | I)[]
//返回值类型会报错,默认的返回值类型是一个联合类型的数组
function fun<P, I>(x: number, y: P, c: I): [P, I] {
    return [y, c];
}
let res = fun(100, 'hello', 27.27);
console.log(res[0].indexOf('e'))
console.log(res[1].toFixed(1))


//创建一个函数, 实现功能: 根据指定的数count和数据value, 创建一个包含count个value的数组
function fun1<U>(count: number, value: U) {
    let arr = []
    for (let i = 0; i < count; i++) {
        arr.push(value)
    }
    return arr;
}

console.log(fun1(3, 'Evelyn'));
console.log(fun1(6, 27));

4-8-2 对象中的泛型变量的使用

泛型接口——解决对象的结构不确定,主要是针对于结构做规范

//语法:
interface 接口名称<泛型变量>{
    属性名1:属性值1,
    属性名2:属性值2,
        ...
    属性名n:泛型变量
}
//如果对象中的属性又是一个对象,且该对象结构不确定,需要考虑定义泛型变量

interface Common<p> {
    status: number,
    statusText: string,
    data: p
}


interface Todolists {
    id: number,
    title: string,
    done: boolean

}

interface Booklists {
    id: number,
    content: string,
    author: string
}

//接口就是用定义对象的规范
let obj1: Common<Todolists> = {
    status: 200,
    statusText: 'ok',
    data: {
        id: 1,
        title: '任务一',
        done: false
    }
}

let obj2: Common<Booklists> = {
    status: 200,
    statusText: 'ok',
    data: {
        id: 1,
        content: '西游记',
        author: '吴承恩'
    }
}

console.log(obj1.status)
console.log(obj1.data.title)
console.log(obj2.data.author)

4-8-3 接口中的泛型变量的使用

【注】泛型引用,要写在接口名、函数名、类名的后面

当对象中的属性点不出来的时候,要给对象加一个接口,进行对象规范

class MyClass<P>{
    num: number
    str: string
    arr: P[]
    constructor(num: number, str: string, arr: P[]) {
        this.num = num;
        this.str = str;
        this.arr = arr;
    }
}

interface MyInt {
    id: number,
    title: string,
    done: false
}

//数组中是number类型
let mc1 = new MyClass<number>(1, 'aa', [1, 2, 3]);
console.log(mc1)//MyClass {num: 1, str: 'aa', arr: Array(3)}

//数组中是对象类型
let mc2 = new MyClass<MyInt>(2, 'bb', [{ id: 1, title: '任务一', done: false }, { id: 1, title: '任务一', done: false }]);
console.log(mc2.arr[0].done)
//如果不指明泛型变量的数据类型,mc2.arr. 就点不出来对象的属性名

4-9 其他

4-9-1 类型声明

typeof:是用来获取 变量的数据类型(number、string、undefined、null、boolean、function) keyof :是用来获取 接口/类的属性组成的联合类型("属性名1"|"属性名2"|"属性名3")

  • type关键字声明类型
  • type 类型名称 = 类型值
  • 类型只能用来声明后使用,不能打印输出!
//typeof使用
let str1 = 'aa';
type strings = typeof str1;
let str: strings = 'Evelyn'
console.log(str)


//type声明类型多用于以下场景:
interface Booklist {
    id: number;
    bookName: string;
    author: string
}

//定义一个books类型的变量
//是一个数组,数组中的每一个元素是一个对象(Booklist)
type books = Booklist[];
let arr1: books = [{ id: 1, bookName: '西游记', author: '吴承恩' }];
console.log(arr1[0].author)

//keyof使用
type book = keyof Booklist;
// console.log(book);//报错,类型只能用来声明后使用,不能打印输出!!!!

function funnn(x: book) {
    console.log(x)
}
funnn('id');//id
funnn('bookName')//bookName

4-9-2 TS脚手架

let root = document.querySelector('#root') as HTMLElement;
//要做类型断言