第一章 快速入门

TypeScript 中文手册

深入理解 TypeScript

TypeScript简介

对比于JS,TS是JS的超集,简单的说就是在 JavaScript 的基础上加入了类型系统,让每个参数都有明确的意义,从而带来了更加智能的提示。

相对于JS而言,TS属于强类型语言,所以对于项目而言,会使代码更加规范,从而解决了大型项目代码的复杂性,其次,浏览器是不识别TS的,所以在编译的时候,TS文件会先编译为JS文件。

  1. TypeScript是JavaScript的超集
  2. 它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
  3. TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
  4. TS完全兼容JS,换言之,任何的JS代码都可以直接当成TS使用。
  5. 相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS

TypeScript 开发环境搭建

  1. 下载Node.js

  2. 安装Node.js

  3. 使用npm全局安装typescript

    • 进入命令行
    • 输入:npm i -g typescript
  4. 创建一个ts文件

  5. 使用tsc对ts文件进行编译

    • 进入命令行

    • 进入ts文件所在目录

    • 执行命令:tsc xxx.ts

基本类型

  • 类型声明

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

    • 通过类型声明可以指定TS中变量(参数、形参)的类型

    • 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错

    • 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值

    • 语法:

      1
      2
      3
      4
      5
      6
      7
      let 变量: 类型;

      let 变量: 类型 = 值;

      function fn(参数: 类型, 参数: 类型): 类型{
      ...
      }
  • 自动类型判断

    • TS拥有自动的类型判断机制
    • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
    • 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明
  • 类型

    类型例子描述
    number1, -33, 2.5任意数字
    string‘hi’, “hi”任意字符串
    booleantrue、false布尔值true或false
    字面量其本身限制变量的值就是该字面量的值
    any*任意类型
    unknown*类型安全的any
    void空值(undefined)没有值(或undefined)
    never没有值不能是任何值
    object{name:’孙悟空’}任意的JS对象
    array[1,2,3]任意JS数组
    tuple[4,5]元组,TS新增类型,固定长度数组
    enumenum{A, B}枚举,TS中新增类型
  • number

    1
    2
    3
    4
    5
    let decimal: number = 6;
    let hex: number = 0xf00d;
    let binary: number = 0b1010;
    let octal: number = 0o744;
    let big: bigint = 100n;
  • boolean

    1
    let isDone: boolean = false;
  • string

    1
    2
    3
    4
    5
    6
    7
    8
    let color: string = "blue";
    color = 'red';

    let fullName: string = `Bob Bobbington`;
    let age: number = 37;
    let sentence: string = `Hello, my name is ${fullName}.

    I'll be ${age + 1} years old next month.`;
  • 字面量

    • 也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围

    • 使用 | 来连接多个类型(联合类型)

    • 目前支持字符串数字布尔三种类型。

      1
      2
      let color: 'red' | 'blue' | 'black';
      let num: 1 | 2 | 3 | 4 | 5;
  • any

    • 表示的是任意类型,一个变量设置类型为any后相当于对该变量关闭了TS的类型检测

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

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

      1
      2
      3
      let d: any = 4;
      d = 'hello';
      d = true;
  • unknown

    • 类型是any,它可以赋值给任意变量

    • unknown 实际上就是一个类型安全的any

    • unknown类型的变量,不能直接赋值给其他变量

      1
      2
      let notSure: unknown = 4;
      notSure = 'hello';
  • nullundefined 两个类型一旦赋值上,就不能在赋值给任何其他类型

  • void

    1
    let unusable: void = undefined;
  • never

    表示一个函数永远不存在返回值,TS会认为类型为 never,那么与 void 相比, never应该是 void子集, 因为 void实际上的返回值为 undefined,而 neverundefined也不行

    符合never的情况有:当抛出异常的情况和无限死循环

    1
    2
    3
    function error(message: string): never {
    throw new Error(message);
    }
  • object

    • {} 用来指定对象中可以包含哪些属性

    • 语法:{属性名:属性值,属性名:属性值}

    • 在属性名后边加上?表示属性是可选的

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

    • 设置函数结构的类型声明:语法:(形参:类型, 形参:类型 ...) => 返回值

      • 在书写的时候,也可以写入返回值的类型,如果写入,则必须要有对应类型的返回值,但通常情况下是省略,因为TS的类型推断功能够正确推断出返回值类型
      • 参数类型
        • 可选参数: 如果函数要配置可有可无的参数时,可以通过 ? 实现,可选参数一定要在最后面
        • 默认参数:函数内可以自己设定其默认参数,用 = 实现
        • 剩余参数:仍可以使用扩展运算符 ...
    • Object(大写的O),代表所有的原始类型或非原始类型都可以进行赋值,除了nullundefined

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      let b: {name: string, age?: number};
      b = {name: '孙悟空', age: 18};

      let c: {name: string, [propName: string]: any};
      c = {name: '猪八戒', age: 18, gender: '男'};

      let d: (a: number ,b: number)=>number;

      let obj: Object;
      obj = 1; // ok
      obj = "a"; // ok
      obj = true; // ok
      obj = {}; // ok
      obj = Symbol() //ok
      obj = 10n //ok
      obj = null; // error
      obj = undefined; // error
  • array

    • 类型[] Array<类型>

      1
      2
      let list: number[] = [1, 2, 3];
      let list: Array<number> = [1, 2, 3];
  • tuple

    • 元组就是固定长度的数组

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

      1
      2
      let x: [string, number];
      x = ["hello", 10];
  • enum

    可以定义一些带名字的常量,这样可以清晰表达意图创建一组有区别的用例

    注意:

    • 枚举的类型只能是 stringnumber
    • 定义的名称不能为关键字
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    enum Color {
    Red,
    Green,
    Blue,
    }
    let c: Color = Color.Green;

    enum Color {
    Red = 1,
    Green,
    Blue,
    }
    let c: Color = Color.Green;

    enum Color {
    Red = 1,
    Green = 2,
    Blue = 4,
    }
    let c: Color = Color.Green;
  • 类型断言

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

      • 第一种

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

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

    • &表示同时 交叉类型:将多个类型合并为一个类型,使用&符号连

      1
      2
      let j: { name: string } & { age: number };
      // j = {name: '孙悟空', age: 18};
    • 类型的别名

      1
      2
      3
      4
      5
      6
      type myType = 1 | 2 | 3 | 4 | 5;
      let k: myType;
      let l: myType;
      let m: myType;

      k = 2;

编译选项

  • 自动编译文件

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

    • 示例:

      1
      tsc xxx.ts -w
  • 自动编译整个项目

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

    • 但是能直接使用tsc命令的前提时,要先在项目根目录下创建一个ts的配置文件 tsconfig.json

    • tsconfig.json是一个JSON文件,添加配置文件后,只需只需 tsc 命令即可完成对整个项目的编译

    • 配置选项:

      • include

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

        • 默认值:[“**表示任意目录/*表示任意文件“]

        • 示例:

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

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

      • exclude

        • 定义需要排除在外的目录

        • 默认值:[“node_modules”, “bower_components”, “jspm_packages”]

        • 示例:

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

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

      • extends

        • 定义被继承的配置文件

        • 示例:

          1
          "extends": "./configs/base"

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

      • files

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

        • 示例:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          "files": [
          "core.ts",
          "sys.ts",
          "types.ts",
          "scanner.ts",
          "parser.ts",
          "utilities.ts",
          "binder.ts",
          "checker.ts",
          "tsc.ts"
          ]

          列表中的文件都会被TS编译器所编译

        • compilerOptions

          • 编译选项是配置文件中非常重要也比较复杂的配置选项

          • 在compilerOptions中包含多个子选项,用来完成对编译的配置

            • 项目选项

              • target

                • 设置ts代码编译的目标版本

                • 可选值:

                  • ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext
                • 示例:

                  1
                  2
                  3
                  "compilerOptions": {
                  "target": "es6"
                  }

                  如上设置,我们所编写的ts代码将会被编译为ES6版本的js代码

              • lib

                • 指定代码运行时所包含的库(宿主环境)

                • 可选值:

                  • ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost ……
                • 示例:

                  1
                  2
                  3
                  4
                  5
                  6
                  "compilerOptions": {
                  "target": "ES6",
                  "lib": ["ES6", "DOM"],
                  "outDir": "dist",
                  "outFile": "dist/aa.js"
                  }
              • module

                • 设置编译后代码使用的模块化系统

                • 可选值:

                  • CommonJS、UMD、AMD、System、ES2020、ESNext、None
                • 示例:

                  1
                  2
                  3
                  "compilerOptions": {
                  "module": "CommonJS"
                  }
              • outDir

                • 编译后文件的所在目录

                • 默认情况下,编译后的js文件会和ts文件位于相同的目录,设置outDir后可以改变编译后文件的位置

                • 示例:

                  1
                  2
                  3
                  "compilerOptions": {
                  "outDir": "dist"
                  }

                  设置后编译后的js文件将会生成到dist目录

              • outFile

                • 将所有的文件编译为一个js文件

                • 默认会将所有的编写在全局作用域中的代码合并为一个js文件,如果module制定了None、System或AMD则会将模块一起合并到文件之中

                • 示例:

                  1
                  2
                  3
                  "compilerOptions": {
                  "outFile": "dist/app.js"
                  }
              • rootDir

                • 指定代码的根目录,默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过rootDir可以手动指定根目录

                • 示例:

                  1
                  2
                  3
                  "compilerOptions": {
                  "rootDir": "./src"
                  }
              • allowJs

                • 是否对js文件编译
              • checkJs

                • 是否对js文件进行检查

                • 示例:

                  1
                  2
                  3
                  4
                  "compilerOptions": {
                  "allowJs": true,
                  "checkJs": true
                  }
              • removeComments

                • 是否删除注释
                • 默认值:false
              • noEmit

                • 不对代码进行编译
                • 默认值:false
              • sourceMap

                • 是否生成sourceMap
                • 默认值:false
            • 严格检查

              • strict
                • 启用所有的严格检查,默认值为true,设置后相当于开启了所有的严格检查
              • alwaysStrict
                • 用来设置编译后的文件是否使用严格模式,默认false
              • noImplicitAny
                • 禁止隐式的any类型
              • noImplicitThis
                • 禁止类型不明确的this
              • strictBindCallApply
                • 严格检查bind、call和apply的参数列表
              • strictFunctionTypes
                • 严格检查函数的类型
              • strictNullChecks
                • 严格的空值检查
              • strictPropertyInitialization
                • 严格检查属性是否初始化
            • 额外检查

              • noFallthroughCasesInSwitch
                • 检查switch语句包含正确的break
              • noImplicitReturns
                • 检查函数没有隐式的返回值
              • noUnusedLocals
                • 检查未使用的局部变量
              • noUnusedParameters
                • 检查未使用的参数
            • 高级

              • allowUnreachableCode
                • 检查不可达代码
                • 可选值:
                  • true,忽略不可达代码
                  • false,不可达代码将引起错误
              • noEmitOnError
                • 有错误的情况下不进行编译
                • 默认值:false

webpack

  • 通常情况下,实际开发中我们都需要使用构建工具对代码进行打包,TS同样也可以结合构建工具一起使用,下边以webpack为例介绍一下如何结合构建工具使用TS。

  • 步骤:

    1. 初始化项目

      • 进入项目根目录,执行命令 npm init -y
        • 主要作用:创建package.json文件
    2. 下载构建工具

      • npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader html-webpack-plugin clean-webpack-plugin
        • 共安装了7个包
          • webpack
            • 构建工具webpack
          • webpack-cli
            • webpack的命令行工具
          • webpack-dev-server
            • webpack的开发服务器
          • typescript
            • ts编译器
          • ts-loader
            • ts加载器,用于在webpack中编译ts文件
          • html-webpack-plugin
            • webpack中html插件,用来自动创建html文件
          • clean-webpack-plugin
            • webpack中的清除插件,每次构建都会先清除目录
    3. 根目录下创建webpack的配置文件webpack.config.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      const path = require("path");
      const HtmlWebpackPlugin = require("html-webpack-plugin");
      const { CleanWebpackPlugin } = require("clean-webpack-plugin");

      module.exports = {
      optimization:{
      minimize: false // 关闭代码压缩,可选
      },

      entry: "./src/index.ts",

      devtool: "inline-source-map",

      devServer: {
      contentBase: './dist'
      },

      output: {
      path: path.resolve(__dirname, "dist"),
      filename: "bundle.js",
      environment: {
      arrowFunction: false // 关闭webpack的箭头函数,可选
      }
      },

      resolve: {
      extensions: [".ts", ".js"]
      },

      module: {
      rules: [
      {
      test: /\.ts$/,
      use: {
      loader: "ts-loader"
      },
      exclude: /node_modules/
      }
      ]
      },

      plugins: [
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
      //title:'TS测试'
      template: "./src/index.html"
      }),
      ]

      }
    4. 根目录下创建tsconfig.json,配置可以根据自己需要

      1
      2
      3
      4
      5
      6
      7
      {
      "compilerOptions": {
      "target": "ES2015",
      "module": "ES2015",
      "strict": true
      }
      }
    5. 修改package.json添加如下配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      {
      ...略...
      "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "build": "webpack",
      "start": "webpack serve --open"
      },
      ...略...
      }
    6. 在src下创建ts文件,并在并命令行执行npm run build对代码进行编译,或者执行npm start来启动开发服务器

Babel

  • 经过一系列的配置,使得TS和webpack已经结合到了一起,除了webpack,开发中还经常需要结合babel来对代码进行转换以使其可以兼容到更多的浏览器,在上述步骤的基础上,通过以下步骤再将babel引入到项目中。

    1. 安装依赖包:

      • npm i -D @babel/core @babel/preset-env babel-loader core-js
      • 共安装了4个包,分别是:
        • @babel/core
          • babel的核心工具
        • @babel/preset-env
          • babel的预定义环境
        • @babel-loader
          • babel在webpack中的加载器
        • core-js
          • core-js用来使老版本的浏览器支持新版ES语法
    2. 修改webpack.config.js配置文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      // 引入一个包
      const path = require('path');
      // 引入html插件
      const HTMLWebpackPlugin = require('html-webpack-plugin');
      // 引入clean插件
      const { CleanWebpackPlugin } = require('clean-webpack-plugin');

      // webpack中的所有的配置信息都应该写在module.exports中
      module.exports = {

      // 指定入口文件
      entry: "./src/index.ts",

      // 指定打包文件所在目录
      output: {
      // 指定打包文件的目录
      path: path.resolve(__dirname, 'dist'),
      // 打包后文件的文件
      filename: "bundle.js",

      // 告诉webpack不使用箭头
      environment:{
      arrowFunction: false
      }
      },

      // 指定webpack打包时要使用模块
      module: {
      // 指定要加载的规则
      rules: [
      {
      // test指定的是规则生效的文件
      test: /\.ts$/,
      // 要使用的loader
      use: [
      // 配置babel
      {
      // 指定加载器
      loader:"babel-loader",
      // 设置babel
      options: {
      // 设置预定义的环境
      presets:[
      [
      // 指定环境的插件
      "@babel/preset-env",
      // 配置信息
      {
      // 要兼容的目标浏览器
      targets:{
      "chrome":"58",
      "ie":"11"
      },
      // 指定corejs的版本
      "corejs":"3",
      // 使用corejs的方式 "usage" 表示按需加载
      "useBuiltIns":"usage"
      }
      ]
      ]
      }
      },
      'ts-loader'
      ],
      // 要排除的文件
      exclude: /node-modules/
      }
      ]
      },

      // 配置Webpack插件
      plugins: [
      new CleanWebpackPlugin(),
      new HTMLWebpackPlugin({
      // title: "这是一个自定义的title"
      template: "./src/index.html"
      }),
      ],

      // 用来设置引用模块
      resolve: {
      extensions: ['.ts', '.js']
      }

      };

      如此一来,使用ts编译后的文件将会再次被babel处理,使得代码可以在大部分浏览器中直接使用,可以在配置选项的targets中指定要兼容的浏览器版本。

第二章:面向对象

面向对象是程序中一个非常重要的思想,它被很多同学理解成了一个比较难,比较深奥的问题,其实不然。面向对象很简单,简而言之就是程序之中所有的操作都需要通过对象来完成。

  • 举例来说:
    • 操作浏览器要使用window对象
    • 操作网页要使用document对象
    • 操作控制台要使用console对象

一切操作都要通过对象,也就是所谓的面向对象,那么对象到底是什么呢?这就要先说到程序是什么,计算机程序的本质就是对现实事物的抽象,抽象的反义词是具体,比如:照片是对一个具体的人的抽象,汽车模型是对具体汽车的抽象等等。程序也是对事物的抽象,在程序中我们可以表示一个人、一条狗、一把枪、一颗子弹等等所有的事物。一个事物到了程序中就变成了一个对象。

在程序中所有的对象都被分成了两个部分:数据和功能,以人为例,人的姓名、性别、年龄、身高、体重等属于数据,人可以说话、走路、吃饭、睡觉这些属于人的功能。数据在对象中被成为属性,而功能就被称为方法。所以简而言之,在程序中一切皆是对象。

类(class)

要想面向对象,操作对象,首先便要拥有对象,那么下一个问题就是如何创建对象。要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说:可以通过Person类来创建人的对象,通过Dog类创建狗的对象,通过Car类来创建汽车的对象,不同的类可以用来创建不同的对象。

  • 定义类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class 类名 {
    属性名: 类型;

    constructor(参数: 类型){
    this.属性名 = 参数;
    }

    方法名(){
    ....
    }

    }
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Person{
    //静态属性
    static name1: string = 'Domesy'

    //成员属性,实际上是通过public上进行修饰,只是省略了
    // 需要注意的是: 在成员属性中,如果不给默认值,并且不使用是会报错的,如果不想报错就给加!
    name: string;
    age: number;
    money!: number;
    // constructor 被称为构造函数
    // 构造函数会在对象创建时调用
    constructor(name: string, age: number){
    // 在实例方法中,this就表示当前当前的实例
    // 在构造函数中当前对象就是当前新建的那个对象
    // 可以通过this向新建的对象中添加属性
    this.name = name;
    this.age = age;
    }

    sayHello(){
    // 在方法中可以通过this来表示当前调用方法的对象
    console.log(`大家好,我是${this.name}`);
    }
    }
  • 使用类:

    1
    2
    const p = new Person('孙悟空', 18);
    p.sayHello();

面向对象的特点

  • 封装

    • 对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装

    • 默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在TS中可以对属性的权限进行设置

    • 私有字段(#)

      在 TS 3.8版本便开始支持ECMACMAScript的私有字段。

      需要注意的是私有字段与常规字段不同,主要的区别是:

      • 私有字段以 # 字符开头,也叫私有名称;
      • 每个私有字段名称都唯一地限定于其包含的类;
      • 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);
      • 私有字段不能在包含的类之外访问,甚至不能被检测到。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      class Info {
      #name: string; //私有字段
      getName: string;

      constructor(name: string) {
      this.#name = name;
      this.getName = name
      }

      setName() {
      return `我的名字是${this.#name}`
      }
      }

      let myName = new Info("Domesy");


      console.log(myName.setName()) // "我的名字是Domesy"
      console.log(myName.getName) // ok "Domesy"
      console.log(myName.#name) // error
      // Property '#name' is not accessible outside class 'Info'
      // because it has a private identifier.(18013)
    • 只读属性(readonly)

      • 如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改
      • readonly修饰,只能在构造函数中初始化,并且在TS中,只允许将interfacetypeclass上的属性标识为readonly
        • readonly实际上只是在编译阶段进行代码检查
        • radonly修饰的词只能在 constructor阶段修改,其他时刻不允许修改
    • TS中属性具有三种修饰符:

      • public(默认值),类中、子类内的任何地方、外部都能调用

      • protected ,类中、子类内的任何地方都能调用,但外部不能调用

      • private ,类中可以调用,子类内的任何地方、外部均不可调用

      • 示例

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        class Person {
        public name: string
        protected age: number
        private tel: number

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

        class Child extends Person {
        constructor(name: string, age: number, tel: number) {
        super(name, age, tel);
        }

        getName(){
        console.log(`我的名字叫${this.name},年龄是${this.age}`) // ok name 和 age可以
        console.log(`电话是${this.tel}`) // error 报错 原因是 tel 拿不出来
        }
        }


        const res = new Child('Domesy', 7, 123456)
        console.log(res.name) // ok Domesy
        console.log(res.age) // error
        console.log(res.tel) // error
    • 属性存取器

      • 对于一些不希望被任意修改的属性,可以将其设置为private

      • 直接将其设置为private将导致无法再通过对象修改其中的属性

      • 我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器

      • 设置属性的方法叫做setter方法,读取属性的方法叫做getter方法

      • 示例:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        class Person{

        private _name: string;
        private _age: number;

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

        /*
        * getter方法用来读取属性
        * setter方法用来设置属性
        * - 它们被称为属性的存取器
        * */

        // 定义方法,用来获取name属性
        // getName(){
        // return this._name;
        // }
        //
        // // 定义方法,用来设置name属性
        // setName(value: string){
        // this._name = value;
        // }


        // 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);

        // per.setName('猪八戒');
        // per.setAge(-33);

        per.name = '猪八戒';
        per.age = -33;
    • 静态属性

      • 静态属性(方法),也称为类属性。使用静态属性无需创建实例,通过类即可直接使用

      • 静态属性(方法)使用static开头

      • 示例:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        class Tools{
        static PI = 3.1415926;

        static sum(num1: number, num2: number){
        return num1 + num2
        }
        }

        console.log(Tools.PI);
        console.log(Tools.sum(123, 456));
    • this

      • 在类中,使用this表示当前对象
  • 继承

    • 继承是面向对象中的又一个特性

    • 通过继承可以将其他类中的属性和方法引入到当前类中

      • 示例:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        class Animal{
        name: string;
        age: number;

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

        class Dog extends Animal{

        bark(){
        console.log(`${this.name}在汪汪叫!`);
        }
        }

        const dog = new Dog('旺财', 4);
        dog.bark();
    • 通过继承可以在不修改类的情况下完成对类的扩展

    • 重写和重载

      • 重写:子类重写继承自父类中的方法

      • 重载:指为同一个函数提供多个类型定义

      • 示例:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        // 重写
        class Person{
        setName(name: string){
        return `我的名字叫${name}`
        }
        }

        class Child extends Person{
        setName(name: string){
        return `你的名字叫${name}`
        }
        }

        const yourName = new Child()
        console.log(yourName.setName('小杜杜')) // "你的名字叫小杜杜"

        // 重载
        class Person1{
        setNameAge(name: string):void;
        setNameAge(name: number):void;
        setNameAge(name:string | number){
        if(typeof name === 'string'){
        console.log(`我的名字是${name}`)
        }else{
        console.log(`我的年龄是${name}`)
        }
        };
        }

        const res = new Person1()
        res.setNameAge('小杜杜') // "我的名字是小杜杜"
        res.setNameAge(7) // "我的年龄是7"

        在子类中可以使用super来完成对父类的引用

        • 如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
        • 在类的方法中 super就表示当前类的父类
    • 抽象类(abstract class)

      用abstract关键字声明的类叫做抽象类,声明的方法叫做抽象方法

      • 抽象类:指不能被实例化,因为它里面包含一个或多个抽象方法。
      • 抽象方法:是指不包含具体实现的方法;

      注:抽象类是不能直接实例化,只能实例化实现了所有抽象方法的子类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      abstract class Person {
      constructor(public name: string){}

      // 抽象方法
      abstract setAge(age: number) :void;
      }

      class Child extends Person {
      constructor(name: string) {
      super(name);
      }

      setAge(age: number): void {
      console.log(`我的名字是${this.name},年龄是${age}`);
      }
      }

      let res = new Person("小杜杜") //error
      let res1 = new Child("小杜杜");

      res1.setAge(7) // "我的名字是小杜杜,年龄是7"

接口(Interface)

接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。

  • 示例(检查对象类型):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface Person{
    name: string;
    sayHello():void;
    }

    function fn(per: Person){
    per.sayHello();
    }

    fn({name:'孙悟空', sayHello() {console.log(`Hello, 我是 ${this.name}`)}});

  • 示例(实现)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    interface Person{
    name: string;
    sayHello():void;
    }

    class Student implements Person{
    constructor(public name: string) {
    }

    sayHello() {
    console.log('大家好,我是'+this.name);
    }
    }

泛型(Generic)

定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。

  • 举个例子:

    1
    2
    3
    function test(arg: any): any{
    return arg;
    }

    上例中,test函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的,由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型

    使用泛型:

    1
    2
    3
    function test<T>(arg: T): T{
    return arg;
    }

    这里的<T>就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。

    那么如何使用上边的函数呢?

    • 方式一(直接使用):

      1
      test(10)

      使用时可以直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式

    • 方式二(指定类型):

      1
      test<number>(10)

      也可以在函数后手动指定泛型

    可以同时指定多个泛型,泛型间使用逗号隔开:

    1
    2
    3
    4
    5
    function test<T, K>(a: T, b: K): K{
    return b;
    }

    test<number, string>(10, "hello");

    使用泛型时,完全可以将泛型当成是一个普通的类去使用

    类中同样可以使用泛型:

    1
    2
    3
    4
    5
    6
    7
    class MyClass<T>{
    prop: T;

    constructor(prop: T){
    this.prop = prop;
    }
    }

    除此之外,也可以对泛型的范围进行约束

    1
    2
    3
    4
    5
    6
    7
    interface MyInter{
    length: number;
    }

    function test<T extends MyInter>(arg: T): number{
    return arg.length;
    }

    使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。

TS断言和类型守卫

TS断言

分为三种:类型断言非空断言确定赋值断言

当断言失效后,可能使用到:双重断言

类型断言

在特定的环境中,我们会比TS知道这个值具体是什么类型,不需要TS去判断,简单的理解就是,类型断言会告诉编译器,你不用给我进行检查,相信我,他就是这个类型

共有两种方式:

  • 尖括号
  • as:推荐
1
2
3
4
5
6
7
//尖括号
let num:any = '小杜杜'
let res1: number = (<string>num).length; // React中会 error

// as 语法
let str: any = 'Domesy';
let res: number = (str as string).length;

但需要注意的是:尖括号语法在React中会报错,原因是与JSX语法会产生冲突,所以只能使用as语法

非空断言

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。

具体而言,x! 将从 x 值域中排除 null 和 undefined 。

那么非空断言操作符到底有什么用呢?下面我们先来看一下非空断言操作符的一些使用场景。

忽略 undefined 和 null 类型

1
2
3
4
5
6
function myFunc(maybeString: string | undefined | null) {
// Type 'string | null | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
const onlyString: string = maybeString; // Error
const ignoreUndefinedAndNull: string = maybeString!; // Ok
}

调用函数时忽略 undefined 类型

1
2
3
4
5
6
7
8
type NumGenerator = () => number;

function myFunc(numGenerator: NumGenerator | undefined) {
// Object is possibly 'undefined'.(2532)
// Cannot invoke an object which is possibly 'undefined'.(2722)
const num1 = numGenerator(); // Error
const num2 = numGenerator!(); //OK
}

因为 ! 非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要特别注意。比如下面这个例子:

1
2
3
const a: number | undefined = undefined;
const b: number = a!;
console.log(b);

以上 TS 代码会编译生成以下 ES5 代码:

1
2
3
4
"use strict";
const a = undefined;
const b = a;
console.log(b);

虽然在 TS 代码中,我们使用了非空断言,使得 const b: number = a!; 语句可以通过 TypeScript 类型检查器的检查。但在生成的 ES5 代码中,! 非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出 undefined

确定赋值断言

TS 2.7版本中引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个 ! 号,以告诉TS该属性会被明确赋值。

为了更好地理解它的作用,我们来看个具体的例子:

1
2
3
4
5
6
7
8
let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error

function initialize() {
x = 10;
}

很明显该异常信息是说变量 x 在赋值前被使用了,要解决该问题,我们可以使用确定赋值断言:

1
2
3
4
5
6
7
let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
x = 10;
}

通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。

双重断言

断言失效后,可能会用到,但一般情况下不会使用

失效的情况:基础类型不能断言为接口

1
2
3
4
5
6
7
interface Info{
name: string;
age: number;
}

const name = '小杜杜' as Info; // error, 原因是不能把 string 类型断言为 一个接口
const name1 = '小杜杜' as any as Info; //ok

类型守卫

类型守卫:是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内

我个人的感觉是,类型守卫就是你可以设置多种类型,但我默认你是什么类型的意思

目前,常有的类型守卫共有4种:in关键字typeof关键字instanceof类型谓词(is)

in关键字

用于判断这个属性是那个里面的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Info {
name: string
age: number
}

interface Info1{
name: string
flage: true
}

const setInfo = (data: Info | Info1) => {
if("age" in data){
console.log(`我的名字是:${data.name},年龄是:${data.age}`)
}

if("flage" in data){
console.log(`我的名字是:${data.name},性别是:${data.flage}`)
}
}

setInfo({name: '小杜杜', age: 7}) // "我的名字是:小杜杜,年龄是:7"
setInfo({name: '小杜杜', flage: true}) // "我的名字是:小杜杜,性别是:true"

typeof关键字

用于判断基本类型,如string | number等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const setInfo = (data: number | string | undefined) => {
if(typeof data === "string"){
console.log(`我的名字是:${data}`)
}

if(typeof data === "number"){
console.log(`我的年龄是:${data}`)
}

if(typeof data === "undefined"){
console.log(data)
}
}

setInfo('小杜杜') // "我的名字是:小杜杜"
setInfo(7) // "我的年龄是:7"
setInfo(undefined) // undefined"

instanceof关键字

用于判断一个实例是不是构造函数,或使用类的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Name {
name: string = '小杜杜'
}

class Age extends Name{
age: number = 7
}

const setInfo = (data: Name) => {
if (data instanceof Age) {
console.log(`我的年龄是${data.age}`);
} else {
console.log(`我的名字是${data.name}`);
}
}

setInfo(new Name()) // "我的名字是小杜杜"
setInfo(new Age()) // "我的年龄是7"

类型谓词(is)

1
2
3
4
5
6
7
function isNumber(x: any): x is number { //默认传入的是number类型
return typeof x === "number";
}

console.log(isNumber(7)) // true
console.log(isNumber('7')) //false
console.log(isNumber(true)) //false

两者的区别

通过上面的介绍,我们可以发现断言与类型守卫的概念非常相似,都是确定参数的类型,但断言更加霸道,它是直接告诉编辑器,这个参数就是这个类型,而类型守卫更像确定这个参数具体是什么类型。

类型别名、接口

类型别名(type)

类型别名:也就是type,用来给一个类型起个新名字

1
2
3
type InfoProps = string | number

const setInfo = (data: InfoProps) => {}

接口(interface)

接口在面向对象语言中表示行为抽象,也可以用来描述对象的形状

使用interface关键字来定义接口

对象的形状

接口可以用来描述对象,主要可以包括以下数据:可读属性只读属性任意属性

  • 可读属性:当我们定义一个接口时,我们的属性可能不需要全都要,这是就需要 ? 来解决
  • 只读属性:用 readonly修饰的属性为只读属性,意思是指允许定义,不允许之后进行更改
  • 任意属性:这个属性极为重要,它是可以用作就算没有定义,也可以使用,比如 [propName: string]: any。比如说我们对组件进行封装,而封装的那个组件并没有导出对应的类型,然而又想让他不报错,这时就可以使用任意属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Props {
a: string;
b: number;
c: boolean;
d?: number; // 可选属性
readonly e: string; //只读属性
[propName: string]: any; //任意属性
}
let res: Props = {
a: '小杜杜',
b: 7,
c: true,
e: 'Domesy',
d: 1, // 有没有d都可以
h: 2 // 任意属性
}

let res.e = 'hi' // error, 原因是可读属性不允许更改

继承

继承:与类一样,接口也存在继承属性,也是使用extends字段

1
2
3
4
5
6
7
8
9
10
11
12
interface nameProps {
name: string
}

interface Props extends nameProps{
age: number
}

const res: Props = {
name: '小杜杜',
age: 7
}

函数类型接口

同时,可以定义函数和类,加new修饰的事,不加new的事函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Props {
(data: number): number
}

const info: Props = (number:number) => number //可定义函数

// 定义函数
class A {
name:string
constructor(name: string){
this.name = name
}
}

interface PropsClass{
new (name: string): A
}

const info1 = (fun: PropsClass, name: string) => new fun(name)

const res = info1(A, "小杜杜")
console.log(res.name) // "小杜杜"

type 和 interface 的区别

通过上面的学习,我们发现类型别名接口非常相似,可以说在大多数情况下,typeinterface是等价的

但在一些特定的场景差距还是比较大的,接下来逐个来看看

基础数据类型

  • typeinterface都可以定义 对象函数
  • type可以定义其他数据类型,如字符串、数字、元祖、联合类型等,而interface不行
1
2
3
4
5
6
7
8
type A = string // 基本类型

type B = string | number // 联合类型

type C = [number, string] // 元祖

const dom = document.createElement("div"); // dom元素
type D = typeof dom

扩展

interface 可以扩展 typetype 也可以扩展为 interface,但两者实现扩展的方式不同。

  • interface 是通过 extends 来实现
  • type 是通过 & 来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// interface 扩展 interface
interface A {
a: string
}
interface B extends A {
b: number
}
const obj:B = { a: `小杜杜`, b: 7 }

// type 扩展 type
type C = { a: string }
type D = C & { b: number }
const obj1:D = { a: `小杜杜`, b: 7 }

// interface 扩展 Type
type E = { a: string }
interface F extends E { b: number }
const obj2:F = { a: `小杜杜`, b: 7 }

// type 扩展 interface
interface G { a: string }
type H = G & {b: number}
const obj3:H = { a: `小杜杜`, b: 7 }

重复定义

interface 可以多次被定义,并且会进行合并,但type不行

1
2
3
4
5
6
7
8
9
10
interface A {
a: string
}
interface A {
b: number
}
const obj:A = { a: `小杜杜`, b: 7 }

type B = { a: string }
type B = { b: number } // error

联合类型(Union Types)

联合类型(Union Types): 表示取值可以为多种类型中的一种,未赋值时联合类型上只能访问两个类型共有的属性和方法,如:

1
2
3
4
const setInfo = (name: string | number) => {}

setInfo('小杜杜')
setInfo(7)

从上面看 setInfo接收一个name,而 name 可以接收 stringnumber类型,那么这个参数便是联合类型

可辨识联合

可辨识联合:包含三个特点,分别是可辨识联合类型类型守卫,

这种类型的本质是:结合联合类型字面量类型的一种类型保护方法。

如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。

也就是上面一起结合使用,这里写个小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
interface A {
type: 1,
name: string
}

interface B {
type: 2
age: number
}

interface C {
type: 3,
sex: boolean
}

// const setInfo = (data: A | B | C) => {
// return data.type // ok 原因是 A 、B、C 都有 type属性
// return data.age // error, 原因是没有判断具体是哪个类型,不能确定是A,还是B,或者是C
// }

const setInfo1 = (data: A | B | C) => {
if (data.type === 1 ) {
console.log(`我的名字是${data.name}`);
} else if (data.type === 2 ){
console.log(`我的年龄是${data.age}`);
} else if (data.type === 3 ){
console.log(`我的性别是${data.sex}`);
}
}

setInfo1({type: 1, name: '小杜杜'}) // "我的名字是小杜杜"
setInfo1({type: 2, age: 7}) // "我的年龄是7"
setInfo1({type: 3, sex: true}) // "我的性别是true"

定义了 ABC 三次接口,但这三个接口都包含type属性,那么type就是可辨识的属性,而其他属性只跟特性的接口相关。

然后通过可辨识属性type,才能使用其相关的属性

泛型

泛型:Generics,是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

也就是说,泛型是允许同一个函数接受不同类型参数的一种模版,与any相比,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型(PS:泛型是整个TS的重点,也是难点,请多多注意~)

为什么需要泛型

我们先看看一个例子:

1
2
3
4
5
6
7
8
9
const calcArray = (data:any):any[] => {
let list = []
for(let i = 0; i < 3; i++){
list.push(data)
}
return list
}

console.log(calcArray('d')) // ["d", "d", "d"]

上述的例子我们发现,在calcArray中传任何类型的参数,返回的数组都是any类型

由于我们不知道传入的数据是什么,所以返回的数据也为any的数组

但我们现在想要的效果是:无论我们传什么类型,都能返回对应的类型,针对这种情况怎么办?所以此时泛型就登场了

泛型语法

我们先用泛型对上面的例子进行改造下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const calcArray = <T>(data:T):T[] => {
let list:T[] = []
for(let i = 0; i < 3; i++){
list.push(data)
}
return list
}

const res:string[] = calcArray<string>('d') // ok
const res1:number[] = calcArray<number>(7) // ok

type Props = {
name: string,
age: number
}
const res3: Props[] = calcArray<Props>({name: '小杜杜', age: 7}) //ok

经过上面的案例,我们发现传入的字符串数字对象,都能返回对应的类型,从而达到我们的目的,接下来我们再看看泛型语法

1
2
3
function identity <T>(value:T) : T {
return value
}

第一次看到这个<T>我们是不是很懵,实际上这个T就是传递的类型,从上述的例子来看,这个<T>就是<string>,要注意一点,这个<string>实际上是可以省略的,因为 TS 具有类型推论,可以自己推断类型

多类型传参

我们有多个未知的类型占位,我们可以定义任何的字母来表示不同的参数类型

1
2
3
4
5
6
7
const calcArray = <T,U>(name:T, age:U): {name:T, age:U} => {
const res: {name:T, age:U} = {name, age}
return res
}

const res = calcArray<string, number>('小杜杜', 7)
console.log(res) // {"name": "小杜杜", "age": 7}

泛型接口

定义接口的时候,我们也可以使用泛型

1
2
3
4
5
6
interface A<T> {
data: T
}

const Info: A<string> = {data: '1'}
console.log(Info.data) // "1"

泛型类

同样泛型也可以定义类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class clacArray<T>{
private arr: T[] = [];

add(value: T) {
this.arr.push(value)
}
getValue(): T {
let res = this.arr[0];
console.log(this.arr)
return res;
}
}

const res = new clacArray()

res.add(1)
res.add(2)
res.add(3)

res.getValue() //[1, 2, 3]
console.log(res.getValue) // 1

泛型类型别名

1
2
3
4
5
6
7
type Info<T> = {
name?: T
age?: T
}

const res:Info<string> = { name: '小杜杜'}
const res1:Info<number> = { age: 7}

泛型默认参数

所谓默认参数,是指定类型,如默认值一样,从实际值参数中也无法推断出类型时,这个默认类型就会起作用。

1
2
3
4
5
6
7
const calcArray = <T = string,>(data:T):T[] => {
let list:T[] = []
for(let i = 0; i < 3; i++){
list.push(data)
}
return list
}

泛型常用字母

用常用的字母来表示一些变量的代表:

  • T:代表Type,定义泛型时通常用作第一个类型变量名称
  • K:代表Key,表示对象中的键类型
  • V:代表Value,表示对象中的值类型
  • E:代表Element,表示的元素类型

常用技巧

在 TS 中有许多关键字和工具类型,在使用上,需要注意泛型上的应用,有的时候结合起来可能就有一定的问题

在此特别需要注意 extendstypeofPartialRecordExcludeOmit这几个工具类型

extends

extends:检验是否拥有其属性 在这里,举个例子,我们知道字符串数组拥有length属性,但number没有这个属性。

1
2
3
const calcArray = <T,>(data:T): number => {
return data.length // error
}

上述的 calcArray的作用只是获取data的数量,但此时在TS中会报错,这是因为TS不确定传来的属性是否具备length这个属性,毕竟每个属性都不可能完全相同

那么这时该怎么解决呢?

我们已经确定,要拿到传过来数据的 length,也就是说传过来的属性必须具备length这个属性,如果没有,则不让他调用这个方法。

换句话说,calcArray需要具备检验属性的功能,对于上述例子就是检验是否有length的功能,这是我们就需要extends这个属性帮我们去鉴定:

1
2
3
4
5
6
7
8
9
10
11
interface Props {
length: number
}

const calcArray = <T extends Props,>(data:T): number => {
return data.length // error
}

calcArray('12') // ok
calcArray([1,3]) //ok
calcArray(2) //error

可以看出calcArray(2)会报错,这是因为number类型并不具备length这个属性

typeof

typeof关键字:我们在类型保护的时候讲解了typeof的作用,除此之外,这个关键字还可以实现推出类型

1
2
3
4
5
6
7
8
9
10
11
let info ={
name:"123",
age: 1,
sex:true,
}
type Props = typeof info
type Props ={
name:string;
age: number;
sex:boolean;
}

keyof

keyof关键字: 可以获取一个对象接口的所有key值,可以检查对象上的键是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Props {
name: string;
age: number;
sex: boolean
}

type PropsKey = keyof Props; //包含 name, age, sex

const res:PropsKey = 'name' // ok
const res1:PropsKey = 'tel' // error

// 泛型中的应用
const getInfo = <T, K extends keyof T>(data: T, key: K): T[K] => {
return data[key]
}

const info = {
name: '小杜杜',
age: 7,
sex: true
}

getInfo(info, 'name'); //ok
getInfo(info, 'tel'); //error

索引访问操作符

索引访问操作符:通过 [] 操作符可进行索引访问,可以访问其中一个属性

1
2
3
4
5
6
interface Props {
name: string;
age: number;
sex: boolean
}
type age = Props['age']//type age = number

in

in:映射类型, 用来映射遍历枚举类型

1
2
3
4
5
6
7
8
9
type namme ='name' | 'age' | 'sex'
type Props = {
[p in name]:any
}
type Props = {
name:any;
age:any;
sex:any;
}

infer

infer:可以是使用为条件语句,可以用 infer 声明一个类型变量并且对它进行使用。如

1
2
3
4
5
type Info<T> = T extends { a: infer U; b: infer U } ? U : never;

type Props = Info<{ a: string; b: number }>; // Props类: string | number

type Props1 = Info<number> // Props类型: never

Partial

Partial语法Partial<T> 作用:将所有属性变为可选的 ?

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Props {
name: string,
age: number
}

const info: Props = {
name: '小杜杜',
age: 7
}

const info1: Partial<Props> = {
name: '小杜杜'
}

从上述代码上来看,name 和 age 属于必填,对于 info 来说必须要设置 name 和 age 属性才行,但对于 info1来说,只要是个对象就可以,至于是否有name、 age属性并不重要

Required

Required语法Required<T> 作用:将所有属性变为必选的,与 Partial相反

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Props {
name: string,
age: number,
sex?: boolean
}

const info: Props = {
name: '小杜杜',
age: 7
}

const info1: Required<Props> = {
name: '小杜杜',
age: 7,
sex: true
}

Readonly

Readonly语法Readonly<T> 作用:将所有属性都加上 readonly 修饰符来实现。也就是说无法修改

1
2
3
4
5
6
7
8
9
10
11
interface Props {
name: string
age: number
}

let info: Readonly<Props> = {
name: '小杜杜',
age: 7
}

info.age = 1 //error read-only 只读属性

从上述代码上来看, Readonly修饰后,属性无法再次更改,智能使用

Record

Record语法Record<K extends keyof any, T>

作用:将 K 中所有的属性的值转化为 T 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Props {
name: string,
age: number
}

type InfoProps = 'JS' | 'TS'

const Info: Record<InfoProps, Props> = {
JS: {
name: '小杜杜',
age: 7
},
TS: {
name: 'TypeScript',
age: 11
}
}

从上述代码上来看, InfoProps的属性分别包含Props的属性

需要注意的一点是:K extends keyof any其类型可以是:stringnumbersymbol

Pick

Pick语法:Pick<T, K extends keyof T>

作用:将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。

1
2
3
4
5
6
7
8
9
10
11
12
interface Props {
name: string,
age: number,
sex: boolean
}

type nameProps = Pick<Props, 'name' | 'age'>

const info: nameProps = {
name: '小杜杜',
age: 7
}

从上述代码上来看, Props原本属性包括nameagesex三个属性,通过 Pick我们吧nameage挑了出来,所以不需要sex属性

Exclude

Exclude语法Exclude<T, U>

作用:将T类型中的U类型剔除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 数字类型
type numProps = Exclude<1 | 2 | 3, 1 | 2> // 3
type numProps1 = Exclude<1, 1 | 2> // nerver
type numProps2 = Exclude<1, 1> // nerver
type numProps3 = Exclude<1 | 2, 7> // 1 2

// 字符串类型
type info = "name" | "age" | "sex"
type info1 = "name" | "age"
type infoProps = Exclude<info, info1> // "sex"

// 类型
type typeProps = Exclude<string | number | (() => void), Function> // string | number

// 对象
type obj = { name: 1, sex: true }
type obj1 = { name: 1 }
type objProps = Exclude<obj, obj1> // nerver

从上述代码上来看,我们比较了下类型上的,当 T 中有 U 就会剔除对应的属性,如果 U 中又的属性 T 中没有,或 T 和 U 刚好一样的情况都会返回 nerver,且对象永远返回nerver

Extra

Extra语法Extra<T, U>

作用:将T 可分配给的类型中提取 U。与 Exclude相反

1
type numProps = Extract<1 | 2 | 3, 1 | 2> // 1 | 2

Omit

Omit语法Omit<T, U>

作用:将已经声明的类型进行属性剔除获得新类型

Exclude的区别:Omit 返回的是新的类型,原理上是在 Exclude之上进行的,Exclude是根据自类型返回的

NonNullable

NonNullable语法NonNullable<T> 作用:从 T 中排除 nullundefined

1
type Props = NonNullable<string | undefined |null>

ReturnType

ReturnType语法ReturnType<T>

作用:用于获取 函数T的返回类型。

1
2
3
4
type Props = ReturnType<() => string> // string
type Props1 = ReturnType<<T extends U, U extends number>() => T>; // number
type Props2 = ReturnType<any>; // any
type Props3 = ReturnType<never>; // any

从上述代码上来看, ReturnType可以接受 any 和 never 类型,原因是这两个类型属于顶级类型,包含函数

Parameters

ParametersParameters<T> 作用:用于获取 获取函数类型的参数类型

1
2
3
4
type Props = Parameters<() => string> // []
type Props1 = Parameters<(data: string) => void> // [string]
type Props2 = Parameters<any>; // unknown[]
type Props3 = Parameters<never>; // never