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