【TypeScript】学习笔记

86 阅读10分钟

一、简介

  • TypeScript是JavaScript的超集
  • TS代码需要通过编译器编译成JS,然后再交由JS解析器执行
  • TS完全兼容JS
  • 优点
    • 相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;
    • TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;
    • TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;
    • 同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS

二、基本类型

1、类型声明

  • 通过类型声明可以指定TS中变量(参数、形参)的类型
  • 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
  • 语法
let 变量: 类型;

let 变量: 类型 = 值;

// 类型3是指定返回值类型
function fn(参数: 类型, 参数: 类型): 类型3{
    ...
}

2、类型

类型例子描述
number1, -33, 2.5任意数字
string'hi', "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,string,boolean

let decimal: number = 6;

let isDone: boolean = false;

let color: string = "blue";
color = 'red';

字面量

let color: 'red' | 'blue' | 'black';

any

any表示任何类型,相当于关闭了TS的类型检测(会祸害别人)

let d: any = 4;
d = 'hello';
d = true;

unknown

表示未知类型的值,实际上就是一个类型安全的any

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

不知道类型的值,最好用unknown

let e:unknown;
e='sss';
let f:string
f=e; // 会报错

void

let unusable: void = undefined;

never

# 表示没有返回值
function error(message: string): never {
  throw new Error(message);
}

object

let l: object;
l = {};

# 在属性后面加上?,表示属性是可选的
// age属性可有可无
let aa: { name: string, age?: number };

# [propName:string]:unknow 表示任意类型的属性
let bb: { name: string, [propName: string]: unknown }
bb = { name: 'lyw', age: 12, sex: '男' }

array

let list: 类型[]
let list: Array<类型> 

tuple

let x: [string, number];
x = ["hello", 10]; 

enum

enum Gender {
  Male = 0,
  Female = 1
}
let i: { name: string, gender: Gender };
i = {
  name: 'lyw',
  gender: Gender.Male
}

3、类型断言

  • 类型断言,可以用来告诉解析器变量的实际类
# 语法
 1.变量 as 类型
 2.<类型>变量
 
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;

4、类型检测

  • typeof:可以用来获取一个变量或对象的类型
interface Hero {
  name: string;
  skill: string;
}

const zed: Hero = { name: "yyqx", skill: "sing" };
type LOL = typeof zed; // 等于 type LOL = Hero
  • instanceof
class NumberObj {
  count: number;
}

function addObj(first: object | NumberObj, second: object | NumberObj) {
  if (first instanceof NumberObj && second instanceof NumberObj) {
    return first.count + second.count;
  }
  return 0;
}
  • keyof:与Object.keys略有相似,只不过keyof取interface的键
interface Point {
    x: number;
    y: number;
}

// type keys = "x" | "y"
type keys = keyof Point;

三、编译选项

1、自动编译文件

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

2、自动编译整个项目

  1. 需要先在项目根目录下创建一个ts的配置文件tsconfig.json
  1. 添加配置文件后,只需tsc命令即可完成对整个项目的编译
tsconfig.json:是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译
  1. 配置选项
  • include:指定需要被编译的ts文件所在目录
# 表示src目录下的所有ts文件都会被编译
#  **表示任意目录,*表示任意文件
"include":["./src/**/*"]
  • exclude:指定不需要被编译的ts文件所在目录
# 默认值:["node_modules", "bower_components", "jspm_packages"]
# 表示scr/hello目录下的所有ts文件不会被编译

"exclude":["./src/hello/**/*"]
  • extends:被继承的配置文件
# 表示:当前配置文件中会自动包含configs目录下base.json中的所有配置信息

"extends":"./configs/base"
  • files:指定被编译文件的列表(需要被编译文件少时才会用到)
# 以下文件都会被TS编译器所编译

"files": [
    "core.ts",
    "sys.ts",
    "types.ts",
    "scanner.ts"
  ]

compilerOptions

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

  • 常规选项

    • target:用来指定ts被编译为的ES的版本
    # 可以为es3、es6、es2015、es2016、es2017
    // es6版本
    "compilerOptions": {
       "target": "ES6"
    } 
    
    • module:指定要使用的模块化的规范
    # 常用:'none', 'commonjs',es6','system'
    
    "module":"es2015"  // es6模块化
    
    • lib:指定项目中要使用的库
    # 一般不会修改,可以选择的很多
    # 可选值:ES5ES6/ES2015ES7/ES2016DOMESNext
    
    "lib":["dom"]
    
    • outDir:指定编译后的js文件所在的目录
    "outDir":"./dist"
    
    • outFile:将所有文件编译为一个js文件
    # 默认会将所有的编写在全局作用域中的代码合并为一个js文件,如果module制定了NoneSystemAMD则会将模块一起合并到文件之中
    
    "outFile":"./dist/app.js"
    
    • rootDir:指定代码的根目录
    # 指定编译后的文件所放目录
    
    "rootDir": "./src"
    
    • allowJs:是否对js文件进行编译,默认是false
    "allowJs":false
    
    • checkJs:是否检查js代码是否符合语法规范,默认是false
    "checkJs":false
    
    • removeComments:是否移除注释
    "removeComments":true
    
    • noEmit:不生成编译后的文件
      "noEmit":false
    
    • noEmitOnError:当存在错误时,不生成编译后的文件
    "noEmitOnError":true
    
  • 检查类属性

    • strict:所有严格检查的总开关,默认值是true
    • alwaysStrict:是否总是以严格模式对代码进行编译
    • noImplicitAny:不允许隐式的any类型
    • noImplicitThis:不允许类型不明确的this
    • strictNullChecks:严格的检查空值
    • noImplicitReturns:检查函数没有隐式的返回值
    • strictFunctionTypes:严格检查函数的类型
    • strictPropertyInitialization:严格检查属性是否初始化
    • noUnusedLocals:检查未使用的局部变量
    • noUnusedParameters:检查未使用的参数
    • strictBindCallApply:严格检查bind、call和apply的参数列表
    • noFallthroughCasesInSwitch:检查switch语句包含正确的break

四、使用webpack打包ts代码

  • 通常情况下,实际开发中我们都需要使用构建工具对代码进行打包,TS同样也可以结合构建工具一起使用,下边以webpack为例介绍一下如何结合构建工具使用TS。 步骤
  1. 初始化:通过cnpm init -y:生成package.json文件
  2. 安装:
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin
 
// webpack:构建工具
// webpack-cli:wenpack的命令行工具
// webpack-dev-server:webpack的开发服务器
// typescript:ts编译器
// ts-loader:ts加载器,用于在webpack中编译ts文件
// html-webpack-plugin:自动创建html文件
// clean-webpack-plugin:清除插件,每次构建都会先清除目录
  1. 配置webpack.config.js文件
// 引入包
const path = require('path
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

// webpack中所有的配置信息都应该卸载module.exports中
module.exports = {
  optimization:{
     minimize: false // 关闭代码压缩,可选
  },
    
  devtool: "inline-source-map",
  
  devServer: { 
      contentBase: './dist'
  },
    
  // 指定入口文件
  entry: './src/index.ts',

  // 指定打包文件所在目录
  output: {
    path: path.resolve(__dirname, 'dist'),   // 指定打包文件的目录
    filename: "bundle.js",
    environment:{
        arrowFunction:false   // 告诉webpack不使用箭头函数
    }
  },

  // 指定webpack打包时要使用的模块
  module: {  
    rules: [
      {
        test: /\.ts$/,   // test指定:规则生效的文件
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
    
  // 设置外来插件
  // 打包html文件的插件:html-webpack-plugin
  plugins: [
     new CleanWebpackPlugin(),
     new HTMLWebpackPlugin({
 	   title:'这是一个标签',      // 设置打包文件的标题
  	   template:'./src/index.html' // 设置打包模板
  }), 
 ]
    
 // 用来设置引用模块
  resolve: {
    extensions: ['.ts', '.js']
  },

  mode: 'development'
}
 
  1. 配置ts编译文件—— tsconfig.js
{
  "compilerOptions": {
    "module": "es2015",
    "target": "es2015",
    "strict": true
  }
}
  1. 修改package.json中的build
{
  ...略...
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    // 用webpack打包
    "build": "webpack",
    # 自动更新操作的结果,打开谷歌浏览器
    "start": "webpack serve --open chrome.exe"
  },
  ...略...
}
  
# 执行
npm run build:对代码进行编译
npm start:启动开发服务器

五、Babel

  • 开发中还经常需要结合babel来对代码进行转换以使其可以兼容到更多的浏览器
  • 在上述步骤的基础上,通过以下步骤再将babel引入到项目中 步骤
  1. 安装依赖包
npm i -D @babel/core @babel/preset-env babel-loader core-js

// @babel/core:babel的核心工具
// @babel/preset-env:babel的预定义环境
// @babel-loader:babel在webpack中的加载器
// core-js:core-js用来使老版本的浏览器支持新版ES语法
  1. 修改webpack.config.js配置文件
...略...
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: [
          // 配置babel
          {
            // 指定加载器
            loader: "babel-loader",
            // 设置babel
            options: {
              // 设置预定义环境
              presets: [[
                // 指定环境的插件
                "@babel/preset-env",
                // 配置信息
                {
                  // 要兼容的目标
                  targets: {
                    "chrome": "67",
                    "ie":"11"
                  },
                  // 指定corejs的版本
                  "corejs": "3",
                  // 使用corejs的方式,“usage”表示按需加载
                  "useBuiltIns": "usage"
                }
              ]]
            }
          }
          ,
          'ts-loader'
        ],
        // 要排除的文件
        exclude: /node_modules/
      }
    ]
  },
...略...

六、对象

1、封装

  • 对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装
  • 默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在TS中可以对属性的权限进行设置
  • 只读属性(readonly):如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改
  • TS中属性具有三种修饰符:
public(默认值):可以在当前类、子类和对象中修改
​
protected:可以在当前类、子类中修改
​
private:只能在类中访问和修改 

2、属性存取器

  • 设置属性的方法叫做setter方法,读取属性的方法叫做getter方法
class Person{
    // 私有属性
    private _name: string;

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

    get name(){
        return this._name;
    }

    set name(name: string){
        this._name = name;
    }

}

const p1 = new Person('孙悟空');
console.log(p1.name); // 通过getter读取name属性
p1.name = '猪八戒'; // 通过setter修改name属性

七、类

属性

  • 实例属性:通过对象调用
  • 静态属性:使用static开头,通过类直接调用 方法
  • 类方法:如果方法以static开头,则方法就是类方法,可以直接通过类去调用
  • 实例方法:通过创建对象,对象调用
# 创建
class Person{
     // 实例属性
	name:string='lyw';
	age:number=16;
	// 静态属性
	static name='yyqx';
	static age=21;
	
	// 类方法
	static sayHello(){
		console.log('Hello');
	}
	// 实例方法
	sayHello(){
		console.log('Hello 11');
	}
}

# 调用
const per=new Person();
// 实例属性
console.log(per.name);
// 静态属性
console.log(Person.name);

1、继承extends

  • 通过继承可以将其他类中的属性和方法引入到当前类中
  • 子类可以扩展父类没有的属性和方法
  • 重写:如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法
class Animal{
        name: string;
        age: number;
        // 父类构造函数
        constructor(name: string, age: number){
            // this指向类对象    
            this.name = name;
            this.age = age;
        }
        run(){
            console.log(`父类中的run方法!`);
        }
    }
    ​
    class Dog extends Animal{
        bark(){
            console.log(`${this.name}在汪汪叫!`);
        }
        // 重写
        run(){
            console.log(`子类中的run方法,会重写父类中的run方法!`);
        }
    }
    ​
    const dog = new Dog('旺财', 4);
    dog.bark();

2、super关键字

  • 作用:调用父类的构造函数constructor
  • 如果在子类中写了构造函数,则在子类构造函数中必须对调用父类的构造函数
class Animal{
    name: string;

    constructor(name: string){
		// this指向类对象    
        this.name = name;
    }
    run(){
        console.log(`父类中的run方法!`);
    }
}

class Dog extends Animal{
    age:number;
	constructor(name:string,age:number){
        // 调用父类构造函数
        super(name);
        this.age=age;
    }
    // 重写
    run(){
        console.log(`子类中的run方法,会重写父类中的run方法!`);
    }
}

const dog = new Dog('旺财', 4);
dog.bark();

3、抽象类abstract

  • 作用:抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
  • 抽象方法
    • 以abstract开头,没有方法体
    • 只能定义在抽象类中,子类必须对抽象方法进行重写
abstract class Animal{
    // 抽象方法
    abstract run(): void;
    bark(){
        console.log('动物在叫~');
    }
}

class Dog extends Animals{
    run(){
        console.log('狗在跑~');
    }
}

八、接口(Interface)

  • 接口的作用类似于抽象类,它所有的属性和方法都不能有实际的值
  • 接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口
  • 作用:接口是去定义一个标准,去限制类
interface Person{
    name: string;
    sayHello():void;
}

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

fn({name:'孙悟空', sayHello() {
    console.log(`Hello, 我是 ${this.name}`)}
});
  • 定义类时,可以使类去实现一个接口(实现接口就是满足接口的要求)
interface Person{
    name: string;
    sayHello():void;
}

class Student implements Person{
    name:sting;
    constructor(name: string) {
        this.name=name;
    }

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

九、泛型(Generic)

  • 作用:在定义函数或者类时,遇到类型不明确就可以使用
  • 下面的<T>就是泛型,T是我们给这个类型起的名字泛型其实很好理解,就表示某个类型
function test<T>(arg: T): T{
     return arg;
 }
  • 怎样使用上边的函数
# 方式一(直接使用)
test(10)

# 方式二(指定类型)
test<number>(10)
  • 同时指定多个泛型,泛型间使用逗号隔开
 function test<T, K>(a: T, b: K): K{
    return b;
}
       ​
test<number, string>(10, "hello");
  • 类中同样可以使用泛型
class MyClass<T>{
    prop: T;
    ​
constructor(prop: T){
      this.prop = prop;
  }
}
  • 对泛型的范围进行约束(extends)
Interface Inter{
	length:number;
}

function fn3<T extends Inter>(a:T):number{
	return a.length;
}

十、 Utility Types

可以理解为基于ts封装的工具类型

Partial<T>

将T中所有的属性转换为可选属性。返回的类型可以是T的任意子集

```
export interface UserModle {
  name: string;
  age?: number;  // 可选属性
  sex: number;
}
​
type JuserModele=Partial<UserModle>
    
# 例子
let a: JuserModele
a={
  name:'sss',
  age:12
}
```

Required<T>

通过将T的所有属性设置为必选属性来构造一个新的类型。与Partial相反

```
type JuserModele2=Require<UserModle>
​
// 例子
let a: JuserModele2
a={
  name:'sss',
  age:12// 必须写
  sex:'男'
}
```

Readonly<T>

将T中所有属性设置为只读

```
type JUserModel3 = Readonly<UserModel>
​
// 等价于
type JUserModel3 = {
    readonly name: string;
    readonly age?: number | undefined;
    readonly sex: number;
}
```

Record<K,T>

K对应对应的key,T是对象value的类型,返回的就是一个声明好的对象

```
type TodoProperty = 'title' | 'description';
​
type Todo = Record<TodoProperty, string>;
​
// 例子
let a: Todo;
a = {
  title: 'sss',
  description: 'ddd'
}
```

Pick<T,K>

在一个声明好的对象中,挑选一部分出来组成一个新的声明对象

```
interface Todo {
  title: string;
  description: string;
  done: boolean;
}
​
type TodoBase = Pick<Todo, "title" | "done">;
​
// 等价于
type TodoBase = {
    title: string;
    done: boolean;
}
```

Omit<T,K>

从T中取出除去K的其他所有属性,与Pick相对

Exclude<T,U>

从T中排除可分配给U的属性,剩余的属性构成新的类型

```
type T0 = Exclude<'a' | 'b' | 'c', 'a'>; 
​
// 等价于
type T0 = "b" | "c"
​
```

NonNullable<T>

去除T中的null和undefined类型

Parameters<T>

返回类型为T的函数的参数类型所组成的数组

```
type T0 = Parameters<() => string>;  // []
​
type T1 = Parameters<(s: string,t:number) => void>;  // [string]
```

ReturnType<T>

function T的返回类型

```
type T0 = ReturnType<() => string>;  // string
​
type T1 = ReturnType<(s: string) => void>;  // void
```

InstanceType<T>

返回构造函数类型T的实例类型; 相当于js中的,不过返回的是对应的实例

```
class C {
  x = 0;
  y = 0;
}
​
type T0 = InstanceType<typeof C>;  // C
​
```