前言
最近做東西都在用ts,有時候寫比較複雜的功能,如果不熟悉,類型寫起來還是挺麻煩的。有這樣一個功能,在這裏,我們就不以我們現有的業務來舉例了,我們還是已Animal
舉例,來說明場景。通過一個工廠來創建不同的動物實例。在這裏我們藉助泛型來實現類型的約束和動態推到指定類型。
基礎類型準備
- 用一個枚舉來定義
Animal
的類型
enum EAnimalType {
dog = 'dog',
cat = 'cat',
bird = 'bird',
}
- 定義不同類型的動物有不同的能力類型
type Dog = {
/** 大叫 */
shoutLoudly: () => void;
}
type Cat = {
say: () => void;
}
type Bird = {
/** 飛 */
fly: () => void;
}
- 定義一個動物的映射類型
type AnimalMap = {
[EAnimalType.dog]: Dog;
[EAnimalType.cat]: Cat;
[EAnimalType.bird]: Bird;
}
最終使用的方式
/**
* 定義一個工廠,用來創建具體動物的實例
* @returns 返回動物的實例
*/
function createAnimalFactory<T extends EAnimalType>(): IAnimal<T> {
// TODO 根據業務具體實現
return {} as IAnimal<T>;
}
// 根據泛型創建狗狗的實例
const dog = createAnimalFactory<EAnimalType.dog>();
dog.shoutLoudly();
// 根據泛型創建鳥的實例
const bird = createAnimalFactory<EAnimalType.bird>();
bird.fly()
基於Interface
的實現 (失敗了)
- 接着我們創建一個
interface
來定義動物基礎接口
export interface IAnimal<T extends EAnimalType> extends IAnimalExtra<T> {
id: number; // 編號
name: string; // 名稱
type: T; // 類型
}
我們看到IAnimal
接口繼承了IAnimalExtra
接口,我們想的是通過泛型T
來動態推導出真實的類型。讓我們來看看IAnimalExtra
接口怎麼寫
- 寫
IAnimalExtra
接口
export type IAnimalExtra<T extends EAnimalType> {
[c in keyof AnimalMap[T]]: AnimalMap[T][c];
}
我們這樣寫,發現調試控制檯報了很多錯,具體分析了下錯誤,接口不支持這種功能。接着我們嘗試,改成type
試一下。
- 最後用
type
去替代IAnimalExtra
export type IAnimalExtra<T extends EAnimalType> = {
[c in keyof AnimalMap[T]]: AnimalMap[T][c];
}
我們用type
,果然不不錯了,證明我們的思路是對的。乍一看,寫的怎麼複雜[c in keyof AnimalMap[T]]: AnimalMap[T][c];
不要怕,我們先具體分析一下這段代碼,就很好理解了。
- 先看
AnimalMap[T]
,可以理解從AnimalMap
類型中獲取對應的類型,近似js中從對象取值 keyof
接受一個Object,生成Object的key的字符串的union(聯合)in
可以遍歷枚舉類型,類似 for...in
整體的功能就是根據泛型T,獲取AnimalMap
中的某個類型,遍歷。之後我們專門寫篇文章,介紹下這塊相關的內容。
extends IAnimalExtra<T>
報錯了
在我們最終認爲可以的情況下,發現有報錯了,內容爲【接口只能擴展對象類型或對象類型與靜態已知成員的交集】
所有內容都基於type
實現
在我們嘗試了多次之後,發現Interface
怎麼也滿足不了需求,接着我們都換成type去試試。
export type IAnimal<T extends EAnimalType> = IAnimalExtra<T> & {
id: number; // 編號
name: string; // 名稱
type: T; // 類型
}
這裏我們用了&
交叉類型類合併接口的類型。
換成type之後,已能完全滿足我們的需求,能根據泛型推斷出我們想要的類型。
完整Demo
/**
* 動物枚舉
*/
export enum EAnimalType {
dog = 'dog',
cat = 'cat',
bird = 'bird',
}
type Dog = {
/** 大叫 */
shoutLoudly: () => void;
}
type Cat = {
say: () => void;
}
type Bird = {
/** 飛 */
fly: () => void;
}
export type AnimalMap = {
[EAnimalType.dog]: Dog;
[EAnimalType.cat]: Cat;
[EAnimalType.bird]: Bird;
}
export type IAnimalExtra<T extends EAnimalType> = {
[c in keyof AnimalMap[T]]: AnimalMap[T][c];
}
export type IAnimal<T extends EAnimalType> = IAnimalExtra<T> & {
id: number; // 編號
name: string; // 名稱
type: T; // 類型
}
/**
* 定義一個工廠,用來創建具體動物的實例
* @returns 返回動物的實例
*/
function createAnimalFactory<T extends EAnimalType>(): IAnimal<T> {
// TODO 根據業務具體實現
return {} as IAnimal<T>;
}
// 根據泛型創建狗狗的實例
const dog = createAnimalFactory<EAnimalType.dog>();
dog.shoutLoudly();
// 根據泛型創建鳥的實例
const bird = createAnimalFactory<EAnimalType.bird>();
bird.fly();
結束語
最近深度使用ts中,有一些感觸,用好類型,前期看着比較費時,但隨着項目的迭代,業務的複雜,對我們後期幫助還是很大的。小夥伴,你們在項目中用ts了嗎?
如果你覺得該文章不錯,不妨
1、點贊,讓更多的人也能看到這篇內容
2、關注我,讓我們成爲長期關係
3、關注公衆號「前端有話說」,裏面已有多篇原創文章,和開發工具,歡迎各位的關注,第一時間閱讀我的文章