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