好好好,能讓 DeepSeek 思考 266 秒,還得是你 TypeScript 類型體操 | 類型系統(tǒng)循環(huán)依賴
大晚上突發(fā)奇想,要鍛煉身體。
做個(gè)什么運(yùn)動(dòng)呢?要選個(gè)室內(nèi)的、適合我這個(gè)小小出租屋的,最好還是有點(diǎn)樂(lè)趣和挑戰(zhàn)性的。
那就做個(gè)體操吧!對(duì), TypeScript 類型體操!
咱第一次做這個(gè)體操,于是想了個(gè)“簡(jiǎn)單點(diǎn)”的場(chǎng)景,練練手。
我的需求是這樣的:
- 有兩個(gè)接口, IEncryptionAlgorithm 和 IEncryptionAlgorithmConfig ,分別是加密算法的接口和配置接口。
- 還有一個(gè) EncryptionService<T, U> , T 是加密算法的接口, U 是配置接口。
// 加密算法配置接口
export interface IEncryptionAlgorithmConfig { }
// 加密算法接口
export interface IEncryptionAlgorithm<T extends IEncryptionAlgorithmConfig> { }
// 加密服務(wù)
export class EncryptionService<
TAlgorithm extends IEncryptionAlgorithm<TConfig>,
TConfig extends IEncryptionAlgorithmConfig
> { }
這樣,用就可以自己定義加密算法和配置了。比如用戶可以實(shí)現(xiàn)一個(gè) AESGCMEncryption 加密算法,和一個(gè) AESGCMEncryptionConfigFromEnv 配置類。
export interface IAESGCMEncryptionConfig extends IEncryptionAlgorithmConfig {
password: string;
salt: Uint8Array;
iv: Uint8Array;
}
export class AESGCMEncryption implements IEncryptionAlgorithm<IAESGCMEncryptionConfig> { }
export class AESGCMEncryptionConfigFromEnv implements IAESGCMEncryptionConfig { }
但是問(wèn)題來(lái)了,對(duì)于 AESGCMEncryption 加密算法,它的配置類必須是 IAESGCMEncryptionConfig ,否則就無(wú)法正確獲取 password 、 salt 、 iv 這三個(gè)屬性。
然而,在目前的類型聲明中,這一點(diǎn)是無(wú)法做到的:
// encryptionService 類型是正確的,沒(méi)問(wèn)題
let encryptionService: EncryptionService<AESGCMEncryption, AESGCMEncryptionConfigFromEnv>;
// encryptionService2 也是正確的,類型系統(tǒng)沒(méi)有報(bào)錯(cuò)
// 但是應(yīng)該報(bào)錯(cuò),因?yàn)?IEncryptionAlgorithmConfig 沒(méi)有 password 、 salt 、 iv 這三個(gè)屬性
// IEncryptionAlgorithmConfig 并不適合 AESGCMEncryption 加密算法
let encryptionService2: EncryptionService<AESGCMEncryption, IEncryptionAlgorithmConfig>;
那么我當(dāng)然是求助我的體操教練 deepseek-r1 。
教練說(shuō):這個(gè)問(wèn)題好解決,但是你先去洗個(gè)澡再回來(lái)。
我說(shuō):老師我看您面色如鐵,怎么好像有點(diǎn)猶豫?
教練說(shuō):幾百年沒(méi)見(jiàn)過(guò)玩 TypeScript 類型體操的了!你也就是走運(yùn),今天我有心情理你!
第一次問(wèn)答 DeepSeek 給出的解決方案:
// 新增類型工具:從算法類中提取配置類型
export type ConfigType<T> = T extends IEncryptionAlgorithm<infer C> ? C : never;
export class EncryptionService<
TAlgorithm extends IEncryptionAlgorithm<TConfig>,
TConfig extends IEncryptionAlgorithmConfig = ConfigType<TAlgorithm>
> { }
很遺憾,并沒(méi)有解決問(wèn)題。
再次向教練描述情況后,教練終于給出了正確方案:
export type EncryptionConfigType<T> = T extends IEncryptionAlgorithm<infer C> ? C : never;
export class EncryptionService<
TAlgorithm extends IEncryptionAlgorithm<any>,
TConfig extends EncryptionConfigType<TAlgorithm> = EncryptionConfigType<TAlgorithm>
> { }
這次終于解決了問(wèn)題。
最后,教練授之以漁。
DeepSeek-R1
以下是關(guān)于這個(gè) TypeScript 類型問(wèn)題的原理剖析,通過(guò)循序漸進(jìn)的分析來(lái)理解背后的類型系統(tǒng)邏輯:
第一章:基礎(chǔ)場(chǎng)景分析
1.原始問(wèn)題
我們有一個(gè)加密服務(wù)類 EncryptionService,它需要兩個(gè)泛型參數(shù):
class EncryptionService<
TAlgorithm extends IEncryptionAlgorithm<TConfig>,
TConfig extends IEncryptionAlgorithmConfig
> {}
期望實(shí)現(xiàn):
- 當(dāng) TAlgorithm 是 AESGCMEncryption 時(shí)
- TConfig 必須 是 IAESGCMEncryptionConfig
- 不能 使用其他類型(如基礎(chǔ)接口 IEncryptionAlgorithmConfig)
2.直觀嘗試
用戶最初嘗試這樣定義:
class EncryptionService<
TAlgorithm extends IEncryptionAlgorithm<TConfig>,
TConfig extends IEncryptionAlgorithmConfig = ConfigType<TAlgorithm>
> {}
期望:
- TConfig 自動(dòng)從 TAlgorithm 中提取配置類型。
- 實(shí)現(xiàn)類型關(guān)聯(lián)約束。
但發(fā)現(xiàn)以下用法沒(méi)有報(bào)錯(cuò):
let service: EncryptionService<AESGCMEncryption, IEncryptionAlgorithmConfig>; // 不報(bào)錯(cuò) ?
第二章:類型系統(tǒng)的局限
1.循環(huán)依賴問(wèn)題
當(dāng)這樣定義時(shí):
TAlgorithm extends IEncryptionAlgorithm<TConfig>
TConfig extends ConfigType<TAlgorithm>
實(shí)際上創(chuàng)建了循環(huán)依賴:
- 要確定 TAlgorithm 需要先知道 TConfig。
- 要確定 TConfig 又需要先知道 TAlgorithm。
TypeScript 無(wú)法解析這種循環(huán)依賴,會(huì)觸發(fā)錯(cuò)誤:
Type parameter 'TConfig' has a circular constraint.ts(2313)
2.類型推導(dǎo)的妥協(xié)
當(dāng)使用:
TConfig extends IEncryptionAlgorithmConfig = ConfigType<TAlgorithm>
實(shí)際上:
- TConfig 的約束是寬泛的 IEncryptionAlgorithmConfig
- 默認(rèn)值使用 ConfigType<TAlgorithm> 推導(dǎo)
結(jié)果:
- 顯式指定 TConfig 時(shí)(如 IEncryptionAlgorithmConfig)。
- 只要滿足 IEncryptionAlgorithmConfig 的約束就能通過(guò)檢查。
- 失去了與 TAlgorithm 的嚴(yán)格關(guān)聯(lián)。
第三章:突破循環(huán)依賴
1.關(guān)鍵思路轉(zhuǎn)變
我們需要:
- 解除 TAlgorithm 和 TConfig 的直接循環(huán)。
- 通過(guò)第三方類型工具建立間接關(guān)聯(lián)。
2.引入類型提取工具
定義條件類型:
type EncryptionConfigType<T> =
T extends IEncryptionAlgorithm<infer C> ? C : never;
這個(gè)類型的作用:
- 如果 T 是實(shí)現(xiàn)了 IEncryptionAlgorithm<C> 的類。
- 則提取出配置類型 C。
3.重構(gòu)泛型參數(shù)
class EncryptionService<
TAlgorithm extends IEncryptionAlgorithm<any>, // 臨時(shí)放寬約束
TConfig extends EncryptionConfigType<TAlgorithm> // 關(guān)鍵約束
> {}
4.分步解析
當(dāng)用戶這樣使用時(shí):
type Test1 = EncryptionConfigType<AESGCMEncryption>;
// 等價(jià)于 IAESGCMEncryptionConfig
type Test2 = EncryptionConfigType<string>; // 得到 never
此時(shí)泛型參數(shù)的邏輯:
- 先確定 TAlgorithm 是某個(gè)加密算法類
- 通過(guò) EncryptionConfigType<TAlgorithm> 提取其配置類型
- 要求 TConfig 必須匹配該配置類型
第四章:類型推導(dǎo)過(guò)程
1.合法用法分析
let service: EncryptionService<AESGCMEncryption>;
推導(dǎo)過(guò)程:
- TAlgorithm = AESGCMEncryption
- TConfig = EncryptionConfigType<AESGCMEncryption> → IAESGCMEncryptionConfig
- 驗(yàn)證 AESGCMEncryption 是否滿足 IEncryptionAlgorithm<any> → 滿足
2.非法用法分析
let service: EncryptionService<AESGCMEncryption, IEncryptionAlgorithmConfig>;
推導(dǎo)過(guò)程:
- TAlgorithm = AESGCMEncryption
- TConfig = IEncryptionAlgorithmConfig
- 檢查 IEncryptionAlgorithmConfig 是否滿足 EncryptionConfigType<AESGCMEncryption> → 等價(jià)于檢查 IEncryptionAlgorithmConfig extends IAESGCMEncryptionConfig → 失敗
第五章:關(guān)鍵差異對(duì)比
1.原始方案的問(wèn)題
class Service<TAlgo extends IEncryptionAlgorithm<TConfig>, TConfig...>
建立了:
TAlgo → TConfig → TAlgo → TConfig...
形成閉環(huán),類型系統(tǒng)無(wú)法展開(kāi)推導(dǎo)
2.最終方案的優(yōu)勢(shì)
class Service<TAlgo extends IEncryptionAlgorithm<any>, TConfig...>
建立:
TAlgo → (通過(guò) EncryptionConfigType) → TConfig
形成單向推導(dǎo)鏈,打破循環(huán)
第六章:技術(shù)總結(jié)
1.核心知識(shí)點(diǎn)
條件類型(Conditional Types):
T extends U ? X : Y
允許根據(jù)類型關(guān)系進(jìn)行類型推導(dǎo)
類型推斷(infer):
T extends IEncryptionAlgorithm<infer C> ? C : never
從泛型類型中提取嵌套類型
泛型約束解耦:通過(guò)中間類型工具打破循環(huán)依賴
2. 設(shè)計(jì)原則
關(guān)注點(diǎn)分離:
- 算法類的定義與其配置類型解耦
- 通過(guò)類型工具建立關(guān)聯(lián)
推導(dǎo)優(yōu)于顯式聲明:
- 優(yōu)先讓類型系統(tǒng)自動(dòng)推導(dǎo)
- 顯式聲明時(shí)進(jìn)行嚴(yán)格檢查
第七章:擴(kuò)展思考
1.如果增加新加密算法
class RSAEncryption implements IEncryptionAlgorithm<IRSAConfig> {}
類型系統(tǒng)會(huì)自動(dòng)保證:
let service: EncryptionService<RSAEncryption>; // TConfig = IRSAConfig
let service: EncryptionService<RSAEncryption, IAESGCMEncryptionConfig>; // 錯(cuò)誤 ?
2.為什么使用 any 是安全的
在:
TAlgorithm extends IEncryptionAlgorithm<any>
中:
- any 僅用于臨時(shí)繞過(guò)類型檢查。
- 實(shí)際約束由 TConfig extends EncryptionConfigType<TAlgorithm> 保證。
- 最終使用時(shí)類型系統(tǒng)仍會(huì)驗(yàn)證實(shí)際類型。
通過(guò)這種層層遞進(jìn)的分析,我們可以理解 TypeScript 類型系統(tǒng)在處理復(fù)雜泛型關(guān)系時(shí)的行為模式,以及如何通過(guò)合理的類型工具設(shè)計(jì)來(lái)實(shí)現(xiàn)嚴(yán)格的類型關(guān)聯(lián)約束。