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