JavaScript的超集TypeScript

528 阅读2分钟

TypeScript

TypeScript是基于JavaScript之上的一门编程语言,之后都简称为ts。ts主要解决js语言类型系统的不足,通过使用ts就可以大大提高代码的可靠程度。

安装ts:

    yarn add typescript --dev

命令行使用示例:

    // ts可以直接使用标准的js来书写
    // 创建xxx.ts文件,添加如下代码
    const hello = (name: string) => console.log(`Hello ${name}`)

    hello('ts')
    
    // 使用tsc命令将ts文件及代码转成js
    yarn tsc xxx.ts
    
    // 执行完成后目录下会生成xxx.js文件
    var hello = function (name) { return console.log("Hello " + name); };
    hello('ts');
    
    // 转换后可以发现我们使用的ES6语法被转化成了ES5的语法,添加的注解也被去除调了

使用ts配置文件

初始化:

    // 控制台执行
    yarn tsc --init
    
    // 执行完命令后会生成一个tsconfig.json

配置文件部分属性作用说明

    {
        "compilerOptions": {
            "target": "es5", // 将ts转换为那一个版本的js
            "module": "commonjs", // 导入、导出模块使用commonjs标准
            "sourceMap": true, // 启用sourceMap,之后可以使用sourceMap通过js匹配对应的ts
            "rootDir": "src", // 要转换的ts文件夹
            "outFile": "dist", // 转换后输出的js文件夹
            "strict": true, // 是否启用严格模式(启用后要添加注解)
            "lib": ["es2015"], // 需要引用的lib库
        }
    }

添加完ts配置文件后,通过命令行命令去转换ts文件时配置文件不会生效,只有当我们去编译整个项目时才会生效。

ts中的6中原始数据类型

ts中的原始数据类型和之前我们学习的flow中的类型基本相似。 不同点:

  • 在严格模式下空值只能是自己所对应的类型(null 对应 null undefined 对应 undefined),在非严格模式下null和undefined通用
  • 在严格模式下string、number、boolean只能对应自己所对应的类型,在非严格模式下以上三种可以对应空值
  • 在tsconfig.json配置文件中target设置为es5时不能使用Symbol数据类型,这是因为target在设置为es5时,ts应用的lib库为lib.es5.d.ts,也就是lib是根据es5的标准去实现的,所以也就不能使用es2015中新增的Symbol,那我们可以将target设置为es2015就可以使用Symbol了;或者不修改target的情况下我们通过配置文件中lib属性类引入es2015对应的lib库,即:"lib": ["es2015"]。此时我们有会发现我们常用的console提示无法使用,这是因为console是浏览器提供的API,当我们引入es2015库时会产生覆盖,这时候我们需要引入浏览器对应的对象库,即:**"lib": ["es2015", "dom"]****。

示例:

    const a: string = 'abc'
    const b: number = 10 // 或 NaN 或 Infinity
    const c: boolean = true // 或 false
    const d: void = undefined
    const e: null = null
    const f: undefined = undefined
    // target 设置为es2015以上才可以使用Symbol
    const symbol = Symbol()

ts中使用中文错误提示

ts本身是支持国际化,只需要设置下即可

命令行显示中文提示:

  • yarn tsc --locale zh-CN

VSCode编辑器中显示中文错误提示:

  • 将编辑器设置为中文即可
  • 或者在VSCode的设置中将TypeScript Locale设置为zh-CN

object类型

ts数据类型中还有一种类型:object类型,object类型并不是专指Object对象,而是指除了原始类型之外的其他对象类型,如:数组、对象、函数。

示例:

    // object类型
    const foo: object = function() {} // []、{}
    
    // 如果要单独表示对象类型可以使用字面量形式
    const obj: {} = { foo: 123 }

数组类型

ts中的饿数组类型和之前介绍的flow中注解基本一致

示例:

    const arr1: Array<number> = [1, 2, 3]
    const arr2: number[] = [4, 5, 6]

元组类型

元组其实就是一个明确元素数量以及元素类型的数组。

示例:

    const tuple: [number, string] = [10, 'abc']
    
    // 使用1:使用下表获取
    const name = tuple[1]
    
    // 使用2:数组结构
    const [ age, name ] = tuple  // React中hook的useState就使用此种方式

枚举类型

通常我们会使用某个分类类型下某些固定,如颜色中有红、黄、蓝灯各种颜色。那我们就可以使用枚举(enum)没表示这些颜色。

示例:

    enum Color {
        red,
        yellow,
        green
    }
    
    // 使用
    const color = Color.red
    
    // 枚举中的类型所对应的值默认是从0开始并每次递增1
    enum Color {
        red = 0,
        yellow = 1,
        green = 2
    }
    
    // 当然我们可以自己设置值, 设置值之后该类型所对应的值就为设置的值
    // 如果后续没有设置,那么也会按照递增1来为后续类型设置值
    enum Color {
        red = 7,
        yellow,
        green
    }
    
    // 枚举中类型的值可以设置为字符串,但是一旦设置了字符串类型之后
    // 设置字符串值之后的类型都需要设置指定的值,因为字符串无法向数值那样自己去递增
    enum Color {
        red,
        yellow = 'aa',
        green = 'bb'
    }

枚举类型数据使用起来非常方便,但是枚举会入侵我们的运行时代码(也就是会影响编译后的结果)。因为枚举类型在编译后会被转换成双向对象,双向对象既可以通过键去获取值,也可以通过值去获取键,既使用Color[7] => red,如果我们在开发过程中不使用这种方式,那我们可以使用常量枚举,这种方式在编译之后枚举会被移除掉,在使用枚举的地方会被替换为原本具体的数值。

常量枚举:

    const enum Color {
        red,
        yellow,
        green
    }

函数类型

ts中的函数类型和之前flow中的函数类型类似,可以设置参数类型,也可以设置参数可选。

函数类型示例:

    // 参数类型是直接在参数后加 : 参数类型
    // 参数可选是在参数后加 ?: 参数类型
    // 参数可选还以以默认值形式表达,即给参数设置默认值后这个参数也是可选的
    // 但是可选参数要放在参数列表最后
    // 如果要接收任意个数参数,可以使用ES2015的rest
    function foo(a: number, b: number, ...rest): string {
        return 'foo'
    }

函数表达式参数以及返回值示例:

    // func为接收函数的变量,在func后边添加了接收的函数入参的类型,箭头函数后边表示的是接收参数对应的返回值
    let func: (a: number, b: number) => string = function (a: number, b: number): string {
        return 'foo'
    }

任意类型

示例:

    function stringify(value: any) {
        return JSON.stringify(value)
    }
    
    // 在语法层面传入任意类型不会报错
    stringify('abc')
    stringify(10)

隐式类型推断

示例:

    // 不使用注解标识变量类型时,ts会自动根据赋值情况做隐式类型推断
    // 1.创建变量并赋值
    let age = 10 // 此时age被推断为number类型,age之后只能被赋值number类型的值
    
    // 2. 创建变量不赋值
    let foo // 此时foo被推断为any类型,之后可以被赋值任意类型
    foo = 10
    foo = 'abc'    

类型断言

有时候我们在使用数据的时候我们知道数据是什么类型的,但是ts不知道,这时候我们在使用数据时ts可能会提示错误,那么我就可以使用断言来告诉ts明确这个数据是对应类型的。

示例:

    // nums来自于某个接口
    const nums = [101, 110, 130]
    // 从nums中获取大于0的数
    const res = nums.find(i => i > 0)
    // 此时res我们知道为一个数字,但是ts不知道,那我我们也就无法直接使用做平方处理
    // 此时需要使用断言告诉ts这就是个number
    const num1 = res as number
    // 或者(但是在jsx语法中不能使用下边这种方式)
    const num2 = <number>res
    // 现在就可以光明正大使用了
    const square = res * res

接口(Interface)

接口就是用来去约束对象的结构,一个对象去实现一个接口,就必须拥有接口中所有的成员。接口中可以设置成员属性,如:可选成员、只读成员(被readonly修饰)

示例:

    // 定义接口
    interface Post {
        title: string
        content: string
        subtitle?: string
        readonly summary: string
    }
    
    function printPost(post: Post) {
        console.log(`${post.title}_${post.content}`)
    }
    
    printPost({
        title: 'postTitle',
        constent: 'postContent'
        summary: '摘要'
    })

上边定义的接口是指定成员,那有时候我们还会用到动态成员,ts也是支持动态成员的。

示例:

    interface Cache {
        // [key: string] 定义了接口中key的类型
        // :string 定义了接口中值的类型
        [key: string]: string
    }
    
    // 使用
     const cache: Cache = {}
     cache.foo = 'value1'
     cache.bar = 'value2'

描述一类具体事务的抽象特征,ts中的类与js中类的使用有所差别

  • 属性要先定义在使用
  • 属性定义后要被调用或被赋予初始值
  • 访问修饰符public(默认)、private(只能类内部访问)、protected(只能类内部或子类中被访问)
  • 可以设置属性的readonly(只读)

示例:

    class Person {
        public name: string
        private age: number
        protected job: string = 'coder'
        readonly gender: boolean
        
        constructor(name: string, age: number, job?: string) {
            this.name = name
            this.age = age
        }
        
        sayHi(msg: string): void {
            console.log(`I am ${this.name}, ${msg}`)
        }
    }
    
    // 通过private我们可以标识一个属性或方法只在在本类中被访问,那我们给constructor添加上private会怎样呢?
    // 给构造函数添加private类型后,该类不能在外部被实例化
    // 那我们就可以通过添加一个static静态方法对该类进行实例化,也就是要想实例化这个类就必须通过静态方法。

类与接口

之前我们说过,接口是约束对象结构的,那对于类来说,类内部的结构包括了方法,如果多个类中有相同的方法那我们就可以使用接口来抽象出共用的方法。

示例:

    // 吃的接口
    interface Eat {
        eat (food: string): void
    }
    
    // 跑的接口
    interface Run {
        run (distance: number): void
    }
    
    // 类去实现接口,实现了接口就要去拥有接口中的方法
    class Person implements Eat, Run {
        eat(food: string): void {
            console.log(`文明进餐:${food}`)
        }
        
        run (distance: number): void {
            console.log(`直立行走:${distance}`)
        }
    }
    
    class Animal implements Eat, Run {
        eat(food: string): void {
            console.log(`凶残进餐:${food}`)
        }
        
        run (distance: number): void {
            console.log(`爬行:${distance}`)
        }
    }

抽象类

抽象类在某种程度上来说跟接口有点类似,也可以用来约束子类中必须要有某个成员,但是不同于接口的是抽象类可以包含一些具体的实现,而接口不能。

示例:

    // 使用abstract描述抽象类
    abstract class Animal {
        eat(food: string): void {
            console.log(`吃: ${food}`)
        }
        
        // 抽象方法,抽象方法不需要有方法体
        abstract run(distance: number): void
    }
    
    // 使用抽象类
    class Dog extends Animal {
        run(distance: number): void {
            console.log(`爬:${distance}`)
        }
    }
    
    const dog = new Dog()
    d.eat('热狗')
    d.run(100)

泛型

泛型是指我们在定义函数、接口或类的时候不去指定具体类型,而在使用的时候才去指定具体类型的特征。

示例:

    // 需求:创建包含指定类型(数字或字符串)、长度的数组并填充
    // 1.创建包含数字的指定长度数组
    function createNumberArray (length: number, value: number): Array<number> {
      return Array<number>(length).fill(value)
    }

    // 2.创建包含字符串的指定长度数组
    function createStringArray (length: number, value: string): Array<string> {
      return Array<string>(length).fill(value)
    }
    
    // 其实我们会发现通过两个方法实现了类似的通能,只是参数类型不同
    // 那我们就可以使用泛型来进行简化,定义时不确定类型,调用时再确定
    function createArray<T> (length: number, value: T): Array<T> {
      return Array<T>(length).fill(value)
    }
    
    // 使用
    const res = createArray(3, 'foo')
    
    
    // 泛型简单来说就是把定义时不能确定的类型作为一个参数,在使用的时候通过参数传递

类型声明

有时候我们使用一些三方模块或之前的一些业务js代码,如果这些代码或模块没有类型声明,这时也就没有强类型语言的提示和感觉,那我们可以使用declare来手动做类型声明。

示例:

    import { camelCase } from 'lodash'
    
    declare function camelCase (input: string): string
    
    // 此时使用时就会有类型说明
    const res = camelCase('types')

其实现在很多三方模块都支持了对应的类型声明模块,如lodash的@types/lodash