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