B站TS教程笔记

undefined

Posted by NeptLiang on August 24, 2022

0x00 前言

因为之前看过部分官方文档,故本文不是完整教程笔记。


0x01 环境搭建

  • 基本使用:

    1. 安装Node.js
    2. 使用npm全局安装TypeScript
        npm i -g typescript
      
    3. 编写ts文件
    4. 使用tsc编译
       tsc xxx.ts
      
  • 自动编译文件:

    编译文件时,使用-w指令后,TS编译器会自动监听文件的变化,并在文件内容发生变化时进行重新编译

      tsc file.ts -w
    
  • 自动编译整个项目:

    如果直接执行tsc指令,则可以自动将当前项目下的所有ts文件编译为js文件

    直接执行tsc命令的前提:在项目根目录下创建TS的配置文件tsconfig.json

  • TS编译器配置文件tsconfig.json配置选项:

    (注:tsconfig.json是“JSON with Comments”,可以写注释)

    • include

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

      路径中的**表示任意目录,*表示任意文件

      • 默认值:["**/*"]

      • 示例:

          "include": ["src/**/*", "tests/**/*"]
        

        上述示例中,src目录和tests目录下的所有文件都会被编译

    • exclude

      定义需要排除在外、不需要被编译文件的目录

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

      • 示例:

          "exclude": ["./src/hello/**/*"]
        

        上述示例中,src下hello目录下的文件都不会被编译

    • extends

      定义被继承的配置文件

      • 示例:

          "extends": "./config/base"
        

        上述示例中,当前配置文件中会自动包含config目录下base.json中的所有配置信息

    • files

      指定被编译文件的列表,只有需要编译的文件少时才会用到

    • compilerOptions

      编译器选项

      JSON with Comments { "include": [ "./src/**/*" ], "compilerOptions": { // compilerOptions 编译器选项 "target": "es2015", // target 指定TS被编译后的ES版本,Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'. "module": "es2015", // module 指定要使用的模块化的规范,Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node16', 'nodenext'. // "lib": [] // lib 指定项目中要使用的库。有默认值,一般不需配置,如果设为空数组就会把默认的覆盖掉。Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'webworker.iterable', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asyncgenerator', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'es2019.array', 'es2019.object', 'es2019.string', 'es2019.symbol', 'es2020.bigint', 'es2020.date', 'es2020.promise', 'es2020.sharedmemory', 'es2020.string', 'es2020.symbol.wellknown', 'es2020.intl', 'es2020.number', 'es2021.promise', 'es2021.string', 'es2021.weakref', 'es2021.intl', 'es2022.array', 'es2022.error', 'es2022.intl', 'es2022.object', 'es2022.string', 'esnext.array', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise', 'esnext.weakref'. "outDir": "./dist", // outDir 用来指定编译输出文件的目录 // "outFile": "./dist/app.js" // 将代码合并为一个文件。Only 'amd' and 'system' modules are supported alongside --outFile // "allowJs": true, // 是否对js文件进行编译,默认是false // "checkJs": true, // 是否检查js代码是否符合语法规范,默认是false "removeComments": true, // 是否移除注释,默认为false // "noEmit": true // 不生成编译后的文件,只进行类型检查,默认为false "noEmitOnError": true, // 有错误时不生成编译后的文件,默认为false "strict": true // 所有严格检查的总开关(等效于下面四个选项),一般建议打开 // "alwaysStrict": true, // 编译后的文件是否使用严格模式,默认false // "noImplicitAny": true, // 不允许隐式的any类型 // "noImplicitThis": true, // 不允许不明确类型的this // "strictNullChecks": true // 严格检查空值 } }

    • 一个细节:导入的模块要被使用才会被编译到js文件里

      编译前的ts文件:

        import { a } from 'ex.js';
      
        console.log('c');
      

      输出的js文件:

        console.log('c');
        export {};
      
  • TypeScript x Webpack

    • 终端:

        npm init
        # -D: 开发依赖,即--save-dev
        # ts-loader: 用于在Webpack中编译ts文件
        npm i -D webpack webpack-cli typescript ts-loader
      
    • webpack.config.js:

        module.exports = {
            module: {
                rules: [
                    //配置loader
                    {
                        test: /\.ts$/,
                        use: 'ts-loader',
                        exclude: /node_modules/
                    }
                ]
            },
            // 设置引用模块
            resolve: {
                extensions: ['.ts', '.js']      //配置了之后,imoprt ts、js文件时,路径中的文件名不用后缀
            }
        }
      

0x02 Basis

  • 例:

      let c: boolean = false;
    
  • 如果变量的声明和赋值是同时进行的,TS可以自动对变量进行类型检测

    故变量的声明和赋值同时进行时,一般不需写类型

      let c = false;
    

    而JS中的函数是不考虑参数的类型和个数的,故TS主要用于约束函数参数


0x03 Types

  • 可以直接使用字面量进行类型声明

      let a: 10;
    
  • 可以使用|来连接多个类型(即联合类型)

      let b: 'male' | 'female';
      let c: boolean | string;
    
  • TS中的类型:

    类型 例子 描述
    number    
    string    
    boolean    
    array    
    object    
    字面量    
    any    
    unknown    
    void 空值(undefined 没有值(或undefined
    never 没有值 不能是任何值
    tuple [4, 5] 元素,TS新增类型,固定长度数组
    enum enum{A, B} 枚举,TS中新增类型
  • any表示的是任意类型,一个变量设置类型为any后相当于关闭了TS对该变量的类型检测

    故使用TS时,不建议使用any类型

    声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any

  • unknown表示未知类型的值

    • any类型的变量可以赋值给任意变量,即使赋给指定了类型的变量,TS也不会提醒
    • unknown类型变量赋值给其他类型的变量则TS会提醒,即使它的值的类型符合被赋值变量的类型

    unknown实际上就是一个类型安全的any,因为unknown类型的变量不能直接赋值给其他变量

    解决办法:

    • 赋值前进行类型判断
    • 类型断言:用来告诉解析器变量的实际类型
      let u: unknown;
      u = true;
      u = 10;
      u = 'hello';
    
      let a;
      a = true;
      a = 20;
      a = 'hi';
    
      let s: string;
      //变量a的类型是any,可以赋值给任意变量,即使赋给指定了类型的变量,TS也不会提醒
      s = a;
    
      u = 'hey';
      //而unknown类型变量赋值给其他类型的变量则TS会提醒,即使它的值也是string类型
     //s = u;   //error TS2322: Type 'unknown' is not assignable to type 'string'.
    
      //处理方式:赋值前进行类型判断
      if (typeof u === 'string') {
          s = u;
      }
    
      /* 
          类型断言
          语法:
              变量名 as 类型;
              <类型>变量名
      */
      s = u as string;
      s = <string>u;
    
  • never表示永远不会返回结果,用于抛错误

      function fn(): never {
          throw new Error('Error!');
      }
    
  • object表示对象,由于过于宽泛,一般不用

    通常用

      type obj = { 属性名1: '属性值', 属性名2: '属性值' }
    

    方式来指定对象中包含哪些属性

    [propName: string]: any 表示任意类型的属性:

      type objType = {
          name: string,
          [propName: string]: any
      };
    
      let obj: objType = { name: '王德发', age: 18, gender: '' };
    
  • 函数结构的类型声明:

    •   type 类型名 = (形参: 类型, 形参: 类型, ...) => 返回值类型
      
    •   let 函数名 = (形参: 类型, 形参: 类型, ...): 返回值类型 => 返回值或函数体
      
    •   function 函数名(形参: 类型, 形参: 类型, ...): 返回值类型 { 函数体 }
      
      type addNumType = (p1: number, p2: number) => number;
      let addNum: addNumType = (a, b) => a + b;
      let concatStr = (a: string, b: string): string => a + b;
        
      let addResult = addNum(1, 2);
      let concatResult = concatStr('1', '2');
      console.log({ addResult, concatResult });   //{ addResult: 3, concatResult: '12' }
    

    (参数名可以随便写)

  • 数组的类型声明:

    • 类型[]
    • Array<类型>
      let strArr: string[] = ['1', '2', 3];   //error TS2322: Type 'number' is not assignable to type 'string'.
      let numArr: Array<number> = [1, 2, '3'];    //error TS2322: Type 'string' is not assignable to type 'number'.
    
  • 元组:即固定长度的数组

    一般长度比较有限(长的话一般用Array),但性能会高些

    语法:[类型, 类型, 类型]

      type tupleType =  [ string, number ];
    
      let h: tupleType = [ 'hello', 123, 456 ];   //error TS2322: Type '[string, number, number]' is not assignable to type 'tupleType'. Source has 3 element(s) but target allows only 2.
    
  • enum: 枚举

    可以不指定具体的值

      //可以不指定具体的值
      enum Gender { male, female }
      //也可以指定具体的值
      enum Province {
          guangdong = '广东',
          guangxi = '广西'
      }
    
      type Human = {
          name: string,
          gender: Gender,
          province: Province
      };
      let wong: Human = {
          name: '王德发',
          gender: Gender.male,
          province: Province.guangdong
      };
      let hou: Human = {
          name: '侯丽榭',
          gender: Gender.female,
          province: Province.guangxi
      };
      console.log(wong, hou);     //{ name: '王德发', gender: 0, province: '广东' } { name: '侯丽榭', gender: 1, province: '广西' }
    
  • 对象模式组合

    &: 与

      type Human = { name: string } & { age: number }
    
      let wong: Human = { name: '王德发' };   //error TS2322: Type '{ name: string; }' is not assignable to type 'Human'. Property 'age' is missing in type '{ name: string; }' but required in type '{ age: number; }'.
    
  • 类型别名

      let a: 1 | 2 | 3 = 1;
      let b: 1 | 2 | 3 = 2;
    
      type TypeA = 4 | 5 | 6;
    
      let d: TypeA = 5;
      let c: TypeA = 6;
      let e: TypeA = 7;   //error TS2322: Type '7' is not assignable to type 'TypeA'.
    

0x04

  • readonly属性修饰符

    只读属性

    声明静态只读属性时须置readonlystatic之后

      readonly name: string = '王德发'
      static readonly num: number = '60 billion'
    
  • abstract修饰符

    抽象类、抽象方法

      abstract class Animal {
          abstract sayHello(): void;
      }
    
  • type

    描述对象的类型

      type myType = {
          name: string,   //逗号分隔
          age: number
      };      //error TS2300: Duplicate identifier 'myType'.
    
      type myType = {
          gender: string
      };      //error TS2300: Duplicate identifier 'myType'.
    
  • interface

    定义类中应该包含哪些属性和方法,同时也可当成类型声明去使用

      interface myInterface {
          name: string;
          age: number;
    
          sayHello(): void;
      }
    
      interface myInterface {     //interface可以多次定义
          gender: string;
      }
    
      const obj: myInterface = {      //作为类型声明来约束对象
          name: 'a',
          age: 3,
          gender: '未知',     //定义结果取interface多次定义的并集
          sayHello: () => {}
      }
      console.log(obj);   // { name: 'a', age: 3, gender: '未知', sayHello: [Function: sayHello] }
    
      //作为接口来约束类
      class MyClass implements myInterface {
          name = '默认名';
          age = 18;
          gender = '';
            
          sayHello() {
              console.log('你好');
          } 
      }
    
  • typeinterface的区别:

    1. type不可重复定义,但interface可以,结果取并集
    2. type内用逗号分隔属性,interface内用分号分隔属性
  • 接口抽象类的区别:

    • 接口中所有方法都是抽象方法
    • 抽象类中的方法既可抽象又可具体
  • 属性的封装

    TS中的属性修饰符:

    • public
    • private: 只能在类内部进行访问(及修改),子类亦无法访问
    • protected: 只能在当前类及其子类中访问(及修改)
      class Person {
          name: string;
    
          private _age: number;
    
          constructor(name: string, age: number) {
              this.name = name;
              this._age = age;
          }
    
          getAge(pwd: string) {
              return pwd === 'password' ?
                  this._age : undefined
          }
      }
    
      const wong = new Person('王德发', 20);
      wong.name = '侯丽榭';
      // console.log(wong._age);     //error TS2341: Property '_age' is private and only accessible within class 'Person'.
      console.log(wong.getAge('password'));   //20
      // wong._age = 80;     //error TS2341: Property '_age' is private and only accessible within class 'Person'.
    
    • 可以直接将属性定义在构造函数中

      下例中的Person类等效于上例中的Person类:

        class Person {
                  
            //将属性定义在构造函数中
            constructor(
                public name: string,    //这里的public必须写,否则只会被当成构造函数的形参而非类的实例属性
                private _age: number
            ) {
                this.name = name;
                this._age = _age;
            }
      
            getAge(pwd: string) {
                return pwd === 'password' ?
                    this._age : undefined
            }
        }
      
        const wong = new Person('王德发', 20);
        wong.name = '侯丽榭';
        // console.log(wong._age);     //error TS2341: Property '_age' is private and only accessible within class 'Person'.
        console.log(wong.getAge('password'));   //20
        // wong._age = 80;     //error TS2341: Property '_age' is private and only accessible within class 'Person'.
      

0x05 泛型

  • 函数与泛型

      function fn<T>(p: T): T {
          return p;
      }
    
      // 可以不指定类型、直接调用具有泛型的函数,TS可以自动对类型进行推断
      let result = fn(10);
      console.log(typeof result, { result });     // number { result: 10 }
      let result2 = fn<string>('hello');
      console.log(typeof result2, { result2 });   // string { result2: 'hello' }
    
      // 指定多个泛型
      function fn2<T, K>(p1: T, p2: K): T {
          console.log(typeof p2, { p2 });     // string { p2: 'hello' }
          return p1;
      }
    
      let result3 = fn2<number, string>(123, 'hello');
      console.log(typeof result3, { result3 });   // number { result3: 123 }
    
  • extends

      interface Inter {
          length: number;
      }
    
      // <T extends Inter> 表示泛型T必须是接口Inter的实现类(子类)
      function fn<T extends Inter>(p: T): number {
          return p.length;
      }
    
      // fn(1111);   // error TS2345: Argument of type 'number' is not assignable to parameter of type 'Inter'.
      console.log(fn('1111'));    // 4
      console.log(fn([1, 2, 3, 4, 5]));   // 5
    
  • 类与泛型

      class MyClass<T> {
          name: T;
    
          constructor(name: T) {
              this.name = name;
          }
      }
    
      const s = new MyClass<string>('王德发');
      const n = new MyClass<number>(114514);
      console.log(typeof s.name, s);      // string MyClass { name: '王德发' }
      console.log(typeof n.name, n);      // number MyClass { name: 114514 }
    

//End of Article


公众号二维码