TS学习笔记

127 阅读16分钟

简介

  1. TypeScriptJavascript的超集
  2. 他对javascript进行了扩展,向Js添加了类型的概念,并增加了许多的特性
  3. TS代码需要由编译器编译为JS,然后由JS解析器执行
  4. TS完全兼容JS
  5. TS拥有静态类型,有更加严格的语法,更强大的功能
  6. TS可以在代码执行之前就完成对代码的检查,减少运行时异常的出现
  7. TS代码可以翻译为任意版本的JS代码,可以解决不同JS运行环境的兼容性问题

基本类型

类型声明

  • 类型声明是TS非常重要的一个特点

  • 当你为一个变量(形参,实参)声明了类型时,当你赋值时,TS就会检查这个参数是不是满足你刚刚声明的类型

  • 类型声明给变量声明了类型,那么以后变量就只可以用这个一种类型

    let 变量:类型;
    let 变量 = 值;//会根据你的值的类型,来判断你的变量的类型
    function fn(参数:类型,参数:类型): 类型{
        
    }
    
类型例子描述
number1,2,-1,0,2.5任意数字
string"hi","hello"任意字符串
booleantrue,false布尔值truefalse
字面量就为设置的字面量限制了这个值就只可以是这个值
any任意类型,TS就不会管这个变量了
就和JS的变量一样
unknown他可以理解为是类型安全的any
void函数的返回值为空值
never没有值不可以是任何值
object{name:'summer'}任意的js对象
array[1,2,3]任意js数组
tuple[4,5]固定好长度的数组
enumenum{A,B}枚举,TS中新增类型

number类型

let num : number;//声明一个变量,同时指定他为Number
//此时你num的类型就已经固定好了,那么就不允许你再去搞其他的类型

let num = 123;//那么他就会直接认为num时number类型的
//当变量的声明和赋值同时进行时,TS会自动检测类型

string类型

let name:string;

boolean类型

let sex:boolean;

字面量

// 可以使用字面量进行类型声明
// 他只是一种声明,他并不是赋值
// 也就是他赋值只可以为10
let a: 10;
console.log("a",a);//undefined
//联合类型
let sex:'male'|'female'
//也就是sex1的值只可以在这两者之间选择,不可以赋值为其他值

any任何类型

  • 显式any
let sth:any;//sth可以为任何类型,也就是这个变量之后的赋值,TS都不会再管他了(类型检查)
  • 隐式any
let sth;//当我们进行声明,但是没有指定类型时,我们会认为这个变量类型为any

unknown类型

//unknown可以理解为类型安全的any
//他和any一样可以赋值为任何类型,但是唯一的不同时,unkown类型不可以赋值给别人
let e:unknown;
let test:string;
//下面会发生报错
test = e;//但是我们就不可以对unknown直接进行赋值
//意思就是,你unknown因为类型不会受到限制,但他只允许你一个人不受到显示,unknown不准你去霍霍其他人
//原因是你赋值的时候,e还是不知道你的类型是什么

//但是any不一样,any可以霍霍别人
let f:any;
test = f;//这边就不会发生报错

//所以正对这种情况,我们需要使用类型断言
  • 类型断言(针对unknown类型不能进行赋值问题)

    变量 as 类型

    <类型>变量

let e:unknown;
e="summer";
let name:string;
//意思就是明确告诉TS编辑器,我这个e就是string类型的
name = e as string//这样就不会报错
//或者
name = <string>e

void类型

当我们函数没有返回值时,我们最好使用void

只要使用了void,就不允许你再有返回值了

注意ts认为,return nullreturn nudefined ,都是空的


function plus(a:number,b:number):void{
    console.log(a+b)
}

// 注意ts认为,return null 和 return nudefined ,都是空的
function add(a, b):void {
    // return 'hello';//报错
    // return null//就是认为空的
    // return undefined//认为是空的
}

假如我们的函数没有写返回值类型,那么TS编辑器会根据你的return来推测你的返回值类型

假如你没有写return,那么TS编辑器会认为你的返回值类型为void

function fn() {
    // return "hello"
    // return null
}

never类型

表示他永远不会有返回结果,一般用来抛出异常之类的

function fn2(): never{
    throw new Error("报错")
}

object类型

object类型包含了太多的类型,Array,function

所以我们对于object类型一般写的更加仔细一点

  • 普通对象类型

注意顺序

let obj1 :{name:string}
//意思就是:我们声明了一个变量obj1,他是一个对象,且他只包含一个属性name,且属性值类型为string
obj={name:"summer"}
//如果你想要更多的属性,就需要扩充类型限制
let obj2 :{name:string,age:number,hobby:object}
obj2 = { name: 'summer', age: 18, hobby: { a: 2 } }
//简便方法
//[propName:string]:any,这样就可以增加很多的属性了
//含义就是属性名为字符串类型,属性值类型可以是任意
let obj3: { name: string, [propName: string]: any }//obj3一定要有name属性,其他都可以
//而且一定要注意顺序

// 当我们有两个必须使用的属性,可以写为&
// obj4,有且仅有name和age属性
let obj4: { name: string } & { age: number }
obj4 = {name:"summer",age:12}

  • 函数类型

    我们使用一种类似于箭头函数的类型声明方式

    注意,你的参数在类型声明时写了几个,那么你后续也只可以使用这几个,且类型保持相同

// 意思就是,fun3是一个函数,且,函数有两个参数,且参数的返回值必须是number
let fun3: (a: number, b: number) => number
// 上面的其实就是一种声明了,你下面进行的就是赋值操作而已
fun3 = (n1, n2) => {
    // 函数必须和刚刚声明的类型保持一致
    return n1+n2
}

console.log(fun3(2,3));
  • 数组类型

    声明方式

    • 类型[]
    • Array<类型>
//1.
let arr:number[]//表明arr只能是一个number类型的数组,里面的数据类型只可以是number,不可以是其他的
arr=[1,2,3]
//2.
let arr1:Array<number>
arr1 = [1,2,3]

元组类型TS新增

元组其实就是固定长度的数组

let t:[number,string]//表示声明一个元组,元组里两个,一个是number,一个是string,且注意书写顺序
h = ['hello', 12]
// h = [111,'summer'],这样就会报错

enum枚举类型

一般用于值相对固定

enum Gender{
    male,
    female
}

// 限制类型
// i是一个对象,含有name属性和sex属性,其中sex属性是一个枚举
let i: { name: string, sex: Gender }
// 因为在实际的开发中,我们的性别这种一般都是用,0,1表示的
i = {
    name: 'summer',
    sex:Gender.male
}
console.log(i.sex === Gender.male);//true

类型别名

// 类型别名
type myType = 1 | 2 | 3 | 4;
let m: myType
let n: myType

类型断言

有些情况下,变量的类型对于我们来说是很明确,但是TS编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型

  • 第一种

    • let someValue: unknown = "this is a string";
      let strLength: number = (someValue as string).length;
      
  • 第二种

    • let someValue: unknown = "this is a string";
      let strLength: number = (<string>someValue).length;
      

编译选项

  • 自动编译文件

    只需要在全局添加-w

    tsc ./xxx.ts -w
    //这个时候我们就可以监听这个xxx.ts文件的变化了,只要发生了变化,那么js对应文件也会发生变化
    
  • 自动编译整个项目

    只输入tsc,那么就会编译当前所有的ts文件

    要是有tsc的前提,就是需要配置一个tsconfig.json文件夹

    {
        // ts编译器的配置文件
        // includes,用来指定哪些文件需要tsc进行编译
        "include": [
            //  "./src/**/*",意思是,src文件夹下的任意目录的任意文件
            // 一个*表示任意文件,两个*表示任意目录
            "./src/**/*"
        ],
        //exclude表示这里面的文件是不需要编译的 
        // exclude含有默认值:['node_modules','bower_components','jspm_packages']
        "exclude": [
            
        ],
        // 一些配置选项
        "compilerOptions": {
            // 用来指定会被编译为什么版本的语言
            "target": "ES6",
            // 模块化,使用的是那个模块化
            // "module": "ES6",
            // 用来指定库,一般我们不去设置
            // "lib": []
            // outDir,指定文件所在的目录,一般我们放在根目录下的dist文件里
            "outDir": "./dist",
            // outFile,将编译好的js代码放在一个js文件里面
            // 这个不可以和module一起使用,除非你的modile应该是amd和system
            // 注意好变量名字不要相同
            "outFile": "./dist/index.js",
            // 是否对我们的js文件进行编译,默认为false
            "allowJs": false,
            // 是否检查js,以ts的标准去检查js
            "checkJs": false,
            // 是否删除注释,true:就是删除注释
            "removeComments": false,
            // 他就是严格检查的总开关,假如true,那么才会进行下面的检查,如果是false,那么直接就是不进行任何的严格检查
            "strict": false,
            // 不生产编译后的文件(js)文件
            "noEmit": false,
            // 当有错误不生成编译后的文件
            // 只要是发生了错误,那么就不会生成js文件
            "noEmitOnError": true,
            // 是否开启严格模式,默认是不开启严格模式的
            "alwaysStrict":true,
            // 当我们没有指定变量的类型时,我们是否使用any类型
            // 就是不允许隐式的any,类如函数传参没有写类型
            "noImplicitAny": true,
            // 不允许有指定不明确的this
            "noImplicitThis": true,
            // 严格的检查空值,对于一些值,他们可能为空
            "strictNullChecks": false
        }
    }
    

    配置项解读:

    1. include

      定义希望被编译的文件所在目录

      "include":['./src/**/*'];//意思就是,会编译src文件夹下的任意文件夹里面的任意文件
      
    2. exclude

      定义不希望被编译的文件所在目录

      默认值:["node_modules", "bower_components", "jspm_packages"]

      "exclude":['node_modules']//不会编译node-modules文件夹
      
    3. extends

      定义被继承的配置文件

      "entends":['./config/base']//当前配置文件中会自动包含`config`文件夹下base.json文件里面的所有配置信息
      
    4. compilerOptions(重要)

      他里面有很多的子项,里面定义的是一些配置选项

      • target:用来指定会编译为何种版本,一般我们使用ES6

        "compileOptions"{
            "target":'ES6';//我们指定转化为ES6这种版本
        }
        
      • module:模块化,用于import,export的格式

        "compileOptions"{
            "module":'ES6';//我们按照ES6的格式进行模块化的引用等
        }
        
      • lib:指定库,我们一般不会去动

      • outDir:指定文件所在的目录,一般我们放在dist文件夹下

        "compileOptions"{
            "outDir":"./dist"
        }
        
      • outFile:将编译好的js文件都放在一个js文件里面方便管理

        "compileOptions"{
            "outFile":'./dist/index.js';//我们将编译好的js文件全部放在dist文加夹下的index.js文件里
        }
        
      • checkJs:是否以TS的标准去检查JS,默认为false

        "compileOptions"{
            "checkJS":true
        }
        
      • removeComment:是否删除注释,就是在TS转化为JS过程中,我们是不是将注释也保留

        "compileOptions"{
            "removeComment":true;//确定删除注释
        }
        
      • strict:严格模式的总开关,假如他是关着的,那么后面的所有严格模式都会关闭

        "compileOptions"{
            "strict":true;//严格模式开启
        }
        
      • noEmit:编译后不产生js文件,默认为false

      • noEmitOnError:只要产生了错误,那么我们就不编译产生js文件

      • noImplicitAny:当我们没有指定类型时,是不是使用Any

        "compileOptions"{
           "noImplicitAny": true, // 就是不允许隐式的any,类如函数传参没有写类型
        }
        

webpack配置

步骤:

  • 初始化项目

    npm init -y:在创建package.json文件

  • 下载构建工具

    npm i -D webpack webpack-cli typescript ts-loader
    
  • 构建一个webpack的配置文件webpack.config.js文件

    // 引入一个包
    // 这个包主要是让和我们来管理路径,路径拼接什么
    const path = require('path')
    // 引入wenpack插件
    const HTMLWebpackPlugin = require('html-webpack-plugin')
    // webpack所有的配置信息都应该写在里面
    module.exports = {
        // 指定入口文件
        entry: './src/index.ts',
        // 指定打包文件的所在目录
        output: {
            // 指定到文件的目录
            path: path.resolve(__dirname, 'dist/assets'),
            // 指定打包好的文件名字
            filename: 'bundle.js',
            clean: true
        },
        // webpack打包时要使用的模块
        module: {
            // 指定加载的规则
            rules: [
                {
                    // tset指定的是规则生效的文件
                    test: /\.ts$/,//匹配所有以.ts结尾的文件
                    use: [
                        // 配置babel
    
                        // 指定加载器
                        'babel-loader',
                        'ts-loader'
                    ],//使用ts-loader去匹配所有以.ts结尾的文件
                    // 指定要排除的文件
                    exclude: /node_modules/
                }
            ]
        },
        // 配置webpack插件
        plugins: [
            new HTMLWebpackPlugin({
                // title:'webpack配置'
                // 设置一个模板
                template: './src/index.html'
            }),//效果就是自动的帮我们创建一个Html文件,并且将相关的资源引入进来
        ],
        devServer: {
            static: {
                directory: path.join(__dirname, 'dist')
            },
            compress: true,
            port: 9000
        },
        // 用来设置引用模块
        // 意思就是后续我们再次进行import的时候,因为import不可以时以ts结尾的,有了这个,我们就可以不写后缀名
        resolve: {
            extensions: ['.ts', '.js']
        }
    }
    
  • 随后我们继续安装一个插件html-webpack-plugin

    效果就是帮助我们自动生成一些html文件,安装好了以后我们就不需要自己去进行引入

    他需要进行在webpack.config.js文件中引用

    // 引入wenpack插件
    const HTMLWebpackPlugin = require('html-webpack-plugin')
    module.export = {
        // 配置webpack插件
        plugins: [
            new HTMLWebpackPlugin({
                // title:'webpack配置'
                // 设置一个模板
                template: './src/index.html'
            }),//效果就是自动的帮我们创建一个Html文件,并且将相关的资源引入进来
        ],
    }
    
  • 安装一个webpack的服务器插件,用于实时的看到页面的变化

    同时也需要在package.json里面进行配置

    {
        ....,
        "scripts": {
        "start": "webpack serve --mode production"
      }
    }
    

    并且在webpack.config.js里面进行配置端口号之类的

    module.export = {
        ...,
            devServer: {
            static: {
                directory: path.join(__dirname, 'dist')
            },
            compress: true,
    //规定端口号为9000
            port: 9000
        }
    }
    
  • 在根目录下创建tscofig.json

    他也是进行对TS编辑器的一些配置

    {
        "include": ["./src/**/*"],
        "compilerOptions": {
            "target": "ES6",
            // 所有编译的ts文件放在dist文件夹里
            // "outDir": "./dist",
            "strict": true
        }
    }
    
  • src下创建ts文件,并执行命令,npm start来开启服务器,或者npm run build对代码进行编译

babel

步骤:

  • 安装依赖包

    npm install -D babel-loader @babel/core @babel/preset-env

  • loader配置

    直接去修改webpack.config.js

    // webpack打包时要使用的模块
        module: {
            // 指定加载的规则
            rules: [
                {
                    // tset指定的是规则生效的文件
                    test: /\.ts$/,//匹配所有以.ts结尾的文件
                    use: [
                        // 配置babel
    
                        // 指定加载器
                        'babel-loader',
                        'ts-loader'
                    ],//使用ts-loader去匹配所有以.ts结尾的文件
                    // 指定要排除的文件
                    exclude: /node_modules/
                }
            ]
        },
    
  • 安装依赖core-js

    npm i -D core-js@3

  • babel配置

    一般babel配置,我们都是放在babel.config.json文件里

    {
      "presets": [
        ["@babel/preset-env", {
          "useBuiltIns": "usage", // 按需引入 corejs 中的模块 
          "corejs": 3, // 核心 js 版本
          "targets": "> 0.25%, not dead" // 浏览器支持范围
        }]
      ]
    }
    

面向对象

类(class)

//使用class关键字来定义一个类
class Person{
    //定义实例属性
    name:string = 'summer';
    //一定需要赋值,如果不赋值,那么就需要使用构造函数,就是传了值
    //定义static属性,这是类的的属性,所以不需要新建对象也可以访问
    static sex:string = "male"
    //设置只读实例属性,那么他就是可以被访问,但是不可以被修改了
    readonly age:number = 12
    //定义方法,如果是以static开头的,那么就是类方法,同样对象无法访问
    sayHello() {
        console.log("hello");
    }
    static eat() {
        console.log("I can eat");
    }
}

//新建一个对象
const person = new Person();
console.log(person);
console.log(person.age, person.name);
//因为age使实例属性,所以可以直接访问和修改
person.age = 14
console.log(person.age);
// 因为name设置了readonly,所以我们只可以通过类本身去访问
console.log(Person.sex);//这种属于静态属性,也就是类上面的属性,不是对象身上的
// person.age = 13,就会报错,因为age是只读属性

// 实例方法的调用
person.sayHello()
// 静态方法的调用
Person.eat()

构造函数(constructor)

// 定义构造函数
class Student{
    // 也可以理解为,你的声明是声明在外边的,但是你的赋值,是在构造函数里面进行的
    name: string;
    age:number
    // 在定义类的时候,这个值是不可以不赋值的
    constructor(name:string,age:number) {
        // 构造函数,在对象创建时,调用constructor
        // 每次调用,都会调用一次构造函数
        // 构造函数里的this,是指向那么对象,谁调用了,那么就指向谁
        console.log("this", this);
        // console.log("性别",this.sex);
        this.name = name;
        this.age = age;  
    }
    // 定义一个方法
    study() {
        console.log("studying");
        // this说到底就是一句话,谁调用他,他就指向谁
        // 在构造函数里,new的时候会调用this,那么这个this,就指向了那个类
        // 但是现在是student.study(),是student这个对象去调用的,所以this,就是student这个对象
        console.log("study方法里面的this",this);   
    }
}

const student = new Student("summer",13)
console.log(student);
student.study()

继承(extends)

我们使用继承来尽可能的增加复用

class Animal {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age
    }
    say() {
        console.log("动物在叫");
        
    }
}
// Dog类继承了Animal类
// 使用继承以后,子类将会拥有父类所有的方法和属性
class Dog extends Animal {
    
    bark() {
        console.log("汪汪哇");
    }
    // 如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法
    // 这种子类覆盖掉父类方法的形式,我们称为方法重写
    say(): void {
        console.log(`${this.name} 在叫`);
        
    }
}
class Cat extends Animal {
    miao() {
        console.log("miao");
    }
}
const dog = new Dog("旺财", 1);
const cat = new Cat("咪咪", 2);
console.log(dog);
dog.say()
cat.say()
console.log(cat);

方法重写

子类继承了父类的方法,但是对方法进行重写

那么子类会覆盖掉父类的方法

抽象类(abstract)

一般我们会使用一个类来进行继承,但是我们不希望这个父类可以使用,所以我们定义了抽象类

当我们不希望这个类不用于创建对象,只用于继承

super

super:super指向的就是他的父类

abstruct

abstruct:我们里面的方法只进行定义,不写里面的函数体,

到时候,子类里进行方法重写

抽象方法只写关于定义,但是不写方法体,需要对象进行重写

abstract class OneAnimal{
    name: string;
    constructor(name:string) {
        this.name = name;
    }
    // 定义了抽象方法,抽象方法使用abstract开头,没有方法体
    // 抽象犯法只能定义在抽象类中,子类必须对抽象方法进行重写
    abstract say(): void;
}
// 继承,并且对方法进行重写
class OneDog extends OneAnimal{
    // 如果在子类里面写了构造函数,那么就需要使用super,对父类的构造函数进行调用
    // 并且父类中有哪些参数,我们子类的构造函数也需要调用,并且super里面也要写明哪些参数是来自父组件的
    age: number;
    constructor(name:string,age:number) {
        super(name);
        this.age = age
    }
    say(): void {
        // super,就是当前OneDog类的父类,他就会直接调用父类的say方法
        // super.say()
        console.log(`${this.name}在说话`);
    }
}
const onedog = new OneDog('旺财',12)
onedog.say()

当我们使用继承,并且使用构造函数时,我们需要使用super,并且里面传入参数,表明哪些是来自父组件的

接口(interface)

接口可以定义类的时候去限制类里面的结构

接口中所有的属性都不能赋实际的值

接口里面只是定义了结构,但是不考虑实际的值

实现接口使用的时implements

接口就是来定义一个类的结构,它也可以当作类型声明来使用,就是那个type来使用

// 接口属于ts新增的
interface myInterface{
    // 里面不写值,只是去定义类型
    name: string;
    age: number;
    gender:string
}
// 定义类时,可以使类去实现一个接口,使用的使implements
class People implements myInterface{
    name: string;
    age: number;
    gender: string;
    constructor(name: string, age: number, gender: string) {
        this.gender = gender;
        this.age = age
        this.name = name
    }
}

抽象类VS接口

  1. 抽象类和接口一样,都只可以定义,但是不可以进行赋值
  2. 抽象类和接口都不可以直接使用,抽象类需要继承(extends),接口需要实现(implements)
  3. 不同:抽象类里可以定义普通方法也可以定义抽象方法,但是接口里面值只可以定义抽象方法

属性封装

修饰符描述
public修饰符的属性可以在任意位置访问(修改)默认值
private私有属性,私有属性只能在类内部进行访问(修改)
只能在类中get和set,才可以被访问
protect受保护属性,这个只有当前类和其类的子类可以访问
class Person{
        private _name: string;
        private _age: number;

        constructor(name: string, age: number) {
            //设置了private,那么他只可以在类的内部进行访问,但是如果不在这个类里面,变为了一个对象去访问这个私有属性,那么就访问不到页修改不了
            this._name = name;
            this._age = age;
        }
        // TS中设置getter方法的方式
    	//	获取属性值使用
        get name(){
            // console.log('get name()执行了!!');
            return this._name;
        }
		//设置属性值时使用
        set name(value){
            this._name = value;
        }

        get age(){
            return this._age;
        }

        set age(value){
            if(value >= 0){
                this._age = value
            }
        }
    }

    const per = new Person('孙悟空', 18);
   // 因为设置了set和get,所以对于私有属性才可以访问
   // 因为但是我们访问的就算不是_name,和_age这个,而是get,set的哪些方法名
    per.name = '猪八戒';
    per.age = -33;
	console.log(pre)

protect只可以被子类和自己使用

 	class A{
        protected num: number;
        constructor(num: number) {
            this.num = num;
        }
    }
    class B extends A{
        test(){
            console.log(this.num);
        }
    }
    const b = new B(123);
	b.num = 11;//报错,因为他只可以在A类和B类使用,不可以再其他地方

简单写属性

 /* class C{
        name: string;
        age: number
        // 可以直接将属性定义在构造函数中
        constructor(name: string, age: number) {
            this.name = name;
             this.age = age;
        }
    }*/

    class C{
        // 可以直接将属性定义在构造函数中
        //这是简写构造函数
        constructor(public name: string, public age: number) {
        }
    }
    const c = new C('xxx', 111);
    console.log(c);

泛型

在定义函数或者类时,如果遇到类型不明确就可以使用泛型

泛型其实就可以理解为一种类型,只是目前我们不知道,并且它是以<>括起来的

function fn<T>(a: T): T{
    return a;
}
// 可以直接调用具有泛型的函数
let result = fn(10); // 不指定泛型,TS可以自动对类型进行推断
let result2 = fn<string>('hello'); // 指定泛型

多个泛型参数

// 泛型可以同时指定多个
//中间使用,隔开
function fn2<T, K>(a: T, b: K):T{
    console.log(b);
    return a;
}
fn2<number, string>(123, 'hello');

对泛型进行限制

interface Inter{
    length: number;
}

// T extends Inter 表示泛型T必须时Inter实现类(子类)
// 泛型限制类型,都是使用entends进行继承,无论是接口还是类
function fn3<T extends Inter>(a: T): number{
    return a.length;
}

泛型可以在类里面使用

class MyClass<T>{
    name: T;
    constructor(name: T) {
        this.name = name;
    }
}

const mc = new MyClass<string>('孙悟空');