一篇学习TypeScript的文章

242 阅读5分钟

1. 什么是 TypeScript

TypeScript 是一种由微软开发的自由和开源的编程语言。它是基于 JavaScript 的一门语言,是 JavaScript 的一个超集。也就是说 TypeScript 在 JavaScript 上多了一些扩展的特性,包括新的类型系统以及对 ES6+ 新特性的支持。 TypeScript 最终也编译为 JavaScript ,所以在任何支持 JavaScript 的运行环境中, 都可以使用 TypeScript 进行开发。

2. 快速上手 TypeScript

2.1 获取 TypeScript

使用 yarn 安装命令行的 TypeScript 编译器

//为了方便管理,我选择项目内安装 TypeScript
$ yarn install typescript --dev
2.2 编译 TypeScript 文件
// xxx 代表需要编译的 ts 文件,便已完成后在项目目录里会生成 xxx.js 文件
$ tsc xxx.ts
2.3 TypeScript 配置文件
//执行完毕后,项目下会多出一个 tsconfig.json
$ yarn tsc --init
{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Basic Options */
    // "incremental": true,                         /* Enable incremental compilation 是否启用增量编译*/
    "target": "es5",                                /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
    "module": "commonjs",                           /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    // "lib": [],                                   /* Specify library files to be included in the compilation. */
    // "allowJs": true,                             /* Allow javascript files to be compiled. */
    // "checkJs": true,                             /* Report errors in .js files. */
    // "jsx": "preserve",                           /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
    // "declaration": true,                         /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                      /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "sourceMap": true,                           /* Generates corresponding '.map' file. */
    // "outFile": "./",                             /* Concatenate and emit output to single file. */
    "outDir": "dist",                              /* Redirect output structure to the directory. */
    "rootDir": "src",                             /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                           /* Enable project compilation */
    // "tsBuildInfoFile": "./",                     /* Specify file to store incremental compilation information */
    // "removeComments": true,                      /* Do not emit comments to output. */
    // "noEmit": true,                              /* Do not emit outputs. */
    // "importHelpers": true,                       /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,                  /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,                     /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                                 /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                       /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                    /* Enable strict null checks. */
    // "strictFunctionTypes": true,                 /* Enable strict checking of function types. */
    // "strictBindCallApply": true,                 /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,        /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                      /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                        /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                      /* Report errors on unused locals. */
    // "noUnusedParameters": true,                  /* Report errors on unused parameters. */
    // "noImplicitReturns": true,                   /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,          /* Report errors for fallthrough cases in switch statement. */
    // "noUncheckedIndexedAccess": true,            /* Include 'undefined' in index signature results */
    // "noImplicitOverride": true,                  /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
    // "noPropertyAccessFromIndexSignature": true,  /* Require undeclared properties from index signatures to use element accesses. */

    /* Module Resolution Options */
    // "moduleResolution": "node",                  /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                             /* Base directory to resolve non-absolute module names. */
    // "paths": {},                                 /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                              /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                             /* List of folders to include type definitions from. */
    // "types": [],                                 /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,        /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                        /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,                    /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,                /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                            /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                               /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,                     /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                       /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,              /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,               /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "skipLibCheck": true,                           /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true        /* Disallow inconsistently-cased references to the same file. */
  }
}
2.4 按配置编译 ts 文件为 js 文件
//上述配置会编译 src 目录下的 ts 文件,并自动在 dist 目录下生成编译后的js文件,以及 .map 文件
$ yarn tsc --init

3. TypeScript 原始类型

TypeScript 原始类型的声明方式与 Flow 基本一致,区别是 Flow 生命时必须赋值,TypeScript 中初始化时可以不赋值。

    //Number 类型
    const a: number = 10;
    
    //String 类型
    const b: string = "semliker";
    
    //Boolean 类型
    const c: boolean = false;
    
    //undefined 类型
    const d:undefined = undefined
    
    //null 类型
    const e:null = null
    
    //void 类型
    const f: void  = undefined;
    
    //Symbol 类型
    const g = Symbol();
    
    //BigInt 类型 
    //我们在使用 BigInt 的时候,必须添加 ESNext 的编译辅助库,如下:
    const h: bigint = 9007199254740991n
    const i: bigint = BigInt(9007199254740991)
    const j: bigint = BigInt("9007199254740991") 

4. TypeScript 其他类型

4.1 Object 类型

    // object 类型是指除了原始类型以外的其它类型
    const foo: object = function () {} // [] // {}

    // 如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」
    const obj: { foo: number, bar: string } = { foo: 123, bar: 'string' }

4.2 Array 类型

    // 数组类型的两种表示方式
    const arr1: Array<number> = [1, 2, 3]
    const arr2: number[] = [1, 2, 3]

    // 案例 -----------------------

    // 如果是 JS,需要判断是不是每个成员都是数字
    // 使用 TS,类型有保障,不用添加类型判断
    function sum (...args: number[]) {
      return args.reduce((prev, current) => prev + current, 0)
    }

    sum(1, 2, 3) // => 6 

4.3 元组(Tuple) 类型

    const tuple: [number, string] = [18, 'zce']
    //写法1
    const age = tuple[0]
    const name = tuple[1]
    //写法2
    const [age, name] = tuple

    // ---------------------

    const entries: [string, number][] = Object.entries({
      foo: 123,
      bar: 456
    })

    const [key, value] = entries[0]

4.4 枚举(Enum) 类型

// 用对象模拟枚举
    // const PostStatus = {
    //   Draft: 0,
    //   Unpublished: 1,
    //   Published: 2
    // }

    // 标准的数字枚举,数字可省略,会自动从 0 开始自增
    enum PostStatus {
       Draft = 0,
       Unpublished = 1,
       Published = 2
    }

    // 数字枚举,枚举值自动基于前一个值自增
    enum PostStatus {
       Draft = 6,
       Unpublished, // => 7
       Published // => 8
    }

    // 字符串枚举
    enum PostStatus {
       Draft = 'aaa',
       Unpublished = 'bbb',
       Published = 'ccc'
    }

    // 常量枚举,不会侵入编译结果,编译后会直接赋值常量
    const enum PostStatus {
      Draft,
      Unpublished,
      Published
    }

    const post = {
      title: 'Hello TypeScript',
      content: 'TypeScript is a typed superset of JavaScript.',
      status: PostStatus.Draft // 3 // 1 // 0
    }

4.5 函数类型

    // ts 中形参与实参必须一致,可选参数需要在变量后:前加上?
    function func1 (a: number, b: number = 10, ...rest: number[]): string {
      return 'func1'
    }

    func1(100, 200)

    func1(100)

    func1(100, 200, 300)

    // -----------------------------------------
    //把函数赋值给变量,变量类型定义
    const func2: (a: number, b: number) => string = function (a: number, b: number): string {
      return 'func2'
    }

4.6 任意类型

    // any 类型不会做类型检查,是任意类型的
    function stringify (value: any) {
      return JSON.stringify(value)
    }

    stringify('string')

    stringify(100)

    stringify(true)

    let foo: any = 'string'

    foo = 100

    foo.bar()

5. TypeScript 隐式类型推断

    // TypeScript 推断 age 为数字类型,赋值为字符串类型会报错
    let age = 18 // number
    // age = 'string'

    // 定义时没有赋值, TypeScript 认为 foo 是一个 any 类型的值
    let foo
    foo = 100
    foo = 'string'

6. TypeScript 类型断言

    // 假定这个 arrs 来自一个明确的接口
    const nums = [100, 200, 300, 400]
    //TypeScript 默认认为 res 的类型为 number 和 undefined,但我们明确知道 res 为数字类型时使用类型断言
    const res = nums.find(i => i > 0)
    //这时候因为 res 可能会是 undefined 类型,所以这里会报错
    // const square = res * res
    
    // 类型断言方法一,使用as
    const num1 = res as number
    //类型断言方法二,使用<>
    // JSX 下不能使用,<>会有冲突
    const num2 = <number>res 

7. TypeScript 接口

    //使用 interface 关键词定义一个 Post 接口
    interface Post {
      title: string
      content: string
      subtitle?: string //可选类型,可以选择性实现
      readonly summary: string //只读,不可修改
    }
    //指定传入的 post 变量必须实现接口 Post,拥有 title、content 属性,否则会报错。
    function printPost (post: Post) {
      console.log(post.title)
      console.log(post.content)
    }
    printPost({
      title: 'Hello TypeScript',
      content: 'A javascript superset'
    })
    //任意类型接口,可任意添加
    interface Cache {
      [prop: string]: string
    }
    const cache: Cache = {}
    cache.foo = 'value1'
    cache.bar = 'value2'

8. TypeScript 类

8.1 TypeScript 中的类,直接用 this 去访问类的属性会报错,需要先在类中明确声明类所拥有的属性。

    class Person {
      name: string // = 'init name'
      age: number

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

      sayHi (msg: string): void {
        console.log(`I am ${this.name}, ${msg}`)
      }
    }

8.2 TypeScript 类的访问修饰符


  public name: string // 公有属性,默认的访问修饰符为 public
  private age: number //私有属性
  protected gender: boolean //受保护的属性,可以在子类中去访问

8.3 TypeScript 类的只读属性

readonly 关键字,如果属性有了访问修饰符,readonly 属性只能加在访问修饰符后。

8.4 类与接口

    //创建 Eat 接口
    interface Eat {
      eat (food: string): void
    }
    //创建 Run 接口
    interface Run {
      run (distance: number): void
    }
    //实现 Eat 和 Run 接口
    class Person implements Eat, Run {
      eat (food: string): void {
        console.log(`优雅的进餐: ${food}`)
      }

      run (distance: number) {
        console.log(`直立行走: ${distance}`)
      }
    }

    class Animal implements Eat, Run {
      eat (food: string): void {
        console.log(`呼噜呼噜的吃: ${food}`)
      }

      run (distance: number) {
        console.log(`爬行: ${distance}`)
      }
    }

8.5 抽象类

    // abstract 关键字实现一个抽象类,抽象类不能用 new 实现,只可以继承
    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 d = new Dog()
    d.eat('嗯西马')
    d.run(100)

8.6 泛型

    // T 表示一个动态类型,让类型定义变成可定义的
    function createArray<T> (length: number, value: T): T[] {
      const arr = Array<T>(length).fill(value)
      return arr
    }
    
    const res = createArray<string>(3, 'foo')