美文网首页
TypeScript笔记

TypeScript笔记

作者: 瓢鳍小虾虎 | 来源:发表于2021-11-20 17:20 被阅读0次

    安装

    npm i -D typescript

    ts不能直接在浏览器环境执行,需要先编译成js文件。
    文件编译指令:
    tsc [tsfile]
    tsc [tsfile] -w 可以自动监视改变并重新编译

    类型

    number
    string
    boolean
    字面量:声明变量可能的值,不能是别的值或者超出字面量的范围。let a: 10|11;a = 10;
    any:任意值,可以被赋值不同类型的值。变量也可以赋值给其它类型的变量,不安全。
    unknown:安全的any。可以是任何类型值。变量不允许直接被赋值给其它非unknown类型的变量。
    void:空值null或者undefined。
    never:不能是任何值。这种返回值一般用于系统报错throw newError()
    object:任意js对象。
    array:数组。
    tuple:固定长度数组。[1,2]声明后就不能变长
    enum:枚举。enum{'a', 'b'}

    变量声明

    let a: number;
    a = 1;
    let b: string = 'abc'

    如果变量声明同时直接赋值,则类型会默认对应声明,
    let a = false
    a = 123 // 提示错误

    sum(a:number, b:number): number { // 声明参数类型和函数返回值类型
    return a + b
    }
    sum(1, '2') // 提示错误,但是编译还是会过,可以通过配置让编译也不通过
    sum(1, 2, 3) // 提示错误,参数个数不匹配

    使用字面量声明:类似于常量,声明了一个或者多个后,后面的代码赋值不能超出范围。
    let a: 'hello'; // a的值只能是'hello'不能声明成别的
    a = 'world' // 提示错误
    let b: 'hello' | 'world'; // b的值只能是'hello'或者'world'

    其他类型:
    let a: any; // 不限制任何类型赋值
    let b; // 如果声明变量不指定类型也不直接赋值,则类型同any

    开发中一般不允许使用any类型,因为any类型的变量可以赋值给任何类型变量,会出现严重的问题:
    let a;
    a = 10;
    a = true;
    let b: number;
    b = a; // tsc编译不报错

    如果真有这种业务场景,推荐使用unknown。unknown相当于是一种安全的any,也可以赋值任意类型,但是不能随便赋值给其它类型的变量,直接复制会报错,如果实现进行过类型检查则可以赋值。
    let a: unknown
    a = 'hello'
    let b: string
    if (typeof a === 'string') { // 方式1
    b = a
    }
    b = a as string // 方式2 使用断言,断言就是告诉编译器,这个变量的类型一定为指定类型
    b = <string>a // 方式3 另一种断言

    let a: object // js中一切皆对象,所以通常不会这样使用,实际上开发者关注的应该是对象的结构(属性方法什么的)
    a = {}
    a = function(){}
    let b: {name: string, age?: number} // 这种使用方式比较推荐,使用b的时候必须设置name属性,age可选
    b = {name: 'sony'}
    let b: {name: string, [propName: string]: any} // 这种方式也比较推荐,[propName: string]: any表示可以追加任意自定义属性,propName是自定义的名字随便写,string表示属性名类型为字符串
    b = {name: 'sony', a: 12}

    let a: Function // 声明函数,但是实际上没人这么写,没什么意义,开发者关注的是函数的结构(参数返回值什么的)
    let a: (arg1: number, arg2: number) => number // 函数的声明方式

    let arr: Array<number> // 声明一个数组,内部元素类型为number
    let arr: number[] // 作用和上面的等价

    let arr: [number, number] // 声明定长数组,赋值的时候必须一一对应,这种方式的优势在于性能比较好,分配固定的内存空间就ok了

    关于枚举enum,它的使用意图和“|”很类似,enum的优势在于兼顾了语义化和存储空间。
    例如声明一个变量let a: '男'|'女',业务编码使用的时候没什么问题,但是数据库或者数据传递的时候通常倾向于更小的空间占用,比如存0或者1这种值,enum就可以满足这个需求。
    开发中可以先定义一个enum类型的类,使用enum中的元素名就代表对应的值(编译器会默认赋值成0、1这种值)
    enum Gender {
    Male,
    Female
    }

    let person: {name: string, gender: Gender}

    person = {name: '张三', gender: Gender.Male} // 相当于gender: 0

    另外,比较特殊的运算符“&”,声明类型的时候表示同时满足的意思:
    let o: {name: string} & {age: number} // 表示变量o赋值的时候必须是一个对象并且同时有name和age属性。

    额外提一下ts中的“?”和“!”的用法:

    1. 作为运算符:“?”用在三目运算符中(例如a?b:c),“!”表示取反(例如!a)
    2. 参数:“?”用在参数中表示可选项。例如function(a: string, b?: number)
    3. 成员变量:“?”表示可选项,“!”表示此项一定有值且不为null
    4. 安全链式调用:
      ?表示可能为null,如果为null就不往下执行,场景:a.b?.c();
      !表示一定不为null,强制让编译器通过安全检查,场景:a.b!.c();或者a.b!.c = xxx

    面向对象

    ts的面向对象使用起来与es6基本一致:

    // class声明和使用
    class Animal {
        static readonly type = '爬行动物' // 静态属性,readonly关键字表示只读
        name = 'lucky' // 实例属性
    
        // 构造函数
        constructor(name: string = 'lucky') {
            this.name = name
        }
    
        // 实例方法,同样,加了static就变成类静态方法了
        eat() {
            console.log(this.name + ' is eating...')
        }
    
        static move() {
            console.log('animal can move')
        }
    }
    const cat = new Animal('happy')
    console.log(cat, Animal.type) // {name: 'happy'} '爬行动物'
    console.log('eat: ')
    cat.eat()
    console.log('move: ')
    Animal.move()
    
    class Dog extends Animal {
        eat() {
            console.log('dog: ' + this.name + ' is eating...')
        }
    }
    console.log(Dog.type)
    const dog = new Dog()
    console.log(dog, Dog.type)
    dog.eat()
    
    interface Human {
        name: string
        eat(): void
        speak(): void
    }
    
    class Soldier implements Human {
        name: string = '001'
        private _level: number
        constructor(name: string, level: number) {
            this.name = name
            this._level = level
        }
    
        get level(): number {
            return this._level
        }
        set level(value: number) {
            this._level = value
        }
    
        eat(): void {
            console.log(`soldier[level ${this.level}] ${this.name} is eating...`)
        }
        speak(): void {
            console.log(`soldier[level ${this.level}] ${this.name} is speaking...`)
        }
    }
    
    const orcSoldier = new Soldier('crom', 1)
    orcSoldier.eat()
    orcSoldier.speak()
    
    // 泛型,生产中不允许使用any类型,如果确实有场景要应对未知类型,或者类型是使用者自定义的,可以考虑使用泛型
    function f<T, K>(t: T, k: K): T {
        console.log(`t: ${t}, k: ${k}`)
        return t
    }
    console.log(f(5, 3)) // 编译器自动识别类型
    console.log(f<string, number>('hello', 1)) // 手动指定类型,更加严谨,推荐方式
    

    es6目前无法支持抽象类、接口、私有属性修饰符private等面向对象的特性,ts编译器支持,但也只局限在检查层面,比如对私有属性赋值,虽然tsc检查错误但是默认仍然可以正常编译成js执行,所以需要配置中"noEmitOnError": true 避免最终代码出现逻辑漏洞。

    编译器配置

    ts文件的需要先经过编译器执行tsc指令编译成js文件才能在浏览器环境执行,开发过程中我们可以创建一个tconfig.json并根据需求配置参数,编译器会读取配置文件并覆盖默认配置。

    ts配置文件跟普通json文件不太一样,里面可以写注释。

    {
      /* include:用于编译指定目录下的ts文件,**表示任意目录,*表示所有文件 */
      "include": ["./src/**/*"],
    
      /* exclude:不编译某些目录下的文件 */
    
      /* extends:表示继承某些配置文件,用于根据需要拆分组合配置 */
    
      /* files:直接编译指定文件名,用于小微型的应用开发,不常用 */
    
      /* compailerOptions: 编译配置项,可以定制编译规则,可以让代码更严谨,减少程序出错概率 */
      "compilerOptions": {
        // target表示编译成什么样的文件,具体指某个es版本,默认会编译成es3,
        // 通常会编译成es6(es2015)再配合webpack的babel-loader处理
        "target": "ES2015",
        // 指定模块化规范
        "module": "ES2015",
    
        // 指定用到了哪些库,方便ts编译器识别,比如用了dom就可以在代码中使用document否则会编译报错。
        // 这项通常不需要配置,默认配置已经比较全。某些场景如nodejs环境或者用到一些默认不支持的lib会考虑配置
        // "lib": ["DOM"]
    
        // allowJS和checkJs一般成对使用,默认值是false,表示是否检查和编译js文件,
        // 有些情况下可能出现js文件模块和ts文件模块编译后无法正常使用的问题,这时候会需要把js文件也编译处理一遍
        "allowJs": true, 
        "checkJs": true,
    
        "removeComments": true, // 是否移除注释,默认是false
    
        // "noEmit": false // 默认false,不生成编译结果(target),不常用,某些情况下可能只是想使用编译检查功能的时候会设置这项
    
        // 默认false,只要编译出错就不生成target,
        // 在我们allowJs为true的时候,js文件即使检查有错误,也会执行编译,这可能会出现错误隐患,
        // 如果要严格控制js文件和ts一视同仁,可以设置为true
        "noEmitOnError": true,
    
        // 严格模式的代码在浏览器环境下执行效率更好,
        // 通常模块化引入的代码默认都是严格模式,有些非模块化代码如果也需要严格模式(开头加use strict),就需要配置此项
        "alwaysStrict": true,
    
        // 以下是一些常用代码安全检查规则
    
        // 默认false,如果配置了strict为true,则所有规则选项(包括下面的选项)都会被覆盖为严格模式,如果想订制,则不要配置此项
        // 开发中建议直接配置strict为true
        // "strict": true,
    
        // 不允许隐式any类型(不明确声明类型),默认是false,any会导致编译器不去判断类型,造成代码隐患 
        "noImplicitAny": true,
    
        // 不允许隐式this(不明确声明this是什么类型),默认是false,原生js中this的指向非常灵活,跟动态调用环境有关系(执行上下文),
        // 这也是bug隐患,如果是单纯声明一个function内部想用this,可以考虑把“this”作为参数传进来
        "noImplicitThis": true,
    
        // 是否严格检查null值,默认false,有些操作如获取dom,然后执行dom.xxx()的时候,dom可能获取失败即是null,这样执行就会报错,
        // 为了避免这个隐患,编译器可以配置这个选项为true,代码中可以用if判断包裹,也可以使用ts语法dom?.xxx()
        "strictNullChecks": true
    
      }
    }
    

    ts-loader

    开发中我们一般会使用webpack打包代码,使用ts-loader编译ts文件再通过babel-loader解决js的兼容性问题
    ts-loader使用的时候建议先到官网上看下版本兼容相关信息,8.x和9.x分别对应webpack4 和webpack5

    npm i -D ts-loader typescript

      resolve: {
        // Add `.ts` and `.tsx` as a resolvable extension.
        extensions: [".ts", ".tsx", ".js"]
      },
      module: {
        rules: [
          // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
          { 
            test: /\.tsx?$/,
            loader: "ts-loader",
            exclude: /node_modules/
          }
        ]
      }
    

    然后别忘了把tconfig.json放到项目根目录下

    相关文章

      网友评论

          本文标题:TypeScript笔记

          本文链接:https://www.haomeiwen.com/subject/ywnpzltx.html