TS学习笔记

174 阅读10分钟

第一章 TS入门

1.1 TS简介

TS是JS的超集,由微软设计。JS是动态类型,不易于维护,而TS是静态类型语言。

image.png

TS新增:更多的类型(云组、抽象类等),具有ES不具有的特性(装饰器、接口等),可以编译成不同版本的JS,大大增强了兼容性。

image.png

1.2 第一个程序Hello,TS!

由于TS是JS的超集,因此所有JS能用的写法TS都能使用。

在HelloTS.ts文件中想打印依然使用的是console.log方法。

console.log('Hello,TS')

利用ts的编译器(使用前需要先利用命令npm i typescript -g全局下载),就可以将ts代码编译为ts代码,仅需在终端中输入命令tsc HelloTS.ts即可,最终会生成一个HelloTS.js文件,默认情况下,即使在ts中有错误(例如将字符串赋值给了一个number类型的变量),但依旧能编译成功生成相应的js文件,并且默认为es3的js(例如,let的声明变成var声明),想要更改js版本与编译的方式,需要对ts的编译器进行配置。

1.3 TS类型声明

1.3.1 基本类型

image.png

let n: number = 1;
let s: string ='s';
let boolen: boolean = true;

// 对一个变量设置成any类型后,相当于对这个变量关闭了ts检测
let ay: any; // 相当于 let ay(隐式声明any类型)

// unknown表示未知变量 
let uk: unknown; uk = 1; uk = 's'; // s = uk 会报错 unknown类型不能给别的类型赋值
if(typeof uk === 'string') {
  s = uk; // 因此unknown可以视作安全类型的any
}
s = uk as string; // 或者使用断言
s = <string> uk; // 断言的第二种写法

// 字面量声明 |表示‘或’(联合类型)
let a: 'male' | 'female' = 'male'; // a = 'man' 报错

// ts会自动判断类型,当声明时没声明类型时会自动判断其类型值
let bl = true;
// bl = 123 // 报错

// 函数的类型声明
function sum(a: number, b:number): number{
  return a + b;
}
let res = sum(123, 456);

// void表示无返回值
function fn1(): void{
  // return null 不表示无返回值哦 表示返回一个空对象
  return undefined //这个可以 可以理解void就是返回undefined  
}

// never表示永远不会返回结果
function fn2(): never{
  throw new Error('报错了'); // never的一般用法,扔出错误,程序停止不返回任何结果
  // return undefined 会报错
}

1.3.2 对象类型

let obj: object = {} // 可以是任意对象(不常用)因为js万物皆对象
obj = function() {} // 不会报错 因为函数也是对象(逆天)

// 定义对象,其中必须只有一个string类型的name属性
// age属性是可选的
let b: {name: string, age?: number} 
b = {name: 'b', age: 18}

// [propName: string]表示还有任意字符串的属性名,而:any指的是该属性可以是任意类型
let c: {name: string, [propName: string]: any};
c = {name: 'c', age: 18, sex: '男'};

// 定义函数类型 (形参1:类型,形参2:类型)=>类型;
let d: (a: number, b: number)=>number;
d = function(n1, n2): number{
  return n1 + n2;
}

// 声明相同类型的数组 两种表达方式
let e: string[];
let g: Array<string>;

// tuple 元组:长度固定的数组(效率高)
let h: [string, string];

// enum 枚举(效率高)
enum Gender {
  Male = 0,
  Female = 1
}

let i: {name: string, gender: Gender} = {
  name: 'zzl',
  gender: Gender.Male 
}
 
// & 表示与 一般用于两个对象的合并
let j: {name: string} & {age: number};
j = {name: 'zzl', age: 21}

// type 关键字取别名
type myType = 1 | 2 | 3 | 4 |5;
let k: myType;

image.png

1.4 TS编译设置

tsc xxx.ts -w开启监视模式,当在TS文件中更改时,JS文件会自动更改(会有一定的延迟)。 直接使用tsc命令可以编译文件夹下所有的ts文件,但必须先配置tsconfig.json文件才行(直接运行指令tsc --init也能初始化tsconfig.json文件)。 使用指令tsc -w可以开启文件夹下的全局监听模式。

tsconfig.json文件配置具体信息

  • include:数组,用来指定哪些ts文件需要被编译
  • exclude:数组,用来指定哪些ts文件被排除在外不进行编译。
  • compileOptions:用来配置编译器如何进行编译,其中包含众多子选项(若不知道要写什么,可以故意写错看终端报错)
    • target:指定js编译的默认版本,例如es3,es2016,esnext(最新选项)等
    • module:指定js模块化的规范,例如commonjs,es2016,esnext等
    • lib:指定项目要使用的库(一般不会更改)
    • outDir:指定文件编译后的目录
    • sourceMap:用于指定是否为编译后的JavaScript代码生成源映射(SourceMap)文件
    • outFile:将全局作用域的代码合并为一个文件(将多个ts文件合并为一个js文件)
    • allowJs:将js文件一同编译,默认为false
    • checkJs:检查js代码是否符合ts规范,默认为false
    • removeComments:是否移除注释,默认为false
    • noEmit:不生成编译后的文件,默认为false(用来检查语法对不对)
    • noEmitOnError:当有错误时不生成编译后文件,默认为false
    • esModuleInterop:使在ES模块系统中导入CommonJs代码不出现类型不兼容的问题
    • forceConsistentCasingInFileNames:检查文件编译时文件名的大小写是否一致
    • strict:所有严格检测的总开关(设置为true,下方的严格检查的都会打开)
    • alwaysStrict:编译后的js设置为严格模式,默认为false
    • noImplicitAny:不允许隐式的any类型,默认为false
    • noImplicitThis:不允许不明确类型的this(this必须有所指)
    • strictNullChecks:严格检查空值(检查是否会出现空指针)

示例代码:

{
  // "include”用来指定哪些ts文件需要被编译,**表示任意目录,*表示任意文件
  "include": [
    "./src/**/*"
  ],
  "exclude": [
    "./src/hello/**/*"
  ],
  // 编译器选项
  "compilerOptions": {
    // 指定js编译的版本
    "target": "es2016",                                
    // 指定js的模块化规范
    "module": "commonjs",                                
    // 指定项目中用倒的库,当设置后有快捷键显示
    "lib": [],
    // outDir指定文件编译后的目录
    "outDir": "./dist",
    // 将全局作用域的代码合并为一个文件(将多个ts文件合并为一个js文件)
    "outFile": "./dist/app.js",
    // 将js文件一同编译,默认为false
    "allowJs": false,
    // 用于指定是否为编译后的JavaScript代码生成源映射(SourceMap)文件
    "sourceMap": false,
    // 检查js代码是否符合ts规范,默认为false
    "checkJs": false,
    // 是否移除注释,默认为false
    "removeComments": false,
    // 不生成编译后的文件,默认为false(用来检查语法对不对)
    "noEmit": false,
    // 当有错误时不生成编译后文件,默认为false
    "noEmitOnError": false,
    // 所有严格检测的总开关(设置为true,下方的严格检查的都会打开)
    "strict": true, 
    // 编译后的js设置为严格模式,默认为false
    "alwaysStrict": false,
    // 不允许隐式的any类型,默认为false
    "noImplicitAny": false,
    // 不允许不明确类型的this
    "noImplicitThis": false,
    // 严格检查空值(检查是否会出现空指针)
    "strictNullChecks": false,
    // 使在ES模块系统中导入CommonJs代码不出现类型不兼容的问题
    "esModuleInterop": true,     
    // 检查文件编译时文件名的大小写是否一致                        
    "forceConsistentCasingInFileNames": true,
  }
}

1.5 使用webpack打包ts打代码

安装依赖指令:npm i -D webpack webpack-cli typescript ts-loader

在项目中新建一个webpack.config.js作为webpack的配置文件,用来指定需要打包的文件

// 引入内置的path模块
const path = require('path');

// webpack的配置信息都写在module.exports之中
module.exports = {
  // 指定入口文件
  entry: "./src/index.ts",
  // 指定打包完文件所在的目录
  output: {
    // 指定打包文件的目录
    path: path.resolve(__dirname, 'dist'),
    // 打包后文件的文件
    filename: 'bundle.js',
    // 配置打包环境
    environment: {
      arrowFunction: false // 设置打包生成的js文件不使用箭头函数
    }
  },
  // 指定webpack打包时要使用的模块
  module: {
    // 指定加载时的规则
    rules: [
      {
        // 指定规则生效的文件(正则表达式)
        test: /\.ts$/,
        // 对文件如何处理
        use: "ts-loader",
        // 要排除的文件
        exclude: /node_modules/ 
      }
    ]
  }
}

之后在src中创建一个tsconfig文件,配置好所有东西后再在package.json文件中的script中的命令中加入"build":"webpack",就可以输入命令npm run bulid进行打包啦。

由webpack创建index.html文件:

首先使用命令npm i -D html-webpack-plugin,下载相关包,之后在webpack配置文件中引入包并配置插件

  const HTMLWEBPACKPLUGIN = require('html-webpack-plugin')
  ...
  // 配置插件
  plugins: [
    new HTMLWEBPACKPLUGIN({
      //title: "这是一个自定义的标签"
      template: "./src/index.html" // 模版文件,生成时以此为模板
    }),
  ]

由webpack配置开发服务器:

首先使用命令npm i -D webpack-dev-server,下载相关包,之后在package.json中的script中添加 "start": "webpack server --open"之后使用命令npm start即可打开服务器(运行前需要在webpack的配置文件中配置mode选项才行),并实现热更新。 由webpack编译时自动清空原来dist目录中的文件:

首先使用命令npm i -D html-webpack-plugin,下载相关包,之后在webpack配置文件中引入包并配置插件

  const { CleanWebpackPlugin } = require('clean-webpack-plugin')
  ...
  // 配置插件
  plugins: [
      new CleanWebpackPlugin()
  ]

配置ts的模块化:

默认情况下,ts文件是不能作为引入模块的,因此需要在webpack之中进行配置:

  // 用来配置引用模块
  resolve: {
    // 以这两者结尾都可以作为模块使用
    extensions: ['.ts', '.js']
  }

引入babel:

ts可以替换简单的语法,但不能转换Promise等ES6新出的东西。因此需要引入babel,使用命令npm i -D @babel/core @babel/preset-env babel-loader core-js来下载babel,之后在webpack.config.json的module之中进行修改,代码如下所示:

// 对文件如何处理,加载器由下往上进行执行
        use: [
          // 配置babel
          {
            // 指定加载器
            loader: 'babel-loader',
            // 设置babel
            options: {
              // 配置babel的环境
              presets: [
                [
                  // 指定环境的插件
                  '@babel/preset-env',
                  // 配置信息
                  {
                    targets: {
                      // 指定兼容的目标浏览器
                      "chrome": "87",
                      "ie": "11"
                    },
                    // 指定core.js的版本
                    "corejs": "3",
                    // 使用core.js的模式,“usage”表示按需加载
                    "useBuiltIns": "usage"
                  }
                ]
              ]
            }
          },
          "ts-loader"
        ],

当打包时,ts文件先去找ts-loader编译为js文件,再去找babel转换为指定版本的js文件。

引入less的依赖:

安装各个包npm i -D less less-loader css-loader style-loader,之后在webpack.config.json中进行设置(在module中新增一个规则),实际上在webpack中,样式是通过js生效的。

// 指定less文件的处理
{
    test: /\.less$/,
    use: [
      "style-loader",
      "css-loader",
      "less-loader"
    ]
}

成功配置好less之后,还需要引入postcss来实现对css文件的转义来适应不同的浏览器(类似于babel对js文件一样)。命令npm i -D postcss postcss-loader postcss-preset-env下载完成后,在webpack的配置文件中继续修改module,代码如下:

use: [
  "style-loader",
  "css-loader",
  // 引入postcss
  {
    loader: "postcss-loader",
    options: {
      postcssOptions: {
        plugins: [
             [
                "postcss-preset-env",
                {
                  browsers: 'last 2 versions'
                }
             ]
        ]
      }
    }
  },
  "less-loader"
]

第二章 面向对象

2.1 在ts中定义类

示例代码:

class Person {
  // 定义实例属性
  name: string = '孙悟空';
  age: number = 18;
  // 定义静态属性
  static DNA: string;
  // 只读属性
  readonly ID: string = '123456';
  // 定义实例方法
  sayHello() {
    console.log('Hello,大家好!');
  }
}

2.2 构造函数与继承与抽象类

示例代码:

abstract class Animal {
  name: string;

  constructor(name: string) {
    // 在示例方法中,this指向当前的实例
    this.name = name;
  };

  // 抽象方法只能定义在抽象类中,没有方法体子类必须重写
  abstract sayHello(): void;
}

class Dog extends Animal {
  age: number;

  constructor(name: string, age: number) {
    // 子类使用构造函数时必须写
    super(name);
    this.age = age;
  };

  // 添加方法
  run(): void {
    console.log('跑');
  };

  sayHello(): void {  
    // 在类中,super表示当前类的父类
    // super.sayHello(); 会报错,不能使用抽象方法
    // 方法重写
    console.log('汪汪汪');
  };
}

class Cat extends Animal {
  sayHello(): void {  
    // 方法重写
    console.log('喵喵喵');
  };
}

2.3 接口

接口中是ts新增的,注意:接口还可以给函数使用,示例代码:

type myType = {
  name: string,
  age: number
}

const obj1: myType = {
  name: '111',
  age: 11
}

// 接口类似于type关键字,用来限制一个类的结构(类似抽象类)
interface myInterface {
  name: string,
  age: number
}
// 接口可以重复定义,最终实现便是两者之并集
interface myInterface {
  gender: '男' | '女'
}

const obj2: myInterface = {
  name: '111',
  age: 11,
  gender: '男'
}

// 接口只定义的只能是抽象的,不能有实际值(不同于抽象类)
interface myInter {
  name: string,
  sayHello(): void
}
// 定义类来实现一个接口
class MyClass implements myInter {
  name: string; 
  constructor(name: string) {
    this.name = name;
  }

  sayHello(): void {
    console.log('Hello');
  }
}
// 利用接口方便函数的定义
interface IMult {
  (x: number, y: number): number;
}
const mult1:IMult = (x, y) => x*y;
const mult2: (x: number, y: number) => number = (x, y) => x*y;

2.4 属性的封装

ts中新增了public,protected,private属性用来修饰成员变量,从未实现对属性的封装,示例代码:

class Person {
  public name: string; // 公开的
  protected age: number; // 只能在该类中与继承的子类中访问
  private _ID: string; // 只能在该类中访问

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

  // 通过定义方法来使外部访问私有成员变量
  // getID(): string {
  //   return this._ID
  // }
  // setID(ID: string): void {
  //   this._ID = ID;
  // }

  // TS中设置相应的getter与setter
  get ID() {
    console.log('get ID执行了!');
    return this._ID;
  }
  set ID(ID: string) {
    this._ID = ID;
  }
}

// 简写方式(语法糖)
class People {
  constructor(private ID: string, protected age: number, public name: string) {}
}

const per = new Person('孙悟空', 18, '123456');
const peo = new People('孙悟空', 18, '123456')
console.log(per.ID);

2.5 泛型

当传入的值不晓得时(函数或者类),可以使用泛型(即不确定的类型),必须先定义泛型,之后才能使用泛型,示例代码如下:

// 简单的函数(返回传入的值),但看不出来返回的是a(可读性不强)
// function fn(a: number): number {
//   return a;
// }
// 当传入的值不晓得时(函数或者类),可以使用泛型(即不确定的类型)
// 必须先定义泛型,之后才能使用泛型
function fn1<T>(a: T): T {
  return a;
}
fn1(10); // 不指定泛型,js自动推断(当数据结构复杂时可能报错)
fn1<String>('Hello') // 指定泛型

// 泛型可以指定多个
function fn2<T, K>(a: T, b: K): T {
  console.log(b);
  return a;
}
fn2<Number, String>(11, 'hello');

interface Inter {
  length: number;
}
// 指定泛型必须是Inter的子类
function fn3<T extends Inter>(a: T): number {
  return a.length;
}
fn3({length: 12})
// fn3({name: '111'}) // 会报错

// 在类中使用泛型
class Test<T> {
  name: T;
  constructor(name: T) {
    this.name = name;
  }
}
const test = new Test('孙悟空');