TypeScript 学习笔记
TypeScript 向 JavaScript 添加了额外的语法(添加了类型),与编辑器更紧密的集成,能在编辑器中尽早发现错误。
TypeScript 代码转换为 JavaScript,它在 JavaScript 运行的任何地方运行:在浏览器中、在 Node.js 或 Deno 上以及在你的应用程序中。
TypeScript 理解 JavaScript 并使用类型推断为你提供出色的工具,而无需额外的代码。
一、环境搭建
- 安装Node.js
在node中文网下载,安装长期支持版
https://nodejs.cn/
- 安装TypeScript(npm安装)
npm i -g typescript
查看typescript的版本
# 正常会显示版本,如:Version 5.2.2
tsc -v
如果出现下面错误
tsc : 无法加载文件 D:\opt\node\node-v18.16.0\tsc.ps1,因为在此系统上禁止运行脚本。
因为 PowerShell默认的执行策略是:Restricted,需要修改为:RemoteSigned
解决办法:
以管理员身份运行 PowerShell,在命令行输入
set-ExecutionPolicy RemoteSigned
在确认地方输入 y 或者 a
再查看设置结果
# 查看设置结果是否为:RemoteSigned get-ExecutionPolicy
二、TypeScript基本语法
2.1 数据类型
数据类型
类型 | 例子 | 说明 |
---|---|---|
number | a: number = 1 | 数字 |
string | b: string = ‘wk’ | 字符串 |
boolean | c: boolean = true | 布尔 |
字面量 | let sex:‘male’ | ‘female’ | 限制变量的值就是该字面量的值,类似定义常量 |
any | 任意类型 | |
unknow | 类型安全的any,不能直接赋值给其它变量。 | |
void | 空值(undefined) | 没有值 |
never | 没有值 | 不能是任何值 |
object | { name: ‘wk’ } | 对象 |
array | [1, 2, 3] | 数组 |
tuple | [4,5] | 元组 |
enum | enum(A, B) | 枚举 |
- 基本类型
-
string
-
number
-
boolean
- 数组
方式一
-
number[]
-
string[]
方式二:
-
Array
-
Array
示例
let a: string[]
let a: Array
- any
对变量的值不进行类型检查,类型可以使用any
let obj: any = {x: 0};
- union 联合类型,可以是多个类型中的一个
let u: string | number | boolean = ‘rose’
u = 18
- object
1)对象必须有name属性
let user: {name: string}
2)对象必须有name属性,age属性可选
let user: {name: string, age?: number}
3)对象必须有name属性,其它属性可用(属性值类型为any)
let user: {name: string, [prop: string]: any}
4)函数func有两个参数,参数类型为string,返回值类型为number
let func: (a: string, b: string) => number;
- 元组
元组是固定长度的数组。
定义元组h, 里面有2个类型为string的值。
let h: [string, string]
- 枚举
enum Gender {
Male = 1,
Female = 0
}
let user = {
gender: Gender.Male
}
- 类型别名
myType是string的别名
type myType = string;
let a: myType
// 定义类型 myType是1 | 2 | 3 | 4 | 5的别名
type myType = 1 | 2 | 3 | 4 | 5
let a: myType
a = 1
2.2 运算符号
- & 与
// 变量j既有属性name, 又有属性age
let j: {name: string} & {age: number}
j = {name: 'wk', age: 40}
- | 或
函数
function 函数名(param1: 参数类型, param2: 参数类型): 返回值类型 {
}
类型断言
let a: string = b as string
或
let a: string = b
变量作用域
变量作用域指定了变量定义的位置。
程序中变量的可用性由变量作用域决定。
TypeScript 有以下几种作用域:
1)全局作用域 − 全局变量定义在程序结构的外部,它可以在你代码的任何位置使用。
2)类作用域 − 这个变量也可以称为 字段。类变量声明在一个类里头,但在类的方法外面。 该变量可以通过类的对象来访问。类变量也可以是静态的,静态的变量可以通过类名直接访问。
3)局部作用域 − 局部变量,局部变量只能在声明它的一个代码块(如:方法)中使用。
以下实例说明了三种作用域的使用:
var global_num = 12 // 全局变量
class Numbers {
num_val = 13; // 实例变量
static sval = 10; // 静态变量
storeNum():void {
var local_num = 14; // 局部变量
}
}
console.log("全局变量为: " + global_num)
console.log(Numbers.sval) // 静态变量
var obj = new Numbers();
console.log("实例变量: " + obj.num_val)
2.3 条件语句
2.3.1 循环语句
- for 循环
- 普通for循环
for (let i = 0; i < 10; i++) {
console.log(i)
}
- for in迭代
let cites = ['西安', '成都', '杭州']
for (let city in cites) {
console.log(city)
}
- for of迭代
const user = { name: 'wk', age: 40 }
for (const prop of user) {
console.log(name)
}
- while循环
let i = 1
while (i < 10) {
i++
}
2.4 函数
2.4.1 普通函数
函数的定义
function 函数名称(参数1: 参数1类型[, …]): 返回值类型 {
}
示例
function sayHello(name: string): string {
return 'Hello'
}
可选参数函数,参数后跟一个?号,表示该参数可有可无
function sayName(name?: string): string {
name = name ? : 'wk'
return name
}
参数默认值函数
function sayName(name: string = 'wk') {
return name
}
2.4.2 箭头函数
箭头函数定义
let 函数变量 = (参数1: 参数1类型[, …]) => {
}
示例:
let sayHi = (ret: string) => {
return 'Hello'
}
2.5 命名空间
命名空间一个最明确的目的就是解决重名问题。命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的。这样在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中。
命名空间语法:
namespace MyNameSpace {
export interface ISomeInterfaceName {}
export class SomeClassName {}
}
以上定义了一个MyNameSpace的命名空间,如果我们需要在外部调用MyNameSpace命名空间中的类和接口,则需要在类和接口添加 export 关键字,要在另外一个命名空间调用语法格式为:
命名空间名.类名
如果一个命名空间在一个单独的 TypeScript 文件中,则应使用三斜杠 /// 引用它,语法格式如下:
/// <reference path = "SomeFileName.ts" />
以下演示定义在不同文件中命名空间的使用:
IShape.ts 文件代码:
namespace Drawing {
export interface IShape {
draw();
}
}
Circle.ts 文件代码:
namespace Drawing {
export class Circle implements IShape {
public draw() {
console.log("Circle is drawn");
}
}
}
Triangle.ts 文件代码:
namespace Drawing {
export class Triangle implements IShape {
public draw() {
console.log("Triangle is drawn");
}
}
}
TestShape.ts 文件代码:
function drawAllShapes(shape: Drawing.IShape) {
shape.draw();
}
drawAllShapes(new Drawing.Circle());
drawAllShapes(new Drawing.Triangle());
2.6 面向对象
2.6.1 类
类定义
方式一
class 类名 {
/**
* 1. 属性前加static为类属性
* 2. 属性前不加static为实例属性
*/
[修饰符][static] 属性名: 类型
constructor(参数: 类型) {
this.属性名 = 参数;
}
/**
* 1. 方法前加static为类方法
* 2. 方法前不加static为实例方法
*/
[static] 方法名(参数: 类型) {
......
}
// getter方法(方式一)
get属性名() {
return this.属性名;
}
/**
* getter方法(方式二)
* 调用 类对象.属性名 例如: person.name
*/
get 属性名() {
return this.属性名;
}
set属性名(value: 属性类型) {
this.属性名 = value
}
/**
* setter方法(方式二)
* 调用 类对象.属性名 = value 例如: person.name = 'wk'
*/
set 属性名(value: 属性类型) {
this.属性名 = value
}
}
属性的修饰符
-
public: 修饰的属性可以在任意地方修改,该值为默认值。
-
protected:只能在当前类和当前类的子类中访问。
-
private: 私有属性只能在类的内部修改。
方式二
class 类名称 {
constructor(<属性修饰符> 属性名: 属性类型) {
}
}
类继承
class 类名 extends 父类 {
/**
* 1. 属性前加static为类属性
* 2. 属性前不加static为实例属性
*/
[static] 属性名: 类型
constructor(参数: 类型) {
this.属性名 = 参数;
}
/**
* 1. 方法前加static为类方法
* 2. 方法前不加static为实例方法
*/
[static] 方法名(参数: 类型) {
......
}
}
使用继承后,子类将继承父类的所有属性和方法
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
console.log('动物叫')
}
}
class Dog extends Animal {
age: number;
constructor(name: string, age: number) {
// 子类中必须调用父类的构造函数
super(name);
this.age = age;
}
/**
* 同名方法重写
*/
sayHello() {
console.log('汪汪')
}
}
2.6.2 抽象类
在class前添加abstract关键字,该类就变成了抽象类。抽象类不能被用来创建对象,可以被继承。
abstract class Animal {
/**
* 抽象方法,子类必须对抽象方法进行重写
*/
abstract sayHello(): void
}
2.6.3 接口
接口用来定义一个类的结构,包含哪些属性、方法。接口也可以当作类型声明来使用。接口是可以重复定义的,对多个接口取并集即为接口的整体定义。接口中的属性不能有实际值。
接口的定义
interface 接口名称 {
属性名: 属性类型
方法名(参数名: 参数类型): 返回值
}
接口的实现
class 类名称 implements 接口名称 {
属性名: 属性类型
constractor(属性名: 属性类型) {
this.属性名 = 属性名
}
方法名(参数名: 参数类型): 返回值 {
}
}
2.6.4 泛型
定义类或函数时,当不确定类型时可以使用泛型。
定义泛型函数
function fn<T>(参数: T): T {
}
泛型函数的调用
// 方式一,不指定泛型类型,由程序自动推断
fn(10)
// 方式二,强制指定泛型类型
fn<string>('hello')
定义多个泛型函数
function fn<T, K>(参数1: T, 参数2: K): T {
}
多个泛型函数的调用
fn<number, string(1, 'wk')
带继承的泛型
// 泛型 T 实现了接口 P 或者 继承了类 P
function fn<T extends P>(a: T): number {
}
定义类
class 类名<T> {
}
2.7 声明文件
在开发过程中不可避免要引用其他第三方的 JavaScript 的库。虽然通过直接引用可以调用库的类和方法,但是却无法使用TypeScript诸如类型检查等特性功能。为了解决这个问题,需要将这些库里的函数和方法体去掉后只保留导出类型声明,而产生了一个描述 JavaScript 库和模块信息的声明文件。通过引用这个声明文件,就可以借用 TypeScript 的各种特性来使用库文件了。
假如我们想使用第三方库,比如 jQuery,我们通常这样获取一个 id 是 foo 的元素:
$('#foo');
// 或
jQuery('#foo');
但是在 TypeScript 中,我们并不知道 $ 或 jQuery 是什么东西,这时我们需要使用 declare 关键字来定义它的类型,帮助 TypeScript 判断我们传入的参数类型对不对。
declare var jQuery: (selector: string) => any;
jQuery('#foo');
declare 定义的类型只会用于编译时的检查,编译结果中会被删除。上例的编译结果是:jQuery(‘#foo’);
声明文件
声明文件以 .d.ts 为后缀,例如:runoob.d.ts,声明文件或模块的语法格式如下:
declare module Module_Name {
}
TypeScript 引入声明文件语法格式:
/// <reference path = " runoob.d.ts" />
当然,很多流行的第三方库的声明文件不需要我们定义了,比如 jQuery 已经有人帮我们定义好了:jQuery in DefinitelyTyped。
以下定义一个第三方库来演示:
CalcThirdPartyJsLib.js 文件代码:
var Runoob;
(function(Runoob) {
var Calc = (function () {
function Calc() {
}
})
Calc.prototype.doSum = function (limit) {
var sum = 0;
for (var i = 0; i <= limit; i++) {
sum = sum + i;
}
return sum;
}
Runoob.Calc = Calc;
return Calc;
})(Runoob || (Runoob = {}));
var test = new Runoob.Calc();
如果我们想在 TypeScript 中引用上面的代码,则需要设置声明文件 Calc.d.ts,代码如下:
Calc.d.ts 文件代码:
declare module Runoob {
export class Calc {
doSum(limit:number): number;
}
}
声明文件不包含实现,它只是类型声明,把声明文件加入到 TypeScript 中:
CalcTest.ts 文件代码:
/// <reference path = "Calc.d.ts" />
var obj = new Runoob.Calc();
// obj.doSum("Hello"); // 编译错误
console.log(obj.doSum(10));
下面这行导致编译错误,因为我们需要传入数字参数:
obj.doSum("Hello");
使用 tsc 命令来编译以上代码文件:
tsc CalcTest.ts
生成的 JavaScript 代码如下:
CalcTest.js 文件代码:
/// <reference path = "Calc.d.ts" />
var obj = new Runoob.Calc();
//obj.doSum("Hello"); // 编译错误
console.log(obj.doSum(10));
最后我们编写一个 runoob.html 文件,引入 CalcTest.js 文件及第三方库 CalcThirdPartyJsLib.js
<html>
<head>
<meta charset="utf-8">
<title>教程</title>
<script src="CalcThirdPartyJsLib.js"></script>
<script src="CalcTest.js"></script>
</head>
<body>
<h1>声明文件测试</h1>
</body>
</html>
三、TypeScript编译
3.1 配置文件
TypeScript的配置文件是tsconfig.json
tsconfig.json
{
// 指定编译的目录 ** 任意目录, * 任务文件。默认编译所有文件
"include": ["./src/**/*"],
// 指定编译排除的目录。默认值:"node_modules", "bower_components", "jspm_packages"
"exclude": ["node_modules"],
// 定义被继承的配置文件,当前配置文件会包含config/base配置文件的内容
// "extends": "./config/base"
// 指定被编译的文件列表(只有需要编译的文件为少数时才会用到)
// "files": [
// "core.ts"
// ]
// 编译器选项
"compilerOptions": {
// ts编译的目标版本。可选值:ES3(默认值), ES5, ES6/ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, EXNext
"target": "ES2015",
/*
* 指定代码运行时所包含的库(宿主环境)。
* 可选值:ES5, ES6/ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, EXNext, DOM, WebWorker, ScriptHost, ...
*/
"lib": ["ES2015", "DOM"],
// 指定编译后使用的模块化系统。可选值:None, CommonJS, UMD, AMD, System, ES6(ES2015), ES2020, ES2022, ESNext, Node16, NodeNext
"module": "ES2015",
// 指定编译后生成文件的目录。默认和源文件在同一目录
"outDir": "./dist",
// 将编译的代码合并为一个文件。注意:只有module值为amd, system时才支持
// "outFile": "./dist/main.js"
// 是否对指定编码目录下的js文件进行编译。默认为false,即编译后是否在输出目录生成js文件对应的文件。
"allowJs": true,
// 是否检查js文件是否符合语法规范。默认false
"checkJs": true,
// 编译后是否移除注释
"removeComments": true,
// 是否不生成编译后的文件(当仅仅验证是否能够编译成功,不需要生成编译后的文件时使用)。默认:true
"noEmit": false,
// 当编译有错误的时候是否生成编译文件。默认false
"noEmitOnError": true,
// 是否打开严格模式,如果打开了,其下的alwaysStrict, noImplicitAny, noImplicitThis, strictNullChecks都打开了
"strict": true,
// 指定编译后的文件是否使用严格模式。默认true
"alwaysStrict": true,
// 当变量不指定类型时,类型默认不为any
"noImplicitAny": true,
// 不允许使用隐士的this
"noImplicitThis": true,
// 严格的检查空值
"strictNullChecks": true,
}
}
- 编译TypeScript
1)创建hello.ts
2)编译
# 编译成功后会在生成 hello.js
tcs hello.ts
编译参数
-
-w 监视文件的变化,如果文件有变化,则对文件进行编译
tsc app.ts -w
四、WebPack打包
创建一个项目目录 demo-project
- 初始化npm
npm init -y
会在项目根目录生成package.js配置文件
{
"name": "demo-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
- 安装webpack
npm install -D webpack webpack-cli
如果安装失败可用换用cnpm安装
webpack.config.js配置文件
在项目根目录创建webpack.config.js配置文件
// 引入path模块,用于拼接路径
const path = require('path')
// webpack配置信息
module.exports = {
// 开始模式 development 或 production
mode: "production",
// 入口文件
entry: "./src/index.ts",
// 打包路径
output: {
// 指定打包文件目录
path: path.resolve(__dirname, 'dist'),
// 指定打包后的文件
filename: 'bound.js'
},
// 指定webpack打包时要使用的模块
module: {
// 指定要加载的规则
rules: [
{
// 匹配以.ts结尾的文件
test: /\.ts$/,
// 使用ts-loader进行加载
use: 'ts-loader',
// 要排除的文件
exclude: /node-modules/
}
]
},
// 配置哪些文件可用当作模块来使用
resolve: {
extensions: ['.ts', '.js']
}
}
注意:因示例中使用了ts,因此还需要安装ts-laoder
npm install -D ts-loader
- TypeScript配置文件
在根目录创建TypeScript的配置文件tsconfig.js
tsconfig.js
{
"compilerOptions" {
"module": "ES2015",
"target": "ES2015",
"strict": true
}
}
- 在package.js的script中添加build指令
"scripts": {
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
}
- 执行build命令
npm run build
- 安装webpack插件
-
html-webpack-plugin
该插件会自动生成一个html页面,并引入生成的js文件。
安装命令:
npm install -D html-webpack-plugin
在webpack.config.js中配置
// 引入htmlWebPackPlugin const HtmlWebpackPlugin = require('html-webpack-plugin') // webpack配置信息 module.exports = { // 配置webpack插件 plugins: [ new HtmlWebpackPlugin({ title: '首页', // 页面title filename: 'index.html' // 生成的文件名 template: path.resolve(__dirname, 'src/index.html'), // 模板文件的绝对路径 }) ] })
-
webpack-dev-server
安装
npm install -D webpack-dev-server
在package.json的script中添加启动脚本
"scripts": { "start": "webpack serve --open" }
-
clean-webpack-plugin
npm install -D clean-webpack-plugin
在webpack.config.js中配置
// 引入clean-webpack-plugin const { CleanWebpackPlugin } = require('clean-webpack-plugin') // webpack配置信息 module.exports = { // 配置webpack插件 new CleanWebpackPlugin() })
-
babel
安装
npm install -D @babel/core @babel/preset-env babel-loader core-js
在webpack.config.js中配置
// webpack配置信息 module.exports = { // 指定webpack打包时要使用的模块 module: { // 指定要加载的规则 rules: [ { // 配置loader use: [ // 配置babel { // babel加载器 loader: "babel-loader", // 设置babel options: { // 设置预定义环境 presets: [ [ // 指定环境插件 "@babel/preset-env", // 配置信息 { // 要兼容的目标浏览器 targets: { "chrome": "88" }, // 指定corejs的版本 "corejs": "3", // 使用corejs的方式: "usage"按需加载 "useBuiltIns": "usage" } ] ] } }, // 使用ts-loader进行加载 'ts-loader' ], // 要排除的文件 exclude: /node-modules/ } ] } }