Bootstrap

TypeScript 学习笔记

TypeScript 中文网: https://www.tslang.cn/docs/home.html

1. 开发环境搭建

TS 需要进行编译为 JS,然后将 JS 代码交给 JS 解析器执行,所以需要安装一个 TS 解析器。

  1. 安装 Node.js

    node -v
    

    检测是否安装完成

  2. 使用 npm 全局安装 typescript

    进入命令行( 直接cmd )输入

    npm i -g typescript
    

    安装后输入 tsc 验证是否安装完成

    // 输出结果开头如下
    Version 5.3.3
    tsc: The TypeScript Compiler - Version 5.3.3
    
  3. 创建一个 ts 文件

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

    进入命令行,进入 ts 文件所在目录。执行命令

    tsc xxx.ts
    

    编译完成后为 js 文件。

2. 基本类型

类型声明

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

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

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

  4. 语法:

    let 变量: 类型;
    let 变量: 类型 =;
    function fn(参数: 类型, 参数: 类型): 类型{
        ...
    }
    

自动类型判断

  1. TS 拥有自动的类型判断机制
  2. 当对变量的声明和赋值是同时进行的, TS 编辑器会自动判断变量的类型, 所以如果变量的声明和赋值同时进行, 可以省略类型声明
  • 声明变量并指定类型

    // 声明一个变量a, 同时指定它的类型为 number
    let a: number;
    
    // 类型只能为数字
    a = 10;
    

    编译后的 JS 文件( 这里使用的 var 而不是 let ,实际编译效果应根据编译设置 )

    // 声明一个变量a, 同时指定它的类型为 number
    var a;
    // 类型只能为数字
    a = 10;
    
  • 声明变量并进行初始化

    let b = false;
    b = true;
    b = 3; // Type 'number' is not assignable to type 'boolean'.
    

    这里如果对变量初始化会自动限定变量类型。等同于 let b :number = false;

  • 函数定义

    //              参数类型    参数类型   返回值类型
    function sum(a: number, b: number): number{
        return a + b;
    }
    
    console.log( sum(123, 456) ); // 579
    

    原来的 JS 由于没有类型限制, 可能会造成许多安全隐患。

2.1 基础类型

类型栗子描述
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 新增类型

2.1.1 number

和 JavaScript 一样,TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015 中引入的二进制和八进制字面量。

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

2.1.2 string

string表示文本数据类型。 和JavaScript一样,可以使用双引号 (") 或单引号 (') 表示字符串。

你还可以使用模版字符串,它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围 ( `),并且以 ${ expr }` 这种形式嵌入表达式

2.1.3 boolean

false or true

2.1.4 字面量

// 10 是一种字面量的数据类型, 而不是作为初始化值
let a: 10;
a = 10;
// a = 11; // Type '11' is not assignable to type '10'.

类似于 const , 此时该变量值只能为 10。

其他使用场景:结合 | 符号

let b: "male" | "female"; // 表示可以从这两个值里面选
b = "male";
b = "female";

同理也衍生出了类型的联合

let c: boolean | string; // c 可以是两种类型中的任意一种
c = true;
c = "dog"; // 均正确

2.1.5 any

any 表示任意类型, 一个变量设置类型为 any 后相当于对该变量关闭了 TS 的类型检测。不建议使用

let d: any;

d = 10;
d = "hello";
d = true; // 可以为任意类型

当声明变量而不进行赋值和指定类型时, 默认为 any, 即 let d; 等价于 let d: any;

当你的数组想要存储任意类型时, 使用这个比较合适。

let list: any[] = [1, true, "dog"]; // 可以为任意类型

list[1] = 100;

2.1.6 unknown

unknown 是类型安全的 any。举例说明

let a; // 类型为 any
a = "dog";
a = 10;

let s: string;
s = a; // 未报错

s 为 string 类型, a 可以为任意类型, 但是此处并没有报错, 而且可以成功编译。实际上此处 any 类型的变量 a 赋值给 s 导致关闭了 s 的类型检测, 不为 string 类型也可以, 所以这个很不友好。

下面是 unknown

let a: unknown; // 类型为 unknown
a = "dog";
a = 10;

let s: string;
s = a; // Type 'unknown' is not assignable to type 'string'.

出现问题, 不能进行赋值。

解决方案:

  1. 使用 typeof 进行类型判断
  2. 类型断言(推荐) 2.1.1 节中介绍
let a: unknown;
a = "dog";

let s: string;
// 使用 typeof 进行类型判断
if (typeof a === "string") {
    s = a;
}

2.1.7 void

某种程度上来说, void 类型像是与 any 类型相反, 它表示没有任何类型。 当一个函数没有返回值时, 会令其返回值类型是 void

function warnUser(): void {
    console.log("This is my warning message");
}

声明一个 void 类型的变量没有什么大用,因为此时只能为它赋予 undefinednull, 同理上面的返回值我们写 return null;return undefined; 都是 void 的返回值类型。

let unusable: void = undefined;

编辑器比较智能, 通常可以自动判断类型

比如如下

function f() { // 返回值类型为 void
    console.log("dog");
}

function f() { // 返回值类型为 true
    return true;
}

function f(num: number) { // 返回值类型为 boolean | number
    if(num > 10){
        return num > 10;
    }else{
        return num;
    }
}

但是如果我们自己指定返回值类型, 在编写代码时会减少出错, 避免不同情况下返回值类型不同。

2.1.8 never

never 类型表示的是那些永不存在的值的类型。 例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never 类型,当它们被永不为真的类型保护所约束时。

never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never

下面是一些返回 never 类型的函数:

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    } // 因为如果能正常结束, 即使不指定返回值,函数也会返回 void, void 不属于 never
}

undefinednull 属于 void , 但是不属于 never 哦。所以返回类型如果为 never, 返回 nullundefined 也报错。

2.1.9 object

JS 中的 object 类型比较宽泛, 比如 Array 数组是对象, {} 也是对象, function 函数也是对象。

通常使用对象来限制成员变量的类型

let obj : {name: string};

obj = {
    name: "二狗"
};

如此需要注意 obj 中有且仅有一个属性名为 name 且类型为 string 的变量, 不能有其他变量或少了类型为 stringname

❓ 如果我不确定对象中是否有 age 属性, 可能有也可能没有, 但我知道一定有 name 该怎么写

let obj : {name: string, age?: number};

obj = {
    name: "二狗",
    age: 18
};
obj = {
    name: "二狗"
}; // 均不报错

在属性名后面加上 ?, 表示属性是可选的, 可有可无

❓ 如果想要任意多属性, 但是名字不确定该怎么写

let obj : {name: string, [propName: string]: any};

obj = {
    name: "dog",
    age: 18,
    address: "狗厂",
    isAdmin: true
};
obj = {
    name: "dog",
    age: 18
}; // 均正确

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

  • [propName: string] 由于 JS 中对象属性都为 string 类型, 所以此处限制为 string
  • [propName: string] 中的 propName 可以使用任何标识符, 写 xxx 都可以的
  • any 限定属性可以为任意类型, 如果需要可以限定为 string 等我们需要的类型

也可以使用箭头函数限定函数的参数及返回值

格式: (形参: 类型, 形参: 类型...) => 返回值类型

// c 可有可无
let f: (a: number, b: number, c?: number) => number;

f = function(n1, n2, n3){
    return n1 + n2 + n3;
};
f = function(n1, n2){
    return n1 + n2;
}; // 均正确

之前的语法同样适用于函数的参数。

2.1.10 Array

有两种方式可以定义数组。 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:

// 数字数组
let list: number[] = [1, 2, 3];

第二种方式是使用数组泛型,Array<元素类型>

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

如果要使用任意类型就使用 any (不推荐)

let list: Array<any>;

2.1.11 tuple

元组是固定长度的数组, 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error 个数顺序都必须相同

当访问一个已知索引的元素,会得到正确的类型:

console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'

当访问一个越界的元素,会使用联合类型替代:

x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型

console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString

x[6] = true; // Error, 布尔不是(string | number)类型

2.1.12 enum

enum 类型是对 JavaScript 标准数据类型的一个补充。

enum Color {
    Red, 
    Green, 
    Blue
} // 定义
let c: Color = Color.Green;

默认情况下,从 0 开始为元素编号,如上面的 Red = 1Green = 2Blue = 3。 也可以手动的指定成员的数值。 例如,将上面的例子改成从 1开始编号

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

或者,全部都采用手动赋值

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

枚举类型提供的一个便利是可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字

enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];

console.log(colorName);  // 显示'Green'因为上面代码里它的值是2

2.1.13 undefined 和 null

TypeScript里,undefinednull 两者各自有自己的类型分别叫做 undefinednull。 和 void相似,它们的本身的类型用处不是很大

// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;

默认情况下 nullundefined 是所有类型的子类型。 就是说你可以把 nullundefined 赋值给 number 类型的变量。

然而,当指定了 --strictNullChecks 标记,nullundefined 只能赋值给 void 和它们各自。 这能避免很多常见的问题。 也许在某处你想传入一个 stringnullundefined,你可以使用联合类型string | null | undefined

鼓励尽可能地使用--strictNullChecks

2.1.14 类型断言

对于 anyunknown , 有时候我们清楚该值为什么类型, 我们可以通过 类型断言 这种方式告诉编译器, 类型断言好比其它语言里的类型转换, 但是不进行特殊的数据检查和解构。 它没有运行时的影响, 只是在编译阶段起作用。

let a: unknown; // 类型为 unknown
a = "dog";

let s: string;
s = a as string; // 不会报错
s = <string>a;

类型断言有两种形式, 一种是尖括号语法, 一种是 as 语法, 两种形式是等价的, 当在 TypeScript 里使用 JSX 时,只有 as 语法断言是被允许的。

let someValue: any = "this is a string";
// 尖括号语法
let strLength: number = (<string>someValue).length;
// as 语法
let strLength: number = (someValue as string).length;

2.1.15 交叉类型与联合类型

交叉类型

当我们限定某个数据类型必须即为 T 类型又为 U 类型时可以使用 &

如我们在 JS 中知道一个对象的原型只能有一个, JS 中也没有接口等的概念, 在 TS 中就提供了, JS 中使用的是混入 mixin 进行解决的。

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void;
}
class ConsoleLogger implements Loggable {
    log() {
        // ...
    }
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();

联合类型

当一个数据类型可为 T 也可以为 U 时, 使用 |

function concat(a: number, b: string | number): number | string {
    return a + b;
}

可以为 number + number, 也可以是数字连接字符串。

2.1.16 类型别名

可以为类型起别名

type ageType = number | string;

let age: ageType; // 限定 age 只能字符串或数字
a = 1;

也可以为 string 起别名, 有点鸡肋。

type myString = string;
let a: myString;

a = "Hello"; // 其他类型会报错
a = 1; // Type 'number' is not assignable to type 'string'.

3. 编译选项

3.1 自动编译文件

tsc xxx.ts -w

编译文件时, 使用 -w 指令后, TS 编译器会自动监视文件的变化, 并在文件发生变化时对文件进行重新编译。缺点:只能指定一个文件, 且需要开启一个终端并且不能关闭。

3.2 自动编译整个项目

  1. 如果直接使用 tsc 指令, 则可以自动将当前项目下的所有 ts 文件编译为 js 文件。
  2. 直接使用 tsc 命令的前提是, 先在 根目录下 创建一个 ts 的配置文件 tsconfig.json
  3. tsconfig.json 是一个 JSON 文件, 添加配置文件后, 只需 tsc 命令即可完成对整个项目的编译

配置选项:

3.2.1 include

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

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

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

所有 src 目录和 tests 目录下的文件都会被编译, 等同于 ["src", "tests"]

  • /** 表示任意目录, 例如 src/part1, src/part2, src/part1/part1-1/part1-1-1 包括多级目录
  • /* 表示任意文件
  • 所以 src/**/* 表示该 src 目录下所有文件。

3.2.2 exclude

定义需要排除在外的目录

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

只写文件夹名, 等价于 node_modules/**/*, 即该文件夹下所有文件都不编译

"exclude" : ["src/part2/**/app.ts"]

src 下 part2 目录下的所有 app.ts 文件不会被编译

3.2.3 extends

定义被继承的配置文件

"extends" : "./configs/base"

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

3.2.4 files

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

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

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

3.2.5 compilerOptions

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

  • target
    设置 ts 代码编译的目标版本
    可选值:ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ES2021、ES2022、ESNext(指最新版本)

      "compilerOptions": {
        "target": "ES6"
      }
    
  • module
    指定使用的模块化的规范
    可选值: none, commonjs, amd, system, umd, es6, es2015, es2020, es2022, esnext, node16, nodenext

    默认值:根据 target 决定, target === "ES6" ? "ES6" : "commonjs"

    "compilerOptions": {
    	"module": "ES6"
    }
    
  • lib
    指定项目中要使用的库列表, 类型为 string[]
    通常来说, 不用设置, 使用默认值即可
    针对于 target ES5 有 DOM, ES5, ScriptHost。针对于 target ES6 有 DOM, ES6, DOM.Iterable, ScriptHost
    可选值有很多, 具体如下

    Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', '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.date', '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', 'es2019.intl', '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.sharedmemory', 'es2022.string', 'es2022.regexp', 'es2023.array', 'es2023.collection', 'esnext.array', 'esnext.collection', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl', 'esnext.disposable', 'esnext.bigint', 'esnext.string', 'esnext.promise', 'esnext.weakref', 'esnext.decorators', 'decorators', 'decorators.legacy'
    
  • outDir
    指定编译后文件所在的目录

      "compilerOptions": {
        "outDir": "./dist"
      }
    

    image-20240218163856540

  • outFile
    设置后, 所有的全局作用域代码会合并到同一个文件中, 不过当涉及到模块化时 module 有限制。

    注意:Only 'amd' and 'system' modules are supported alongside --outFile.
    所以其实这个也不常用, 了解即可

      "compilerOptions": {
        "outDir": "./dist",
        "outFile": "./dist/app.js"
      }
    

    如此, 会将上面例子中生成的两个 app.jsm.js 合并到一个 app.js 文件中。

  • allowJs
    是否允许编译 JavaScript 文件, 默认情况下为 false
    比如文件夹 src 下有 app.tsm.tshello.js, 输出目录 outDir 设置为 ./dist, 此时编译后在 dist 目录下只会产生 app.jsm.js, 不会有 hello.js。我们设置 allowJs 就是允许编译 JS 文件。

      "compilerOptions": {
        "allowJs": true
      }
    

    这里配置文件对 js 文件也会产生影响, 比如我们在 js 文件中使用了 let 关键字, 我们不设置 target 属性(采用默认值ES3), 则对 ts, js 文件都会转为 es3 规范的代码, let 都变为 var

  • checkJs
    .js 文件中报告错误。与 allowJs 配合使用, 检测 js 代码是否符合 ts 语法规范。

    let a = 3;
    a = "hello";
    

    不设置 "checkJs" : true 时, 是不会报错的, 因为符合 JS 语法规范, 但是设置之后就会报错, 因为不符合 TS 语法规范, a 变量是 number 类型, 不能赋值 string 类型了。

  • removeComments
    删除所有注释,除了以 /!*开头的版权信息。默认为 false

  • noEmit
    默认为 false。设置为 true 后编译器不会生成任何输出文件,而是只会进行类型检查。

  • noEmitOnError
    默认为 false, 设置为 true 后当有错误时不生成编译后的文件。

    let a: number = 10;
    a = "hello";
    

    该代码作为 ts 会报错, 但是强制编译也会生成文件, 这通常不是我们想要的, 所以可以开启这个。

  • alwaysStrict
    默认为 false, 开启后将以严格模式解析并为每个源文件生成 "use strict"语句。

    🐯 当使用了模块导出功能 export 时, 是强制开启了严格模式的, 所以就不会为每个文件添加 use strict 语句了。

  • noImplicitAny
    当使用 any 类型的变量时, 会默认关闭类型检测, 所以很不推荐使用, 有些情况下变量会默认使用 any 类型。不易察觉
    该配置项默认值为 false, 开启后, 当表达式和声明上有隐含的 any 类型时报错 (注意是隐含的, 如果显式声明 any 类型不会报错)。

    设置后

      "compilerOptions": {
        "noImplicitAny": true
      }
    

    对于 ts 代码

// 显式声明 any 不会报错
let b: any = 10;
b = “hello”;

// 隐式 any 会报错
function fn(n1, n2){ // Parameter ‘n2’ implicitly has an ‘any’ type.
return n1 + n2;
}


- `noImplicitThis`
默认情况下为 `false`, 设置为 `true` 后当 `this` 表达式的值为 `any` 类型的时候,生成一个错误。

```ts
function fn(){
   alert(this); // this 为 any 类型, 会报错, 因为它取决于调用者, 可能为对象, 也可能为 window
}

function fn2(this: window){
   alert(this); // 设置 this 类型, 如此不会再报错
}
  • strictNullChecks
    默认为 false, 在严格的 null 检查模式下, nullundefined 值不包含在任何类型里,只允许用它们自己和 any 来赋值 (有个例外, undefined可以赋值到 void)。

    严格检测空值

    let box1 = document.getElementById("box"); // 获取 Id 为 box 的组件
    box1.addEventListener("click", function (){
        alert("hello");
    }); // 添加事件
    

    在不开启前, 这里是不会报错的, 但是如果获取不到 box, 则变量 box1 是空值, 无法添加事件, 会出错。所以代码并不严格。所以开启比较好, 此处代码应该进行改进

    解决方案1: 加入空值判断

    if(box1 !== null){
        box1.addEventListener("click", function (){
            alert("hello");
        });
    }
    

    解决方案2: ?.
    可选链 ?. 前面的值为 undefined 或者 null,它会停止运算并返回 undefined

    box1?.addEventListener("click", function (){
        alert("hello");
    });
    
  • strict
    所有严格模式的总开关
    启用 strict 相当于启用 noImplicitAny, noImplicitThis, alwaysStrictstrictNullChecksstrictFunctionTypesstrictPropertyInitialization

4. webpack 打包 ts 代码

通过 webpack 编译 ts 代码

步骤如下:

  1. 初始化项目

    • 进入项目根目录, 执行命令 npm init -y, 根目录

      image-20240219092829779 主要作用: 创建 package.json 文件

  2. 下载构建工具

    npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin
    

    -D 表示后面安装的都是开发依赖

    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

    // 引入一个包
    const path = require('path');
    // 引入 html 插件
    const HTMLWebpackPlugin = require('html-webpack-plugin');
    // 引入 clean 插件
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    // webpack 中所有的配置信息都应该写在 module.exports 中
    module.exports = {
        optimization: {
            minimize: false // 关闭代码压缩, 可选
        },
    
        // 指定入口文件
        entry: './src/app.ts',
    
        mode: "development",
        // 指定打包文件所在目录
        output: {
            // 打包后文件目录
            path: path.resolve(__dirname, 'dist'),
            // 打包后文件的名字
            filename: "bundle.js"
        },
    
        resolve: {
            // 设置引用模块, ts, js 都可以作为模块并引入
            extensions: [".ts", ".js"]
        },
    
        // 指定 webpack 打包时要使用的模块
        module: {
            // 指定加载的规则
            rules: [
                {
                    // 指定规则生效的文件
                    test: /\.ts$/, // 以 .ts 结尾的文件
                    // 使用的 loader
                    use: {
                        loader: "ts-loader"
                    },
                    // 要排除的文件
                    exclude: /node-modules/
                }
            ]
        },
    
        // 配置 webpack 插件
        plugins: [
            new CleanWebpackPlugin(),
            new HTMLWebpackPlugin({
                title: "dog title"
            }),
        ]
    };
    
  4. 根目录下创建 tsconfig.json, 配置根据自己需要

    {
      "compilerOptions": {
        "target": "ES6",
        "module": "ES6",
        "strict": true
      }
    }
    
  5. 修改 package.json 添加如下配置

    {
      // 略
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack", // 构建工具使用 webpack
        "start": "webpack serve --open" // 启动服务器
      },
    }
    
  6. 执行 npm start 来启动开发服务器

4.1 Babel

经过一系列的配置,使得 TS 和 webpack 已经结合到了一起,除了webpack,开发中还经常需要结合 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 语法

  2. 修改 webpack.config.js 配置文件 (只改 use 配置, environment 中的那个可以不修改)

    output: {
    	// ...略...
        environment: {
            arrowFunction: false // 关闭 webpack 的箭头函数
        }
    },
    // ...略...
    module: {
        // 指定加载的规则
        rules: [
            {
                // 指定规则生效的文件
                test: /\.ts$/, // 以 .ts 结尾的文件
                // 使用的 loader
                use: [
                    // 配置加载器
                    {
                      // 指定加载器
                        loader: "babel-loader",
                        // 设置 babel
                        options: {
                            // 设置预定义的环境
                            presets:[
                                [
                                    // 指定环境的插件
                                    "@babel/preset-env",
                                    // 配置信息
                                    {
                                        targets:{
                                            // 要兼容到的浏览器版本
                                            "chrome": "88",
                                            "ie": "11" // IE11 不支持 const 及 let
                                        },
                                        // 指定 corejs 的版本, 下载的 core-js 是 3.36.0, 所以此处写 3 即可
                                        "corejs": "3",
                                        // 使用 corejs 的方式
                                        "useBuiltIns": "usage" // usage 按需加载
                                    }
                                ]
                            ]
                        }
                    },{
                        loader: "ts-loader"
                    }
                ],
                // 要排除的文件
                exclude: /node-modules/
            }
        ]
    },
    // ...略...
    
    • 如此一来,使用 ts 编译后的文件将会再次被 babel 处理,使得代码可以在大部分浏览器中直接使用,可以在配置选项的 targets 中指定要兼容的浏览器版本。

5. 面向对象

抽象类和接口都是 TS 中独有的, 编译为 JS 后都没有了。

5.1 类

定义类

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

示例

class Person{
    // 实例属性
    name: string;
    // readonly 设置只读属性, 该属性只能读取不能修改, 并且该属性必须在声明时或构造函数中进行初始化
    readonly age: number;
    // 静态属性
    static num: number = 0;

    // 构造函数只能有一个
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
        // 静态属性只能通过类名访问
        Person.num++;
    }
    
    // 方法也可以加 static
    sayHello(){
        console.log(`my name is ${this.name}`);
    }
}

staticreadonly 可以同时修饰变量

5.2 继承

在 TypeScript 里,成员都默认为 public, 所以属性和方法都会被继承。

如果在子类中写了构造函数, 则必须对父类的构造函数进行调用。使用 super(...) 调用父类构造函数, 使用 super.方法名() 调用父类方法

class Animal{
    name: string;
    age: number;
    
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    
    sayHello(){
        console.log("动物在叫~~");
    }
}
// name, age, sayHello() 都会被继承, 构造器也会有默认的
class Dog extends Animal{
    
    // 未添加构造器, 则会采用父类的构造器
    
    // 添加新的方法
    run(){
        console.log(`${this.name}在跑~~`);
    }
    // 方法重写
    sayHello() {
        console.log("汪汪汪~~");
    }
}

class Cat extends Animal{
    
    gender: number;
    
    constructor(name: string, age: number, gender: number) {
        // 调用父类构造函数
        super(name, age);
        this.gender = gender;
    }
    sayHello() {
        super.sayHello();
        console.log("喵喵喵~~");
    }
}

5.3 抽象类

抽象类不能用来创建对象, 是专门用来被继承的类

抽象类中可以添加抽象方法

// 抽象类
abstract class Animal{
    name: string;
    age: number;
	
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
	// 抽象方法只能定义在抽象类中, 子类必须实现
    abstract sayHello(): void;
}

class Dog extends Animal{
    // 方法重写
    sayHello() {
        console.log("汪汪汪~~");
    }
}

5.4 接口

接口用来定义一个类结构, 用来定义一个类中应该包含哪些属性和方法, 而不用考虑实际值, 里面的方法都是抽象的

接口和抽象类的区别是抽象类中的方法可以不是抽象的, 而接口中的方法全部是抽象的

// 接口
interface Animal{
    name: string;
    age?: number; // 可选属性

    sayHello(): void;
}
// 实现类
class Dog implements Animal{
    name: string;

    constructor(name: string) {
        this.name = name;
    }
    sayHello(): void {
        console.log(`我是${this.name}`);
    }
    
}

一个类可以实现多个接口

接口可以当成类型声明去使用

// type myInter 和 interface myInterface 作用一样
type myInter = {
    name: string,
    age?: number, // 属性可选
    sayHello(): void
}

interface myInterface{
    name: string;
    age?: number;

    sayHello(): void;
}
// 接口可以当成类型声明去使用
let it: myInterface = {
    name: "二狗",
    sayHello() {
        console.log(`我是${this.name}`);
    }
}
it.sayHello();

5.5. 属性的封装

三种修饰符, 继承(extends)时可以继承 protected public 修饰的成员, private 不会被继承

  • public 修饰的属性可以在任意位置访问(默认值)
  • private 私有属性, 私有属性只能在类内部访问
  • protected 受保护的属性, 只能在当前类和当前类的子类中进行访问

将属性设置为 private 并设置存取器有两种风格

风格1: _属性名 + getter/setter

class Dog{
    private _name: string;
    private _age: number;

	// 由 age 控制 _age
    get age(): number {
        return this._age;
    }

    set age(value: number) {
        this._age = value;
    }

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

}

let dog = new Dog("二狗", 21);
console.log(dog.age); // 通过类似于属性的方式进行访问, 实际调用的还是方法, 同 JS
dog.age = 18;

风格2:通过 public 方法

class Dog{
    private name: string;
    private age: number;

	// public 公共方法
    getAge(): number {
        return this.age;
    }

    setAge(value: number) {
        this.age = value;
    }

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

}

let dog = new Dog("二狗", 21);
console.log(dog.getAge()); // get set
dog.setAge(18);

可以直接将属性定义在构造函数中, 形式更简洁

class Dog{
    // 在构造函数中写上变量修饰符
    constructor(private _name: string, private _age: number) {
    }

	// ...getter and setter
}

等价于

class Dog{
    private _name: string;
    private _age: number;
    
    constructor(name: string, age: number) {
        this._name = name;
        this._age = age;
    }
    // ...getter and setter
}

6. 泛型

观察如下例子

function identity(a: number): number {
    return a;
}

函数接收一个 number 类型的参数并进行返回

❓ 问题引入:当不明确参数类型时如何设定?

function identity(a: any): any {
    return a;
}

TS 中 any 可以解决这个问题, 但是有一个缺点:参数和返回值类型都是 any, 所以参数和返回值类型不一定相同(本函数肯定一致, 只是举例说明), 可以参数为 number, 返回值为 string

所以可以使用 泛型 来解决, 调用函数时推荐使用 <> 指定泛型类型, 避免出错

function identity<T>(a: T): T{
    return a;
}
// 可以直接调用具有泛型的函数, 不指定泛型, TS 可以自动对类型进行推断
console.log( identity(2) ); // 2
// 也可以指定泛型
console.log( identity<string>('hello') );

多种类型

function f<T, K, R>(a: T, b: K): R{
    
}

6.1 接口+泛型

泛型约束可以对泛型进行约束 <T extends Inter>, 不需要使用 implements, 用 extends 也可以对接口进行使用。

// 定义接口
interface Inter{
    length: number;
    toLowerCase(): string;
}
// 泛型函数
function f<T extends Inter>(a: T): number{
    return a.toLowerCase().length;
}

let result = f( {length: 5, toLowerCase(){
    return "myInter".toLowerCase() // 瞎写的, 没有实际含义嗷
    }
} );
console.log( result ); // 7
console.log( f("string") ); // 6

这里定义了 Inter 接口, 里面有一个属性和方法, 而泛型 T 限定了 T extends Inter, 其实不要求继承了接口 Inter, 只要类中有 length 属性和 toLowerCase() 方法即可 (string 类肯定没继承接口 Inter, 但是也可以正确执行)

6.2 类+泛型

// 类泛型
class Dog<T>{
    name: T;
    constructor(name: T) {
        this.name = name;
    }
}

const dog = new Dog<string>("二狗");

问题解决

1. WebStorm 无法执行 tsc 编辑命令

在终端运行命令 tsc basis.ts 报错

tsc : 无法加载文件 C:\Users\16331\AppData\Roaming\npm\tsc.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参
阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。

解决方案:管理员身份运行 windows powershell 输入 set-ExecutionPolicy RemoteSigned 选择 A 或者 Y,回车。

https://blog.csdn.net/lxbin/article/details/115313811

;