戰(zhàn):從零實(shí)現(xiàn) TypeScript 內(nèi)置類型 Exclude<T, U>)
示例工程【免費(fèi)下載鏈接】type-challengesCollection of TypeScript type challenges with online judge項(xiàng)目地址https://gitcode.com/GitHub_Trending/ty/type-challenges點(diǎn)擊查看免費(fèi)下載導(dǎo)讀本文以 type-challenges 題庫第 00043 題Exclude的日文/中文/英文題面為骨架講解如何在不借助內(nèi)置ExcludeT, U的情況下用條件類型與分布式行為實(shí)現(xiàn)從聯(lián)合類型T中排除可賦值給U的類型。讀完本文你將掌握聯(lián)合類型、條件類型分布式求值的底層原理并能夠獨(dú)立通過倉庫中的類型級測試用例為后續(xù)處理Omit、Pick等聯(lián)合類型操作打下基礎(chǔ)。題目背景內(nèi)置類型 Exclude 是什么ExcludeT, U是 TypeScript 標(biāo)準(zhǔn)庫中的一個(gè)內(nèi)置類型工具其作用正如倉庫中 README.ja.md日文題面與 README.zh-CN.md中文題面所述從聯(lián)合類型T中排除U中可賦值的類型來構(gòu)造一個(gè)新的類型原文檔英文原文Exclude fromTthose types that are assignable toU。題目給出的示例為type Result MyExcludea | b | c, a // b | c也就是說題目要求我們自己實(shí)現(xiàn)一個(gè)名為MyExclude的類型它的行為必須與內(nèi)置的ExcludeT, U完全一致但不允許直接使用內(nèi)置Exclude本身。這是一道標(biāo)記為easy初級難度、標(biāo)簽為built-in內(nèi)置類型與union聯(lián)合類型的基礎(chǔ)題其元數(shù)據(jù)可在 info.yml 中查看。準(zhǔn)備工作倉庫中的模板與測試在動手實(shí)現(xiàn)之前先了解本倉庫為該題提供的兩個(gè)關(guān)鍵文件起點(diǎn)模板template.tstemplates.ts 只給出一個(gè)占位定義類型被寫死為any等待我們替換為真正的實(shí)現(xiàn)type MyExcludeT, U any類型級測試test-cases.tstest-cases.ts 是本題的類型級驗(yàn)證用例通過type-challenges/utils中導(dǎo)出的Equal與Expect工具進(jìn)行編譯期斷言import type { Equal, Expect } from type-challenges/utils type cases [ ExpectEqualMyExcludea | b | c, a, b | c, ExpectEqualMyExcludea | b | c, a | b, c, ExpectEqualMyExcludestring | number | (() void), Function, string | number, ]三個(gè)用例分別覆蓋了三種典型場景單個(gè)字面量排除從a | b | c中排除a期望得到b | c聯(lián)合類型排除從a | b | c中排除a | b期望得到c函數(shù)類型排除從string | number | (() void)中排除Function期望得到string | number因?yàn)?) void可賦值給Function從而被剔除。這三個(gè)用例是判斷實(shí)現(xiàn)是否正確的唯一標(biāo)準(zhǔn)只要MyExclude滿足上述三條斷言就等價(jià)于內(nèi)置Exclude。測試工具Equal的實(shí)現(xiàn)位于 utils/index.d.ts它通過比較兩個(gè)函數(shù)類型在泛型下的返回值是否一致來嚴(yán)格判斷X與Y是否完全相同export type EqualX, Y (T() T extends X ? 1 : 2) extends (T() T extends Y ? 1 : 2) ? true : false這意味著我們不能用any、unknown等寬泛類型蒙混過關(guān)MyExclude的結(jié)果必須與期望類型精確相等。核心解法利用條件類型的分布式求值分布式條件類型Distributive Conditional Types要理解解法首先要理解 TypeScript 條件類型的一個(gè)重要特性當(dāng)T extends U ? X : Y中的T是裸類型參數(shù)naked type parameter且被傳入一個(gè)聯(lián)合類型時(shí)條件類型會被分發(fā)distribute到聯(lián)合類型的每一個(gè)成員上。也就是說對于T a | b | c條件類型T extends U ? X : Y會被展開為(a extends U ? X : Y) | (b extends U ? X : Y) | (c extends U ? X : Y)這正是實(shí)現(xiàn)Exclude的基石對聯(lián)合類型的每個(gè)成員分別判斷再重新組合成新的聯(lián)合類型。實(shí)現(xiàn) MyExclude利用分布式條件類型只需一行即可完成實(shí)現(xiàn)type MyExcludeT, U T extends U ? never : T逐項(xiàng)拆解其執(zhí)行過程當(dāng)T a | b | c、U a時(shí)條件類型分發(fā)為三個(gè)獨(dú)立的判斷a extends a ? never : a結(jié)果為never、b extends a ? never : b結(jié)果為b、c extends a ? never : c結(jié)果為c最終合并為b | c與題目示例一致當(dāng)U a | b時(shí)a與b均命中extends U分支返回never只剩c當(dāng)T string | number | (() void)、U Function時(shí)由于() void可賦值給Function該成員被剔除結(jié)果為string | number。這里never的選擇非常關(guān)鍵在聯(lián)合類型中never會被自動吸收即a | never等價(jià)于a因此被排除的成員不會殘留任何痕跡。這也是排除Exclude語義在類型層面的自然映射。為什么不直接寫ExcludeT, U題目明確要求不使用內(nèi)置的ExcludeT, U這是 type-challenges 系列題目的通用規(guī)則見本倉庫 README.zh-CN.md 中關(guān)于題目源自實(shí)際遇到的類型問題不得直接使用內(nèi)置類型本身的說明。該約束的意義在于強(qiáng)迫學(xué)習(xí)者理解內(nèi)置類型底層的實(shí)現(xiàn)原理而非黑盒調(diào)用幫助建立條件類型 分布式求值 never 吸收的類型思維這套組合拳在后續(xù)MyOmit、MyPick、Exclude的兄弟題如Omit、RequiredKeys、OptionalKeys中會反復(fù)出現(xiàn)。進(jìn)階為什么第一個(gè)參數(shù)必須是裸類型參數(shù)分布式求值有一個(gè)容易踩坑的前提被分發(fā)的類型參數(shù)必須是裸類型參數(shù)。如果T被包裹在類型運(yùn)算中例如T[]、ReadonlyT、(T) void條件類型將不再分發(fā)而是對整體進(jìn)行一次性判斷。我們可以做一個(gè)反例驗(yàn)證。若把實(shí)現(xiàn)寫成// 錯(cuò)誤示范T 不是裸類型參數(shù)不會分發(fā) type WrongExcludeT, U T[] extends U[] ? never : T當(dāng)傳入a | b | c時(shí)T被整體視為(a | b | c)[]參與判斷而不是對每個(gè)成員分別判斷結(jié)果與期望完全不符。這也是為什么MyExclude的實(shí)現(xiàn)必須保持T extends U ? never : T這樣直接使用T的形態(tài)。順帶一提倉庫 utils 中的UnionToIntersectionutils/index.d.ts正是反向利用了這個(gè)特性——通過U extends any ? (k: U) void : never強(qiáng)制分發(fā)后再求交集可見分布式條件類型在高級類型編程中的核心地位。如何在倉庫中驗(yàn)證你的解答本倉庫采用 pnpm workspace 管理type-challenges/utils作為工作區(qū)包被引用見根目錄 package.json 與 utils/package.json。驗(yàn)證方式如下本地類型檢查修改 template.ts 中的MyExclude實(shí)現(xiàn)使 test-cases.ts 中的三個(gè)Expect斷言全部通過若任一用例不滿足Equal斷言TypeScript 編譯器會直接報(bào)錯(cuò)無需運(yùn)行任何測試框架在線挑戰(zhàn)題目入口位于 README.md 中的 Take the Challenge 徽章鏈接提交后可在線上 Judge 環(huán)境獲得即時(shí)反饋難度遞進(jìn)建議完成本題后可繼續(xù)嘗試同倉庫中依賴聯(lián)合類型與條件類型的進(jìn)階題例如Omit00003-medium-omit、ReadonlyKeys等進(jìn)一步鞏固分布式條件類型的應(yīng)用。小結(jié)本文從 type-challenges 第 00043 題出發(fā)完成了從題目解讀、測試用例分析到源碼實(shí)現(xiàn)的全過程ExcludeT, U的語義是從T中排除所有可賦值給U的聯(lián)合成員標(biāo)準(zhǔn)實(shí)現(xiàn)type MyExcludeT, U T extends U ? never : T借助分布式條件類型逐個(gè)成員判斷再用never的聯(lián)合吸收特性剔除目標(biāo)成員驗(yàn)證標(biāo)準(zhǔn)是倉庫中 test-cases.ts 的三條Equal斷言它們覆蓋了單字面量排除、聯(lián)合排除和函數(shù)類型排除三種場景。掌握了這一定義你就掌握了 TypeScript 聯(lián)合類型運(yùn)算中最基礎(chǔ)、最常用的一塊拼圖。贊分享示例工程【免費(fèi)下載鏈接】type-challengesCollection of TypeScript type challenges with online judge項(xiàng)目地址https://gitcode.com/GitHub_Trending/ty/type-challenges點(diǎn)擊查看免費(fèi)下載相關(guān)推薦實(shí)現(xiàn)內(nèi)置 ExcludeT, U從聯(lián)合類型中排除指定成員type-challenges 第 43 題實(shí)現(xiàn)內(nèi)置 ExcludeT, U 從聯(lián)合類型中排除指定成員type challenges 第 43 題 本指南圍繞 type challenges 倉庫示例工程SANA 安裝指南從零搭建環(huán)境到 Diffusers 快速推理實(shí)戰(zhàn)SANA 安裝指南從零搭建環(huán)境到 Diffusers 快速推理實(shí)戰(zhàn) 本指南是 SANAEfficient High Resolution Image Syn示例工程從零實(shí)現(xiàn) TypeScript 內(nèi)置 Pick 工具類型type-challenges 第 4 題完整實(shí)戰(zhàn)解析從零實(shí)現(xiàn) TypeScript 內(nèi)置 Pick 工具類型type challenges 第 4 題完整實(shí)戰(zhàn)解析 導(dǎo)讀 本文基于 type challenges示例工程上一篇Evolution API緩存策略詳解Redis與本地緩存協(xié)同下一篇Mac Mouse Fix終極指南讓普通鼠標(biāo)在macOS上媲美蘋果觸控板的完整教程創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考