TypeScript 中文网: https://www.tslang.cn/docs/home.html
1. 开发环境搭建
TS 需要进行编译为 JS,然后将 JS 代码交给 JS 解析器执行,所以需要安装一个 TS 解析器。
-
安装 Node.js
node -v
检测是否安装完成
-
使用 npm 全局安装 typescript
进入命令行( 直接cmd )输入
npm i -g typescript
安装后输入
tsc
验证是否安装完成// 输出结果开头如下 Version 5.3.3 tsc: The TypeScript Compiler - Version 5.3.3
-
创建一个 ts 文件
-
使用 tsc 对 ts 文件进行编译
进入命令行,进入 ts 文件所在目录。执行命令
tsc xxx.ts
编译完成后为 js 文件。
2. 基本类型
类型声明
-
类型声明是 TS 非常重要的一个特点
-
通过类型声明可以指定 TS 中变量 (参数, 形参) 的类型
-
指定类型后, 当为变量赋值时, TS 编译器会自动检测值是否符合类型声明, 符合则赋值, 否则报错
-
语法:
let 变量: 类型; let 变量: 类型 = 值; function fn(参数: 类型, 参数: 类型): 类型{ ... }
自动类型判断
- TS 拥有自动的类型判断机制
- 当对变量的声明和赋值是同时进行的, 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 基础类型
类型 | 栗子 | 描述 |
---|---|---|
number | 1, -33, 2.5 | 任意数字 |
string | ‘hi’, “hi”, `hi` | 任意字符串 |
boolean | true、false | 布尔值 true 或 false |
字面量 | 其本身 | |
any | * | 任意类型 |
unknown | * | 类型安全的 any |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {name: ‘二狗’} | 任意 JS 对象 |
array | [1, 2, 3] | 任意 JS 数组 |
tuple | [4, 5] | 元组, TS 新增类型, 固定长度的数组 |
enum | enum{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'.
出现问题, 不能进行赋值。
解决方案:
- 使用
typeof
进行类型判断 - 类型断言(推荐) 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
类型的变量没有什么大用,因为此时只能为它赋予 undefined
和 null
, 同理上面的返回值我们写 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
}
undefined
和 null
属于 void
, 但是不属于 never
哦。所以返回类型如果为 never
, 返回 null
或 undefined
也报错。
2.1.9 object
JS 中的 object 类型比较宽泛, 比如 Array
数组是对象, {}
也是对象, function
函数也是对象。
通常使用对象来限制成员变量的类型
let obj : {name: string};
obj = {
name: "二狗"
};
如此需要注意 obj
中有且仅有一个属性名为 name
且类型为 string
的变量, 不能有其他变量或少了类型为 string
的 name
。
❓ 如果我不确定对象中是否有 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 = 1
,Green = 2
,Blue = 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里,undefined
和 null
两者各自有自己的类型分别叫做 undefined
和 null
。 和 void
相似,它们的本身的类型用处不是很大
// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;
默认情况下 null
和 undefined
是所有类型的子类型。 就是说你可以把 null
和 undefined
赋值给 number
类型的变量。
然而,当指定了 --strictNullChecks
标记,null
和 undefined
只能赋值给 void
和它们各自。 这能避免很多常见的问题。 也许在某处你想传入一个 string
或 null
或 undefined
,你可以使用联合类型string | null | undefined
。
鼓励尽可能地使用
--strictNullChecks
2.1.14 类型断言
对于 any
和 unknown
, 有时候我们清楚该值为什么类型, 我们可以通过 类型断言 这种方式告诉编译器, 类型断言好比其它语言里的类型转换, 但是不进行特殊的数据检查和解构。 它没有运行时的影响, 只是在编译阶段起作用。
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 自动编译整个项目
- 如果直接使用
tsc
指令, 则可以自动将当前项目下的所有 ts 文件编译为 js 文件。 - 直接使用
tsc
命令的前提是, 先在 根目录下 创建一个 ts 的配置文件tsconfig.json
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" }
-
outFile
设置后, 所有的全局作用域代码会合并到同一个文件中, 不过当涉及到模块化时module
有限制。注意:
Only 'amd' and 'system' modules are supported alongside --outFile.
所以其实这个也不常用, 了解即可"compilerOptions": { "outDir": "./dist", "outFile": "./dist/app.js" }
如此, 会将上面例子中生成的两个
app.js
、m.js
合并到一个app.js
文件中。 -
allowJs
是否允许编译 JavaScript 文件, 默认情况下为false
比如文件夹src
下有app.ts
、m.ts
、hello.js
, 输出目录outDir
设置为./dist
, 此时编译后在 dist 目录下只会产生app.js
和m.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
检查模式下,null
和undefined
值不包含在任何类型里,只允许用它们自己和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
,alwaysStrict
,strictNullChecks
和strictFunctionTypes
和strictPropertyInitialization
。
4. webpack 打包 ts 代码
通过 webpack 编译 ts 代码
步骤如下:
-
初始化项目
-
进入项目根目录, 执行命令
npm init -y
, 根目录主要作用: 创建
package.json
文件
-
-
下载构建工具
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin
-D
表示后面安装的都是开发依赖webpack
构建工具webpackwebpack-cli
webpack的命令行工具webpack-dev-server
webpack的开发服务器, 相当于一个内置的服务器typescript
ts编译器ts-loader
ts加载器,用于在webpack中编译ts文件html-webpack-plugin
webpack中html插件,用来自动创建html文件clean-webpack-plugin
webpack中的清除插件,每次构建都会先清除目录 (原来的目录下文件在每次更新时不会自动清除, 即原来的多余文件不会删除, 只可能覆盖, 所以需要这个插件) -
根目录下创建 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" }), ] };
-
根目录下创建
tsconfig.json
, 配置根据自己需要{ "compilerOptions": { "target": "ES6", "module": "ES6", "strict": true } }
-
修改
package.json
添加如下配置{ // 略 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", // 构建工具使用 webpack "start": "webpack serve --open" // 启动服务器 }, }
-
执行
npm start
来启动开发服务器
4.1 Babel
经过一系列的配置,使得 TS 和 webpack 已经结合到了一起,除了webpack,开发中还经常需要结合 babel 来对代码进行转换以使其可以兼容到更多的浏览器,在上述步骤的基础上,通过以下步骤再将 babel 引入到项目中。
-
安装依赖包
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 语法 -
修改
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
中指定要兼容的浏览器版本。
- 如此一来,使用 ts 编译后的文件将会再次被 babel 处理,使得代码可以在大部分浏览器中直接使用,可以在配置选项的
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}`);
}
}
static
和 readonly
可以同时修饰变量
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