1. 程式人生 > 實用技巧 >TypeScript 學習筆記

TypeScript 學習筆記

相關資訊

視訊地址:https://www.bilibili.com/video/BV1Xy4y1v7S2?from=search&seid=335637898792380515

github:https://github.com/haveadate

碼雲:https://gitee.com/haveadate

email:[email protected]

初識 TS

安裝

# 安裝
npm i typescript -g
# 驗證是否安裝成功
C:\Users\haveadate>tsc -v
Version 4.1.3

初步介紹

  • JavaScript 為基礎構建的語言;
  • 可以在任何支援 JavaScript
    的平臺中執行;
  • 不能被 JS 解析器直接執行(預先需要將 .ts 檔案轉成 .js 檔案);
  • 是一個 JavaScript 的超集;
  • TypeScript 擴充套件了 JavaScript,並添加了型別;

將 .ts 檔案解析成 .js 檔案

tsc xxx.ts

語法

宣告變數並指定型別

// 宣告一個變數 stuName 同時指定它的型別為 string
let stuName: string;
// stuName 的型別設定成了 string,在以後的使用過程中,stuName 的值只能是 string 型別
stuName = '張三';
// 報錯:不能將 number 型別賦值給 string 型別
stuName = 123;
$ tsc index
index.ts:6:1 - error TS2322: Type 'number' is not assignable to type 'string'.

宣告變數並賦值、指定型別

// 宣告一個變數並賦值,同時隱式指定變數型別是 string
let stuName = '張三';
// 丟擲錯誤,型別不匹配
// index.ts:4:1 - error TS2322: Type 'number' is not assignable to type 'string'.
stuName = 123;

指定函式引數型別和返回值型別

// 若型別不匹配,同樣會報錯
function sum(num1: number, num2: number): number {
	return num1 + num2;
}

TS 變數型別

基本型別

string、number、boolean

字面量型別

let num: 10;,此時該變數值只能10,若賦其它值給 num,編譯成 JS 時會報錯;

聯合型別

let gender = 'male' | 'female';,此時 gender 可以為 'male',也可以為 'female';不僅僅是如此,還可以指定變數可以是多個變數型別:let gender = string | boolean;

any

​ 表示任意型別。一個變數指定型別為 any,相當於對該變數關閉了 TS 的型別檢測,若宣告變數(不賦值)不指定型別,則 TS 解析器會自動指定變數的型別為 any(隱式的 any);

unknown

​ 實際上是一個安全的 any。和 any 型別變數相比,unknown 型別的變數同樣可以是任意型別,不過any 型別的變數可以直接賦值給其它型別的變數(編譯不會報錯,但是這樣操作是不安全的),unknown 型別的變數不能直接賦值給其它型別的變數(可通過 型別斷言 賦值給其它型別的變數);

void

​ 空值(undefined),常用於表示函式返回值型別為 null、undefined

never

​ 沒有值,常用於表示函式沒有返回值,即函式不能執行完,執行過程必須中斷(例如丟擲異常);

object

​ 使用方式和其它型別類似,不同的是,在 JS 中陣列、函式、{} 等都是 object 型別,以下程式碼都能成功編譯:

let _var: object;
_var = [1, 2, 3];
_var = {};
_var = (num1, num2) => num1 + num2;

​ 以上程式碼說明在指定型別為 object 時並沒有很好的效果,不過比較有用的是結合字面量型別限制物件的屬性

// obj1 變數儲存的物件有且只能有一個值型別為 string 的 name 屬性
let obj1: { name: string };
obj1 = { name: '張三' };

// 在屬性名後面加上 ?,表示該屬性是可選的(obj2 變數儲存物件包含該屬性是可選的)
let obj2: { name: string; age?: number };
obj2 = { name: '李四' };
obj2 = { name: '李四', age: 22 };

// 通過以下語法,表示 obj3 物件必須包含 name 屬性,其它的隨意
let obj3: { name: string; [propName: string]: any };
obj3 = { name: '王五', age: 22, sex: 'male' };

Fuction

​ 指定變數型別為函式(Function)。類似地,給直接指定變數型別為 Function 也沒有太多意義,最主要的是限制函式的結構,語法和限制 object 類似。

// 類似地,也可以指定函式結構宣告:
// 		語法:(形參:型別, 形參:型別...) => 返回值型別
let sum: (num1: number, num2: number) => number;
// 形參名不一定保持一致,即 num1、num2變數名不要求保持一致
sum = (n1, n2) => n1 + n2;

Array

// 陣列的型別宣告:
// 		型別[]
// 		Array<型別>
let arr1: string[];
arr1 = ['香蕉', '蘋果', '梨'];
let arr2: Array<string>;
arr2 = ['香蕉', '蘋果', '梨']

tuple(元組)

// 元組:固定長度的陣列
// 		語法:[型別, 型別, 型別...]
let tuple1: [string, string, string];
tuple1 = ['20173101', '張三', 'male'];

enum(列舉)

// 定義一個列舉
enum Gender {
	Male = 0,
	Female = 1
}

// 指定變數型別為列舉
let sex: Gender;
sex = Gender.Male;

& 操作符

// & 表示同時
let obj: { name: string } & { gender: number };
obj = { name: '張三', gender: 1 };

類型別名

// 定義類型別名
type stuInfo = { name: string } & { gender: number };
let obj: stuInfo;
obj = { name: '李四', gender: 0 };

型別斷言

語法:

​ 變數 as 型別

​ or

​ <型別>變數

​ 通過型別斷言,可以將 unknown 型別變數賦值給其它型別變數,如果型別 unknown 型別變數值和其它型別不匹配也不會報錯【感覺會埋下隱患】。

let variable: unknown;
variable = '張三';
let uName: string = variable as string;
variable = 22;
let uAge: number = <number>variable;

TS 編譯選項

對某個檔案進行監視

# 對 index.ts 檔案進行監視,-w 就是監聽監視的意思(watch);
# 如果檔案發生改變,就會自動進行編譯
tsc index -w

​ 通過 Ctrl + C命令關閉監視;通過此方式,如果要監視多個檔案,這意味著要開啟多個命令列視窗,對不同的檔案進行監視,這顯然不是很方便的。

對多個檔案進行編譯和監視

​ 若要同時對多個檔案進行編譯、監視,那麼需要新增 TS 編譯器的配置檔案 tsconfig.json 檔案(為 {} 就可以了)並執行以下命令:

# 對指定目錄下的所有 .ts 檔案進行編譯
$ tsc
# 對指定目錄下的所有 .ts 檔案進行監視
$ tsc -w

配置tsconfig.json

tsconfig.json 是 TS 編譯器的配置檔案,TS 編譯器可以根據它的資訊來對程式碼進行編譯【在路徑中,** 表示任意目錄,* 表示任意檔案】。

配置項:

  • "include":用來指定哪些目錄需要被編譯(相對於 tsconfig.json 檔案而言)【Array】;

  • "exclude":用來指定需要排除編譯的目錄【Array】;與 "include"通常只需要使用其中一個就可以了,預設值為 ["node_modules", "bower_components", "jspm_packages"]

  • "extends":定義被繼承的配置檔案;"extends": "./config.base":表示當前配置檔案會自動包含 config目錄下 base.json 中所有配置資訊;

  • "files":指定被編譯檔案的列表,只有需要被編譯的檔案少時才會用到【Array】;

  • "compilerOptions":編譯選項是配置檔案中非常重要也比較複雜的配置選項【Object】;

    • "target":用來指定 TS 被編譯為 ES 的版本;可選值為: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'esnext',預設為 'es3'
    • "module":用來指定要使用的模組化規範;可選值為:'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'esnext'【'es6' <=> 'es2015'】;
    • "lib":用來指定專案中要使用的庫,在前端頁面開發時通常都修改,在使用 Node 時,通常需要修改【可通過亂寫一個執行,通過報錯結果檢視可填寫的值】【Array】;
    • "outDir":用來指定編譯後文件所在目錄;通常為 "./dist"
    • "outFile":將程式碼合併為一個檔案,設定 outFile 後,所有的全域性作用域中的程式碼會合併到同一個檔案中;
    • "allowJs":是否對 JS 檔案進行編譯(特別是在設定編譯輸出目錄時),預設為 false
    • "checkJs":是否檢查 JS 程式碼是否符合 TS 語法規範(型別賦值必須一致),預設為 false
    • "removeComments":編譯時,是否移除註釋,預設為 false
    • "noEmit":不生成編譯後的檔案,即是否只執行編譯,檢查 TS 檔案的語法,不生成 JS 檔案,預設值為 false
    • "noEmitOnError":編譯當有錯誤時,不生成編譯後的檔案,預設值為 false

    編寫程式碼具體的設定:

    • "alwaysStrict":用來設定編譯後的檔案是否使用嚴格模式,預設為 false
    • "noImplicitAny":不允許使用隱式的 any 型別,預設為 false
    • "noImplicitThis":不允許使用不明確型別的 this,預設為 false
    • "strictNullChecks":嚴格地檢查空值,預設為 false
    • "strict":上面 4 個嚴格程式碼檢查的總開關;

webpack 打包 TS 程式碼

安裝 webpack 和 TS 相關包

# 快速生成 package.json 檔案
$ npm init -y

# -D 表示 --save-dev,與之對應的 -S 表示 --save
$ npm i -S webpack webpack-cli typescript ts-loader

新增 webpack 配置檔案 webpack.config.js

// 引入路徑包
const path = require('path')

// webpack 中的所有配置資訊都應該寫在 module.exports 中
module.exports = {
	// 指定入口檔案
	entry: './src/index.ts',
	// 指定打包檔案所在目錄
	output: {
		// 指定打包檔案的目錄
		path: path.resolve(__dirname, 'dist'), // 當前目錄下建立 dist 資料夾
		// 設定打包後的檔名
		filename: 'bundle.js'
	},
	// 指定 webpack 打包時要使用的模組
	module: {
		// 指定要載入的規則
		rules: [{
			// test 指定的是規則生效的檔案
			test: /\.ts$/,
			// 要使用的 loader
			use: 'ts-loader',
			// 要排除的檔案
			exclude: /node_modules/
		}]
	}
}

新增 ts 配置檔案 tsconfig.json

{
	"compilerOptions": {
		"module": "es2015",
		"target": "es2015",
		"strict": true
	}
}

webpack 外掛之 html-webpack-plugin

​ 對於 html-webpack-plugin 外掛,其作用就是根據打包生成的 .js、.css 檔案,自動生成引用這些檔案的 .html 檔案。

安裝

$ npm i html-webpack-plugin -D

基礎配置 (in webpack.config.js)

// ...
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // ...
  // 配置 webpack 外掛
	plugins: [
		new HtmlWebpackPlugin()
	]
}

使用 html 模板 (in webpack.config.js)

// ...
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // ...
  // 配置 webpack 外掛
	plugins: [
		new HtmlWebpackPlugin({
      template: './src/index.html'
    })
	]
}

webpack 伺服器(webpack-dev-server)

webpack-dev-server 能根據程式碼改變而自動重新整理頁面,和 HBuilderX 內建伺服器類似;不過不同的是,就算是修改 TS 檔案後,也能自動監聽並編譯,重新整理網頁。

安裝

$ npm i webpack-dev-server -D

在 package.json 檔案 scripts 屬性中新增如下:

"scripts": {
		"start": "webpack serve --open chrome.exe"
}

​ 使用 npm run start 命令通過 Chrome 瀏覽器執行 webpack 伺服器上的網頁,從而達到修改程式碼,網頁自動響應的效果。

webpack 外掛之 clean-webpack-plugin

​ 事實上,對於 dist 資料夾下打包的檔案,每次打包時都是生成的新檔案覆蓋舊檔案,在某些情況下可能出現舊檔案遺留的情況,這樣不是好。clean-webpack-plugin 外掛的作用就是每次打包檔案時都會先清除掉舊檔案。

安裝:

$ npm i clean-webpack-plugin -D

配置 (in webpack.config.js):

// ...
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  // ...
  // 配置 webpack 外掛
	plugins: [
    // ...
		new CleanWebpackPlugin()
	]
}

webpack 設定引用模組

​ 預設地,在 JS、TS 檔案模組之間的互相引用,webpack 打包時並不認識,通過以下配置,使 webpack 認識到哪些模組是可以引用的 (in webpack.config.js):

// ...
module.exports = {
  // ...
  
  // 設定引用模組
	resolve: {
		extensions: ['.ts', '.js']
	}
}

瀏覽器相容外掛之 babel

​ 對於 TS 來說,本身 tsconfig.json 檔案已經能夠將 TS 編譯成指定版本的 JS 檔案,但是在瀏覽器相容方面,這是遠遠不夠的,需要用到 babel 這個第三方模組。

安裝:

$ npm i @babel/core @babel/preset-env babel-loader core-js -D

配置 (in webpack.config.js):

// 指定 webpack 打包時要使用的模組
module: {
	// 指定要載入的規則
	rules: [{
		// test 指定的是規則生效的檔案
		test: /\.ts$/,
		// 要使用的 loader, babel 同樣也需要對此進行載入
		use: [{
				loader: 'babel-loader', // 指定載入器
				options: { // 設定 babel
					presets: [ // 設定預定義環境
						[
							"@babel/preset-env", // 指定環境外掛
							// 配置資訊
							{
								targets: { // 需要適配的瀏覽器版本
									"ie": "8"
								},
								"corejs": "3", // 指定 corejs 的版本
								"useBuiltIns": "usage" // 使用 corejs 的方式,"usage" 表示按需載入
							}
						]
					]
				}
			},
			'ts-loader' // 相對位置越靠後,越先執行
		],
		// 要排除的檔案
		exclude: /node_modules/
	}]
}

面向物件

例項屬性

// 定義 Person 類
class Person {
	name: string;
	gender: string = 'male';
}

靜態屬性

class Login {
	static State: boolean = false;
}

// 使用
console.log(Login.State)

只讀屬性

// 定義 Person 類
class Person {
	// 有型別自動判斷,不需要寫 name: string 也可以
	readonly name = '張三';
}

​ 值得注意的是:readonly 關鍵字也可以用在靜態屬性上。

例項方法

// 定義 Person 類
class Person {
	// 定義例項屬性
	name: string = '張三';
	gender: string = '男';

	// 定義例項方法
	sayHello() {
		console.log('大家好');
	}
}

const person = new Person();
person.sayHello();

靜態方法

// 定義 Person 類
class Person {
	// 定義例項屬性
	name: string = '張三';
	gender: string = '男';

	// 定義例項方法
	static sayHello() {
		console.log('大家好');
	}
}

Person.sayHello();

建構函式

// 定義 Person 類
class Person {
	name: string;
	age: number;

	// 建構函式
	constructor(name: string, age: number) {
		this.name = name;
		this.age = age;
	}

	introduce() {
		console.log(`大家好,我叫${this.name},今年${this.age}歲。`);
	}
}

const zhangsan = new Person('張三', 22);
zhangsan.introduce(); // 大家好,我叫張三,今年22歲。

繼承

// 定義動物類
class Animal {
	name: string;
	age: number;

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

	sayHello() {
		console.log('動物在叫');
	}
}

// 定義貓類,並繼承動物類
class Cat extends Animal {
	// 重寫 sayHello 方法
	sayHello() {
		console.log('喵喵喵~~~');
	}
}

// 定義狗類,並繼承動物類
class Dog extends Animal {
	// 重寫 sayHello 方法
	sayHello() {
		console.log('汪汪汪~~~');
	}
}

// 例項化阿貓、阿狗
const cat = new Cat('貓咪', 2);
const dog = new Dog('旺財', 4);
console.log(cat); // Cat { name: '貓咪', age: 2 }
console.log(dog); // Dog { name: '旺財', age: 4 }
cat.sayHello(); // 喵喵喵~~~
dog.sayHello(); // 汪汪汪~~~

super 關鍵字

// 定義動物類
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 方法
	sayHello() {
		// 呼叫父類 sayHello 方法
		// super.sayHello();

		console.log('汪汪汪~~');
	}
}

const dog = new Dog('旺財', 2);
dog.sayHello();

super 關鍵字,在子類中表示父類物件;如果子類中沒有寫建構函式,那麼在建立子類物件時,會預設呼叫父類建構函式。

抽象類

// 定義動物抽象類
abstract class Animal {
	name: string;
	constructor(name: string) {
		this.name = name;
	}

	// 定義抽象方法
	// 抽象方法使用 abstract 開頭,沒有方法體
	// 抽象方法只能定義在抽象類中,子類必須對抽象方法進行重寫
	abstract sayHello(): void;
}

// 定義狗類
class Dog extends Animal {
	// 沒有重寫構造方法,預設會呼叫父類構造方法

	// 重寫 sayHello 方法
	sayHello() {
		console.log('汪汪汪~~~');
	}
}

const dog = new Dog('旺財');
dog.sayHello();

​ 抽象類與其它類區別不大,只是不能用來建立物件,只能用來繼承

介面

// 定義基本資訊介面
interface IBaseInfo {
	name: string;
	age: number;

	sayHello(): void;
}

class Dog implements IBaseInfo {
	name: string;
	age: number;

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

	sayHello() {
		console.log('汪汪汪~~~');
	}
}

​ 介面用來定義一個類結構,用來定義一個類應該包含哪些屬性和哪些方法,同時介面也可以當成型別宣告去使用。在定義時,所有的屬性都不能有實際的值,所有的方法都沒有方法體,介面只定義物件的結構而不考慮實際值。

屬性的封裝

​ 和其它高階語言類似,TS 同樣有 public(預設值)、protected、private 關鍵字修飾屬性和方法,對屬性而言,同樣也有 getter、setter,和 C# 尤為相像,是同一個公司設計的語言。getter、setter 使用方式如下:

class Dog {
	private _name: string;

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

	// getter、setter
	get name() {
		return this._name;
	}

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

​ 對於宣告類的結構,還有一種簡便的方式,即可以直接將屬性定義在建構函式中:

class Dog {
	constructor(public name: string, public age: number) {}
}

const dog = new Dog('旺財', 2);
console.log(dog.name, dog.age); // 旺財 2

泛型

// 在定義函式或者類時,如果遇到型別不明確就可以使用泛型
// 泛型指代字母是任意的,並沒有明確規定是 T
function print1(data) {
    console.log(data);
}
print1('hello, typescript'); // 不指定泛型,TS 可以自動對型別進行判斷
print1(123); // 顯示指定泛型,不容易出錯
// 指定多個泛型
function print2(data1, data2) {
    console.log(data1, data2);
}
print2('張三', 22);
// T extends IToString 表示泛型 T (資料 data)必須實現 IToString 介面
function print3(data) {
    console.log(data);
    data.introduce();
}
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    introduce() {
        console.log(`你好,我叫${this.name},今年${this.age}歲。`);
    }
}
const lisi = new Person('李四', 20);
print3(lisi);
// 在類中同樣也可以使用泛型
class MyClass {
    constructor(info) {
        this.info = info;
    }
    getType() {
        console.log(typeof this.info);
    }
}
const mc = new MyClass('張三');
mc.getType();

貪吃蛇案例

專案環境搭建(基於 webpack)

新增 package.json (使用 Node 管理包)

# 快速生成 package.json
$ npm init -y

安裝 webpack 、ts 相關包

$ npm i webpack webpack-cli typescript ts-loader -S

新增 tsconfig.json

{
	"compilerOptions": {
		"module": "es6",
		"target": "es3",
		"removeComments": true,
		"strict": true,
		"noEmitOnError": true
	}
}

安裝 html-webpack-plugin、clean-webpack-plugin 等 webpack 外掛

$ npm i html-webpack-plugin clean-webpack-plugin webpack-dev-server -D

安裝專案相容工具 babel

$ npm i @babel/core @babel/preset-env babel-loader core-js -D

新增 webpack.config.js,對以上內容配置如下

// 引入路徑包
const path = require('path')
// 引入 webpack 外掛
const HtmlWebpackPlugin = require('html-webpack-plugin') // 以某個 .html 檔案為模板,生成引用 TS 生成 .js 檔案的 .html 檔案
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 每次 TS 生成 dist 資料夾時,都清空 dist 資料夾下的檔案, 做到舊結果不干擾結果

// webpack 中的所有配置資訊都應該寫在 module.exports 中
module.exports = {
	// 指定入口檔案
	entry: './src/index.ts',
	// 指定打包檔案所在目錄
	output: {
		// 指定打包檔案的目錄
		path: path.resolve(__dirname, 'dist'), // 當前目錄下建立 dist 資料夾
		// 設定打包後的檔名
		filename: 'bundle.js',
		// 告訴 webpack 不使用箭頭函式
		environment: {
			arrowFunction: false
		}
	},
	// 指定 webpack 打包時要使用的模組
	module: {
		// 指定要載入的規則
		rules: [
			{
				// test 指定的是規則生效的檔案
				test: /\.ts$/,
				// 要使用的 loader, babel 同樣也需要對此進行載入
				use: [
					{ // 【再將 JS 程式碼通過 babel 轉換】
						loader: 'babel-loader', // 指定載入器
						options: { // 設定 babel
							presets: [ // 設定預定義環境
								[
									"@babel/preset-env", // 指定環境外掛
									// 配置資訊
									{
										targets: { // 需要適配的瀏覽器版本
											"ie": "8"
										},
										"corejs": "3", // 指定 corejs 的版本
										"useBuiltIns": "usage" // 使用 corejs 的方式,"usage" 表示按需載入
									}
								]
							]
						}
					},
					'ts-loader' // 相對位置越靠後,越先執行【先將 TS 程式碼轉換】
				],
				// 要排除的檔案
				exclude: /node_modules/
			},
		]
	},
	// 配置 webpack 外掛
	plugins: [
		new HtmlWebpackPlugin({
			template: './src/index.html'
		}),
		new CleanWebpackPlugin()
	],
	// 設定引用模組
	resolve: {
		extensions: ['.ts', '.js']
	},
	mode: "production" // 打包的環境,"development" | "production" | "none",開發|實際運用|無 
}

​ 當然,這只是對於 webpack 打包 TS 而言,還有其它內容暫未新增。

具體的,還是參照本文件的 snake 案例中 webpack.config.js 檔案配置。

新增 less 處理模組

$ npm i less less-loader css-loader style-loader -D

新增 css 相容性處理(postcss)

$ npm i postcss postcss-loader postcss-preset-env -D