[{"data":1,"prerenderedAt":2859},["ShallowReactive",2],{"article-/topics/typescript/typescript-type-guards-evolution-custom-assertion-functions":3,"related-typescript":678,"content-query-lGdRsgtCdu":2320},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":12,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":23,"_type":672,"_id":673,"_source":674,"_file":675,"_stem":676,"_extension":677},"/topics/typescript/typescript-type-guards-evolution-custom-assertion-functions","typescript",false,"","TypeScript 类型守卫的演进：从 typeof 到自定义守卫与断言函数","类型守卫是 TypeScript 控制流分析的核心机制。本文从内置守卫（typeof、instanceof、in）出发，过渡到自定义类型谓词，再到断言函数和断言签名，最后讨论窄化在不同控制流结构中的边界行为。","2026-06-04","HTMLPAGE 团队",[13,14,15,16,17],"TypeScript","类型守卫","类型收窄","断言函数","控制流分析","/images/articles/typescript-type-guards-evolution-custom-assertion-functions-featured.jpg","typescript type guard assertion function code",27427258,"https://www.pexels.com/photo/line-of-code-on-laptop-screen-27427258/",16,{"type":24,"children":25,"toc":656},"root",[26,34,72,78,83,88,98,109,215,219,229,238,248,252,262,271,288,293,306,315,328,333,342,347,352,357,366,391,396,405,410,433,439,444,453,458,467,473,478,487,492,497,638],{"type":27,"tag":28,"props":29,"children":30},"element","p",{},[31],{"type":32,"value":33},"text","类型守卫（type guard）是 TypeScript 控制流分析的核心机制。它让编译器在特定代码路径里把一个宽类型窄化成更具体的类型。",{"type":27,"tag":28,"props":35,"children":36},{},[37,39,46,48,54,56,62,64,70],{"type":32,"value":38},"很多人会用 ",{"type":27,"tag":40,"props":41,"children":43},"code",{"className":42},[],[44],{"type":32,"value":45},"typeof",{"type":32,"value":47}," 和 ",{"type":27,"tag":40,"props":49,"children":51},{"className":50},[],[52],{"type":32,"value":53},"instanceof",{"type":32,"value":55},"，但到了需要自定义守卫的时候就卡住了——不知道怎么写类型谓词，不理解 ",{"type":27,"tag":40,"props":57,"children":59},{"className":58},[],[60],{"type":32,"value":61},"asserts",{"type":32,"value":63}," 签名的用途，不清楚 ",{"type":27,"tag":40,"props":65,"children":67},{"className":66},[],[68],{"type":32,"value":69},"in",{"type":32,"value":71}," 操作符的窄化规则。",{"type":27,"tag":73,"props":74,"children":76},"h2",{"id":75},"内置守卫",[77],{"type":32,"value":75},{"type":27,"tag":28,"props":79,"children":80},{},[81],{"type":32,"value":82},"TypeScript 编译器内置了对几个 JavaScript 操作符的类型窄化支持。",{"type":27,"tag":84,"props":85,"children":86},"h3",{"id":45},[87],{"type":32,"value":45},{"type":27,"tag":28,"props":89,"children":90},{},[91,96],{"type":27,"tag":40,"props":92,"children":94},{"className":93},[],[95],{"type":32,"value":45},{"type":32,"value":97}," 是最基础的窄化方式，但它的局限性很明显——只能区分 primitive 类型：",{"type":27,"tag":99,"props":100,"children":104},"pre",{"className":101,"code":103,"language":5,"meta":7},[102],"language-typescript","function format(value: string | number | Date): string {\n  if (typeof value === 'string') return value.trim()\n  if (typeof value === 'number') return value.toFixed(2)\n  // 这里 value 是 Date\n  return value.toISOString()\n}\n",[105],{"type":27,"tag":40,"props":106,"children":107},{"__ignoreMap":7},[108],{"type":32,"value":103},{"type":27,"tag":28,"props":110,"children":111},{},[112,117,119,125,127,133,134,140,141,147,148,154,155,161,162,168,169,175,177,183,185,190,192,198,200,206,208,213],{"type":27,"tag":40,"props":113,"children":115},{"className":114},[],[116],{"type":32,"value":45},{"type":32,"value":118}," 能识别的类型只有：",{"type":27,"tag":40,"props":120,"children":122},{"className":121},[],[123],{"type":32,"value":124},"string",{"type":32,"value":126},"、",{"type":27,"tag":40,"props":128,"children":130},{"className":129},[],[131],{"type":32,"value":132},"number",{"type":32,"value":126},{"type":27,"tag":40,"props":135,"children":137},{"className":136},[],[138],{"type":32,"value":139},"boolean",{"type":32,"value":126},{"type":27,"tag":40,"props":142,"children":144},{"className":143},[],[145],{"type":32,"value":146},"symbol",{"type":32,"value":126},{"type":27,"tag":40,"props":149,"children":151},{"className":150},[],[152],{"type":32,"value":153},"bigint",{"type":32,"value":126},{"type":27,"tag":40,"props":156,"children":158},{"className":157},[],[159],{"type":32,"value":160},"undefined",{"type":32,"value":126},{"type":27,"tag":40,"props":163,"children":165},{"className":164},[],[166],{"type":32,"value":167},"function",{"type":32,"value":126},{"type":27,"tag":40,"props":170,"children":172},{"className":171},[],[173],{"type":32,"value":174},"object",{"type":32,"value":176},"。注意 ",{"type":27,"tag":40,"props":178,"children":180},{"className":179},[],[181],{"type":32,"value":182},"null",{"type":32,"value":184}," 也会被 ",{"type":27,"tag":40,"props":186,"children":188},{"className":187},[],[189],{"type":32,"value":45},{"type":32,"value":191}," 报为 ",{"type":27,"tag":40,"props":193,"children":195},{"className":194},[],[196],{"type":32,"value":197},"'object'",{"type":32,"value":199},"，这是一个历史遗留 bug，但 TypeScript 在窄化时做了修正——",{"type":27,"tag":40,"props":201,"children":203},{"className":202},[],[204],{"type":32,"value":205},"typeof x === 'object'",{"type":32,"value":207}," 会排除 ",{"type":27,"tag":40,"props":209,"children":211},{"className":210},[],[212],{"type":32,"value":182},{"type":32,"value":214},"。",{"type":27,"tag":84,"props":216,"children":217},{"id":53},[218],{"type":32,"value":53},{"type":27,"tag":28,"props":220,"children":221},{},[222,227],{"type":27,"tag":40,"props":223,"children":225},{"className":224},[],[226],{"type":32,"value":53},{"type":32,"value":228}," 用原型链检查来窄化对象类型：",{"type":27,"tag":99,"props":230,"children":233},{"className":231,"code":232,"language":5,"meta":7},[102],"class ApiError {\n  constructor(public code: number, public message: string) {}\n}\n\nclass NetworkError {\n  constructor(public message: string, public retryable: boolean) {}\n}\n\nfunction handleError(err: ApiError | NetworkError) {\n  if (err instanceof ApiError) {\n    // err: ApiError\n    console.log(`API Error ${err.code}: ${err.message}`)\n  } else {\n    // err: NetworkError\n  }\n}\n",[234],{"type":27,"tag":40,"props":235,"children":236},{"__ignoreMap":7},[237],{"type":32,"value":232},{"type":27,"tag":28,"props":239,"children":240},{},[241,246],{"type":27,"tag":40,"props":242,"children":244},{"className":243},[],[245],{"type":32,"value":53},{"type":32,"value":247}," 的窄化基于原型链——不是基于结构兼容性。所以两个没有继承关系的类，即使结构相同也不会相互匹配。",{"type":27,"tag":84,"props":249,"children":250},{"id":69},[251],{"type":32,"value":69},{"type":27,"tag":28,"props":253,"children":254},{},[255,260],{"type":27,"tag":40,"props":256,"children":258},{"className":257},[],[259],{"type":32,"value":69},{"type":32,"value":261}," 操作符检查属性是否存在，也能触发窄化：",{"type":27,"tag":99,"props":263,"children":266},{"className":264,"code":265,"language":5,"meta":7},[102],"type Dog = { bark: () => void }\ntype Cat = { meow: () => void }\n\nfunction makeSound(animal: Dog | Cat) {\n  if ('bark' in animal) {\n    // animal: Dog —— 因为只有 Dog 有 bark 方法\n    animal.bark()\n  } else {\n    // animal: Cat\n    animal.meow()\n  }\n}\n",[267],{"type":27,"tag":40,"props":268,"children":269},{"__ignoreMap":7},[270],{"type":32,"value":265},{"type":27,"tag":28,"props":272,"children":273},{},[274,279,281,286],{"type":27,"tag":40,"props":275,"children":277},{"className":276},[],[278],{"type":32,"value":69},{"type":32,"value":280}," 的窄化规则略有不同：如果被检查的属性只在联合的一个成员中出现，那就窄化到那个成员。如果属性在多个成员中都存在（即使类型不同），",{"type":27,"tag":40,"props":282,"children":284},{"className":283},[],[285],{"type":32,"value":69},{"type":32,"value":287}," 不会产生窄化。",{"type":27,"tag":73,"props":289,"children":291},{"id":290},"自定义类型谓词",[292],{"type":32,"value":290},{"type":27,"tag":28,"props":294,"children":295},{},[296,298,304],{"type":32,"value":297},"当内置守卫不够用时，可以写自定义类型守卫。核心是函数的返回类型写成 ",{"type":27,"tag":40,"props":299,"children":301},{"className":300},[],[302],{"type":32,"value":303},"x is Type",{"type":32,"value":305}," 的形式——这就是类型谓词（type predicate）：",{"type":27,"tag":99,"props":307,"children":310},{"className":308,"code":309,"language":5,"meta":7},[102],"interface User { kind: 'user'; name: string; email: string }\ninterface Admin { kind: 'admin'; name: string; role: string; permissions: string[] }\n\nfunction isAdmin(person: User | Admin): person is Admin {\n  return (person as Admin).permissions !== undefined\n}\n\nfunction handlePerson(person: User | Admin) {\n  if (isAdmin(person)) {\n    // person: Admin\n    console.log(person.role)\n  }\n}\n",[311],{"type":27,"tag":40,"props":312,"children":313},{"__ignoreMap":7},[314],{"type":32,"value":309},{"type":27,"tag":28,"props":316,"children":317},{},[318,320,326],{"type":32,"value":319},"类型谓词 ",{"type":27,"tag":40,"props":321,"children":323},{"className":322},[],[324],{"type":32,"value":325},"person is Admin",{"type":32,"value":327}," 告诉编译器：如果这个函数返回 true，那么入参的类型就收窄到 Admin。这相当于一个编译器的信任声明——它不检查函数体是否正确实现了这个断言。",{"type":27,"tag":84,"props":329,"children":331},{"id":330},"类型谓词的常见陷阱",[332],{"type":32,"value":330},{"type":27,"tag":99,"props":334,"children":337},{"className":335,"code":336,"language":5,"meta":7},[102],"// 错误的守卫——检查的是 user，却说 is Admin\nfunction isAdminWrong(person: User | Admin): person is Admin {\n  return (person as User).email !== undefined  // 实际检查的是 email\n}\n",[338],{"type":27,"tag":40,"props":339,"children":340},{"__ignoreMap":7},[341],{"type":32,"value":336},{"type":27,"tag":28,"props":343,"children":344},{},[345],{"type":32,"value":346},"编译器不会报错。类型谓词是开发者对编译器的一个承诺——你必须保证实现是正确的。写类型谓词时，测试覆盖率比类型安全更重要。",{"type":27,"tag":73,"props":348,"children":350},{"id":349},"断言函数与断言签名",[351],{"type":32,"value":349},{"type":27,"tag":28,"props":353,"children":354},{},[355],{"type":32,"value":356},"断言函数（assertion function）是类型守卫的另一种形式。它不返回 boolean，而是在条件不满足时抛出错误：",{"type":27,"tag":99,"props":358,"children":361},{"className":359,"code":360,"language":5,"meta":7},[102],"function assertString(value: unknown): asserts value is string {\n  if (typeof value !== 'string') {\n    throw new Error(`Expected string, got ${typeof value}`)\n  }\n}\n\nfunction process(input: unknown) {\n  assertString(input)\n  // input: string —— 如果执行到了这里，断言必然满足\n  console.log(input.toUpperCase())\n}\n",[362],{"type":27,"tag":40,"props":363,"children":364},{"__ignoreMap":7},[365],{"type":32,"value":360},{"type":27,"tag":28,"props":367,"children":368},{},[369,375,377,383,385,390],{"type":27,"tag":40,"props":370,"children":372},{"className":371},[],[373],{"type":32,"value":374},"asserts value is string",{"type":32,"value":376}," 的含义是：这个函数要么抛异常，要么把 ",{"type":27,"tag":40,"props":378,"children":380},{"className":379},[],[381],{"type":32,"value":382},"value",{"type":32,"value":384}," 的类型收窄到 ",{"type":27,"tag":40,"props":386,"children":388},{"className":387},[],[389],{"type":32,"value":124},{"type":32,"value":214},{"type":27,"tag":28,"props":392,"children":393},{},[394],{"type":32,"value":395},"断言函数的实际价值是在数据验证场景：",{"type":27,"tag":99,"props":397,"children":400},{"className":398,"code":399,"language":5,"meta":7},[102],"interface Config {\n  host: string\n  port: number\n  tls: boolean\n}\n\nfunction assertConfig(obj: unknown): asserts obj is Config {\n  if (typeof obj !== 'object' || obj === null) throw new Error('not an object')\n  if (!('host' in obj) || typeof (obj as any).host !== 'string') throw new Error('invalid host')\n  if (!('port' in obj) || typeof (obj as any).port !== 'number') throw new Error('invalid port')\n  if (!('tls' in obj) || typeof (obj as any).tls !== 'boolean') throw new Error('invalid tls')\n}\n\nfunction loadConfig(data: unknown) {\n  assertConfig(data)\n  // data: Config\n  return { url: `https://${data.host}:${data.port}`, tls: data.tls }\n}\n",[401],{"type":27,"tag":40,"props":402,"children":403},{"__ignoreMap":7},[404],{"type":32,"value":399},{"type":27,"tag":28,"props":406,"children":407},{},[408],{"type":32,"value":409},"断言函数和类型谓词的区别：",{"type":27,"tag":411,"props":412,"children":413},"ul",{},[414,428],{"type":27,"tag":415,"props":416,"children":417},"li",{},[418,420,426],{"type":32,"value":419},"类型谓词返回 boolean，用于 ",{"type":27,"tag":40,"props":421,"children":423},{"className":422},[],[424],{"type":32,"value":425},"if/else",{"type":32,"value":427}," 分支",{"type":27,"tag":415,"props":429,"children":430},{},[431],{"type":32,"value":432},"断言函数抛出或不抛出，用于校验前置条件后继续执行",{"type":27,"tag":73,"props":434,"children":436},{"id":435},"窄化边界哪些结构会触发窄化",[437],{"type":32,"value":438},"窄化边界：哪些结构会触发窄化",{"type":27,"tag":28,"props":440,"children":441},{},[442],{"type":32,"value":443},"不是所有检查都会触发窄化。TypeScript 的控制流分析在以下几种结构里生效：",{"type":27,"tag":99,"props":445,"children":448},{"className":446,"code":447,"language":5,"meta":7},[102],"// 1. if/else 分支\nif (typeof x === 'string') { /* 窄化到 string */ }\n\n// 2. switch/case\nswitch (x.type) {\n  case 'a': /* 窄化到 TypeA */\n}\n\n// 3. 逻辑运算符\nconst val = typeof x === 'string' ? x : x.toString()\n// 两个分支都窄化\n\n// 4. 提前 return\nfunction example(x: string | null) {\n  if (x === null) return\n  // x: string\n}\n\n// 5. 赋值（解构不总是触发窄化）\nlet y: string | number = 'hello'\ny = 42\n// y: number —— 赋值会更新变量的类型\n",[449],{"type":27,"tag":40,"props":450,"children":451},{"__ignoreMap":7},[452],{"type":32,"value":447},{"type":27,"tag":28,"props":454,"children":455},{},[456],{"type":32,"value":457},"但以下情况不会触发窄化：",{"type":27,"tag":99,"props":459,"children":462},{"className":460,"code":461,"language":5,"meta":7},[102],"// 解构到变量后，原始窄化不传递\ninterface Wrapper { value: string | number }\nfunction bad({ value }: Wrapper) {\n  if (typeof value === 'string') {\n    // value: string —— 可以\n  }\n}\n\n// 但是如果 value 是非原始类型，解构后的属性窄化在早期版本可能失效\n// 在较新的 TypeScript 中，解构的属性窄化是支持的\n",[463],{"type":27,"tag":40,"props":464,"children":465},{"__ignoreMap":7},[466],{"type":32,"value":461},{"type":27,"tag":73,"props":468,"children":470},{"id":469},"自定义守卫-vs-类型断言",[471],{"type":32,"value":472},"自定义守卫 vs 类型断言",{"type":27,"tag":28,"props":474,"children":475},{},[476],{"type":32,"value":477},"守卫和断言有一个根本区别：守卫是安全的类型窄化，断言是覆盖编译器的类型声明。",{"type":27,"tag":99,"props":479,"children":482},{"className":480,"code":481,"language":5,"meta":7},[102],"// 断言（不推荐）\nfunction process(obj: unknown) {\n  const data = obj as Config\n  // 编译器不检查 obj 是否真的是 Config\n}\n\n// 守卫（推荐）\nfunction processSafe(obj: unknown) {\n  if (isConfig(obj)) {\n    // obj: Config —— 编译器在 if 块内确认\n  }\n}\n",[483],{"type":27,"tag":40,"props":484,"children":485},{"__ignoreMap":7},[486],{"type":32,"value":481},{"type":27,"tag":28,"props":488,"children":489},{},[490],{"type":32,"value":491},"断言只是告诉编译器\"我知道它是什么类型\"。自定义守卫则让编译器参与到类型验证的流程里。",{"type":27,"tag":73,"props":493,"children":495},{"id":494},"总结",[496],{"type":32,"value":494},{"type":27,"tag":498,"props":499,"children":500},"table",{},[501,525],{"type":27,"tag":502,"props":503,"children":504},"thead",{},[505],{"type":27,"tag":506,"props":507,"children":508},"tr",{},[509,515,520],{"type":27,"tag":510,"props":511,"children":512},"th",{},[513],{"type":32,"value":514},"守卫类型",{"type":27,"tag":510,"props":516,"children":517},{},[518],{"type":32,"value":519},"适用场景",{"type":27,"tag":510,"props":521,"children":522},{},[523],{"type":32,"value":524},"安全性",{"type":27,"tag":526,"props":527,"children":528},"tbody",{},[529,551,571,592,615],{"type":27,"tag":506,"props":530,"children":531},{},[532,541,546],{"type":27,"tag":533,"props":534,"children":535},"td",{},[536],{"type":27,"tag":40,"props":537,"children":539},{"className":538},[],[540],{"type":32,"value":45},{"type":27,"tag":533,"props":542,"children":543},{},[544],{"type":32,"value":545},"Primitive 类型区分",{"type":27,"tag":533,"props":547,"children":548},{},[549],{"type":32,"value":550},"编译器保证",{"type":27,"tag":506,"props":552,"children":553},{},[554,562,567],{"type":27,"tag":533,"props":555,"children":556},{},[557],{"type":27,"tag":40,"props":558,"children":560},{"className":559},[],[561],{"type":32,"value":53},{"type":27,"tag":533,"props":563,"children":564},{},[565],{"type":32,"value":566},"类实例检查",{"type":27,"tag":533,"props":568,"children":569},{},[570],{"type":32,"value":550},{"type":27,"tag":506,"props":572,"children":573},{},[574,582,587],{"type":27,"tag":533,"props":575,"children":576},{},[577],{"type":27,"tag":40,"props":578,"children":580},{"className":579},[],[581],{"type":32,"value":69},{"type":27,"tag":533,"props":583,"children":584},{},[585],{"type":32,"value":586},"属性存在检查",{"type":27,"tag":533,"props":588,"children":589},{},[590],{"type":32,"value":591},"编译器保证（有边界）",{"type":27,"tag":506,"props":593,"children":594},{},[595,605,610],{"type":27,"tag":533,"props":596,"children":597},{},[598,599],{"type":32,"value":319},{"type":27,"tag":40,"props":600,"children":602},{"className":601},[],[603],{"type":32,"value":604},"x is T",{"type":27,"tag":533,"props":606,"children":607},{},[608],{"type":32,"value":609},"自定义条件窄化",{"type":27,"tag":533,"props":611,"children":612},{},[613],{"type":32,"value":614},"开发者保证",{"type":27,"tag":506,"props":616,"children":617},{},[618,629,634],{"type":27,"tag":533,"props":619,"children":620},{},[621,623],{"type":32,"value":622},"断言签名 ",{"type":27,"tag":40,"props":624,"children":626},{"className":625},[],[627],{"type":32,"value":628},"asserts x is T",{"type":27,"tag":533,"props":630,"children":631},{},[632],{"type":32,"value":633},"前置条件验证",{"type":27,"tag":533,"props":635,"children":636},{},[637],{"type":32,"value":614},{"type":27,"tag":28,"props":639,"children":640},{},[641,643,648,649,654],{"type":32,"value":642},"类型守卫的演进路径是：从编译器内置的硬编码规则（",{"type":27,"tag":40,"props":644,"children":646},{"className":645},[],[647],{"type":32,"value":45},{"type":32,"value":126},{"type":27,"tag":40,"props":650,"children":652},{"className":651},[],[653],{"type":32,"value":53},{"type":32,"value":655},"），到开发者可定制的类型谓词，再到可以中断执行流的断言签名。越往后的守卫越灵活，但越需要开发者保证正确性——编译器信任了你的实现。",{"title":7,"searchDepth":657,"depth":657,"links":658},3,[659,665,668,669,670,671],{"id":75,"depth":660,"text":75,"children":661},2,[662,663,664],{"id":45,"depth":657,"text":45},{"id":53,"depth":657,"text":53},{"id":69,"depth":657,"text":69},{"id":290,"depth":660,"text":290,"children":666},[667],{"id":330,"depth":657,"text":330},{"id":349,"depth":660,"text":349},{"id":435,"depth":660,"text":438},{"id":469,"depth":660,"text":472},{"id":494,"depth":660,"text":494},"markdown","content:topics:typescript:typescript-type-guards-evolution-custom-assertion-functions.md","content","topics/typescript/typescript-type-guards-evolution-custom-assertion-functions.md","topics/typescript/typescript-type-guards-evolution-custom-assertion-functions","md",[679,1116,1804],{"_path":680,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":681,"description":682,"date":683,"topic":5,"author":11,"tags":684,"image":689,"featured":690,"readingTime":691,"body":692,"_type":672,"_id":1113,"_source":674,"_file":1114,"_stem":1115,"_extension":677},"/topics/typescript/typescript-vue3-best-practices","TypeScript 在 Vue 3 中的最佳实践","深度讲解如何在 Vue 3 中高效使用 TypeScript，包括类型定义、接口设计、generics 应用、常见错误等完整指南。","2025-12-27",[13,685,686,687,688],"Vue 3","类型安全","最佳实践","接口设计","/images/topics/typescript-vue3.jpg",true,12,{"type":24,"children":693,"toc":1086},[694,699,704,710,716,725,731,740,746,751,760,765,774,779,788,794,800,809,815,824,830,836,845,851,860,866,875,881,890,896,905,911,920,926,935,939,944,1046,1051],{"type":27,"tag":73,"props":695,"children":697},{"id":696},"typescript-在-vue-3-中的最佳实践",[698],{"type":32,"value":681},{"type":27,"tag":28,"props":700,"children":701},{},[702],{"type":32,"value":703},"TypeScript 让 Vue 开发更加安全可靠。本文讲解如何在 Vue 3 中高效使用 TypeScript。",{"type":27,"tag":73,"props":705,"children":707},{"id":706},"_1-基础类型定义",[708],{"type":32,"value":709},"1. 基础类型定义",{"type":27,"tag":84,"props":711,"children":713},{"id":712},"组件-props-的类型定义",[714],{"type":32,"value":715},"组件 Props 的类型定义",{"type":27,"tag":99,"props":717,"children":720},{"className":718,"code":719,"language":5,"meta":7},[102],"// ✅ 完整的类型定义\n\nimport { PropType } from 'vue'\n\ninterface User {\n  id: string\n  name: string\n  email: string\n  role: 'admin' | 'user' | 'guest'\n}\n\ninterface Props {\n  // 必需的属性\n  title: string\n  \n  // 可选属性\n  count?: number\n  \n  // 对象类型\n  user?: User\n  \n  // 数组类型\n  items?: (string | number)[]\n  \n  // 函数类型\n  onSubmit?: (data: any) => void\n  \n  // 字面量类型\n  size?: 'sm' | 'md' | 'lg'\n  \n  // any 类型 (避免)\n  // data?: any\n}\n\nexport default {\n  props: {\n    title: {\n      type: String,\n      required: true\n    },\n    \n    count: {\n      type: Number,\n      default: 0\n    },\n    \n    user: {\n      type: Object as PropType\u003CUser>,\n      default: () => ({})\n    },\n    \n    size: {\n      type: String,\n      default: 'md',\n      validator: (value: string) => ['sm', 'md', 'lg'].includes(value)\n    }\n  }\n}\n\n// 在 \u003Cscript setup> 中\n\u003Cscript setup lang=\"ts\">\ninterface Props {\n  title: string\n  count?: number\n}\n\nwithDefaults(defineProps\u003CProps>(), {\n  count: 0\n})\n\u003C/script>\n",[721],{"type":27,"tag":40,"props":722,"children":723},{"__ignoreMap":7},[724],{"type":32,"value":719},{"type":27,"tag":84,"props":726,"children":728},{"id":727},"组件-emits-的类型定义",[729],{"type":32,"value":730},"组件 Emits 的类型定义",{"type":27,"tag":99,"props":732,"children":735},{"className":733,"code":734,"language":5,"meta":7},[102],"// ✅ 类型安全的事件发射\n\ninterface Emits {\n  (e: 'submit', data: { name: string; email: string }): void\n  (e: 'cancel'): void\n  (e: 'delete', id: string): void\n}\n\n// 选项式 API\nexport default {\n  emits: {\n    submit: (data: { name: string; email: string }) => {\n      // 可选: 验证\n      return data.name && data.email\n    },\n    cancel: null,\n    delete: (id: string) => id !== ''\n  }\n}\n\n// \u003Cscript setup>\n\u003Cscript setup lang=\"ts\">\nconst emit = defineEmits\u003C{\n  submit: [data: { name: string; email: string }]\n  cancel: []\n  delete: [id: string]\n}>()\n\nconst handleSubmit = () => {\n  emit('submit', { name: 'Alice', email: 'alice@example.com' })\n}\n\u003C/script>\n",[736],{"type":27,"tag":40,"props":737,"children":738},{"__ignoreMap":7},[739],{"type":32,"value":734},{"type":27,"tag":73,"props":741,"children":743},{"id":742},"_2-高级类型模式",[744],{"type":32,"value":745},"2. 高级类型模式",{"type":27,"tag":84,"props":747,"children":749},{"id":748},"泛型组件",[750],{"type":32,"value":748},{"type":27,"tag":99,"props":752,"children":755},{"className":753,"code":754,"language":5,"meta":7},[102],"// components/List.vue\n\u003Cscript setup lang=\"ts\" generic=\"T extends Record\u003Cstring, any>\">\ninterface Props {\n  items: T[]\n  keyField?: keyof T\n}\n\nconst props = withDefaults(defineProps\u003CProps>(), {\n  keyField: 'id' as keyof T\n})\n\nconst emit = defineEmits\u003C{\n  select: [item: T]\n}>()\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv\n      v-for=\"item in items\"\n      :key=\"item[keyField]\"\n      @click=\"emit('select', item)\"\n    >\n      {{ item }}\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n// 使用泛型组件\n\u003Cscript setup lang=\"ts\">\ninterface User {\n  id: string\n  name: string\n}\n\nconst users = ref\u003CUser[]>([\n  { id: '1', name: 'Alice' },\n  { id: '2', name: 'Bob' }\n])\n\nconst handleSelect = (user: User) => {\n  console.log('选中:', user.name)\n}\n\u003C/script>\n\n\u003Ctemplate>\n  \u003C!-- ✅ 类型完全推断 -->\n  \u003CList\n    :items=\"users\"\n    key-field=\"id\"\n    @select=\"handleSelect\"\n  />\n\u003C/template>\n",[756],{"type":27,"tag":40,"props":757,"children":758},{"__ignoreMap":7},[759],{"type":32,"value":754},{"type":27,"tag":84,"props":761,"children":763},{"id":762},"条件类型和分布式条件类型",[764],{"type":32,"value":762},{"type":27,"tag":99,"props":766,"children":769},{"className":767,"code":768,"language":5,"meta":7},[102],"// 条件类型 (Conditional Types)\n\n// 基础条件类型\ntype IsString\u003CT> = T extends string ? true : false\n\ntype A = IsString\u003C'hello'>  // true\ntype B = IsString\u003Cnumber>   // false\n\n// 分布式条件类型\ntype Flatten\u003CT> = T extends Array\u003Cinfer U> ? U : T\n\ntype Str = Flatten\u003Cstring[]>        // string\ntype Num = Flatten\u003Cnumber>          // number\ntype Mixed = Flatten\u003C(string | number)[]>  // string | number\n\n// 实战: 提取 Promise 的结果类型\ntype Awaited\u003CT> = T extends Promise\u003Cinfer U> ? U : T\n\ntype Result = Awaited\u003CPromise\u003Cstring>>  // string\n\n// 实战: API 响应类型\ninterface APIResponse\u003CT> {\n  code: number\n  message: string\n  data: T\n}\n\ntype ExtractData\u003CT> = T extends APIResponse\u003Cinfer D> ? D : never\n\ntype UserData = ExtractData\u003CAPIResponse\u003C{ name: string }>>  // { name: string }\n",[770],{"type":27,"tag":40,"props":771,"children":772},{"__ignoreMap":7},[773],{"type":32,"value":768},{"type":27,"tag":84,"props":775,"children":777},{"id":776},"复杂的接口设计",[778],{"type":32,"value":776},{"type":27,"tag":99,"props":780,"children":783},{"className":781,"code":782,"language":5,"meta":7},[102],"// 实战: 表单验证框架\n\n// 1. 定义字段验证规则\ninterface FieldRule {\n  required?: boolean\n  minLength?: number\n  maxLength?: number\n  pattern?: RegExp\n  custom?: (value: any) => boolean | string\n}\n\n// 2. 为每个字段定义规则\ninterface FormSchema {\n  [fieldName: string]: FieldRule\n}\n\n// 3. 带验证的表单处理\nclass FormValidator\u003CT extends Record\u003Cstring, any>> {\n  constructor(private schema: FormSchema) {}\n  \n  validate(data: T): Record\u003Ckeyof T, string[]> {\n    const errors: Record\u003Cstring, string[]> = {}\n    \n    for (const [field, rules] of Object.entries(this.schema)) {\n      const errors_list: string[] = []\n      const value = data[field]\n      \n      if (rules.required && !value) {\n        errors_list.push(`${field} 是必需的`)\n      }\n      \n      if (rules.minLength && value?.length \u003C rules.minLength) {\n        errors_list.push(`${field} 至少需要 ${rules.minLength} 个字符`)\n      }\n      \n      if (errors_list.length > 0) {\n        errors[field] = errors_list\n      }\n    }\n    \n    return errors as Record\u003Ckeyof T, string[]>\n  }\n}\n\n// 使用\ninterface LoginForm {\n  email: string\n  password: string\n}\n\nconst validator = new FormValidator\u003CLoginForm>({\n  email: { required: true, pattern: /^.+@.+\\..+$/ },\n  password: { required: true, minLength: 6 }\n})\n\nconst errors = validator.validate({\n  email: 'invalid',\n  password: '123'\n})\n",[784],{"type":27,"tag":40,"props":785,"children":786},{"__ignoreMap":7},[787],{"type":32,"value":782},{"type":27,"tag":73,"props":789,"children":791},{"id":790},"_3-组合式-api-的类型定义",[792],{"type":32,"value":793},"3. 组合式 API 的类型定义",{"type":27,"tag":84,"props":795,"children":797},{"id":796},"composable-的返回类型",[798],{"type":32,"value":799},"Composable 的返回类型",{"type":27,"tag":99,"props":801,"children":804},{"className":802,"code":803,"language":5,"meta":7},[102],"// composables/useCounter.ts\n\nimport { ref, computed, Ref } from 'vue'\n\n// 定义返回类型\ninterface UseCounterReturn {\n  count: Ref\u003Cnumber>\n  double: ComputedRef\u003Cnumber>\n  increment: () => void\n  reset: () => void\n}\n\n// 实现 composable\nexport const useCounter = (initialValue: number = 0): UseCounterReturn => {\n  const count = ref(initialValue)\n  \n  const double = computed(() => count.value * 2)\n  \n  const increment = () => count.value++\n  \n  const reset = () => count.value = initialValue\n  \n  return {\n    count,\n    double,\n    increment,\n    reset\n  }\n}\n\n// 使用时自动推断类型\n\u003Cscript setup lang=\"ts\">\nconst { count, double, increment } = useCounter(10)\n// count: Ref\u003Cnumber>\n// double: ComputedRef\u003Cnumber>\n// increment: () => void\n\u003C/script>\n",[805],{"type":27,"tag":40,"props":806,"children":807},{"__ignoreMap":7},[808],{"type":32,"value":803},{"type":27,"tag":84,"props":810,"children":812},{"id":811},"composable-的泛型",[813],{"type":32,"value":814},"Composable 的泛型",{"type":27,"tag":99,"props":816,"children":819},{"className":817,"code":818,"language":5,"meta":7},[102],"// composables/useFetch.ts\n\ninterface UseFetchReturn\u003CT> {\n  data: Ref\u003CT | null>\n  loading: Ref\u003Cboolean>\n  error: Ref\u003CError | null>\n  refresh: () => Promise\u003Cvoid>\n}\n\nexport const useFetch = async \u003CT = any>(\n  url: string | Ref\u003Cstring>,\n  options?: FetchOptions\n): Promise\u003CUseFetchReturn\u003CT>> => {\n  const data = ref\u003CT | null>(null)\n  const loading = ref(false)\n  const error = ref\u003CError | null>(null)\n  \n  const refresh = async () => {\n    loading.value = true\n    try {\n      const response = await $fetch\u003CT>(url, options)\n      data.value = response\n    } catch (e) {\n      error.value = e as Error\n    } finally {\n      loading.value = false\n    }\n  }\n  \n  await refresh()\n  \n  return { data, loading, error, refresh }\n}\n\n// 使用\n\u003Cscript setup lang=\"ts\">\ninterface User {\n  id: string\n  name: string\n  email: string\n}\n\nconst { data: users, loading } = await useFetch\u003CUser[]>('/api/users')\n// users: Ref\u003CUser[] | null>\n// loading: Ref\u003Cboolean>\n\u003C/script>\n",[820],{"type":27,"tag":40,"props":821,"children":822},{"__ignoreMap":7},[823],{"type":32,"value":818},{"type":27,"tag":73,"props":825,"children":827},{"id":826},"_4-常见类型错误和解决方案",[828],{"type":32,"value":829},"4. 常见类型错误和解决方案",{"type":27,"tag":84,"props":831,"children":833},{"id":832},"错误-1-any-类型滥用",[834],{"type":32,"value":835},"错误 1: Any 类型滥用",{"type":27,"tag":99,"props":837,"children":840},{"className":838,"code":839,"language":5,"meta":7},[102],"// ❌ 避免\nconst handleClick = (event: any) => {\n  console.log(event.target.value)  // 无类型检查\n}\n\nconst fetchData = async (url: any) => {\n  const response = await $fetch(url)\n  return response  // any 类型\n}\n\n// ✅ 正确\nconst handleClick = (event: MouseEvent) => {\n  if (event.target instanceof HTMLInputElement) {\n    console.log(event.target.value)\n  }\n}\n\ninterface FetchOptions {\n  method?: 'GET' | 'POST'\n  headers?: Record\u003Cstring, string>\n  body?: Record\u003Cstring, any>\n}\n\nconst fetchData = async (url: string, options?: FetchOptions) => {\n  const response = await $fetch(url, options)\n  return response\n}\n",[841],{"type":27,"tag":40,"props":842,"children":843},{"__ignoreMap":7},[844],{"type":32,"value":839},{"type":27,"tag":84,"props":846,"children":848},{"id":847},"错误-2-类型断言滥用",[849],{"type":32,"value":850},"错误 2: 类型断言滥用",{"type":27,"tag":99,"props":852,"children":855},{"className":853,"code":854,"language":5,"meta":7},[102],"// ❌ 避免 (类型断言隐藏问题)\nconst data = response as User[]\nconst count = element as HTMLInputElement\n\n// ✅ 正确 (类型保护)\nconst isUserArray = (data: unknown): data is User[] => {\n  return Array.isArray(data) && data.every(item => 'id' in item)\n}\n\nif (isUserArray(response)) {\n  // 现在 response 被确定为 User[]\n}\n\nconst parseElement = (element: Element): HTMLInputElement | null => {\n  if (element instanceof HTMLInputElement) {\n    return element\n  }\n  return null\n}\n",[856],{"type":27,"tag":40,"props":857,"children":858},{"__ignoreMap":7},[859],{"type":32,"value":854},{"type":27,"tag":84,"props":861,"children":863},{"id":862},"错误-3-props-类型和运行时定义不匹配",[864],{"type":32,"value":865},"错误 3: Props 类型和运行时定义不匹配",{"type":27,"tag":99,"props":867,"children":870},{"className":868,"code":869,"language":5,"meta":7},[102],"// ❌ 避免 (类型和运行时不一致)\ninterface Props {\n  user: User | null\n}\n\nexport default {\n  props: {\n    user: String  // ❌ 类型是 object，运行时是 string!\n  }\n}\n\n// ✅ 正确\ninterface Props {\n  user?: User | null\n}\n\nexport default {\n  props: {\n    user: {\n      type: Object as PropType\u003CUser | null>,\n      default: null\n    }\n  }\n}\n",[871],{"type":27,"tag":40,"props":872,"children":873},{"__ignoreMap":7},[874],{"type":32,"value":869},{"type":27,"tag":73,"props":876,"children":878},{"id":877},"_5-pinia-store-的类型定义",[879],{"type":32,"value":880},"5. Pinia Store 的类型定义",{"type":27,"tag":99,"props":882,"children":885},{"className":883,"code":884,"language":5,"meta":7},[102],"// stores/useUserStore.ts\n\ninterface User {\n  id: string\n  name: string\n  email: string\n  role: 'admin' | 'user'\n}\n\ninterface UserState {\n  users: User[]\n  currentUser: User | null\n  loading: boolean\n  error: string | null\n}\n\nexport const useUserStore = defineStore('user', {\n  state: (): UserState => ({\n    users: [],\n    currentUser: null,\n    loading: false,\n    error: null\n  }),\n  \n  getters: {\n    isAdmin: (state) => state.currentUser?.role === 'admin',\n    \n    getUserById: (state) => (id: string): User | undefined => {\n      return state.users.find(user => user.id === id)\n    }\n  },\n  \n  actions: {\n    async fetchUsers(): Promise\u003Cvoid> {\n      this.loading = true\n      try {\n        const users = await $fetch\u003CUser[]>('/api/users')\n        this.users = users\n      } catch (error: any) {\n        this.error = error.message\n      } finally {\n        this.loading = false\n      }\n    },\n    \n    setCurrentUser(user: User | null): void {\n      this.currentUser = user\n    }\n  }\n})\n\n// 使用\n\u003Cscript setup lang=\"ts\">\nconst userStore = useUserStore()\n\n// 所有都有完整的类型提示\nconst { currentUser, isAdmin } = storeToRefs(userStore)\nconst user = userStore.getUserById('123')  // User | undefined\n\u003C/script>\n",[886],{"type":27,"tag":40,"props":887,"children":888},{"__ignoreMap":7},[889],{"type":32,"value":884},{"type":27,"tag":73,"props":891,"children":893},{"id":892},"_6-api-响应的类型定义",[894],{"type":32,"value":895},"6. API 响应的类型定义",{"type":27,"tag":99,"props":897,"children":900},{"className":898,"code":899,"language":5,"meta":7},[102],"// types/api.ts\n\n// 通用 API 响应格式\ninterface APIResponse\u003CT> {\n  code: number\n  message: string\n  data: T\n  timestamp: number\n}\n\n// 分页响应\ninterface PaginatedResponse\u003CT> {\n  items: T[]\n  total: number\n  page: number\n  pageSize: number\n  hasMore: boolean\n}\n\n// 具体的 API 类型\n\ninterface User {\n  id: string\n  name: string\n  email: string\n  avatar?: string\n  createdAt: string\n}\n\ninterface UserResponse extends APIResponse\u003CUser> {}\n\ninterface UsersListResponse extends APIResponse\u003CPaginatedResponse\u003CUser>> {}\n\ntype CreateUserRequest = Omit\u003CUser, 'id' | 'createdAt'>\n\n// API 调用类型安全\n\u003Cscript setup lang=\"ts\">\nconst getUserList = async (): Promise\u003CUsersListResponse> => {\n  return await $fetch('/api/users')\n}\n\nconst createUser = async (data: CreateUserRequest): Promise\u003CUserResponse> => {\n  return await $fetch('/api/users', {\n    method: 'POST',\n    body: data\n  })\n}\n\n// 使用\nconst response = await getUserList()\n// response: UsersListResponse\n// response.data.items: User[]\n\u003C/script>\n",[901],{"type":27,"tag":40,"props":902,"children":903},{"__ignoreMap":7},[904],{"type":32,"value":899},{"type":27,"tag":73,"props":906,"children":908},{"id":907},"_7-类型工具函数",[909],{"type":32,"value":910},"7. 类型工具函数",{"type":27,"tag":99,"props":912,"children":915},{"className":913,"code":914,"language":5,"meta":7},[102],"// types/utils.ts\n\n// 1. 提取对象键的联合类型\ntype Keys\u003CT> = keyof T\ntype UserKeys = Keys\u003CUser>  // 'id' | 'name' | 'email'\n\n// 2. 提取对象值的联合类型\ntype Values\u003CT> = T[keyof T]\ntype UserValues = Values\u003CUser>  // string | number\n\n// 3. 部分可选\ntype PartialBy\u003CT, K extends keyof T> = Omit\u003CT, K> & Partial\u003CPick\u003CT, K>>\n\ntype UserWithOptionalEmail = PartialBy\u003CUser, 'email'>\n// { id: string; name: string; email?: string }\n\n// 4. 深度只读\ntype DeepReadonly\u003CT> = {\n  readonly [P in keyof T]: DeepReadonly\u003CT[P]>\n}\n\n// 5. 记录类型\ntype UserRole = 'admin' | 'user' | 'guest'\ntype RolePermissions = Record\u003CUserRole, string[]>\n\nconst permissions: RolePermissions = {\n  admin: ['read', 'write', 'delete'],\n  user: ['read', 'write'],\n  guest: ['read']\n}\n\n// 6. 排除类型\ntype Exclude\u003CT, U> = T extends U ? never : T\ntype Admin = Exclude\u003CUserRole, 'guest' | 'user'>  // 'admin'\n\n// 7. 提取类型\ntype Extract\u003CT, U> = T extends U ? T : never\ntype NotAdmin = Extract\u003CUserRole, Exclude\u003CUserRole, 'admin'>>  // 'user' | 'guest'\n",[916],{"type":27,"tag":40,"props":917,"children":918},{"__ignoreMap":7},[919],{"type":32,"value":914},{"type":27,"tag":73,"props":921,"children":923},{"id":922},"_8-最佳实践总结",[924],{"type":32,"value":925},"8. 最佳实践总结",{"type":27,"tag":99,"props":927,"children":930},{"className":928,"code":929,"language":5,"meta":7},[102],"// ✅ TypeScript 最佳实践清单\n\n// 1. 优先使用 interface 定义数据结构\ninterface User {\n  id: string\n  name: string\n}\n\n// 2. 为函数参数和返回值添加类型\nfunction getUser(id: string): Promise\u003CUser> {\n  // ...\n}\n\n// 3. 避免使用 any，使用 unknown 然后类型保护\n// ❌ const data: any\n// ✅ const data: unknown\nif (typeof data === 'object' && data !== null) {\n  // 现在可以安全地使用 data\n}\n\n// 4. 使用字面量类型替代字符串常量\n// ❌ status: string\n// ✅ status: 'pending' | 'success' | 'error'\n\n// 5. 充分利用泛型\nconst useData = \u003CT>(url: string): Promise\u003CT> => {\n  // ...\n}\n\n// 6. 为 Vue 组件的 Props 和 Emits 定义类型\ninterface Props {\n  title: string\n  count?: number\n}\n\ntype Emits = {\n  'update:title': [title: string]\n  'increment': []\n}\n",[931],{"type":27,"tag":40,"props":932,"children":933},{"__ignoreMap":7},[934],{"type":32,"value":929},{"type":27,"tag":73,"props":936,"children":937},{"id":494},[938],{"type":32,"value":494},{"type":27,"tag":28,"props":940,"children":941},{},[942],{"type":32,"value":943},"TypeScript 在 Vue 3 中的优势：",{"type":27,"tag":498,"props":945,"children":946},{},[947,963],{"type":27,"tag":502,"props":948,"children":949},{},[950],{"type":27,"tag":506,"props":951,"children":952},{},[953,958],{"type":27,"tag":510,"props":954,"children":955},{},[956],{"type":32,"value":957},"特性",{"type":27,"tag":510,"props":959,"children":960},{},[961],{"type":32,"value":962},"优势",{"type":27,"tag":526,"props":964,"children":965},{},[966,982,998,1014,1030],{"type":27,"tag":506,"props":967,"children":968},{},[969,977],{"type":27,"tag":533,"props":970,"children":971},{},[972],{"type":27,"tag":973,"props":974,"children":975},"strong",{},[976],{"type":32,"value":686},{"type":27,"tag":533,"props":978,"children":979},{},[980],{"type":32,"value":981},"编译时发现错误",{"type":27,"tag":506,"props":983,"children":984},{},[985,993],{"type":27,"tag":533,"props":986,"children":987},{},[988],{"type":27,"tag":973,"props":989,"children":990},{},[991],{"type":32,"value":992},"自动补全",{"type":27,"tag":533,"props":994,"children":995},{},[996],{"type":32,"value":997},"IDE 提示更准确",{"type":27,"tag":506,"props":999,"children":1000},{},[1001,1009],{"type":27,"tag":533,"props":1002,"children":1003},{},[1004],{"type":27,"tag":973,"props":1005,"children":1006},{},[1007],{"type":32,"value":1008},"可维护性",{"type":27,"tag":533,"props":1010,"children":1011},{},[1012],{"type":32,"value":1013},"代码意图更明确",{"type":27,"tag":506,"props":1015,"children":1016},{},[1017,1025],{"type":27,"tag":533,"props":1018,"children":1019},{},[1020],{"type":27,"tag":973,"props":1021,"children":1022},{},[1023],{"type":32,"value":1024},"重构安全",{"type":27,"tag":533,"props":1026,"children":1027},{},[1028],{"type":32,"value":1029},"改变代码有反馈",{"type":27,"tag":506,"props":1031,"children":1032},{},[1033,1041],{"type":27,"tag":533,"props":1034,"children":1035},{},[1036],{"type":27,"tag":973,"props":1037,"children":1038},{},[1039],{"type":32,"value":1040},"文档作用",{"type":27,"tag":533,"props":1042,"children":1043},{},[1044],{"type":32,"value":1045},"类型即文档",{"type":27,"tag":73,"props":1047,"children":1049},{"id":1048},"相关资源",[1050],{"type":32,"value":1048},{"type":27,"tag":411,"props":1052,"children":1053},{},[1054,1066,1076],{"type":27,"tag":415,"props":1055,"children":1056},{},[1057],{"type":27,"tag":1058,"props":1059,"children":1063},"a",{"href":1060,"rel":1061},"https://www.typescriptlang.org/docs/",[1062],"nofollow",[1064],{"type":32,"value":1065},"TypeScript 官方手册",{"type":27,"tag":415,"props":1067,"children":1068},{},[1069],{"type":27,"tag":1058,"props":1070,"children":1073},{"href":1071,"rel":1072},"https://vuejs.org/guide/typescript/overview.html",[1062],[1074],{"type":32,"value":1075},"Vue 3 TypeScript 指南",{"type":27,"tag":415,"props":1077,"children":1078},{},[1079],{"type":27,"tag":1058,"props":1080,"children":1083},{"href":1081,"rel":1082},"https://www.typescriptlang.org/docs/handbook/2/types-from-types.html",[1062],[1084],{"type":32,"value":1085},"TypeScript 高级类型",{"title":7,"searchDepth":657,"depth":657,"links":1087},[1088,1089,1093,1098,1102,1107,1108,1109,1110,1111,1112],{"id":696,"depth":660,"text":681},{"id":706,"depth":660,"text":709,"children":1090},[1091,1092],{"id":712,"depth":657,"text":715},{"id":727,"depth":657,"text":730},{"id":742,"depth":660,"text":745,"children":1094},[1095,1096,1097],{"id":748,"depth":657,"text":748},{"id":762,"depth":657,"text":762},{"id":776,"depth":657,"text":776},{"id":790,"depth":660,"text":793,"children":1099},[1100,1101],{"id":796,"depth":657,"text":799},{"id":811,"depth":657,"text":814},{"id":826,"depth":660,"text":829,"children":1103},[1104,1105,1106],{"id":832,"depth":657,"text":835},{"id":847,"depth":657,"text":850},{"id":862,"depth":657,"text":865},{"id":877,"depth":660,"text":880},{"id":892,"depth":660,"text":895},{"id":907,"depth":660,"text":910},{"id":922,"depth":660,"text":925},{"id":494,"depth":660,"text":494},{"id":1048,"depth":660,"text":1048},"content:topics:typescript:typescript-vue3-best-practices.md","topics/typescript/typescript-vue3-best-practices.md","topics/typescript/typescript-vue3-best-practices",{"_path":1117,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1118,"description":1119,"date":10,"topic":5,"author":11,"tags":1120,"image":1126,"imageQuery":1127,"pexelsPhotoId":1128,"pexelsUrl":1129,"featured":6,"readingTime":1130,"body":1131,"_type":672,"_id":1801,"_source":674,"_file":1802,"_stem":1803,"_extension":677},"/topics/typescript/typescript-conditional-types-infer-pattern-matching","TypeScript 条件类型与 infer：从模式匹配到递归类型推导的实战路径","条件类型和 infer 是 TypeScript 类型系统中最灵活也最容易被误用的机制。本文从实际代码场景出发，讲清条件类型的匹配规则、infer 的推断时机、递归类型推导的边界，以及它们如何帮助写出更精确的工具类型。",[13,1121,1122,1123,1124,1125],"条件类型","infer","类型推导","递归类型","高级类型","/images/articles/typescript-conditional-types-infer-pattern-matching-featured.jpg","typescript code conditional type programming laptop screen",34803986,"https://www.pexels.com/photo/modern-laptop-displaying-code-in-cozy-workspace-34803986/",19,{"type":24,"children":1132,"toc":1788},[1133,1169,1174,1180,1208,1217,1222,1231,1252,1264,1273,1278,1287,1293,1303,1308,1317,1322,1398,1410,1419,1436,1442,1447,1456,1461,1470,1476,1485,1491,1500,1513,1519,1528,1534,1539,1548,1553,1581,1587,1592,1601,1606,1615,1620,1625,1630,1639,1644,1650,1783],{"type":27,"tag":28,"props":1134,"children":1135},{},[1136,1138,1143,1145,1151,1153,1159,1161,1167],{"type":32,"value":1137},"条件类型和 ",{"type":27,"tag":40,"props":1139,"children":1141},{"className":1140},[],[1142],{"type":32,"value":1122},{"type":32,"value":1144}," 是 TypeScript 类型系统里最强大的两个机制，也是很多人文档看完但不会用的两个概念。文档告诉你 ",{"type":27,"tag":40,"props":1146,"children":1148},{"className":1147},[],[1149],{"type":32,"value":1150},"T extends U ? X : Y",{"type":32,"value":1152}," 是条件类型，",{"type":27,"tag":40,"props":1154,"children":1156},{"className":1155},[],[1157],{"type":32,"value":1158},"infer R",{"type":32,"value":1160}," 可以提取类型变量，但实际看到 ",{"type":27,"tag":40,"props":1162,"children":1164},{"className":1163},[],[1165],{"type":32,"value":1166},"ReturnType\u003CT>",{"type":32,"value":1168}," 的源码时还是懵的。",{"type":27,"tag":28,"props":1170,"children":1171},{},[1172],{"type":32,"value":1173},"这不是理解能力的问题。条件类型的难点不在语法，而在它运行在一个和值空间完全不同的逻辑系统里。类型不是数据，不能计算，不存在运行时流程控制。条件类型的\"条件\"本质上是类型结构的模式匹配——它不看取值范围，只看结构形状。",{"type":27,"tag":73,"props":1175,"children":1177},{"id":1176},"条件类型的匹配规则结构兼容不是完全相等",[1178],{"type":32,"value":1179},"条件类型的匹配规则：结构兼容，不是完全相等",{"type":27,"tag":28,"props":1181,"children":1182},{},[1183,1185,1191,1193,1199,1201,1206],{"type":32,"value":1184},"条件类型 ",{"type":27,"tag":40,"props":1186,"children":1188},{"className":1187},[],[1189],{"type":32,"value":1190},"T extends U ? A : B",{"type":32,"value":1192}," 的判断标准，和函数签名里的 ",{"type":27,"tag":40,"props":1194,"children":1196},{"className":1195},[],[1197],{"type":32,"value":1198},"extends",{"type":32,"value":1200}," 约束其实是同一套规则：",{"type":27,"tag":973,"props":1202,"children":1203},{},[1204],{"type":32,"value":1205},"U 能不能收容 T",{"type":32,"value":1207},"。不是 T 和 U 长得一样，而是 T 的类型的值能不能赋值给 U。",{"type":27,"tag":99,"props":1209,"children":1212},{"className":1210,"code":1211,"language":5,"meta":7},[102],"type IsString\u003CT> = T extends string ? true : false\n\ntype R1 = IsString\u003C'hello'>  // true —— 字面量 'hello' 可以赋值给 string\ntype R2 = IsString\u003C42>       // false —— number 不能赋值给 string\ntype R3 = IsString\u003Cstring>   // true —— string extends string\n",[1213],{"type":27,"tag":40,"props":1214,"children":1215},{"__ignoreMap":7},[1216],{"type":32,"value":1211},{"type":27,"tag":28,"props":1218,"children":1219},{},[1220],{"type":32,"value":1221},"看起来简单，但换成对象类型就很容易判断错：",{"type":27,"tag":99,"props":1223,"children":1226},{"className":1224,"code":1225,"language":5,"meta":7},[102],"type HasName\u003CT> = T extends { name: string } ? 'yes' : 'no'\n\ntype R4 = HasName\u003C{ name: 'alice' }>          // 'yes' —— 含 name 且兼容\ntype R5 = HasName\u003C{ name: string; age: number }>  // 'yes' —— 多字段不影响\ntype R6 = HasName\u003C{ age: number }>             // 'no' —— 缺 name\n",[1227],{"type":27,"tag":40,"props":1228,"children":1229},{"__ignoreMap":7},[1230],{"type":32,"value":1225},{"type":27,"tag":28,"props":1232,"children":1233},{},[1234,1236,1242,1244,1250],{"type":32,"value":1235},"这里经常有人困惑：为什么 ",{"type":27,"tag":40,"props":1237,"children":1239},{"className":1238},[],[1240],{"type":32,"value":1241},"{ name: string; age: number }",{"type":32,"value":1243}," 能匹配 ",{"type":27,"tag":40,"props":1245,"children":1247},{"className":1246},[],[1248],{"type":32,"value":1249},"{ name: string }",{"type":32,"value":1251},"？因为条件类型看的是 U（右侧）能不能收容 T（左侧），不是反过来。对象类型的兼容性是结构子类型——多字段的结构可以被少字段的结构收容，只要被检查的字段满足要求。",{"type":27,"tag":28,"props":1253,"children":1254},{},[1255,1257,1262],{"type":32,"value":1256},"把这个规则反过来，就是 ",{"type":27,"tag":40,"props":1258,"children":1260},{"className":1259},[],[1261],{"type":32,"value":1198},{"type":32,"value":1263}," 约束里的分配条件类型（distributive conditional types）：",{"type":27,"tag":99,"props":1265,"children":1268},{"className":1266,"code":1267,"language":5,"meta":7},[102],"type ToArray\u003CT> = T extends unknown ? T[] : never\n\ntype R7 = ToArray\u003Cstring | number>\n// string[] | number[] —— 不是 (string | number)[]\n",[1269],{"type":27,"tag":40,"props":1270,"children":1271},{"__ignoreMap":7},[1272],{"type":32,"value":1267},{"type":27,"tag":28,"props":1274,"children":1275},{},[1276],{"type":32,"value":1277},"联合类型会被拆开分别匹配再合并，这就是分配律。关掉分配律的方法是用方括号包裹泛型参数：",{"type":27,"tag":99,"props":1279,"children":1282},{"className":1280,"code":1281,"language":5,"meta":7},[102],"type ToArrayNonDist\u003CT> = [T] extends [unknown] ? T[] : never\n\ntype R8 = ToArrayNonDist\u003Cstring | number>\n// (string | number)[]\n",[1283],{"type":27,"tag":40,"props":1284,"children":1285},{"__ignoreMap":7},[1286],{"type":32,"value":1281},{"type":27,"tag":73,"props":1288,"children":1290},{"id":1289},"infer-的实质在匹配位置声明类型变量",[1291],{"type":32,"value":1292},"infer 的实质：在匹配位置声明类型变量",{"type":27,"tag":28,"props":1294,"children":1295},{},[1296,1301],{"type":27,"tag":40,"props":1297,"children":1299},{"className":1298},[],[1300],{"type":32,"value":1122},{"type":32,"value":1302}," 不是什么黑魔法——它只是在条件类型的 extends 子句中声明一个待推导的类型变量，然后让 TypeScript 在匹配过程中自动填充它。",{"type":27,"tag":28,"props":1304,"children":1305},{},[1306],{"type":32,"value":1307},"最简单的例子是从函数类型提取返回值类型：",{"type":27,"tag":99,"props":1309,"children":1312},{"className":1310,"code":1311,"language":5,"meta":7},[102],"type MyReturnType\u003CT> = T extends (...args: any[]) => infer R ? R : never\n\ntype Fn = (x: number, y: string) => boolean\ntype R9 = MyReturnType\u003CFn>  // boolean\n",[1313],{"type":27,"tag":40,"props":1314,"children":1315},{"__ignoreMap":7},[1316],{"type":32,"value":1311},{"type":27,"tag":28,"props":1318,"children":1319},{},[1320],{"type":32,"value":1321},"流程是这样的：",{"type":27,"tag":1323,"props":1324,"children":1325},"ol",{},[1326,1337,1348,1373,1388],{"type":27,"tag":415,"props":1327,"children":1328},{},[1329,1331],{"type":32,"value":1330},"传入 ",{"type":27,"tag":40,"props":1332,"children":1334},{"className":1333},[],[1335],{"type":32,"value":1336},"(x: number, y: string) => boolean",{"type":27,"tag":415,"props":1338,"children":1339},{},[1340,1342],{"type":32,"value":1341},"检查 ",{"type":27,"tag":40,"props":1343,"children":1345},{"className":1344},[],[1346],{"type":32,"value":1347},"(x: number, y: string) => boolean extends (...args: any[]) => infer R",{"type":27,"tag":415,"props":1349,"children":1350},{},[1351,1357,1359,1365,1367],{"type":27,"tag":40,"props":1352,"children":1354},{"className":1353},[],[1355],{"type":32,"value":1356},"(...args: any[])",{"type":32,"value":1358}," 匹配 ",{"type":27,"tag":40,"props":1360,"children":1362},{"className":1361},[],[1363],{"type":32,"value":1364},"(x: number, y: string)",{"type":32,"value":1366},"，剩余 ",{"type":27,"tag":40,"props":1368,"children":1370},{"className":1369},[],[1371],{"type":32,"value":1372},"=> boolean",{"type":27,"tag":415,"props":1374,"children":1375},{},[1376,1381,1383],{"type":27,"tag":40,"props":1377,"children":1379},{"className":1378},[],[1380],{"type":32,"value":1158},{"type":32,"value":1382}," 被推导为 ",{"type":27,"tag":40,"props":1384,"children":1386},{"className":1385},[],[1387],{"type":32,"value":139},{"type":27,"tag":415,"props":1389,"children":1390},{},[1391,1393],{"type":32,"value":1392},"返回 ",{"type":27,"tag":40,"props":1394,"children":1396},{"className":1395},[],[1397],{"type":32,"value":139},{"type":27,"tag":28,"props":1399,"children":1400},{},[1401,1403,1408],{"type":32,"value":1402},"但 ",{"type":27,"tag":40,"props":1404,"children":1406},{"className":1405},[],[1407],{"type":32,"value":1122},{"type":32,"value":1409}," 有一个容易踩的坑——它只在条件类型为 true 的分支里可用：",{"type":27,"tag":99,"props":1411,"children":1414},{"className":1412,"code":1413,"language":5,"meta":7},[102],"// 错误用法\ntype ExtractPromiseBad\u003CT> = T extends Promise\u003Cinfer U> ? U : never\ntype X = ExtractPromiseBad\u003CPromise\u003Cstring>>  // string —— 正确\n\n// 但不代表 infer 可以出现在任意位置\ntype Wrong\u003CT> = infer U extends T ? U : never  // 语法错误\n",[1415],{"type":27,"tag":40,"props":1416,"children":1417},{"__ignoreMap":7},[1418],{"type":32,"value":1413},{"type":27,"tag":28,"props":1420,"children":1421},{},[1422,1427,1429,1434],{"type":27,"tag":40,"props":1423,"children":1425},{"className":1424},[],[1426],{"type":32,"value":1122},{"type":32,"value":1428}," 必须出现在 ",{"type":27,"tag":40,"props":1430,"children":1432},{"className":1431},[],[1433],{"type":32,"value":1198},{"type":32,"value":1435}," 子句的右侧，而且只能在条件为 true 时才能被使用。",{"type":27,"tag":73,"props":1437,"children":1439},{"id":1438},"递归类型推导条件类型-infer-的组合",[1440],{"type":32,"value":1441},"递归类型推导：条件类型 + infer 的组合",{"type":27,"tag":28,"props":1443,"children":1444},{},[1445],{"type":32,"value":1446},"真正体现 infer 威力的是递归类型推导。考虑一个场景：从嵌套对象中提取所有叶子路径的值的类型。",{"type":27,"tag":99,"props":1448,"children":1451},{"className":1449,"code":1450,"language":5,"meta":7},[102],"type DeepValue\u003CT> = T extends Record\u003Cstring, infer V>\n  ? V extends Record\u003Cstring, any>\n    ? DeepValue\u003CV>\n    : V\n  : T\n\ntype Obj = { a: { b: { c: string } } }\ntype R10 = DeepValue\u003CObj>  // string\n",[1452],{"type":27,"tag":40,"props":1453,"children":1454},{"__ignoreMap":7},[1455],{"type":32,"value":1450},{"type":27,"tag":28,"props":1457,"children":1458},{},[1459],{"type":32,"value":1460},"递归类型推导需要特别注意终止条件。如果没有终止条件，TypeScript 会达到递归深度上限（通常是 50 层）然后报错：",{"type":27,"tag":99,"props":1462,"children":1465},{"className":1463,"code":1464,"language":5,"meta":7},[102],"type InfiniteDeep\u003CT> = T extends Record\u003Cstring, infer V>\n  ? InfiniteDeep\u003CV>\n  : never\n// 编译器可能报 \"Type instantiation is excessively deep and possibly infinite\"\n",[1466],{"type":27,"tag":40,"props":1467,"children":1468},{"__ignoreMap":7},[1469],{"type":32,"value":1464},{"type":27,"tag":84,"props":1471,"children":1473},{"id":1472},"实用模式-1展开-promise",[1474],{"type":32,"value":1475},"实用模式 1：展开 Promise",{"type":27,"tag":99,"props":1477,"children":1480},{"className":1478,"code":1479,"language":5,"meta":7},[102],"type Unwrap\u003CT> = T extends Promise\u003Cinfer U> ? Unwrap\u003CU> : T\n\ntype R11 = Unwrap\u003CPromise\u003CPromise\u003CPromise\u003Cstring>>>>\n// string —— 递归展开所有层级\n",[1481],{"type":27,"tag":40,"props":1482,"children":1483},{"__ignoreMap":7},[1484],{"type":32,"value":1479},{"type":27,"tag":84,"props":1486,"children":1488},{"id":1487},"实用模式-2提取函数参数类型",[1489],{"type":32,"value":1490},"实用模式 2：提取函数参数类型",{"type":27,"tag":99,"props":1492,"children":1495},{"className":1493,"code":1494,"language":5,"meta":7},[102],"type MyParameters\u003CT> = T extends (...args: infer P) => any ? P : never\n\ntype Fn2 = (a: number, b: string, c: boolean) => void\ntype Params = MyParameters\u003CFn2>\n// [number, string, boolean] —— 元组类型\n",[1496],{"type":27,"tag":40,"props":1497,"children":1498},{"__ignoreMap":7},[1499],{"type":32,"value":1494},{"type":27,"tag":28,"props":1501,"children":1502},{},[1503,1505,1511],{"type":32,"value":1504},"注意这里 infer P 不是单个类型，而是参数元组。TypeScript 会把 ",{"type":27,"tag":40,"props":1506,"children":1508},{"className":1507},[],[1509],{"type":32,"value":1510},"...args",{"type":32,"value":1512}," 的展开类型自动推断为元组。",{"type":27,"tag":84,"props":1514,"children":1516},{"id":1515},"实用模式-3条件推断-模板字面量",[1517],{"type":32,"value":1518},"实用模式 3：条件推断 + 模板字面量",{"type":27,"tag":99,"props":1520,"children":1523},{"className":1521,"code":1522,"language":5,"meta":7},[102],"type ExtractId\u003CT extends string> =\n  T extends `id-${infer Rest}` ? Rest : never\n\ntype R12 = ExtractId\u003C'id-abc123'>  // 'abc123'\ntype R13 = ExtractId\u003C'name-xyz'>   // never\n",[1524],{"type":27,"tag":40,"props":1525,"children":1526},{"__ignoreMap":7},[1527],{"type":32,"value":1522},{"type":27,"tag":73,"props":1529,"children":1531},{"id":1530},"实战场景实现一个类型安全的路径提取器",[1532],{"type":32,"value":1533},"实战场景：实现一个类型安全的路径提取器",{"type":27,"tag":28,"props":1535,"children":1536},{},[1537],{"type":32,"value":1538},"下面用一个稍微复杂一点的例子，展示条件类型和 infer 的组合威力。假设你需要一个类型，能从深层嵌套对象中提取指定路径的值类型：",{"type":27,"tag":99,"props":1540,"children":1543},{"className":1541,"code":1542,"language":5,"meta":7},[102],"type PathValue\u003CT, P extends string> =\n  P extends `${infer K}.${infer Rest}`\n    ? K extends keyof T\n      ? PathValue\u003CT[K], Rest>\n      : never\n    : P extends keyof T\n      ? T[P]\n      : never\n\ntype Data = {\n  user: {\n    profile: {\n      name: string\n      age: number\n    }\n    settings: {\n      theme: 'light' | 'dark'\n    }\n  }\n}\n\ntype R14 = PathValue\u003CData, 'user.profile.name'>  // string\ntype R15 = PathValue\u003CData, 'user.settings.theme'>  // 'light' | 'dark'\ntype R16 = PathValue\u003CData, 'user.profile.email'>  // never —— 路径不存在\n",[1544],{"type":27,"tag":40,"props":1545,"children":1546},{"__ignoreMap":7},[1547],{"type":32,"value":1542},{"type":27,"tag":28,"props":1549,"children":1550},{},[1551],{"type":32,"value":1552},"这个类型有两个递归分支：",{"type":27,"tag":1323,"props":1554,"children":1555},{},[1556,1569],{"type":27,"tag":415,"props":1557,"children":1558},{},[1559,1561,1567],{"type":32,"value":1560},"如果路径包含 ",{"type":27,"tag":40,"props":1562,"children":1564},{"className":1563},[],[1565],{"type":32,"value":1566},".",{"type":32,"value":1568},"，用模板字面量拆分出第一段和剩余路径",{"type":27,"tag":415,"props":1570,"children":1571},{},[1572,1574,1579],{"type":32,"value":1573},"如果路径不包含 ",{"type":27,"tag":40,"props":1575,"children":1577},{"className":1576},[],[1578],{"type":32,"value":1566},{"type":32,"value":1580},"，直接取 key",{"type":27,"tag":73,"props":1582,"children":1584},{"id":1583},"infer-的逆协变与逆变",[1585],{"type":32,"value":1586},"infer 的逆协变与逆变",{"type":27,"tag":28,"props":1588,"children":1589},{},[1590],{"type":32,"value":1591},"当 infer 出现在不同位置时，它的赋值行为不同。这是很多人踩坑的地方：",{"type":27,"tag":99,"props":1593,"children":1596},{"className":1594,"code":1595,"language":5,"meta":7},[102],"// 协变位置（函数返回值）\ntype CoExtract\u003CT> = T extends () => infer R ? R : never\ntype R17 = CoExtract\u003C() => string | number>  // string | number\n\n// 逆变位置（函数参数）\ntype ContraExtract\u003CT> = T extends (x: infer P) => any ? P : never\ntype R18 = ContraExtract\u003C(x: string | number) => void>\n// string | number —— 看起来一样\n",[1597],{"type":27,"tag":40,"props":1598,"children":1599},{"__ignoreMap":7},[1600],{"type":32,"value":1595},{"type":27,"tag":28,"props":1602,"children":1603},{},[1604],{"type":32,"value":1605},"但在泛型中，逆变位置的 infer 会有不同的行为：",{"type":27,"tag":99,"props":1607,"children":1610},{"className":1608,"code":1609,"language":5,"meta":7},[102],"type UnionToIntersection\u003CT> =\n  (T extends any ? (x: T) => void : never) extends (x: infer R) => void\n    ? R\n    : never\n\ntype R19 = UnionToIntersection\u003Cstring | number>\n// string & number —— 联合转交叉的经典实现\n",[1611],{"type":27,"tag":40,"props":1612,"children":1613},{"__ignoreMap":7},[1614],{"type":32,"value":1609},{"type":27,"tag":28,"props":1616,"children":1617},{},[1618],{"type":32,"value":1619},"这个技巧利用了函数参数的逆变位置：当多个类型分布在同一个逆变位置上时，TypeScript 会尝试取交集。",{"type":27,"tag":73,"props":1621,"children":1623},{"id":1622},"递归类型推导的性能问题",[1624],{"type":32,"value":1622},{"type":27,"tag":28,"props":1626,"children":1627},{},[1628],{"type":32,"value":1629},"递归条件类型不是免费的。每层递归都会增加类型实例化的深度。以下场景尤其需要注意：",{"type":27,"tag":99,"props":1631,"children":1634},{"className":1632,"code":1633,"language":5,"meta":7},[102],"// 逐个元素处理的递归（性能较差的写法）\ntype DeepReadonlyArray\u003CT extends any[]> = {\n  [K in keyof T]: T[K] extends object\n    ? DeepReadonlyArray\u003CT[K]>\n    : T[K]\n}\n\n// 用映射类型减少递归深度（优化写法）\ntype DeepReadonly\u003CT> = {\n  readonly [K in keyof T]: T[K] extends Record\u003Cstring, any>\n    ? DeepReadonly\u003CT[K]>\n    : T[K]\n}\n",[1635],{"type":27,"tag":40,"props":1636,"children":1637},{"__ignoreMap":7},[1638],{"type":32,"value":1633},{"type":27,"tag":28,"props":1640,"children":1641},{},[1642],{"type":32,"value":1643},"映射类型的单层操作不会增加递归深度，而递归条件类型每展开一层都计数。对于可能超过 30 层嵌套的结构，先评估是否真的需要这么深的递归。",{"type":27,"tag":73,"props":1645,"children":1647},{"id":1646},"总结条件类型-infer-的使用建议",[1648],{"type":32,"value":1649},"总结：条件类型 + infer 的使用建议",{"type":27,"tag":498,"props":1651,"children":1652},{},[1653,1674],{"type":27,"tag":502,"props":1654,"children":1655},{},[1656],{"type":27,"tag":506,"props":1657,"children":1658},{},[1659,1664,1669],{"type":27,"tag":510,"props":1660,"children":1661},{},[1662],{"type":32,"value":1663},"场景",{"type":27,"tag":510,"props":1665,"children":1666},{},[1667],{"type":32,"value":1668},"使用方式",{"type":27,"tag":510,"props":1670,"children":1671},{},[1672],{"type":32,"value":1673},"注意事项",{"type":27,"tag":526,"props":1675,"children":1676},{},[1677,1699,1721,1747,1765],{"type":27,"tag":506,"props":1678,"children":1679},{},[1680,1685,1694],{"type":27,"tag":533,"props":1681,"children":1682},{},[1683],{"type":32,"value":1684},"提取函数返回值类型",{"type":27,"tag":533,"props":1686,"children":1687},{},[1688],{"type":27,"tag":40,"props":1689,"children":1691},{"className":1690},[],[1692],{"type":32,"value":1693},"(...args: any[]) => infer R",{"type":27,"tag":533,"props":1695,"children":1696},{},[1697],{"type":32,"value":1698},"简单直接，无限参数兼容",{"type":27,"tag":506,"props":1700,"children":1701},{},[1702,1707,1716],{"type":27,"tag":533,"props":1703,"children":1704},{},[1705],{"type":32,"value":1706},"提取 Promise 内部类型",{"type":27,"tag":533,"props":1708,"children":1709},{},[1710],{"type":27,"tag":40,"props":1711,"children":1713},{"className":1712},[],[1714],{"type":32,"value":1715},"Promise\u003Cinfer T>",{"type":27,"tag":533,"props":1717,"children":1718},{},[1719],{"type":32,"value":1720},"递归展开需加终止条件",{"type":27,"tag":506,"props":1722,"children":1723},{},[1724,1729,1734],{"type":27,"tag":533,"props":1725,"children":1726},{},[1727],{"type":32,"value":1728},"路径拆分",{"type":27,"tag":533,"props":1730,"children":1731},{},[1732],{"type":32,"value":1733},"模板字面量 + infer",{"type":27,"tag":533,"props":1735,"children":1736},{},[1737,1739,1745],{"type":32,"value":1738},"注意 ",{"type":27,"tag":40,"props":1740,"children":1742},{"className":1741},[],[1743],{"type":32,"value":1744},"keyof",{"type":32,"value":1746}," 配合",{"type":27,"tag":506,"props":1748,"children":1749},{},[1750,1755,1760],{"type":27,"tag":533,"props":1751,"children":1752},{},[1753],{"type":32,"value":1754},"联合转交叉",{"type":27,"tag":533,"props":1756,"children":1757},{},[1758],{"type":32,"value":1759},"逆变位置 infer",{"type":27,"tag":533,"props":1761,"children":1762},{},[1763],{"type":32,"value":1764},"理解原理再使用",{"type":27,"tag":506,"props":1766,"children":1767},{},[1768,1773,1778],{"type":27,"tag":533,"props":1769,"children":1770},{},[1771],{"type":32,"value":1772},"递归对象类型",{"type":27,"tag":533,"props":1774,"children":1775},{},[1776],{"type":32,"value":1777},"映射类型 + 条件",{"type":27,"tag":533,"props":1779,"children":1780},{},[1781],{"type":32,"value":1782},"评估深度，避免爆炸",{"type":27,"tag":28,"props":1784,"children":1785},{},[1786],{"type":32,"value":1787},"条件类型的难点从来不在语法，而在于它遵循的是结构子类型规则，不是等值规则。写条件类型时，心里应该想着\"这个形状能不能匹配那个形状\"，而不是\"这两个值相不相等\"。",{"title":7,"searchDepth":657,"depth":657,"links":1789},[1790,1791,1792,1797,1798,1799,1800],{"id":1176,"depth":660,"text":1179},{"id":1289,"depth":660,"text":1292},{"id":1438,"depth":660,"text":1441,"children":1793},[1794,1795,1796],{"id":1472,"depth":657,"text":1475},{"id":1487,"depth":657,"text":1490},{"id":1515,"depth":657,"text":1518},{"id":1530,"depth":660,"text":1533},{"id":1583,"depth":660,"text":1586},{"id":1622,"depth":660,"text":1622},{"id":1646,"depth":660,"text":1649},"content:topics:typescript:typescript-conditional-types-infer-pattern-matching.md","topics/typescript/typescript-conditional-types-infer-pattern-matching.md","topics/typescript/typescript-conditional-types-infer-pattern-matching",{"_path":1805,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1806,"description":1807,"date":10,"topic":5,"author":11,"tags":1808,"image":1814,"imageQuery":1815,"pexelsPhotoId":1816,"pexelsUrl":1817,"featured":6,"readingTime":22,"body":1818,"_type":672,"_id":2317,"_source":674,"_file":2318,"_stem":2319,"_extension":677},"/topics/typescript/typescript-decorators-real-world-validation-logging-di","TypeScript 装饰器的实际使用场景","TypeScript 装饰器在经历了多次提案变更后，终于在 5.0 版本支持了 ECMAScript 标准的装饰器。本文对比新旧装饰器语法，讲解类装饰器、方法装饰器、访问器装饰器和属性装饰器的实际用途，并用四种模式展示它们在项目中的应用。",[13,1809,1810,1811,1812,1813],"装饰器","依赖注入","日志","校验","AOP","/images/articles/typescript-decorators-real-world-validation-logging-di-featured.jpg","typescript decorator pattern code programming laptop",34804022,"https://www.pexels.com/photo/colorful-code-on-screen-with-light-patterns-34804022/",{"type":24,"children":1819,"toc":2308},[1820,1841,1846,1851,1856,1865,1885,1891,1896,1905,1910,1918,1923,1929,1934,1943,1948,1954,1959,1968,1974,1979,1988,2001,2007,2012,2145,2150,2159,2200,2204,2303],{"type":27,"tag":28,"props":1821,"children":1822},{},[1823,1825,1831,1833,1839],{"type":32,"value":1824},"TypeScript 装饰器经历了一段不太平滑的演进。早期版本（5.0 之前）使用的是实验性的\"传统装饰器\"（experimental decorators），需要在 ",{"type":27,"tag":40,"props":1826,"children":1828},{"className":1827},[],[1829],{"type":32,"value":1830},"tsconfig.json",{"type":32,"value":1832}," 中开启 ",{"type":27,"tag":40,"props":1834,"children":1836},{"className":1835},[],[1837],{"type":32,"value":1838},"experimentalDecorators: true",{"type":32,"value":1840},"。5.0 之后开始支持 ECMAScript 标准的装饰器提案（Stage 3），默认不兼容。",{"type":27,"tag":28,"props":1842,"children":1843},{},[1844],{"type":32,"value":1845},"目前大部分遗留项目仍然在用传统装饰器，新项目可以逐步采用标准装饰器。本文以传统装饰器为例来说明模式——这些模式在新标准下同样适用，只是语法和参数有差异。",{"type":27,"tag":73,"props":1847,"children":1849},{"id":1848},"四种装饰器的签名",[1850],{"type":32,"value":1848},{"type":27,"tag":28,"props":1852,"children":1853},{},[1854],{"type":32,"value":1855},"传统装饰器有四类，每种接收的参数不同：",{"type":27,"tag":99,"props":1857,"children":1860},{"code":1858,"language":5,"meta":7,"className":1859},"// 类装饰器\ntype ClassDecorator = (target: Function) => void | Function\n\n// 方法装饰器\ntype MethodDecorator = \u003CT>(\n  target: T,\n  propertyKey: string | symbol,\n  descriptor: TypedPropertyDescriptor\u003Cany>\n) => void | TypedPropertyDescriptor\u003Cany>\n\n// 访问器装饰器（getter/setter）\ntype AccessorDecorator = \u003CT>(\n  target: T,\n  propertyKey: string | symbol,\n  descriptor: TypedPropertyDescriptor\u003Cany>\n) => void | TypedPropertyDescriptor\u003Cany>\n\n// 属性装饰器\ntype PropertyDecorator = (\n  target: Object,\n  propertyKey: string | symbol\n) => void\n",[102],[1861],{"type":27,"tag":40,"props":1862,"children":1863},{"__ignoreMap":7},[1864],{"type":32,"value":1858},{"type":27,"tag":28,"props":1866,"children":1867},{},[1868,1870,1876,1877,1883],{"type":32,"value":1869},"方法装饰器和属性装饰器虽然都接收 ",{"type":27,"tag":40,"props":1871,"children":1873},{"className":1872},[],[1874],{"type":32,"value":1875},"target",{"type":32,"value":47},{"type":27,"tag":40,"props":1878,"children":1880},{"className":1879},[],[1881],{"type":32,"value":1882},"propertyKey",{"type":32,"value":1884},"，但方法装饰器额外接收属性描述符（descriptor），所以可以修改方法的行为。属性装饰器没有 descriptor，只能做元数据附加。",{"type":27,"tag":73,"props":1886,"children":1888},{"id":1887},"模式-1自动日志方法装饰器",[1889],{"type":32,"value":1890},"模式 1：自动日志（方法装饰器）",{"type":27,"tag":28,"props":1892,"children":1893},{},[1894],{"type":32,"value":1895},"最常用的装饰器模式——给方法加日志，不侵入业务逻辑：",{"type":27,"tag":99,"props":1897,"children":1900},{"code":1898,"language":5,"meta":7,"className":1899},"function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n  const originalMethod = descriptor.value\n\n  descriptor.value = function (...args: any[]) {\n    console.log(`[${new Date().toISOString()}] ${propertyKey} called with:`, args)\n    const start = performance.now()\n    const result = originalMethod.apply(this, args)\n    const duration = performance.now() - start\n    console.log(`[${new Date().toISOString()}] ${propertyKey} completed in ${duration.toFixed(2)}ms`)\n    return result\n  }\n\n  return descriptor\n}\n\nclass UserService {\n  @log\n  async getUser(id: string) {\n    // 实际的业务逻辑\n    return db.users.find(id)\n  }\n\n  @log\n  async updateUser(id: string, data: Partial\u003CUser>) {\n    return db.users.update(id, data)\n  }\n}\n",[102],[1901],{"type":27,"tag":40,"props":1902,"children":1903},{"__ignoreMap":7},[1904],{"type":32,"value":1898},{"type":27,"tag":28,"props":1906,"children":1907},{},[1908],{"type":32,"value":1909},"输出：",{"type":27,"tag":99,"props":1911,"children":1913},{"code":1912},"[2026-06-04T10:00:00.000Z] getUser called with: [\"123\"]\n[2026-06-04T10:00:00.150Z] getUser completed in 150.23ms\n",[1914],{"type":27,"tag":40,"props":1915,"children":1916},{"__ignoreMap":7},[1917],{"type":32,"value":1912},{"type":27,"tag":28,"props":1919,"children":1920},{},[1921],{"type":32,"value":1922},"这种模式的优点是所有方法的日志行为一致，修改日志格式只需要改一个装饰器。而且每个装饰器是独立的——可以给重要方法加日志，给高频方法跳过。",{"type":27,"tag":73,"props":1924,"children":1926},{"id":1925},"模式-2输入校验方法装饰器-元数据",[1927],{"type":32,"value":1928},"模式 2：输入校验（方法装饰器 + 元数据）",{"type":27,"tag":28,"props":1930,"children":1931},{},[1932],{"type":32,"value":1933},"给方法参数加校验前置条件：",{"type":27,"tag":99,"props":1935,"children":1938},{"code":1936,"language":5,"meta":7,"className":1937},"type ValidationRule = (value: any) => string | null // null 表示通过\n\nfunction validate(rules: Record\u003Cstring, ValidationRule>) {\n  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n    const originalMethod = descriptor.value\n\n    descriptor.value = function (...args: any[]) {\n      // 通过参数名匹配规则（需要额外元数据来映射参数名）\n      const paramNames = getParamNames(originalMethod)\n      for (const [name, rule] of Object.entries(rules)) {\n        const index = paramNames.indexOf(name)\n        if (index === -1) continue\n        const error = rule(args[index])\n        if (error !== null) {\n          throw new ValidationError(`Validation failed for ${propertyKey}.${name}: ${error}`)\n        }\n      }\n\n      return originalMethod.apply(this, args)\n    }\n\n    return descriptor\n  }\n}\n\nclass UserController {\n  @validate({\n    email: (v: any) => typeof v === 'string' && v.includes('@') ? null : 'Invalid email',\n    age: (v: any) => typeof v === 'number' && v >= 0 && v \u003C= 150 ? null : 'Invalid age'\n  })\n  async createUser(email: string, age: number) {\n    // ...\n  }\n}\n",[102],[1939],{"type":27,"tag":40,"props":1940,"children":1941},{"__ignoreMap":7},[1942],{"type":32,"value":1936},{"type":27,"tag":28,"props":1944,"children":1945},{},[1946],{"type":32,"value":1947},"这里不需要在每个方法体里手写校验逻辑。如果将来校验规则变了，只需要改装饰器参数。",{"type":27,"tag":73,"props":1949,"children":1951},{"id":1950},"模式-3方法节流防抖方法装饰器",[1952],{"type":32,"value":1953},"模式 3：方法节流/防抖（方法装饰器）",{"type":27,"tag":28,"props":1955,"children":1956},{},[1957],{"type":32,"value":1958},"在 UI 事件或 API 调用场景下，节流和防抖是常见需求：",{"type":27,"tag":99,"props":1960,"children":1963},{"code":1961,"language":5,"meta":7,"className":1962},"function throttle(ms: number) {\n  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n    const originalMethod = descriptor.value\n    let lastCall = 0\n\n    descriptor.value = function (...args: any[]) {\n      const now = Date.now()\n      if (now - lastCall \u003C ms) return\n      lastCall = now\n      return originalMethod.apply(this, args)\n    }\n\n    return descriptor\n  }\n}\n\nclass AnalyticsTracker {\n  @throttle(1000) // 每秒最多上报一次\n  trackEvent(name: string, data: Record\u003Cstring, any>) {\n    // 发送分析事件\n    navigator.sendBeacon('/api/analytics', JSON.stringify({ name, data }))\n  }\n}\n",[102],[1964],{"type":27,"tag":40,"props":1965,"children":1966},{"__ignoreMap":7},[1967],{"type":32,"value":1961},{"type":27,"tag":73,"props":1969,"children":1971},{"id":1970},"模式-4依赖注入类装饰器-属性装饰器",[1972],{"type":32,"value":1973},"模式 4：依赖注入（类装饰器 + 属性装饰器）",{"type":27,"tag":28,"props":1975,"children":1976},{},[1977],{"type":32,"value":1978},"依赖注入是类级别装饰器的典型应用。先定义一个容器：",{"type":27,"tag":99,"props":1980,"children":1983},{"code":1981,"language":5,"meta":7,"className":1982},"const container = new Map\u003Cstring, any>()\n\nfunction Injectable() {\n  return function (target: any) {\n    // 标记这个类可以被注入\n    Reflect.defineMetadata('injectable', true, target)\n  }\n}\n\nfunction Inject(token: string) {\n  return function (target: any, propertyKey: string | symbol) {\n    // 在属性上存储依赖标记\n    Reflect.defineMetadata('inject', token, target, propertyKey)\n  }\n}\n\n// 自动装配\nfunction autoInject\u003CT extends { new (...args: any[]): any }>(target: T): T {\n  return class extends target {\n    constructor(...args: any[]) {\n      super(...args)\n      // 遍历原型链，查找需要注入的属性\n      const instance = this as any\n      const proto = Object.getPrototypeOf(instance)\n      for (const key of Object.getOwnPropertyNames(proto)) {\n        const token = Reflect.getMetadata('inject', proto, key)\n        if (token && container.has(token)) {\n          instance[key] = container.get(token)\n        }\n      }\n    }\n  }\n}\n\n@Injectable()\nclass Logger {\n  log(msg: string) { console.log(msg) }\n}\n\n@Injectable()\n@autoInject\nclass UserServiceDI {\n  @Inject('Logger')\n  private logger!: Logger\n\n  async getUser(id: string) {\n    this.logger.log(`Fetching user ${id}`)\n    // ...\n  }\n}\n",[102],[1984],{"type":27,"tag":40,"props":1985,"children":1986},{"__ignoreMap":7},[1987],{"type":32,"value":1981},{"type":27,"tag":28,"props":1989,"children":1990},{},[1991,1993,1999],{"type":32,"value":1992},"实际项目一般用 ",{"type":27,"tag":40,"props":1994,"children":1996},{"className":1995},[],[1997],{"type":32,"value":1998},"reflect-metadata",{"type":32,"value":2000}," 库来处理元数据操作。这里的关键是：属性装饰器只能标记\"需要注入什么\"，具体的注入逻辑在类装饰器（autoInject）中执行。",{"type":27,"tag":73,"props":2002,"children":2004},{"id":2003},"传统装饰器-vs-标准装饰器",[2005],{"type":32,"value":2006},"传统装饰器 vs 标准装饰器",{"type":27,"tag":28,"props":2008,"children":2009},{},[2010],{"type":32,"value":2011},"TypeScript 5.0 开始支持 ECMAScript 标准装饰器。主要区别：",{"type":27,"tag":498,"props":2013,"children":2014},{},[2015,2036],{"type":27,"tag":502,"props":2016,"children":2017},{},[2018],{"type":27,"tag":506,"props":2019,"children":2020},{},[2021,2026,2031],{"type":27,"tag":510,"props":2022,"children":2023},{},[2024],{"type":32,"value":2025},"方面",{"type":27,"tag":510,"props":2027,"children":2028},{},[2029],{"type":32,"value":2030},"传统装饰器（experimental）",{"type":27,"tag":510,"props":2032,"children":2033},{},[2034],{"type":32,"value":2035},"标准装饰器（Standard）",{"type":27,"tag":526,"props":2037,"children":2038},{},[2039,2062,2088,2106,2127],{"type":27,"tag":506,"props":2040,"children":2041},{},[2042,2047,2057],{"type":27,"tag":533,"props":2043,"children":2044},{},[2045],{"type":32,"value":2046},"tsconfig 配置",{"type":27,"tag":533,"props":2048,"children":2049},{},[2050,2052],{"type":32,"value":2051},"需要 ",{"type":27,"tag":40,"props":2053,"children":2055},{"className":2054},[],[2056],{"type":32,"value":1838},{"type":27,"tag":533,"props":2058,"children":2059},{},[2060],{"type":32,"value":2061},"不需要",{"type":27,"tag":506,"props":2063,"children":2064},{},[2065,2070,2079],{"type":27,"tag":533,"props":2066,"children":2067},{},[2068],{"type":32,"value":2069},"方法装饰器参数",{"type":27,"tag":533,"props":2071,"children":2072},{},[2073],{"type":27,"tag":40,"props":2074,"children":2076},{"className":2075},[],[2077],{"type":32,"value":2078},"(target, key, descriptor)",{"type":27,"tag":533,"props":2080,"children":2081},{},[2082],{"type":27,"tag":40,"props":2083,"children":2085},{"className":2084},[],[2086],{"type":32,"value":2087},"(target, context)",{"type":27,"tag":506,"props":2089,"children":2090},{},[2091,2096,2101],{"type":27,"tag":533,"props":2092,"children":2093},{},[2094],{"type":32,"value":2095},"返回值",{"type":27,"tag":533,"props":2097,"children":2098},{},[2099],{"type":32,"value":2100},"descriptor 或无",{"type":27,"tag":533,"props":2102,"children":2103},{},[2104],{"type":32,"value":2105},"新函数或 void",{"type":27,"tag":506,"props":2107,"children":2108},{},[2109,2114,2122],{"type":27,"tag":533,"props":2110,"children":2111},{},[2112],{"type":32,"value":2113},"元数据支持",{"type":27,"tag":533,"props":2115,"children":2116},{},[2117],{"type":27,"tag":40,"props":2118,"children":2120},{"className":2119},[],[2121],{"type":32,"value":1998},{"type":27,"tag":533,"props":2123,"children":2124},{},[2125],{"type":32,"value":2126},"需要装饰器自行管理",{"type":27,"tag":506,"props":2128,"children":2129},{},[2130,2135,2140],{"type":27,"tag":533,"props":2131,"children":2132},{},[2133],{"type":32,"value":2134},"与 Babel 兼容",{"type":27,"tag":533,"props":2136,"children":2137},{},[2138],{"type":32,"value":2139},"不一致",{"type":27,"tag":533,"props":2141,"children":2142},{},[2143],{"type":32,"value":2144},"一致",{"type":27,"tag":28,"props":2146,"children":2147},{},[2148],{"type":32,"value":2149},"标准装饰器的方法装饰器签名：",{"type":27,"tag":99,"props":2151,"children":2154},{"code":2152,"language":5,"meta":7,"className":2153},"function logged\u003CT extends (...args: any[]) => any>(\n  target: (this: void, ...args: Parameters\u003CT>) => ReturnType\u003CT>,\n  context: ClassMethodDecoratorContext\n) {\n  const methodName = String(context.name)\n\n  function replacementMethod(this: any, ...args: any[]) {\n    console.log(`LOG: Entering method '${methodName}'.`)\n    const result = target.call(this, ...args)\n    console.log(`LOG: Exiting method '${methodName}'.`)\n    return result\n  }\n\n  return replacementMethod\n}\n",[102],[2155],{"type":27,"tag":40,"props":2156,"children":2157},{"__ignoreMap":7},[2158],{"type":32,"value":2152},{"type":27,"tag":28,"props":2160,"children":2161},{},[2162,2164,2170,2171,2177,2178,2184,2185,2191,2192,2198],{"type":32,"value":2163},"context 对象提供了 ",{"type":27,"tag":40,"props":2165,"children":2167},{"className":2166},[],[2168],{"type":32,"value":2169},"name",{"type":32,"value":126},{"type":27,"tag":40,"props":2172,"children":2174},{"className":2173},[],[2175],{"type":32,"value":2176},"kind",{"type":32,"value":126},{"type":27,"tag":40,"props":2179,"children":2181},{"className":2180},[],[2182],{"type":32,"value":2183},"static",{"type":32,"value":126},{"type":27,"tag":40,"props":2186,"children":2188},{"className":2187},[],[2189],{"type":32,"value":2190},"private",{"type":32,"value":126},{"type":27,"tag":40,"props":2193,"children":2195},{"className":2194},[],[2196],{"type":32,"value":2197},"addInitializer",{"type":32,"value":2199}," 等信息，比传统装饰器的签名更丰富。",{"type":27,"tag":73,"props":2201,"children":2202},{"id":494},[2203],{"type":32,"value":494},{"type":27,"tag":498,"props":2205,"children":2206},{},[2207,2228],{"type":27,"tag":502,"props":2208,"children":2209},{},[2210],{"type":27,"tag":506,"props":2211,"children":2212},{},[2213,2218,2223],{"type":27,"tag":510,"props":2214,"children":2215},{},[2216],{"type":32,"value":2217},"装饰器类型",{"type":27,"tag":510,"props":2219,"children":2220},{},[2221],{"type":32,"value":2222},"能做的事",{"type":27,"tag":510,"props":2224,"children":2225},{},[2226],{"type":32,"value":2227},"典型用途",{"type":27,"tag":526,"props":2229,"children":2230},{},[2231,2249,2267,2285],{"type":27,"tag":506,"props":2232,"children":2233},{},[2234,2239,2244],{"type":27,"tag":533,"props":2235,"children":2236},{},[2237],{"type":32,"value":2238},"类装饰器",{"type":27,"tag":533,"props":2240,"children":2241},{},[2242],{"type":32,"value":2243},"替换或包装构造函数",{"type":27,"tag":533,"props":2245,"children":2246},{},[2247],{"type":32,"value":2248},"依赖注入、注册到框架",{"type":27,"tag":506,"props":2250,"children":2251},{},[2252,2257,2262],{"type":27,"tag":533,"props":2253,"children":2254},{},[2255],{"type":32,"value":2256},"方法装饰器",{"type":27,"tag":533,"props":2258,"children":2259},{},[2260],{"type":32,"value":2261},"修改方法行为",{"type":27,"tag":533,"props":2263,"children":2264},{},[2265],{"type":32,"value":2266},"日志、校验、重试、缓存",{"type":27,"tag":506,"props":2268,"children":2269},{},[2270,2275,2280],{"type":27,"tag":533,"props":2271,"children":2272},{},[2273],{"type":32,"value":2274},"访问器装饰器",{"type":27,"tag":533,"props":2276,"children":2277},{},[2278],{"type":32,"value":2279},"修改 getter/setter",{"type":27,"tag":533,"props":2281,"children":2282},{},[2283],{"type":32,"value":2284},"响应式计算、延迟初始化",{"type":27,"tag":506,"props":2286,"children":2287},{},[2288,2293,2298],{"type":27,"tag":533,"props":2289,"children":2290},{},[2291],{"type":32,"value":2292},"属性装饰器",{"type":27,"tag":533,"props":2294,"children":2295},{},[2296],{"type":32,"value":2297},"附加元数据",{"type":27,"tag":533,"props":2299,"children":2300},{},[2301],{"type":32,"value":2302},"注入标记、序列化映射",{"type":27,"tag":28,"props":2304,"children":2305},{},[2306],{"type":32,"value":2307},"装饰器的核心价值在于横切关注点（cross-cutting concerns）的分离。日志、校验、权限检查这类逻辑不适合散落在每个方法体内——它们横跨多个方法，最好用一个装饰器统一管理。使用装饰器时要注意的一个原则是：它应该增强方法但不改变方法的语义，否则会引入难以调试的隐式行为。",{"title":7,"searchDepth":657,"depth":657,"links":2309},[2310,2311,2312,2313,2314,2315,2316],{"id":1848,"depth":660,"text":1848},{"id":1887,"depth":660,"text":1890},{"id":1925,"depth":660,"text":1928},{"id":1950,"depth":660,"text":1953},{"id":1970,"depth":660,"text":1973},{"id":2003,"depth":660,"text":2006},{"id":494,"depth":660,"text":494},"content:topics:typescript:typescript-decorators-real-world-validation-logging-di.md","topics/typescript/typescript-decorators-real-world-validation-logging-di.md","topics/typescript/typescript-decorators-real-world-validation-logging-di",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":2321,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":2322,"_type":672,"_id":673,"_source":674,"_file":675,"_stem":676,"_extension":677},[13,14,15,16,17],{"type":24,"children":2323,"toc":2845},[2324,2328,2356,2360,2364,2368,2377,2385,2472,2476,2485,2493,2502,2506,2515,2523,2538,2542,2552,2560,2570,2574,2582,2586,2590,2594,2602,2623,2627,2635,2639,2656,2660,2664,2672,2676,2684,2688,2692,2700,2704,2708,2829],{"type":27,"tag":28,"props":2325,"children":2326},{},[2327],{"type":32,"value":33},{"type":27,"tag":28,"props":2329,"children":2330},{},[2331,2332,2337,2338,2343,2344,2349,2350,2355],{"type":32,"value":38},{"type":27,"tag":40,"props":2333,"children":2335},{"className":2334},[],[2336],{"type":32,"value":45},{"type":32,"value":47},{"type":27,"tag":40,"props":2339,"children":2341},{"className":2340},[],[2342],{"type":32,"value":53},{"type":32,"value":55},{"type":27,"tag":40,"props":2345,"children":2347},{"className":2346},[],[2348],{"type":32,"value":61},{"type":32,"value":63},{"type":27,"tag":40,"props":2351,"children":2353},{"className":2352},[],[2354],{"type":32,"value":69},{"type":32,"value":71},{"type":27,"tag":73,"props":2357,"children":2358},{"id":75},[2359],{"type":32,"value":75},{"type":27,"tag":28,"props":2361,"children":2362},{},[2363],{"type":32,"value":82},{"type":27,"tag":84,"props":2365,"children":2366},{"id":45},[2367],{"type":32,"value":45},{"type":27,"tag":28,"props":2369,"children":2370},{},[2371,2376],{"type":27,"tag":40,"props":2372,"children":2374},{"className":2373},[],[2375],{"type":32,"value":45},{"type":32,"value":97},{"type":27,"tag":99,"props":2378,"children":2380},{"className":2379,"code":103,"language":5,"meta":7},[102],[2381],{"type":27,"tag":40,"props":2382,"children":2383},{"__ignoreMap":7},[2384],{"type":32,"value":103},{"type":27,"tag":28,"props":2386,"children":2387},{},[2388,2393,2394,2399,2400,2405,2406,2411,2412,2417,2418,2423,2424,2429,2430,2435,2436,2441,2442,2447,2448,2453,2454,2459,2460,2465,2466,2471],{"type":27,"tag":40,"props":2389,"children":2391},{"className":2390},[],[2392],{"type":32,"value":45},{"type":32,"value":118},{"type":27,"tag":40,"props":2395,"children":2397},{"className":2396},[],[2398],{"type":32,"value":124},{"type":32,"value":126},{"type":27,"tag":40,"props":2401,"children":2403},{"className":2402},[],[2404],{"type":32,"value":132},{"type":32,"value":126},{"type":27,"tag":40,"props":2407,"children":2409},{"className":2408},[],[2410],{"type":32,"value":139},{"type":32,"value":126},{"type":27,"tag":40,"props":2413,"children":2415},{"className":2414},[],[2416],{"type":32,"value":146},{"type":32,"value":126},{"type":27,"tag":40,"props":2419,"children":2421},{"className":2420},[],[2422],{"type":32,"value":153},{"type":32,"value":126},{"type":27,"tag":40,"props":2425,"children":2427},{"className":2426},[],[2428],{"type":32,"value":160},{"type":32,"value":126},{"type":27,"tag":40,"props":2431,"children":2433},{"className":2432},[],[2434],{"type":32,"value":167},{"type":32,"value":126},{"type":27,"tag":40,"props":2437,"children":2439},{"className":2438},[],[2440],{"type":32,"value":174},{"type":32,"value":176},{"type":27,"tag":40,"props":2443,"children":2445},{"className":2444},[],[2446],{"type":32,"value":182},{"type":32,"value":184},{"type":27,"tag":40,"props":2449,"children":2451},{"className":2450},[],[2452],{"type":32,"value":45},{"type":32,"value":191},{"type":27,"tag":40,"props":2455,"children":2457},{"className":2456},[],[2458],{"type":32,"value":197},{"type":32,"value":199},{"type":27,"tag":40,"props":2461,"children":2463},{"className":2462},[],[2464],{"type":32,"value":205},{"type":32,"value":207},{"type":27,"tag":40,"props":2467,"children":2469},{"className":2468},[],[2470],{"type":32,"value":182},{"type":32,"value":214},{"type":27,"tag":84,"props":2473,"children":2474},{"id":53},[2475],{"type":32,"value":53},{"type":27,"tag":28,"props":2477,"children":2478},{},[2479,2484],{"type":27,"tag":40,"props":2480,"children":2482},{"className":2481},[],[2483],{"type":32,"value":53},{"type":32,"value":228},{"type":27,"tag":99,"props":2486,"children":2488},{"className":2487,"code":232,"language":5,"meta":7},[102],[2489],{"type":27,"tag":40,"props":2490,"children":2491},{"__ignoreMap":7},[2492],{"type":32,"value":232},{"type":27,"tag":28,"props":2494,"children":2495},{},[2496,2501],{"type":27,"tag":40,"props":2497,"children":2499},{"className":2498},[],[2500],{"type":32,"value":53},{"type":32,"value":247},{"type":27,"tag":84,"props":2503,"children":2504},{"id":69},[2505],{"type":32,"value":69},{"type":27,"tag":28,"props":2507,"children":2508},{},[2509,2514],{"type":27,"tag":40,"props":2510,"children":2512},{"className":2511},[],[2513],{"type":32,"value":69},{"type":32,"value":261},{"type":27,"tag":99,"props":2516,"children":2518},{"className":2517,"code":265,"language":5,"meta":7},[102],[2519],{"type":27,"tag":40,"props":2520,"children":2521},{"__ignoreMap":7},[2522],{"type":32,"value":265},{"type":27,"tag":28,"props":2524,"children":2525},{},[2526,2531,2532,2537],{"type":27,"tag":40,"props":2527,"children":2529},{"className":2528},[],[2530],{"type":32,"value":69},{"type":32,"value":280},{"type":27,"tag":40,"props":2533,"children":2535},{"className":2534},[],[2536],{"type":32,"value":69},{"type":32,"value":287},{"type":27,"tag":73,"props":2539,"children":2540},{"id":290},[2541],{"type":32,"value":290},{"type":27,"tag":28,"props":2543,"children":2544},{},[2545,2546,2551],{"type":32,"value":297},{"type":27,"tag":40,"props":2547,"children":2549},{"className":2548},[],[2550],{"type":32,"value":303},{"type":32,"value":305},{"type":27,"tag":99,"props":2553,"children":2555},{"className":2554,"code":309,"language":5,"meta":7},[102],[2556],{"type":27,"tag":40,"props":2557,"children":2558},{"__ignoreMap":7},[2559],{"type":32,"value":309},{"type":27,"tag":28,"props":2561,"children":2562},{},[2563,2564,2569],{"type":32,"value":319},{"type":27,"tag":40,"props":2565,"children":2567},{"className":2566},[],[2568],{"type":32,"value":325},{"type":32,"value":327},{"type":27,"tag":84,"props":2571,"children":2572},{"id":330},[2573],{"type":32,"value":330},{"type":27,"tag":99,"props":2575,"children":2577},{"className":2576,"code":336,"language":5,"meta":7},[102],[2578],{"type":27,"tag":40,"props":2579,"children":2580},{"__ignoreMap":7},[2581],{"type":32,"value":336},{"type":27,"tag":28,"props":2583,"children":2584},{},[2585],{"type":32,"value":346},{"type":27,"tag":73,"props":2587,"children":2588},{"id":349},[2589],{"type":32,"value":349},{"type":27,"tag":28,"props":2591,"children":2592},{},[2593],{"type":32,"value":356},{"type":27,"tag":99,"props":2595,"children":2597},{"className":2596,"code":360,"language":5,"meta":7},[102],[2598],{"type":27,"tag":40,"props":2599,"children":2600},{"__ignoreMap":7},[2601],{"type":32,"value":360},{"type":27,"tag":28,"props":2603,"children":2604},{},[2605,2610,2611,2616,2617,2622],{"type":27,"tag":40,"props":2606,"children":2608},{"className":2607},[],[2609],{"type":32,"value":374},{"type":32,"value":376},{"type":27,"tag":40,"props":2612,"children":2614},{"className":2613},[],[2615],{"type":32,"value":382},{"type":32,"value":384},{"type":27,"tag":40,"props":2618,"children":2620},{"className":2619},[],[2621],{"type":32,"value":124},{"type":32,"value":214},{"type":27,"tag":28,"props":2624,"children":2625},{},[2626],{"type":32,"value":395},{"type":27,"tag":99,"props":2628,"children":2630},{"className":2629,"code":399,"language":5,"meta":7},[102],[2631],{"type":27,"tag":40,"props":2632,"children":2633},{"__ignoreMap":7},[2634],{"type":32,"value":399},{"type":27,"tag":28,"props":2636,"children":2637},{},[2638],{"type":32,"value":409},{"type":27,"tag":411,"props":2640,"children":2641},{},[2642,2652],{"type":27,"tag":415,"props":2643,"children":2644},{},[2645,2646,2651],{"type":32,"value":419},{"type":27,"tag":40,"props":2647,"children":2649},{"className":2648},[],[2650],{"type":32,"value":425},{"type":32,"value":427},{"type":27,"tag":415,"props":2653,"children":2654},{},[2655],{"type":32,"value":432},{"type":27,"tag":73,"props":2657,"children":2658},{"id":435},[2659],{"type":32,"value":438},{"type":27,"tag":28,"props":2661,"children":2662},{},[2663],{"type":32,"value":443},{"type":27,"tag":99,"props":2665,"children":2667},{"className":2666,"code":447,"language":5,"meta":7},[102],[2668],{"type":27,"tag":40,"props":2669,"children":2670},{"__ignoreMap":7},[2671],{"type":32,"value":447},{"type":27,"tag":28,"props":2673,"children":2674},{},[2675],{"type":32,"value":457},{"type":27,"tag":99,"props":2677,"children":2679},{"className":2678,"code":461,"language":5,"meta":7},[102],[2680],{"type":27,"tag":40,"props":2681,"children":2682},{"__ignoreMap":7},[2683],{"type":32,"value":461},{"type":27,"tag":73,"props":2685,"children":2686},{"id":469},[2687],{"type":32,"value":472},{"type":27,"tag":28,"props":2689,"children":2690},{},[2691],{"type":32,"value":477},{"type":27,"tag":99,"props":2693,"children":2695},{"className":2694,"code":481,"language":5,"meta":7},[102],[2696],{"type":27,"tag":40,"props":2697,"children":2698},{"__ignoreMap":7},[2699],{"type":32,"value":481},{"type":27,"tag":28,"props":2701,"children":2702},{},[2703],{"type":32,"value":491},{"type":27,"tag":73,"props":2705,"children":2706},{"id":494},[2707],{"type":32,"value":494},{"type":27,"tag":498,"props":2709,"children":2710},{},[2711,2729],{"type":27,"tag":502,"props":2712,"children":2713},{},[2714],{"type":27,"tag":506,"props":2715,"children":2716},{},[2717,2721,2725],{"type":27,"tag":510,"props":2718,"children":2719},{},[2720],{"type":32,"value":514},{"type":27,"tag":510,"props":2722,"children":2723},{},[2724],{"type":32,"value":519},{"type":27,"tag":510,"props":2726,"children":2727},{},[2728],{"type":32,"value":524},{"type":27,"tag":526,"props":2730,"children":2731},{},[2732,2751,2770,2789,2809],{"type":27,"tag":506,"props":2733,"children":2734},{},[2735,2743,2747],{"type":27,"tag":533,"props":2736,"children":2737},{},[2738],{"type":27,"tag":40,"props":2739,"children":2741},{"className":2740},[],[2742],{"type":32,"value":45},{"type":27,"tag":533,"props":2744,"children":2745},{},[2746],{"type":32,"value":545},{"type":27,"tag":533,"props":2748,"children":2749},{},[2750],{"type":32,"value":550},{"type":27,"tag":506,"props":2752,"children":2753},{},[2754,2762,2766],{"type":27,"tag":533,"props":2755,"children":2756},{},[2757],{"type":27,"tag":40,"props":2758,"children":2760},{"className":2759},[],[2761],{"type":32,"value":53},{"type":27,"tag":533,"props":2763,"children":2764},{},[2765],{"type":32,"value":566},{"type":27,"tag":533,"props":2767,"children":2768},{},[2769],{"type":32,"value":550},{"type":27,"tag":506,"props":2771,"children":2772},{},[2773,2781,2785],{"type":27,"tag":533,"props":2774,"children":2775},{},[2776],{"type":27,"tag":40,"props":2777,"children":2779},{"className":2778},[],[2780],{"type":32,"value":69},{"type":27,"tag":533,"props":2782,"children":2783},{},[2784],{"type":32,"value":586},{"type":27,"tag":533,"props":2786,"children":2787},{},[2788],{"type":32,"value":591},{"type":27,"tag":506,"props":2790,"children":2791},{},[2792,2801,2805],{"type":27,"tag":533,"props":2793,"children":2794},{},[2795,2796],{"type":32,"value":319},{"type":27,"tag":40,"props":2797,"children":2799},{"className":2798},[],[2800],{"type":32,"value":604},{"type":27,"tag":533,"props":2802,"children":2803},{},[2804],{"type":32,"value":609},{"type":27,"tag":533,"props":2806,"children":2807},{},[2808],{"type":32,"value":614},{"type":27,"tag":506,"props":2810,"children":2811},{},[2812,2821,2825],{"type":27,"tag":533,"props":2813,"children":2814},{},[2815,2816],{"type":32,"value":622},{"type":27,"tag":40,"props":2817,"children":2819},{"className":2818},[],[2820],{"type":32,"value":628},{"type":27,"tag":533,"props":2822,"children":2823},{},[2824],{"type":32,"value":633},{"type":27,"tag":533,"props":2826,"children":2827},{},[2828],{"type":32,"value":614},{"type":27,"tag":28,"props":2830,"children":2831},{},[2832,2833,2838,2839,2844],{"type":32,"value":642},{"type":27,"tag":40,"props":2834,"children":2836},{"className":2835},[],[2837],{"type":32,"value":45},{"type":32,"value":126},{"type":27,"tag":40,"props":2840,"children":2842},{"className":2841},[],[2843],{"type":32,"value":53},{"type":32,"value":655},{"title":7,"searchDepth":657,"depth":657,"links":2846},[2847,2852,2855,2856,2857,2858],{"id":75,"depth":660,"text":75,"children":2848},[2849,2850,2851],{"id":45,"depth":657,"text":45},{"id":53,"depth":657,"text":53},{"id":69,"depth":657,"text":69},{"id":290,"depth":660,"text":290,"children":2853},[2854],{"id":330,"depth":657,"text":330},{"id":349,"depth":660,"text":349},{"id":435,"depth":660,"text":438},{"id":469,"depth":660,"text":472},{"id":494,"depth":660,"text":494},1780533153862]