課程大綱
- 搭建 TypeScript 開發環境。
- 掌握 TypeScript 的基礎類型,聯合類型和交叉類型。
- 詳細類型斷言的作用和用法。
- 掌握 TypeScript 中函數、類的類型聲明方式。
- 掌握類型別名、接口的作用和定義。
- 掌握泛型的應用場景,熟練應用泛型。
- 靈活運用條件類型、映射類型與內置類型。
- 創建和使用自定義類型。
- 理解命名空間、模塊的概念已經使用場景。
- 詳細 TS 中的類型保護、裝飾器。
- 巧妙運用類型推導和化簡代碼。
- 深入理解 TypeScript 類型層級系統。
- 詳細函數的變與逆變。
- 深入研究 infer 的用法與技巧。
- 詳細探究符號類型。
- 靈活編寫與運用類型聲明文件,擴展 TypeScript 的類型系統。
- 掌握 TS 中類型文件查找規則。
- 深刻使用裝飾器,運用反射元數據擴展裝飾器的功能,實現控制反轉、依賴注入。
- 深度解析 TSConfig 配置文件。
- TS 類型體操
ts基礎
目前大部分企業的中大型前端項目都採用了 Typescript,那麼為甚麼我們需要它?
JavaScript 的核心特點就是靈活,但隨著項目規模的增大,靈活反而增加開發者的心智負擔。例如在代碼中一個變量可以被賦予字符串、布爾、數字,甚至是函數,這樣就充滿了不確定性。而且這些不確定性可能需要在代碼運行的時候才能被發現,所以我們需要類型的約束。
當然不可否認的是有了類型的加持多少會影響開發效率,但是可以讓大型項目更加健壯
- Typescript 更像後端 JAVA,讓 JS 可以開發大型企業應用;
- TS 提供的類型系統可以幫助我們在寫代碼時提供豐富的語法提示;
- 在編寫代碼時會對代碼進行類型檢查從而避免很多線上錯誤;
越來越多的項目開始擁抱 TS 了,典型的 Vue3、Pinia、第三方工具庫,後端 NodeJS 等。我們也經常為以上編程擁有了更好的支持去編寫 .d.ts 文件。
1. ts是一門語言
- ts 是 js 的一個超集,擴展了語法添加了靜態類型支持以及其他一些新特性
環境配置
全局安裝
npm install typescript -g
# 我們可以用tsc來把ts轉成js
# 我們要把甚麼轉換成甚麼,所以我們要先生成一個配置
# 在當前項目下創建一個配置文件tsc --init
# --watch
tsc --init
# 插件code runner這個插件 需要額外安裝一個ts-node的包
npm init -y
npm install typescript rollup -D
npm install rollup-plugin-typescript -D
npm i @rollup/plugin-node-resolve rollup-plugin-serve -D
# rollup.config.js
# error lens 插件
學習ts就是學習類型,ts的類型分類(DOM,Promise,原始方法)基礎類型,
ts中:後面的都是類型 = 後面的都是值
ts 一切從安全的角度出發,看能不能賦值,就看安全不安全
ts 還有自動的類型推導,不用見到變量就寫類型,而是推斷的不正確,我們才需要自己編寫
基礎類型
let abc: string = 'Hello World';
console.log(abc);
let name = 'John Doe'; // 當前模塊中,作用域隔離
let age = 20;
let handsome: boolean = true;
// 原始類似標識都是小寫的,而類名都是大寫的它描述的實例(都是對象)
// 類型註解:告訴TS變量的類型
// 類型推斷:TS會自動嘗試分析變量的類型
// 類型註解優先級高於類型推斷
// 類型註解:變量名: 類型 = 值
// 類型推斷:變量名 = 值
let s1: string = 'Hello World';
let s2: String = new String('Hello World');
let s3: String = 'Hello World'; // 會自動轉換為對象
// 數組聲明
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
// 元組:固定長度的數組
let tuple: [string, number] = ['Hello', 123];
// 元組的越界問題
// tuple[2] = 'World'; // 會報錯
let tuple2: readonly [string, number, boolean] = ['Hello', 123, true];
// tuple2[0] = 'World'; // 會報錯
export { name };
枚舉(自帶類型的對象)
let abc: string = 'Hello World';
console.log(abc);
let name = 'John Doe'; // 當前模塊中,作用域隔離
let age = 20;
let handsome: boolean = true;
// 原始類似標識都是小寫的,而類名都是大寫的它描述的實例(都是對象)
// 類型註解:告訴TS變量的類型
// 類型推斷:TS會自動嘗試分析變量的類型
// 類型註解優先級高於類型推斷
// 類型註解:變量名: 類型 = 值
// 類型推斷:變量名 = 值
let s1: string = 'Hello World';
let s2: String = new String('Hello World');
let s3: String = 'Hello World'; // 會自動轉換為對象
// 數組聲明
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
// 元組:固定長度的數組
let tuple: [string, number] = ['Hello', 123];
// 元組的越界問題
// tuple[2] = 'World'; // 會報錯
let tuple2: readonly [string, number, boolean] = ['Hello', 123, true];
// tuple2[0] = 'World'; // 會報錯
// 枚舉 自帶類型的對象,自動增長
enum USER_ROLE {
USER,
ADMIN = 6,
MANAGER,
OTHER = 'other', // 異構枚舉
}
/*
USER = 0,
ADMIN = 6,
MANAGER = 7,
*/
console.log(USER_ROLE.USER); // 0
// 如果不需要對象可以直接採用常量枚舉
const enum USER_ROLE2 {
USER,
ADMIN,
MANAGER,
}
console.log(USER_ROLE2.USER); // 0
export { name };
null 和 undefined
// 嚴格模式下,不允許使用 any 類型
// nul 和 undefined 是任何類型的子類型
let x: number | null | undefined = 1;
// let y: number = undefined; // 會報錯
// let z: number = null; // 會報錯
// strict:false 可以使用 any 類型
never類型
永遠不
// never 類型 它是任何類型的子類型,也可以賦值給任何類型
// never 類型的變量只能被賦值為 never 類型
function fn1(): never {
throw new Error('報錯了');
}
// 類型保護,保障程序的不缺失
// never 是永遠到達不了的終點
function fn2(): never {
while (true) {}
}
function validate(x: never) {
console.log(x);
}
// 針對不同的類型做不同的處理
function fn3(x: number | string | boolean) {
// 類似有收斂的作用
if (typeof x === 'number') {
console.log(x.toFixed(2));
return;
}
if (typeof x === 'string') {
console.log(x.trim());
return;
}
if (typeof x === 'boolean') {
console.log(x.valueOf());
return;
}
//守衛語句
validate(x); // 如果類型不匹配,會報錯 這個邏輯應該是永遠不會執行的所以上在還是有沒覆蓋到的邏輯(要添加boolean類型的判斷)
}
object的類型
// object 類型 object {} Object
let obj: object = { name: 'John Doe' };
// obj.name = 'Jane Doe'; // 會報錯
// 小寫的 object 是對象類型,大寫的 Object 是對象的構造函數
!號非空斷言
聯合類型
// 一般我們會基於額外的類型來擴展定義類型 有點類似枚舉
type Direction = "up" | "down" | "right" | "left"
let direction :Direction = "left"
// type和interface的區別
type women =
| {
wealthy: true;
waste: string;
}
| {
wealthy: false;
norality: string;
};
// 是富人一定不是節儉的人,是節儉的人一定不是富人
可以利用聯合類型來做到屬性之間的互斥
斷言
斷言有可能出問題,出問題後果自負
type women =
| {
wealthy: true;
waste: string;
}
| {
wealthy: false;
norality: string;
};
// 是富人一定不是節儉的人,是節儉的人一定不是富人(類比約定,不嚴謹)
// 斷言 把某個類型斷言為為已經存在的一種類型
let ele = document.getElementById('app');
ele!.innerHTML = 'Hello World'; // 繞過TS的類型檢查 ele?.innerHTML = 'Hello World'; // 可選鏈 操作符(取值)
// false || true = true
// false && true = false
// null ?? 'Hello World' = 'Hello World' // 空值合併操作符
// as 類型斷言 可以強制把某個類型斷言為另外一個類型 as 類型
// 雙重斷言,我們可以把一個值斷言成為 any 類型,然後再斷言成為另外一個類型
// 類型斷言不是類型轉換,只是告訴TS編譯器,這個值是甚麼類型
// 類型斷言只在編譯階段起作用,不會影響真實的值
函數
// 函數類型
// 函數聲明 function定義 函數表達式
function sum(a: number, b: number): number {
return a + b;
}
let sum2 = function (a: number, b: number): number {
return a + b;
};
let sum3: (a: number, b: number) => number = function (a, b) {
return a + b;
};
// 類型比較長可以寫成下面
type Sum = (a: number, b: number) => number;
let sum4: Sum = function (a, b) {
return a + b;
};
// 會根據上下文推導賦予值的類型
let sum5: Sum = (a, b) => a + b;
// 常見的類型推導的方式
// 從右向左 根據賦值進行推導
let name = "jianz"
let age = 20
// 根據返回值進行類型推導
// void 表示不關心返回的具體類型(不校驗)
// 可選參數(?)
let sum6 = (a: string = 'a', b?: string): string => {
return a + b;
};
// 對象類型
let person = {
name: 'John Doe',
age: 20,
};
// ts中的this類型需要手動指定,默認是函數的第一個參數
function getVal(this: typeof person, key: keyof typeof person) {
return this[key]; // this指向調用者,必須要有類型斷言才能使用{
}
let r = getVal.call(person, 'name');
console.log(r);
重載
// 重載 (一般是有限操作), 它只是一個偽重載,只是一個類型的重載
function toArray(value: number): number[];
function toArray(value: string): string[];
function toArray(value: number | string) {
if (typeof value === 'string') {
return value.split('');
} else {
return value
.toString()
.split('')
.map((item) => parseInt(item));
}
}
let ar = toArray(123);
console.log(ar);
let ar1 = toArray('123');
console.log(ar1);
類(本身就可以充當類型)
它可以描述實例(類類型)
// 類
class Circle {
// 類的屬性 ts中所有的屬性都要先聲明再使用
PI = 3.14;
// 類的構造函數
constructor(public radius: number) {
this.radius = radius;
}
// 類的方法
area() {
return this.PI * this.radius ** 2;
}
}
// 還有一種寫法
class Animals {
constructor(public name: string) {
// this.name = name; // 賦值也可以省去
}
eat() {
console.log(this.name + ' is eating');
}
}
const a1 = new Animals('dog');
a1.eat();
// 訪問修飾
// public 公共的 默認的
// private 私有的 只能在類的內部訪問
// protected 受保護的 只能在類的內部和子類中訪問
// readonly 只讀的 初始化之後不能修改
// static 靜態的
// abstract 抽象的
// #號開頭的屬性是私有屬性 只能在類的內部訪問 不能在類的外部訪問(這個是一個js語法)
// 不能new
class Singleton {
private static instance: Singleton;
private constructor() {}
static getInstance() {
if (!this.instance) {
this.instance = new Singleton();
}
return this.instance;
}
}
let sg1 = Singleton.getInstance();
// 抽象類不能new
// 不能實例化,只能被繼承
// 抽象類中可以擁有具體賓實現
abstract class Animal {
constructor(public name: string) {}
abstract eat(): void; // 原型上的方法 默認採用這種方法更好些
abstract play:()=>void; // 實例上的方法 ts中不做區分,但是一般的是實例方法
}
接口
接口可以理解為對行為的抽象(沒有具體的實現) ,可以用於描述 對象,函數,類,混合類型
// 接口是對行為的抽象,而抽象類是對類的抽象
// 用接口描述函數
// interface 描述的是形狀或結構
interface IFullname {
firstname: string;
lastname: string;
}
type IFn = (obj: IFullname) => string;
let fn: IFn = ({ firstname, lastname }: IFullname): string => {
return firstname + lastname;
};
fn({ firstname: 'John', lastname: 'Doe' });
// 1. 如果只是用來描述結構我們採用interface
// 2. 如果涉及到聯合類型,則只能使用type
// 3. type不能擴展,interface可以擴展
// 4. type不能重名,interface可以重名
// 5. type可以使用typeof獲取實例的類型,interface不行
// 6. type可以使用keyof獲取key的類型,interface不行
// 7. type可以使用infer獲取函數返回值的類型,interface不行
// 8. type可以使用extends實現交叉類型,interface不行
// 9. type可以使用條件類型,interface不行
// 10. type可以使用映射類型,interface不行
// 11. type可以使用索引訪問操作符,interface不行
// 可以用接口描述混合類型
interface IFn1{
(a:number,b:number):number;
prop1:string;
prop2:number;
}
// let fn11:IFn1 = (a,b)=>a+b; // 這樣就報錯了,因為fn11沒有prop1和prop2屬性且let定義可以重新被賦值
const fn11:IFn1 = (a,b)=>a+b;
fn11.prop1 = 'Hello';
fn11.prop2 = 123;
// 一般情況下我們使用interface來描述對象,如果需要使用聯合類型,交叉類型,元組,只能使用type
interface IVeg {
readonly color: string;
size: number;
taste?: 'sweet' | 'sour' | 'bitter'; // 可選屬性
}
let veg: IVeg = {
color: 'red',
size: 20,
};
// veg.color = 'green'; // 會報錯
如果對象中的屬性,多於接口定義的,
- 可以採用斷言來賦值
- 可以基於接口的特性寫一個同名的接口
- 產生新類型,通過繼承原有屬性的方式
- 類型兼容
[key:string]:any任意類型擴展
interface ICar {
color: string;
a: 1;
b: 2;
}
type ValueOf = ICar[keyof ICar]; // 1 | 2 | string
主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/4409