[{"data":1,"prerenderedAt":2291},["ShallowReactive",2],{"article-/topics/typescript/typescript-event-map-payload-contract-subscription-safety":3,"related-typescript":575,"content-query-ZJ2kxxAdRh":1863},{"_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":569,"_id":570,"_source":571,"_file":572,"_stem":573,"_extension":574},"/topics/typescript/typescript-event-map-payload-contract-subscription-safety","typescript",false,"","TypeScript 事件系统建模：事件映射、payload 约束与订阅端类型安全","事件系统最容易失控的地方，不是发不出去，而是名字、payload 和订阅约定慢慢漂移。本文从 event map、发布订阅 API 设计和版本演进出发，讲清 TypeScript 如何让事件系统保持可协作。","2026-06-08","HTMLPAGE 团队",[13,14,15,16,17],"TypeScript","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/",16,{"type":24,"children":25,"toc":557},"root",[26,67,72,79,91,96,111,116,122,127,136,141,150,155,161,166,179,191,196,214,220,225,230,248,253,259,280,298,303,309,314,337,342,347,375,380,385,390,480,485,515,520],{"type":27,"tag":28,"props":29,"children":30},"element","p",{},[31,34,41,43,49,51,57,59,65],{"type":32,"value":33},"text","很多系统一开始的事件总线都很轻：",{"type":27,"tag":35,"props":36,"children":38},"code",{"className":37},[],[39],{"type":32,"value":40},"emit(name, payload)",{"type":32,"value":42},"，再配一个 ",{"type":27,"tag":35,"props":44,"children":46},{"className":45},[],[47],{"type":32,"value":48},"on(name, handler)",{"type":32,"value":50}," 就能跑起来。真正的问题不会立刻出现，而是随着事件增多、订阅方变多、场景变复杂，名字和 payload 会慢慢失去同步。某个地方仍然发 ",{"type":27,"tag":35,"props":52,"children":54},{"className":53},[],[55],{"type":32,"value":56},"user.updated",{"type":32,"value":58},"，另一个地方已经开始监听 ",{"type":27,"tag":35,"props":60,"children":62},{"className":61},[],[63],{"type":32,"value":64},"user.profile.updated",{"type":32,"value":66},"；某次迭代里 payload 多了一个字段，但下游还按旧形态读取；有的 handler 以为收到的是单对象，另一个地方却开始传数组。",{"type":27,"tag":28,"props":68,"children":69},{},[70],{"type":32,"value":71},"TypeScript 在事件系统里的价值，不是把事件总线写得更花，而是把“事件名和 payload 的对应关系”变成可检查的契约。只要这一层没有被显式建模，系统后面再怎么拆模块、加队列、做回放，事件协作都很容易继续靠记忆和文档维持。",{"type":27,"tag":73,"props":74,"children":76},"h2",{"id":75},"最危险的接口长这样事件名是-stringpayload-是-any",[77],{"type":32,"value":78},"最危险的接口长这样：事件名是 string，payload 是 any",{"type":27,"tag":80,"props":81,"children":86},"pre",{"className":82,"code":84,"language":85,"meta":7},[83],"language-ts","function emit(event: string, payload: any) {\n  // ...\n}\n","ts",[87],{"type":27,"tag":35,"props":88,"children":89},{"__ignoreMap":7},[90],{"type":32,"value":84},{"type":27,"tag":28,"props":92,"children":93},{},[94],{"type":32,"value":95},"这类写法的坏处不是“没有类型提示”这么简单，而是它让系统的两个核心约束完全脱离了关系：",{"type":27,"tag":97,"props":98,"children":99},"ul",{},[100,106],{"type":27,"tag":101,"props":102,"children":103},"li",{},[104],{"type":32,"value":105},"哪些事件名是合法的。",{"type":27,"tag":101,"props":107,"children":108},{},[109],{"type":32,"value":110},"某个事件对应的 payload 应该长什么样。",{"type":27,"tag":28,"props":112,"children":113},{},[114],{"type":32,"value":115},"只要这两个约束还靠人脑维护，规模一大就一定会漂移。",{"type":27,"tag":73,"props":117,"children":119},{"id":118},"event-map-是最稳的起点先把名字和-payload-绑定起来",[120],{"type":32,"value":121},"event map 是最稳的起点：先把名字和 payload 绑定起来",{"type":27,"tag":28,"props":123,"children":124},{},[125],{"type":32,"value":126},"一种非常实用的建模方式，是先定义事件映射表：",{"type":27,"tag":80,"props":128,"children":131},{"className":129,"code":130,"language":85,"meta":7},[83],"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",[132],{"type":27,"tag":35,"props":133,"children":134},{"__ignoreMap":7},[135],{"type":32,"value":130},{"type":27,"tag":28,"props":137,"children":138},{},[139],{"type":32,"value":140},"一旦这张表存在，发布和订阅接口都可以围绕它推导：",{"type":27,"tag":80,"props":142,"children":145},{"className":143,"code":144,"language":85,"meta":7},[83],"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",[146],{"type":27,"tag":35,"props":147,"children":148},{"__ignoreMap":7},[149],{"type":32,"value":144},{"type":27,"tag":28,"props":151,"children":152},{},[153],{"type":32,"value":154},"这类 API 的意义不只是补全更舒服，而是让“事件名”和“事件负载”在类型层面无法脱钩。只要事件名选错、payload 形状不对，问题会在提交代码前就暴露。",{"type":27,"tag":73,"props":156,"children":158},{"id":157},"事件设计真正要防的是同名异义和异名同义",[159],{"type":32,"value":160},"事件设计真正要防的是“同名异义”和“异名同义”",{"type":27,"tag":28,"props":162,"children":163},{},[164],{"type":32,"value":165},"事件系统最容易出现的两类混乱是：",{"type":27,"tag":97,"props":167,"children":168},{},[169,174],{"type":27,"tag":101,"props":170,"children":171},{},[172],{"type":32,"value":173},"同名异义：同一个事件名在不同模块里承载不同语义。",{"type":27,"tag":101,"props":175,"children":176},{},[177],{"type":32,"value":178},"异名同义：同一类业务事实被多个名字重复表达。",{"type":27,"tag":28,"props":180,"children":181},{},[182,184,189],{"type":32,"value":183},"比如 ",{"type":27,"tag":35,"props":185,"children":187},{"className":186},[],[188],{"type":32,"value":56},{"type":32,"value":190}," 这个名字，看起来什么都能装。用户改昵称、改角色、改手机号、改订阅偏好都能叫 updated。短期很方便，长期却让订阅方不知道自己到底该监听什么，也让 payload 越长越杂。",{"type":27,"tag":28,"props":192,"children":193},{},[194],{"type":32,"value":195},"更稳的做法往往是：",{"type":27,"tag":97,"props":197,"children":198},{},[199,204,209],{"type":27,"tag":101,"props":200,"children":201},{},[202],{"type":32,"value":203},"名称按业务事实切分，而不是按“发生过变化”这种宽泛概念命名。",{"type":27,"tag":101,"props":205,"children":206},{},[207],{"type":32,"value":208},"payload 只携带当前事件需要承诺的最小信息。",{"type":27,"tag":101,"props":210,"children":211},{},[212],{"type":32,"value":213},"大量共享字段通过公共对象或 metadata 包装，而不是每个事件都随意展开。",{"type":27,"tag":73,"props":215,"children":217},{"id":216},"订阅端类型安全的关键不在泛型而在-handler-语义是否稳定",[218],{"type":32,"value":219},"订阅端类型安全的关键，不在泛型，而在 handler 语义是否稳定",{"type":27,"tag":28,"props":221,"children":222},{},[223],{"type":32,"value":224},"发布端和订阅端的类型常常被讨论成“泛型能不能写出来”，其实更重要的问题是：handler 是否能基于事件名做出稳定假设。如果一个事件 payload 经常改、字段意义经常变，哪怕泛型写对了，订阅端也不会真正稳定。",{"type":27,"tag":28,"props":226,"children":227},{},[228],{"type":32,"value":229},"所以团队在设计 event map 时，最好把这几件事一起定清：",{"type":27,"tag":97,"props":231,"children":232},{},[233,238,243],{"type":27,"tag":101,"props":234,"children":235},{},[236],{"type":32,"value":237},"这个事件代表什么业务事实。",{"type":27,"tag":101,"props":239,"children":240},{},[241],{"type":32,"value":242},"订阅方最少需要拿到哪些字段。",{"type":27,"tag":101,"props":244,"children":245},{},[246],{"type":32,"value":247},"哪些字段未来允许扩展，哪些字段一旦变化就算破坏式变更。",{"type":27,"tag":28,"props":249,"children":250},{},[251],{"type":32,"value":252},"TypeScript 能帮你守住结构，但不能替你定义清业务语义。",{"type":27,"tag":73,"props":254,"children":256},{"id":255},"一个常见失败案例事件总线很通用但所有变更都在悄悄破约",[257],{"type":32,"value":258},"一个常见失败案例：事件总线很“通用”，但所有变更都在悄悄破约",{"type":27,"tag":28,"props":260,"children":261},{},[262,264,270,272,278],{"type":32,"value":263},"某团队有一套抽象得很漂亮的事件总线封装，",{"type":27,"tag":35,"props":265,"children":267},{"className":266},[],[268],{"type":32,"value":269},"emit",{"type":32,"value":271}," 和 ",{"type":27,"tag":35,"props":273,"children":275},{"className":274},[],[276],{"type":32,"value":277},"on",{"type":32,"value":279}," 都做成了泛型，但事件名本身没有中心化建模，而是由各模块自己声明字符串字面量。结果几个月后出现了典型问题：",{"type":27,"tag":97,"props":281,"children":282},{},[283,288,293],{"type":27,"tag":101,"props":284,"children":285},{},[286],{"type":32,"value":287},"同一个名字在多个地方被重复定义。",{"type":27,"tag":101,"props":289,"children":290},{},[291],{"type":32,"value":292},"payload 字段逐步追加，但没有人通知所有订阅者。",{"type":27,"tag":101,"props":294,"children":295},{},[296],{"type":32,"value":297},"少数 handler 开始自己断言 payload 类型，绕过总线约束。",{"type":27,"tag":28,"props":299,"children":300},{},[301],{"type":32,"value":302},"问题不在总线 API，而在契约没有集中。只要 event map 不是系统级事实，而是多个文件分散声明，漂移迟早会发生。",{"type":27,"tag":73,"props":304,"children":306},{"id":305},"事件版本演进要像-api-一样认真",[307],{"type":32,"value":308},"事件版本演进要像 API 一样认真",{"type":27,"tag":28,"props":310,"children":311},{},[312],{"type":32,"value":313},"很多团队对 HTTP API 很谨慎，却对事件变更很随意。实际上，事件一旦被多个消费者订阅，它就是另一种 API。比较值得固定的规则包括：",{"type":27,"tag":97,"props":315,"children":316},{},[317,322,327,332],{"type":27,"tag":101,"props":318,"children":319},{},[320],{"type":32,"value":321},"payload 新增字段通常问题不大，但删除和重命名要视作破坏式变更。",{"type":27,"tag":101,"props":323,"children":324},{},[325],{"type":32,"value":326},"需要重大语义变化时，优先引入新事件名，而不是偷偷改旧 payload。",{"type":27,"tag":101,"props":328,"children":329},{},[330],{"type":32,"value":331},"公共事件和内部事件分层，不要让局部实现细节暴露给全局订阅方。",{"type":27,"tag":101,"props":333,"children":334},{},[335],{"type":32,"value":336},"如果事件跨进程或跨服务传播，运行时 schema 校验也要补上。",{"type":27,"tag":28,"props":338,"children":339},{},[340],{"type":32,"value":341},"事件不是“消息发出去就算完”，而是长期协作契约的一部分。",{"type":27,"tag":73,"props":343,"children":345},{"id":344},"一份事件系统建模检查表",[346],{"type":32,"value":344},{"type":27,"tag":97,"props":348,"children":349},{},[350,355,360,365,370],{"type":27,"tag":101,"props":351,"children":352},{},[353],{"type":32,"value":354},"事件名和 payload 是否被一张中心化 event map 绑定。",{"type":27,"tag":101,"props":356,"children":357},{},[358],{"type":32,"value":359},"事件命名是否表达业务事实，而不是宽泛动作。",{"type":27,"tag":101,"props":361,"children":362},{},[363],{"type":32,"value":364},"payload 是否只承诺最小必要字段，而非把整个对象一股脑透出去。",{"type":27,"tag":101,"props":366,"children":367},{},[368],{"type":32,"value":369},"订阅方是否能仅凭事件名获得稳定的 payload 类型。",{"type":27,"tag":101,"props":371,"children":372},{},[373],{"type":32,"value":374},"破坏式变更是否按 API 升级一样被认真对待。",{"type":27,"tag":73,"props":376,"children":378},{"id":377},"总结",[379],{"type":32,"value":377},{"type":27,"tag":28,"props":381,"children":382},{},[383],{"type":32,"value":384},"TypeScript 让事件系统最值得做的一件事，就是把“事件名和 payload 的关系”从口头约定变成编译期契约。只要 event map 足够集中、命名足够克制、版本演进足够明确，发布订阅就不会随着规模扩大而变成字符串驱动的隐性耦合。",{"type":27,"tag":28,"props":386,"children":387},{},[388],{"type":32,"value":389},"本批次专题导航：",{"type":27,"tag":97,"props":391,"children":392},{},[393,420,445,463],{"type":27,"tag":101,"props":394,"children":395},{},[396,398,405,407,413,414],{"type":32,"value":397},"工程边界：",{"type":27,"tag":399,"props":400,"children":402},"a",{"href":401},"/topics/typescript/typescript-project-references-tsconfig-layering-incremental-build-boundaries",[403],{"type":32,"value":404},"TypeScript 项目引用与 tsconfig 分层",{"type":32,"value":406},"、",{"type":27,"tag":399,"props":408,"children":410},{"href":409},"/topics/typescript/typescript-monorepo-dependency-boundaries-path-alias-exports-cycles",[411],{"type":32,"value":412},"TypeScript Monorepo 依赖边界治理",{"type":32,"value":406},{"type":27,"tag":399,"props":415,"children":417},{"href":416},"/topics/typescript/typescript-typecheck-performance-optimization-large-repo-playbook",[418],{"type":32,"value":419},"TypeScript 类型检查性能优化",{"type":27,"tag":101,"props":421,"children":422},{},[423,425,431,432,438,439],{"type":32,"value":424},"协议协作：",{"type":27,"tag":399,"props":426,"children":428},{"href":427},"/topics/typescript/typescript-public-api-design-exported-types-breaking-change-control",[429],{"type":32,"value":430},"TypeScript 公共 API 设计",{"type":32,"value":406},{"type":27,"tag":399,"props":433,"children":435},{"href":434},"/topics/typescript/typescript-runtime-validation-static-types-schema-error-modeling",[436],{"type":32,"value":437},"TypeScript 运行时校验与静态类型协作",{"type":32,"value":406},{"type":27,"tag":399,"props":440,"children":442},{"href":441},"/topics/typescript/typescript-openapi-contract-codegen-handwritten-types-versioning",[443],{"type":32,"value":444},"TypeScript 与 OpenAPI 契约协同",{"type":27,"tag":101,"props":446,"children":447},{},[448,450,456,457],{"type":32,"value":449},"落地复用：",{"type":27,"tag":399,"props":451,"children":453},{"href":452},"/topics/typescript/typescript-design-patterns-factory-strategy-adapter-proxy",[454],{"type":32,"value":455},"TypeScript 设计模式实战",{"type":32,"value":406},{"type":27,"tag":399,"props":458,"children":460},{"href":459},"/topics/typescript/typescript-test-data-builders-fixtures-mock-type-safety",[461],{"type":32,"value":462},"TypeScript 测试数据构建",{"type":27,"tag":101,"props":464,"children":465},{},[466,468,473,474],{"type":32,"value":467},"状态建模：",{"type":27,"tag":399,"props":469,"children":470},{"href":4},[471],{"type":32,"value":472},"TypeScript 事件系统建模",{"type":32,"value":406},{"type":27,"tag":399,"props":475,"children":477},{"href":476},"/topics/typescript/typescript-form-state-validation-error-modeling-guide",[478],{"type":32,"value":479},"TypeScript 表单与错误状态建模",{"type":27,"tag":28,"props":481,"children":482},{},[483],{"type":32,"value":484},"本系列导航：",{"type":27,"tag":97,"props":486,"children":487},{},[488,497,506],{"type":27,"tag":101,"props":489,"children":490},{},[491,493],{"type":32,"value":492},"如果你还没把边界输入做成可信数据，先读 ",{"type":27,"tag":399,"props":494,"children":495},{"href":434},[496],{"type":32,"value":437},{"type":27,"tag":101,"props":498,"children":499},{},[500,502],{"type":32,"value":501},"若你要把事件流继续落到页面状态，再看 ",{"type":27,"tag":399,"props":503,"children":504},{"href":476},[505],{"type":32,"value":479},{"type":27,"tag":101,"props":507,"children":508},{},[509,511],{"type":32,"value":510},"如果你希望对外契约和事件契约一起收口，可读 ",{"type":27,"tag":399,"props":512,"children":513},{"href":427},[514],{"type":32,"value":430},{"type":27,"tag":28,"props":516,"children":517},{},[518],{"type":32,"value":519},"延伸阅读：",{"type":27,"tag":97,"props":521,"children":522},{},[523,532,541,548],{"type":27,"tag":101,"props":524,"children":525},{},[526],{"type":27,"tag":399,"props":527,"children":529},{"href":528},"/topics/typescript/typescript-template-literal-types-real-world-applications",[530],{"type":32,"value":531},"TypeScript 模板字面量类型在真实项目中的应用",{"type":27,"tag":101,"props":533,"children":534},{},[535],{"type":27,"tag":399,"props":536,"children":538},{"href":537},"/topics/typescript/typescript-discriminated-unions-exhaustive-check-state-management",[539],{"type":32,"value":540},"TypeScript 可辨识联合与穷举检查",{"type":27,"tag":101,"props":542,"children":543},{},[544],{"type":27,"tag":399,"props":545,"children":546},{"href":434},[547],{"type":32,"value":437},{"type":27,"tag":101,"props":549,"children":550},{},[551],{"type":27,"tag":399,"props":552,"children":554},{"href":553},"/topics/realtime-applications-guide",[555],{"type":32,"value":556},"实时应用开发完全指南",{"title":7,"searchDepth":558,"depth":558,"links":559},3,[560,562,563,564,565,566,567,568],{"id":75,"depth":561,"text":78},2,{"id":118,"depth":561,"text":121},{"id":157,"depth":561,"text":160},{"id":216,"depth":561,"text":219},{"id":255,"depth":561,"text":258},{"id":305,"depth":561,"text":308},{"id":344,"depth":561,"text":344},{"id":377,"depth":561,"text":377},"markdown","content:topics:typescript:typescript-event-map-payload-contract-subscription-safety.md","content","topics/typescript/typescript-event-map-payload-contract-subscription-safety.md","topics/typescript/typescript-event-map-payload-contract-subscription-safety","md",[576,1020,1435],{"_path":577,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":578,"description":579,"date":580,"topic":5,"author":11,"tags":581,"image":586,"featured":587,"readingTime":588,"body":589,"_type":569,"_id":1017,"_source":571,"_file":1018,"_stem":1019,"_extension":574},"/topics/typescript/typescript-vue3-best-practices","TypeScript 在 Vue 3 中的最佳实践","深度讲解如何在 Vue 3 中高效使用 TypeScript，包括类型定义、接口设计、generics 应用、常见错误等完整指南。","2025-12-27",[13,582,583,584,585],"Vue 3","类型安全","最佳实践","接口设计","/images/topics/typescript-vue3.jpg",true,12,{"type":24,"children":590,"toc":990},[591,596,601,607,614,624,630,639,645,650,659,664,673,678,687,693,699,708,714,723,729,735,744,750,759,765,774,780,789,795,804,810,819,825,834,838,843,951,956],{"type":27,"tag":73,"props":592,"children":594},{"id":593},"typescript-在-vue-3-中的最佳实践",[595],{"type":32,"value":578},{"type":27,"tag":28,"props":597,"children":598},{},[599],{"type":32,"value":600},"TypeScript 让 Vue 开发更加安全可靠。本文讲解如何在 Vue 3 中高效使用 TypeScript。",{"type":27,"tag":73,"props":602,"children":604},{"id":603},"_1-基础类型定义",[605],{"type":32,"value":606},"1. 基础类型定义",{"type":27,"tag":608,"props":609,"children":611},"h3",{"id":610},"组件-props-的类型定义",[612],{"type":32,"value":613},"组件 Props 的类型定义",{"type":27,"tag":80,"props":615,"children":619},{"className":616,"code":618,"language":5,"meta":7},[617],"language-typescript","// ✅ 完整的类型定义\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",[620],{"type":27,"tag":35,"props":621,"children":622},{"__ignoreMap":7},[623],{"type":32,"value":618},{"type":27,"tag":608,"props":625,"children":627},{"id":626},"组件-emits-的类型定义",[628],{"type":32,"value":629},"组件 Emits 的类型定义",{"type":27,"tag":80,"props":631,"children":634},{"className":632,"code":633,"language":5,"meta":7},[617],"// ✅ 类型安全的事件发射\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",[635],{"type":27,"tag":35,"props":636,"children":637},{"__ignoreMap":7},[638],{"type":32,"value":633},{"type":27,"tag":73,"props":640,"children":642},{"id":641},"_2-高级类型模式",[643],{"type":32,"value":644},"2. 高级类型模式",{"type":27,"tag":608,"props":646,"children":648},{"id":647},"泛型组件",[649],{"type":32,"value":647},{"type":27,"tag":80,"props":651,"children":654},{"className":652,"code":653,"language":5,"meta":7},[617],"// 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",[655],{"type":27,"tag":35,"props":656,"children":657},{"__ignoreMap":7},[658],{"type":32,"value":653},{"type":27,"tag":608,"props":660,"children":662},{"id":661},"条件类型和分布式条件类型",[663],{"type":32,"value":661},{"type":27,"tag":80,"props":665,"children":668},{"className":666,"code":667,"language":5,"meta":7},[617],"// 条件类型 (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",[669],{"type":27,"tag":35,"props":670,"children":671},{"__ignoreMap":7},[672],{"type":32,"value":667},{"type":27,"tag":608,"props":674,"children":676},{"id":675},"复杂的接口设计",[677],{"type":32,"value":675},{"type":27,"tag":80,"props":679,"children":682},{"className":680,"code":681,"language":5,"meta":7},[617],"// 实战: 表单验证框架\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",[683],{"type":27,"tag":35,"props":684,"children":685},{"__ignoreMap":7},[686],{"type":32,"value":681},{"type":27,"tag":73,"props":688,"children":690},{"id":689},"_3-组合式-api-的类型定义",[691],{"type":32,"value":692},"3. 组合式 API 的类型定义",{"type":27,"tag":608,"props":694,"children":696},{"id":695},"composable-的返回类型",[697],{"type":32,"value":698},"Composable 的返回类型",{"type":27,"tag":80,"props":700,"children":703},{"className":701,"code":702,"language":5,"meta":7},[617],"// 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",[704],{"type":27,"tag":35,"props":705,"children":706},{"__ignoreMap":7},[707],{"type":32,"value":702},{"type":27,"tag":608,"props":709,"children":711},{"id":710},"composable-的泛型",[712],{"type":32,"value":713},"Composable 的泛型",{"type":27,"tag":80,"props":715,"children":718},{"className":716,"code":717,"language":5,"meta":7},[617],"// 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",[719],{"type":27,"tag":35,"props":720,"children":721},{"__ignoreMap":7},[722],{"type":32,"value":717},{"type":27,"tag":73,"props":724,"children":726},{"id":725},"_4-常见类型错误和解决方案",[727],{"type":32,"value":728},"4. 常见类型错误和解决方案",{"type":27,"tag":608,"props":730,"children":732},{"id":731},"错误-1-any-类型滥用",[733],{"type":32,"value":734},"错误 1: Any 类型滥用",{"type":27,"tag":80,"props":736,"children":739},{"className":737,"code":738,"language":5,"meta":7},[617],"// ❌ 避免\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",[740],{"type":27,"tag":35,"props":741,"children":742},{"__ignoreMap":7},[743],{"type":32,"value":738},{"type":27,"tag":608,"props":745,"children":747},{"id":746},"错误-2-类型断言滥用",[748],{"type":32,"value":749},"错误 2: 类型断言滥用",{"type":27,"tag":80,"props":751,"children":754},{"className":752,"code":753,"language":5,"meta":7},[617],"// ❌ 避免 (类型断言隐藏问题)\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",[755],{"type":27,"tag":35,"props":756,"children":757},{"__ignoreMap":7},[758],{"type":32,"value":753},{"type":27,"tag":608,"props":760,"children":762},{"id":761},"错误-3-props-类型和运行时定义不匹配",[763],{"type":32,"value":764},"错误 3: Props 类型和运行时定义不匹配",{"type":27,"tag":80,"props":766,"children":769},{"className":767,"code":768,"language":5,"meta":7},[617],"// ❌ 避免 (类型和运行时不一致)\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",[770],{"type":27,"tag":35,"props":771,"children":772},{"__ignoreMap":7},[773],{"type":32,"value":768},{"type":27,"tag":73,"props":775,"children":777},{"id":776},"_5-pinia-store-的类型定义",[778],{"type":32,"value":779},"5. Pinia Store 的类型定义",{"type":27,"tag":80,"props":781,"children":784},{"className":782,"code":783,"language":5,"meta":7},[617],"// 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",[785],{"type":27,"tag":35,"props":786,"children":787},{"__ignoreMap":7},[788],{"type":32,"value":783},{"type":27,"tag":73,"props":790,"children":792},{"id":791},"_6-api-响应的类型定义",[793],{"type":32,"value":794},"6. API 响应的类型定义",{"type":27,"tag":80,"props":796,"children":799},{"className":797,"code":798,"language":5,"meta":7},[617],"// 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",[800],{"type":27,"tag":35,"props":801,"children":802},{"__ignoreMap":7},[803],{"type":32,"value":798},{"type":27,"tag":73,"props":805,"children":807},{"id":806},"_7-类型工具函数",[808],{"type":32,"value":809},"7. 类型工具函数",{"type":27,"tag":80,"props":811,"children":814},{"className":812,"code":813,"language":5,"meta":7},[617],"// 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",[815],{"type":27,"tag":35,"props":816,"children":817},{"__ignoreMap":7},[818],{"type":32,"value":813},{"type":27,"tag":73,"props":820,"children":822},{"id":821},"_8-最佳实践总结",[823],{"type":32,"value":824},"8. 最佳实践总结",{"type":27,"tag":80,"props":826,"children":829},{"className":827,"code":828,"language":5,"meta":7},[617],"// ✅ 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",[830],{"type":27,"tag":35,"props":831,"children":832},{"__ignoreMap":7},[833],{"type":32,"value":828},{"type":27,"tag":73,"props":835,"children":836},{"id":377},[837],{"type":32,"value":377},{"type":27,"tag":28,"props":839,"children":840},{},[841],{"type":32,"value":842},"TypeScript 在 Vue 3 中的优势：",{"type":27,"tag":844,"props":845,"children":846},"table",{},[847,866],{"type":27,"tag":848,"props":849,"children":850},"thead",{},[851],{"type":27,"tag":852,"props":853,"children":854},"tr",{},[855,861],{"type":27,"tag":856,"props":857,"children":858},"th",{},[859],{"type":32,"value":860},"特性",{"type":27,"tag":856,"props":862,"children":863},{},[864],{"type":32,"value":865},"优势",{"type":27,"tag":867,"props":868,"children":869},"tbody",{},[870,887,903,919,935],{"type":27,"tag":852,"props":871,"children":872},{},[873,882],{"type":27,"tag":874,"props":875,"children":876},"td",{},[877],{"type":27,"tag":878,"props":879,"children":880},"strong",{},[881],{"type":32,"value":583},{"type":27,"tag":874,"props":883,"children":884},{},[885],{"type":32,"value":886},"编译时发现错误",{"type":27,"tag":852,"props":888,"children":889},{},[890,898],{"type":27,"tag":874,"props":891,"children":892},{},[893],{"type":27,"tag":878,"props":894,"children":895},{},[896],{"type":32,"value":897},"自动补全",{"type":27,"tag":874,"props":899,"children":900},{},[901],{"type":32,"value":902},"IDE 提示更准确",{"type":27,"tag":852,"props":904,"children":905},{},[906,914],{"type":27,"tag":874,"props":907,"children":908},{},[909],{"type":27,"tag":878,"props":910,"children":911},{},[912],{"type":32,"value":913},"可维护性",{"type":27,"tag":874,"props":915,"children":916},{},[917],{"type":32,"value":918},"代码意图更明确",{"type":27,"tag":852,"props":920,"children":921},{},[922,930],{"type":27,"tag":874,"props":923,"children":924},{},[925],{"type":27,"tag":878,"props":926,"children":927},{},[928],{"type":32,"value":929},"重构安全",{"type":27,"tag":874,"props":931,"children":932},{},[933],{"type":32,"value":934},"改变代码有反馈",{"type":27,"tag":852,"props":936,"children":937},{},[938,946],{"type":27,"tag":874,"props":939,"children":940},{},[941],{"type":27,"tag":878,"props":942,"children":943},{},[944],{"type":32,"value":945},"文档作用",{"type":27,"tag":874,"props":947,"children":948},{},[949],{"type":32,"value":950},"类型即文档",{"type":27,"tag":73,"props":952,"children":954},{"id":953},"相关资源",[955],{"type":32,"value":953},{"type":27,"tag":97,"props":957,"children":958},{},[959,970,980],{"type":27,"tag":101,"props":960,"children":961},{},[962],{"type":27,"tag":399,"props":963,"children":967},{"href":964,"rel":965},"https://www.typescriptlang.org/docs/",[966],"nofollow",[968],{"type":32,"value":969},"TypeScript 官方手册",{"type":27,"tag":101,"props":971,"children":972},{},[973],{"type":27,"tag":399,"props":974,"children":977},{"href":975,"rel":976},"https://vuejs.org/guide/typescript/overview.html",[966],[978],{"type":32,"value":979},"Vue 3 TypeScript 指南",{"type":27,"tag":101,"props":981,"children":982},{},[983],{"type":27,"tag":399,"props":984,"children":987},{"href":985,"rel":986},"https://www.typescriptlang.org/docs/handbook/2/types-from-types.html",[966],[988],{"type":32,"value":989},"TypeScript 高级类型",{"title":7,"searchDepth":558,"depth":558,"links":991},[992,993,997,1002,1006,1011,1012,1013,1014,1015,1016],{"id":593,"depth":561,"text":578},{"id":603,"depth":561,"text":606,"children":994},[995,996],{"id":610,"depth":558,"text":613},{"id":626,"depth":558,"text":629},{"id":641,"depth":561,"text":644,"children":998},[999,1000,1001],{"id":647,"depth":558,"text":647},{"id":661,"depth":558,"text":661},{"id":675,"depth":558,"text":675},{"id":689,"depth":561,"text":692,"children":1003},[1004,1005],{"id":695,"depth":558,"text":698},{"id":710,"depth":558,"text":713},{"id":725,"depth":561,"text":728,"children":1007},[1008,1009,1010],{"id":731,"depth":558,"text":734},{"id":746,"depth":558,"text":749},{"id":761,"depth":558,"text":764},{"id":776,"depth":561,"text":779},{"id":791,"depth":561,"text":794},{"id":806,"depth":561,"text":809},{"id":821,"depth":561,"text":824},{"id":377,"depth":561,"text":377},{"id":953,"depth":561,"text":953},"content:topics:typescript:typescript-vue3-best-practices.md","topics/typescript/typescript-vue3-best-practices.md","topics/typescript/typescript-vue3-best-practices",{"_path":452,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1021,"description":1022,"date":10,"topic":5,"author":11,"tags":1023,"image":1028,"imageQuery":1029,"pexelsPhotoId":1030,"pexelsUrl":1031,"featured":6,"readingTime":1032,"body":1033,"_type":569,"_id":1432,"_source":571,"_file":1433,"_stem":1434,"_extension":574},"TypeScript 设计模式实战：工厂、策略、适配器与代理怎样保持类型安全","设计模式在 TypeScript 里不该变成 class 套娃。本文从工厂、策略、适配器和代理四个高频场景出发，讲清如何让抽象保持灵活，同时不牺牲类型安全和可读性。",[13,1024,1025,1026,1027],"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":1034,"toc":1423},[1035,1040,1053,1059,1064,1073,1086,1092,1097,1106,1119,1125,1146,1155,1160,1166,1171,1180,1185,1191,1196,1231,1236,1242,1341,1345,1350,1354,1384,1388],{"type":27,"tag":28,"props":1036,"children":1037},{},[1038],{"type":32,"value":1039},"很多人一提到“设计模式 + TypeScript”，脑子里马上冒出一套厚重的 class 结构：抽象工厂、基类、接口、子类、再来几层继承。问题在于，前端和 Node 项目里真正需要的，往往不是把经典 OO 图谱原样搬进来，而是把“变化点”抽象出来，同时保证调用方还能读懂类型。",{"type":27,"tag":28,"props":1041,"children":1042},{},[1043,1045,1051],{"type":32,"value":1044},"TypeScript 在设计模式里的价值，不是让模式更像 Java，而是让模式更诚实。哪些输入是合法的、哪些策略实现必须覆盖、适配器是否真的完成字段转换、代理是否保留原函数签名，这些都可以由类型系统提前约束。如果模式一上来就把类型信息打成 ",{"type":27,"tag":35,"props":1046,"children":1048},{"className":1047},[],[1049],{"type":32,"value":1050},"any",{"type":32,"value":1052},"，那它带来的通常不是灵活，而是延后的风险。",{"type":27,"tag":73,"props":1054,"children":1056},{"id":1055},"工厂模式把怎么创建隔离出来但别把输入做成万能配置桶",[1057],{"type":32,"value":1058},"工厂模式：把“怎么创建”隔离出来，但别把输入做成万能配置桶",{"type":27,"tag":28,"props":1060,"children":1061},{},[1062],{"type":32,"value":1063},"工厂模式最适合解决创建逻辑和环境差异问题，比如：不同运行环境要创建不同客户端，不同支付方式要创建不同处理器。",{"type":27,"tag":80,"props":1065,"children":1068},{"className":1066,"code":1067,"language":85,"meta":7},[83],"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",[1069],{"type":27,"tag":35,"props":1070,"children":1071},{"__ignoreMap":7},[1072],{"type":32,"value":1067},{"type":27,"tag":28,"props":1074,"children":1075},{},[1076,1078,1084],{"type":32,"value":1077},"真正要避免的，不是工厂本身，而是把工厂入参做成一个什么都能塞的 ",{"type":27,"tag":35,"props":1079,"children":1081},{"className":1080},[],[1082],{"type":32,"value":1083},"Record\u003Cstring, any>",{"type":32,"value":1085},"。工厂模式一旦把输入边界放宽，调用方就会失去类型提示，后面连工厂是否真的支持某个组合都看不出来。",{"type":27,"tag":73,"props":1087,"children":1089},{"id":1088},"策略模式别用-if-else-链拖着业务跑把变化面显式枚举出来",[1090],{"type":32,"value":1091},"策略模式：别用 if else 链拖着业务跑，把变化面显式枚举出来",{"type":27,"tag":28,"props":1093,"children":1094},{},[1095],{"type":32,"value":1096},"策略模式最值钱的地方，是把“可替换规则”显式列出来，让新增策略变成扩展而不是改旧逻辑。",{"type":27,"tag":80,"props":1098,"children":1101},{"className":1099,"code":1100,"language":85,"meta":7},[83],"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",[1102],{"type":27,"tag":35,"props":1103,"children":1104},{"__ignoreMap":7},[1105],{"type":32,"value":1100},{"type":27,"tag":28,"props":1107,"children":1108},{},[1109,1111,1117],{"type":32,"value":1110},"这里 ",{"type":27,"tag":35,"props":1112,"children":1114},{"className":1113},[],[1115],{"type":32,"value":1116},"Record\u003CPricingStrategy, ...>",{"type":32,"value":1118}," 的意义很大：只要你新增一个策略名，TypeScript 会强制你把实现补齐。这样策略模式不只是结构好看，而是具备穷举约束。",{"type":27,"tag":73,"props":1120,"children":1122},{"id":1121},"适配器模式真正重要的是把外部不稳定结构拦在边界外",[1123],{"type":32,"value":1124},"适配器模式：真正重要的是把外部不稳定结构拦在边界外",{"type":27,"tag":28,"props":1126,"children":1127},{},[1128,1130,1136,1138,1144],{"type":32,"value":1129},"适配器最常被低估。很多团队明明已经接了多个第三方服务，却还在业务层到处判断“这个平台字段叫 ",{"type":27,"tag":35,"props":1131,"children":1133},{"className":1132},[],[1134],{"type":32,"value":1135},"full_name",{"type":32,"value":1137},"，那个平台叫 ",{"type":27,"tag":35,"props":1139,"children":1141},{"className":1140},[],[1142],{"type":32,"value":1143},"displayName",{"type":32,"value":1145},"”。本质上，这就是缺了适配器层。",{"type":27,"tag":80,"props":1147,"children":1150},{"className":1148,"code":1149,"language":85,"meta":7},[83],"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",[1151],{"type":27,"tag":35,"props":1152,"children":1153},{"__ignoreMap":7},[1154],{"type":32,"value":1149},{"type":27,"tag":28,"props":1156,"children":1157},{},[1158],{"type":32,"value":1159},"适配器模式的关键，不是写一个转换函数，而是把外部世界的不稳定命名和字段结构限制在边界里，别让业务层长期直接面对这些差异。",{"type":27,"tag":73,"props":1161,"children":1163},{"id":1162},"代理模式包装额外行为时最怕把原始签名弄丢",[1164],{"type":32,"value":1165},"代理模式：包装额外行为时，最怕把原始签名弄丢",{"type":27,"tag":28,"props":1167,"children":1168},{},[1169],{"type":32,"value":1170},"代理模式常用于日志、缓存、权限检查和重试。它最常见的问题是：包装之后，原函数签名丢了，返回值也宽了，调用方只能面对一个模糊函数。",{"type":27,"tag":80,"props":1172,"children":1175},{"className":1173,"code":1174,"language":85,"meta":7},[83],"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",[1176],{"type":27,"tag":35,"props":1177,"children":1178},{"__ignoreMap":7},[1179],{"type":32,"value":1174},{"type":27,"tag":28,"props":1181,"children":1182},{},[1183],{"type":32,"value":1184},"这类泛型代理的价值在于：你加了额外行为，但原始参数和返回值仍然保留下来。否则代理模式会把“附加控制”变成“类型信息丢失”。",{"type":27,"tag":73,"props":1186,"children":1188},{"id":1187},"一个常见失败案例模式是抽象了类型却退化成-any",[1189],{"type":32,"value":1190},"一个常见失败案例：模式是抽象了，类型却退化成 any",{"type":27,"tag":28,"props":1192,"children":1193},{},[1194],{"type":32,"value":1195},"很多项目在做所谓“模式升级”时，会出现一种反效果：结构更复杂了，类型却更差了。常见表现有：",{"type":27,"tag":97,"props":1197,"children":1198},{},[1199,1210,1215,1226],{"type":27,"tag":101,"props":1200,"children":1201},{},[1202,1204],{"type":32,"value":1203},"工厂接收 ",{"type":27,"tag":35,"props":1205,"children":1207},{"className":1206},[],[1208],{"type":32,"value":1209},"config: any",{"type":27,"tag":101,"props":1211,"children":1212},{},[1213],{"type":32,"value":1214},"策略表写成对象，但 key 没有联合类型约束",{"type":27,"tag":101,"props":1216,"children":1217},{},[1218,1220],{"type":32,"value":1219},"适配器只返回 ",{"type":27,"tag":35,"props":1221,"children":1223},{"className":1222},[],[1224],{"type":32,"value":1225},"Record\u003Cstring, unknown>",{"type":27,"tag":101,"props":1227,"children":1228},{},[1229],{"type":32,"value":1230},"代理函数包装后不保留原始签名",{"type":27,"tag":28,"props":1232,"children":1233},{},[1234],{"type":32,"value":1235},"这种代码看起来“更有架构感”，实际上却把很多风险从编译期挪回了运行时。模式如果不能提升边界清晰度和替换安全性，通常只是引入了新的复杂度。",{"type":27,"tag":73,"props":1237,"children":1239},{"id":1238},"决策表什么时候该上模式什么时候先别上",[1240],{"type":32,"value":1241},"决策表：什么时候该上模式，什么时候先别上",{"type":27,"tag":844,"props":1243,"children":1244},{},[1245,1266],{"type":27,"tag":848,"props":1246,"children":1247},{},[1248],{"type":27,"tag":852,"props":1249,"children":1250},{},[1251,1256,1261],{"type":27,"tag":856,"props":1252,"children":1253},{},[1254],{"type":32,"value":1255},"场景",{"type":27,"tag":856,"props":1257,"children":1258},{},[1259],{"type":32,"value":1260},"更适合什么模式",{"type":27,"tag":856,"props":1262,"children":1263},{},[1264],{"type":32,"value":1265},"不建议做的事",{"type":27,"tag":867,"props":1267,"children":1268},{},[1269,1287,1305,1323],{"type":27,"tag":852,"props":1270,"children":1271},{},[1272,1277,1282],{"type":27,"tag":874,"props":1273,"children":1274},{},[1275],{"type":32,"value":1276},"创建逻辑随环境变化",{"type":27,"tag":874,"props":1278,"children":1279},{},[1280],{"type":32,"value":1281},"工厂",{"type":27,"tag":874,"props":1283,"children":1284},{},[1285],{"type":32,"value":1286},"把所有可选项塞进一个大配置对象",{"type":27,"tag":852,"props":1288,"children":1289},{},[1290,1295,1300],{"type":27,"tag":874,"props":1291,"children":1292},{},[1293],{"type":32,"value":1294},"规则可替换、可扩展",{"type":27,"tag":874,"props":1296,"children":1297},{},[1298],{"type":32,"value":1299},"策略",{"type":27,"tag":874,"props":1301,"children":1302},{},[1303],{"type":32,"value":1304},"继续堆 if else 并靠注释解释",{"type":27,"tag":852,"props":1306,"children":1307},{},[1308,1313,1318],{"type":27,"tag":874,"props":1309,"children":1310},{},[1311],{"type":32,"value":1312},"第三方结构不稳定",{"type":27,"tag":874,"props":1314,"children":1315},{},[1316],{"type":32,"value":1317},"适配器",{"type":27,"tag":874,"props":1319,"children":1320},{},[1321],{"type":32,"value":1322},"让业务层到处处理字段差异",{"type":27,"tag":852,"props":1324,"children":1325},{},[1326,1331,1336],{"type":27,"tag":874,"props":1327,"children":1328},{},[1329],{"type":32,"value":1330},"需要附加日志、缓存、权限",{"type":27,"tag":874,"props":1332,"children":1333},{},[1334],{"type":32,"value":1335},"代理",{"type":27,"tag":874,"props":1337,"children":1338},{},[1339],{"type":32,"value":1340},"包装后丢失原函数签名",{"type":27,"tag":73,"props":1342,"children":1343},{"id":377},[1344],{"type":32,"value":377},{"type":27,"tag":28,"props":1346,"children":1347},{},[1348],{"type":32,"value":1349},"TypeScript 里的设计模式，关键不是“更像面向对象”，而是让变化面、稳定面和边界责任表达得更清楚。工厂控制创建，策略控制规则替换，适配器隔离外部差异，代理保留签名的同时叠加附加能力。只要模式引入后类型信息还在，团队就能真正享受到抽象带来的收益。",{"type":27,"tag":28,"props":1351,"children":1352},{},[1353],{"type":32,"value":484},{"type":27,"tag":97,"props":1355,"children":1356},{},[1357,1366,1375],{"type":27,"tag":101,"props":1358,"children":1359},{},[1360,1362],{"type":32,"value":1361},"如果你想先稳住抽象层对外承诺，接着看 ",{"type":27,"tag":399,"props":1363,"children":1364},{"href":427},[1365],{"type":32,"value":430},{"type":27,"tag":101,"props":1367,"children":1368},{},[1369,1371],{"type":32,"value":1370},"若你要把模式继续落到测试和用例上，再看 ",{"type":27,"tag":399,"props":1372,"children":1373},{"href":459},[1374],{"type":32,"value":462},{"type":27,"tag":101,"props":1376,"children":1377},{},[1378,1380],{"type":32,"value":1379},"如果你接下来要处理业务状态与错误流，再读 ",{"type":27,"tag":399,"props":1381,"children":1382},{"href":476},[1383],{"type":32,"value":479},{"type":27,"tag":28,"props":1385,"children":1386},{},[1387],{"type":32,"value":519},{"type":27,"tag":97,"props":1389,"children":1390},{},[1391,1400,1407,1414],{"type":27,"tag":101,"props":1392,"children":1393},{},[1394],{"type":27,"tag":399,"props":1395,"children":1397},{"href":1396},"/topics/typescript/typescript-generic-constraints-conditional-decision-guide",[1398],{"type":32,"value":1399},"TypeScript 泛型约束与条件泛型的实际决策",{"type":27,"tag":101,"props":1401,"children":1402},{},[1403],{"type":27,"tag":399,"props":1404,"children":1405},{"href":537},[1406],{"type":32,"value":540},{"type":27,"tag":101,"props":1408,"children":1409},{},[1410],{"type":27,"tag":399,"props":1411,"children":1412},{"href":427},[1413],{"type":32,"value":430},{"type":27,"tag":101,"props":1415,"children":1416},{},[1417],{"type":27,"tag":399,"props":1418,"children":1420},{"href":1419},"/topics/frontend/vue-component-design-patterns-essentials",[1421],{"type":32,"value":1422},"Vue 组件设计模式精选",{"title":7,"searchDepth":558,"depth":558,"links":1424},[1425,1426,1427,1428,1429,1430,1431],{"id":1055,"depth":561,"text":1058},{"id":1088,"depth":561,"text":1091},{"id":1121,"depth":561,"text":1124},{"id":1162,"depth":561,"text":1165},{"id":1187,"depth":561,"text":1190},{"id":1238,"depth":561,"text":1241},{"id":377,"depth":561,"text":377},"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":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":1436,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":1437,"_type":569,"_id":570,"_source":571,"_file":572,"_stem":573,"_extension":574},[13,14,15,16,17],{"type":24,"children":1438,"toc":1853},[1439,1467,1471,1475,1483,1487,1498,1502,1506,1510,1518,1522,1530,1534,1538,1542,1553,1563,1567,1582,1586,1590,1594,1609,1613,1617,1633,1648,1652,1656,1660,1679,1683,1687,1710,1714,1718,1722,1787,1791,1818,1822],{"type":27,"tag":28,"props":1440,"children":1441},{},[1442,1443,1448,1449,1454,1455,1460,1461,1466],{"type":32,"value":33},{"type":27,"tag":35,"props":1444,"children":1446},{"className":1445},[],[1447],{"type":32,"value":40},{"type":32,"value":42},{"type":27,"tag":35,"props":1450,"children":1452},{"className":1451},[],[1453],{"type":32,"value":48},{"type":32,"value":50},{"type":27,"tag":35,"props":1456,"children":1458},{"className":1457},[],[1459],{"type":32,"value":56},{"type":32,"value":58},{"type":27,"tag":35,"props":1462,"children":1464},{"className":1463},[],[1465],{"type":32,"value":64},{"type":32,"value":66},{"type":27,"tag":28,"props":1468,"children":1469},{},[1470],{"type":32,"value":71},{"type":27,"tag":73,"props":1472,"children":1473},{"id":75},[1474],{"type":32,"value":78},{"type":27,"tag":80,"props":1476,"children":1478},{"className":1477,"code":84,"language":85,"meta":7},[83],[1479],{"type":27,"tag":35,"props":1480,"children":1481},{"__ignoreMap":7},[1482],{"type":32,"value":84},{"type":27,"tag":28,"props":1484,"children":1485},{},[1486],{"type":32,"value":95},{"type":27,"tag":97,"props":1488,"children":1489},{},[1490,1494],{"type":27,"tag":101,"props":1491,"children":1492},{},[1493],{"type":32,"value":105},{"type":27,"tag":101,"props":1495,"children":1496},{},[1497],{"type":32,"value":110},{"type":27,"tag":28,"props":1499,"children":1500},{},[1501],{"type":32,"value":115},{"type":27,"tag":73,"props":1503,"children":1504},{"id":118},[1505],{"type":32,"value":121},{"type":27,"tag":28,"props":1507,"children":1508},{},[1509],{"type":32,"value":126},{"type":27,"tag":80,"props":1511,"children":1513},{"className":1512,"code":130,"language":85,"meta":7},[83],[1514],{"type":27,"tag":35,"props":1515,"children":1516},{"__ignoreMap":7},[1517],{"type":32,"value":130},{"type":27,"tag":28,"props":1519,"children":1520},{},[1521],{"type":32,"value":140},{"type":27,"tag":80,"props":1523,"children":1525},{"className":1524,"code":144,"language":85,"meta":7},[83],[1526],{"type":27,"tag":35,"props":1527,"children":1528},{"__ignoreMap":7},[1529],{"type":32,"value":144},{"type":27,"tag":28,"props":1531,"children":1532},{},[1533],{"type":32,"value":154},{"type":27,"tag":73,"props":1535,"children":1536},{"id":157},[1537],{"type":32,"value":160},{"type":27,"tag":28,"props":1539,"children":1540},{},[1541],{"type":32,"value":165},{"type":27,"tag":97,"props":1543,"children":1544},{},[1545,1549],{"type":27,"tag":101,"props":1546,"children":1547},{},[1548],{"type":32,"value":173},{"type":27,"tag":101,"props":1550,"children":1551},{},[1552],{"type":32,"value":178},{"type":27,"tag":28,"props":1554,"children":1555},{},[1556,1557,1562],{"type":32,"value":183},{"type":27,"tag":35,"props":1558,"children":1560},{"className":1559},[],[1561],{"type":32,"value":56},{"type":32,"value":190},{"type":27,"tag":28,"props":1564,"children":1565},{},[1566],{"type":32,"value":195},{"type":27,"tag":97,"props":1568,"children":1569},{},[1570,1574,1578],{"type":27,"tag":101,"props":1571,"children":1572},{},[1573],{"type":32,"value":203},{"type":27,"tag":101,"props":1575,"children":1576},{},[1577],{"type":32,"value":208},{"type":27,"tag":101,"props":1579,"children":1580},{},[1581],{"type":32,"value":213},{"type":27,"tag":73,"props":1583,"children":1584},{"id":216},[1585],{"type":32,"value":219},{"type":27,"tag":28,"props":1587,"children":1588},{},[1589],{"type":32,"value":224},{"type":27,"tag":28,"props":1591,"children":1592},{},[1593],{"type":32,"value":229},{"type":27,"tag":97,"props":1595,"children":1596},{},[1597,1601,1605],{"type":27,"tag":101,"props":1598,"children":1599},{},[1600],{"type":32,"value":237},{"type":27,"tag":101,"props":1602,"children":1603},{},[1604],{"type":32,"value":242},{"type":27,"tag":101,"props":1606,"children":1607},{},[1608],{"type":32,"value":247},{"type":27,"tag":28,"props":1610,"children":1611},{},[1612],{"type":32,"value":252},{"type":27,"tag":73,"props":1614,"children":1615},{"id":255},[1616],{"type":32,"value":258},{"type":27,"tag":28,"props":1618,"children":1619},{},[1620,1621,1626,1627,1632],{"type":32,"value":263},{"type":27,"tag":35,"props":1622,"children":1624},{"className":1623},[],[1625],{"type":32,"value":269},{"type":32,"value":271},{"type":27,"tag":35,"props":1628,"children":1630},{"className":1629},[],[1631],{"type":32,"value":277},{"type":32,"value":279},{"type":27,"tag":97,"props":1634,"children":1635},{},[1636,1640,1644],{"type":27,"tag":101,"props":1637,"children":1638},{},[1639],{"type":32,"value":287},{"type":27,"tag":101,"props":1641,"children":1642},{},[1643],{"type":32,"value":292},{"type":27,"tag":101,"props":1645,"children":1646},{},[1647],{"type":32,"value":297},{"type":27,"tag":28,"props":1649,"children":1650},{},[1651],{"type":32,"value":302},{"type":27,"tag":73,"props":1653,"children":1654},{"id":305},[1655],{"type":32,"value":308},{"type":27,"tag":28,"props":1657,"children":1658},{},[1659],{"type":32,"value":313},{"type":27,"tag":97,"props":1661,"children":1662},{},[1663,1667,1671,1675],{"type":27,"tag":101,"props":1664,"children":1665},{},[1666],{"type":32,"value":321},{"type":27,"tag":101,"props":1668,"children":1669},{},[1670],{"type":32,"value":326},{"type":27,"tag":101,"props":1672,"children":1673},{},[1674],{"type":32,"value":331},{"type":27,"tag":101,"props":1676,"children":1677},{},[1678],{"type":32,"value":336},{"type":27,"tag":28,"props":1680,"children":1681},{},[1682],{"type":32,"value":341},{"type":27,"tag":73,"props":1684,"children":1685},{"id":344},[1686],{"type":32,"value":344},{"type":27,"tag":97,"props":1688,"children":1689},{},[1690,1694,1698,1702,1706],{"type":27,"tag":101,"props":1691,"children":1692},{},[1693],{"type":32,"value":354},{"type":27,"tag":101,"props":1695,"children":1696},{},[1697],{"type":32,"value":359},{"type":27,"tag":101,"props":1699,"children":1700},{},[1701],{"type":32,"value":364},{"type":27,"tag":101,"props":1703,"children":1704},{},[1705],{"type":32,"value":369},{"type":27,"tag":101,"props":1707,"children":1708},{},[1709],{"type":32,"value":374},{"type":27,"tag":73,"props":1711,"children":1712},{"id":377},[1713],{"type":32,"value":377},{"type":27,"tag":28,"props":1715,"children":1716},{},[1717],{"type":32,"value":384},{"type":27,"tag":28,"props":1719,"children":1720},{},[1721],{"type":32,"value":389},{"type":27,"tag":97,"props":1723,"children":1724},{},[1725,1743,1761,1774],{"type":27,"tag":101,"props":1726,"children":1727},{},[1728,1729,1733,1734,1738,1739],{"type":32,"value":397},{"type":27,"tag":399,"props":1730,"children":1731},{"href":401},[1732],{"type":32,"value":404},{"type":32,"value":406},{"type":27,"tag":399,"props":1735,"children":1736},{"href":409},[1737],{"type":32,"value":412},{"type":32,"value":406},{"type":27,"tag":399,"props":1740,"children":1741},{"href":416},[1742],{"type":32,"value":419},{"type":27,"tag":101,"props":1744,"children":1745},{},[1746,1747,1751,1752,1756,1757],{"type":32,"value":424},{"type":27,"tag":399,"props":1748,"children":1749},{"href":427},[1750],{"type":32,"value":430},{"type":32,"value":406},{"type":27,"tag":399,"props":1753,"children":1754},{"href":434},[1755],{"type":32,"value":437},{"type":32,"value":406},{"type":27,"tag":399,"props":1758,"children":1759},{"href":441},[1760],{"type":32,"value":444},{"type":27,"tag":101,"props":1762,"children":1763},{},[1764,1765,1769,1770],{"type":32,"value":449},{"type":27,"tag":399,"props":1766,"children":1767},{"href":452},[1768],{"type":32,"value":455},{"type":32,"value":406},{"type":27,"tag":399,"props":1771,"children":1772},{"href":459},[1773],{"type":32,"value":462},{"type":27,"tag":101,"props":1775,"children":1776},{},[1777,1778,1782,1783],{"type":32,"value":467},{"type":27,"tag":399,"props":1779,"children":1780},{"href":4},[1781],{"type":32,"value":472},{"type":32,"value":406},{"type":27,"tag":399,"props":1784,"children":1785},{"href":476},[1786],{"type":32,"value":479},{"type":27,"tag":28,"props":1788,"children":1789},{},[1790],{"type":32,"value":484},{"type":27,"tag":97,"props":1792,"children":1793},{},[1794,1802,1810],{"type":27,"tag":101,"props":1795,"children":1796},{},[1797,1798],{"type":32,"value":492},{"type":27,"tag":399,"props":1799,"children":1800},{"href":434},[1801],{"type":32,"value":437},{"type":27,"tag":101,"props":1803,"children":1804},{},[1805,1806],{"type":32,"value":501},{"type":27,"tag":399,"props":1807,"children":1808},{"href":476},[1809],{"type":32,"value":479},{"type":27,"tag":101,"props":1811,"children":1812},{},[1813,1814],{"type":32,"value":510},{"type":27,"tag":399,"props":1815,"children":1816},{"href":427},[1817],{"type":32,"value":430},{"type":27,"tag":28,"props":1819,"children":1820},{},[1821],{"type":32,"value":519},{"type":27,"tag":97,"props":1823,"children":1824},{},[1825,1832,1839,1846],{"type":27,"tag":101,"props":1826,"children":1827},{},[1828],{"type":27,"tag":399,"props":1829,"children":1830},{"href":528},[1831],{"type":32,"value":531},{"type":27,"tag":101,"props":1833,"children":1834},{},[1835],{"type":27,"tag":399,"props":1836,"children":1837},{"href":537},[1838],{"type":32,"value":540},{"type":27,"tag":101,"props":1840,"children":1841},{},[1842],{"type":27,"tag":399,"props":1843,"children":1844},{"href":434},[1845],{"type":32,"value":437},{"type":27,"tag":101,"props":1847,"children":1848},{},[1849],{"type":27,"tag":399,"props":1850,"children":1851},{"href":553},[1852],{"type":32,"value":556},{"title":7,"searchDepth":558,"depth":558,"links":1854},[1855,1856,1857,1858,1859,1860,1861,1862],{"id":75,"depth":561,"text":78},{"id":118,"depth":561,"text":121},{"id":157,"depth":561,"text":160},{"id":216,"depth":561,"text":219},{"id":255,"depth":561,"text":258},{"id":305,"depth":561,"text":308},{"id":344,"depth":561,"text":344},{"id":377,"depth":561,"text":377},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":1864,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":1865,"_type":569,"_id":570,"_source":571,"_file":572,"_stem":573,"_extension":574},[13,14,15,16,17],{"type":24,"children":1866,"toc":2281},[1867,1895,1899,1903,1911,1915,1926,1930,1934,1938,1946,1950,1958,1962,1966,1970,1981,1991,1995,2010,2014,2018,2022,2037,2041,2045,2061,2076,2080,2084,2088,2107,2111,2115,2138,2142,2146,2150,2215,2219,2246,2250],{"type":27,"tag":28,"props":1868,"children":1869},{},[1870,1871,1876,1877,1882,1883,1888,1889,1894],{"type":32,"value":33},{"type":27,"tag":35,"props":1872,"children":1874},{"className":1873},[],[1875],{"type":32,"value":40},{"type":32,"value":42},{"type":27,"tag":35,"props":1878,"children":1880},{"className":1879},[],[1881],{"type":32,"value":48},{"type":32,"value":50},{"type":27,"tag":35,"props":1884,"children":1886},{"className":1885},[],[1887],{"type":32,"value":56},{"type":32,"value":58},{"type":27,"tag":35,"props":1890,"children":1892},{"className":1891},[],[1893],{"type":32,"value":64},{"type":32,"value":66},{"type":27,"tag":28,"props":1896,"children":1897},{},[1898],{"type":32,"value":71},{"type":27,"tag":73,"props":1900,"children":1901},{"id":75},[1902],{"type":32,"value":78},{"type":27,"tag":80,"props":1904,"children":1906},{"className":1905,"code":84,"language":85,"meta":7},[83],[1907],{"type":27,"tag":35,"props":1908,"children":1909},{"__ignoreMap":7},[1910],{"type":32,"value":84},{"type":27,"tag":28,"props":1912,"children":1913},{},[1914],{"type":32,"value":95},{"type":27,"tag":97,"props":1916,"children":1917},{},[1918,1922],{"type":27,"tag":101,"props":1919,"children":1920},{},[1921],{"type":32,"value":105},{"type":27,"tag":101,"props":1923,"children":1924},{},[1925],{"type":32,"value":110},{"type":27,"tag":28,"props":1927,"children":1928},{},[1929],{"type":32,"value":115},{"type":27,"tag":73,"props":1931,"children":1932},{"id":118},[1933],{"type":32,"value":121},{"type":27,"tag":28,"props":1935,"children":1936},{},[1937],{"type":32,"value":126},{"type":27,"tag":80,"props":1939,"children":1941},{"className":1940,"code":130,"language":85,"meta":7},[83],[1942],{"type":27,"tag":35,"props":1943,"children":1944},{"__ignoreMap":7},[1945],{"type":32,"value":130},{"type":27,"tag":28,"props":1947,"children":1948},{},[1949],{"type":32,"value":140},{"type":27,"tag":80,"props":1951,"children":1953},{"className":1952,"code":144,"language":85,"meta":7},[83],[1954],{"type":27,"tag":35,"props":1955,"children":1956},{"__ignoreMap":7},[1957],{"type":32,"value":144},{"type":27,"tag":28,"props":1959,"children":1960},{},[1961],{"type":32,"value":154},{"type":27,"tag":73,"props":1963,"children":1964},{"id":157},[1965],{"type":32,"value":160},{"type":27,"tag":28,"props":1967,"children":1968},{},[1969],{"type":32,"value":165},{"type":27,"tag":97,"props":1971,"children":1972},{},[1973,1977],{"type":27,"tag":101,"props":1974,"children":1975},{},[1976],{"type":32,"value":173},{"type":27,"tag":101,"props":1978,"children":1979},{},[1980],{"type":32,"value":178},{"type":27,"tag":28,"props":1982,"children":1983},{},[1984,1985,1990],{"type":32,"value":183},{"type":27,"tag":35,"props":1986,"children":1988},{"className":1987},[],[1989],{"type":32,"value":56},{"type":32,"value":190},{"type":27,"tag":28,"props":1992,"children":1993},{},[1994],{"type":32,"value":195},{"type":27,"tag":97,"props":1996,"children":1997},{},[1998,2002,2006],{"type":27,"tag":101,"props":1999,"children":2000},{},[2001],{"type":32,"value":203},{"type":27,"tag":101,"props":2003,"children":2004},{},[2005],{"type":32,"value":208},{"type":27,"tag":101,"props":2007,"children":2008},{},[2009],{"type":32,"value":213},{"type":27,"tag":73,"props":2011,"children":2012},{"id":216},[2013],{"type":32,"value":219},{"type":27,"tag":28,"props":2015,"children":2016},{},[2017],{"type":32,"value":224},{"type":27,"tag":28,"props":2019,"children":2020},{},[2021],{"type":32,"value":229},{"type":27,"tag":97,"props":2023,"children":2024},{},[2025,2029,2033],{"type":27,"tag":101,"props":2026,"children":2027},{},[2028],{"type":32,"value":237},{"type":27,"tag":101,"props":2030,"children":2031},{},[2032],{"type":32,"value":242},{"type":27,"tag":101,"props":2034,"children":2035},{},[2036],{"type":32,"value":247},{"type":27,"tag":28,"props":2038,"children":2039},{},[2040],{"type":32,"value":252},{"type":27,"tag":73,"props":2042,"children":2043},{"id":255},[2044],{"type":32,"value":258},{"type":27,"tag":28,"props":2046,"children":2047},{},[2048,2049,2054,2055,2060],{"type":32,"value":263},{"type":27,"tag":35,"props":2050,"children":2052},{"className":2051},[],[2053],{"type":32,"value":269},{"type":32,"value":271},{"type":27,"tag":35,"props":2056,"children":2058},{"className":2057},[],[2059],{"type":32,"value":277},{"type":32,"value":279},{"type":27,"tag":97,"props":2062,"children":2063},{},[2064,2068,2072],{"type":27,"tag":101,"props":2065,"children":2066},{},[2067],{"type":32,"value":287},{"type":27,"tag":101,"props":2069,"children":2070},{},[2071],{"type":32,"value":292},{"type":27,"tag":101,"props":2073,"children":2074},{},[2075],{"type":32,"value":297},{"type":27,"tag":28,"props":2077,"children":2078},{},[2079],{"type":32,"value":302},{"type":27,"tag":73,"props":2081,"children":2082},{"id":305},[2083],{"type":32,"value":308},{"type":27,"tag":28,"props":2085,"children":2086},{},[2087],{"type":32,"value":313},{"type":27,"tag":97,"props":2089,"children":2090},{},[2091,2095,2099,2103],{"type":27,"tag":101,"props":2092,"children":2093},{},[2094],{"type":32,"value":321},{"type":27,"tag":101,"props":2096,"children":2097},{},[2098],{"type":32,"value":326},{"type":27,"tag":101,"props":2100,"children":2101},{},[2102],{"type":32,"value":331},{"type":27,"tag":101,"props":2104,"children":2105},{},[2106],{"type":32,"value":336},{"type":27,"tag":28,"props":2108,"children":2109},{},[2110],{"type":32,"value":341},{"type":27,"tag":73,"props":2112,"children":2113},{"id":344},[2114],{"type":32,"value":344},{"type":27,"tag":97,"props":2116,"children":2117},{},[2118,2122,2126,2130,2134],{"type":27,"tag":101,"props":2119,"children":2120},{},[2121],{"type":32,"value":354},{"type":27,"tag":101,"props":2123,"children":2124},{},[2125],{"type":32,"value":359},{"type":27,"tag":101,"props":2127,"children":2128},{},[2129],{"type":32,"value":364},{"type":27,"tag":101,"props":2131,"children":2132},{},[2133],{"type":32,"value":369},{"type":27,"tag":101,"props":2135,"children":2136},{},[2137],{"type":32,"value":374},{"type":27,"tag":73,"props":2139,"children":2140},{"id":377},[2141],{"type":32,"value":377},{"type":27,"tag":28,"props":2143,"children":2144},{},[2145],{"type":32,"value":384},{"type":27,"tag":28,"props":2147,"children":2148},{},[2149],{"type":32,"value":389},{"type":27,"tag":97,"props":2151,"children":2152},{},[2153,2171,2189,2202],{"type":27,"tag":101,"props":2154,"children":2155},{},[2156,2157,2161,2162,2166,2167],{"type":32,"value":397},{"type":27,"tag":399,"props":2158,"children":2159},{"href":401},[2160],{"type":32,"value":404},{"type":32,"value":406},{"type":27,"tag":399,"props":2163,"children":2164},{"href":409},[2165],{"type":32,"value":412},{"type":32,"value":406},{"type":27,"tag":399,"props":2168,"children":2169},{"href":416},[2170],{"type":32,"value":419},{"type":27,"tag":101,"props":2172,"children":2173},{},[2174,2175,2179,2180,2184,2185],{"type":32,"value":424},{"type":27,"tag":399,"props":2176,"children":2177},{"href":427},[2178],{"type":32,"value":430},{"type":32,"value":406},{"type":27,"tag":399,"props":2181,"children":2182},{"href":434},[2183],{"type":32,"value":437},{"type":32,"value":406},{"type":27,"tag":399,"props":2186,"children":2187},{"href":441},[2188],{"type":32,"value":444},{"type":27,"tag":101,"props":2190,"children":2191},{},[2192,2193,2197,2198],{"type":32,"value":449},{"type":27,"tag":399,"props":2194,"children":2195},{"href":452},[2196],{"type":32,"value":455},{"type":32,"value":406},{"type":27,"tag":399,"props":2199,"children":2200},{"href":459},[2201],{"type":32,"value":462},{"type":27,"tag":101,"props":2203,"children":2204},{},[2205,2206,2210,2211],{"type":32,"value":467},{"type":27,"tag":399,"props":2207,"children":2208},{"href":4},[2209],{"type":32,"value":472},{"type":32,"value":406},{"type":27,"tag":399,"props":2212,"children":2213},{"href":476},[2214],{"type":32,"value":479},{"type":27,"tag":28,"props":2216,"children":2217},{},[2218],{"type":32,"value":484},{"type":27,"tag":97,"props":2220,"children":2221},{},[2222,2230,2238],{"type":27,"tag":101,"props":2223,"children":2224},{},[2225,2226],{"type":32,"value":492},{"type":27,"tag":399,"props":2227,"children":2228},{"href":434},[2229],{"type":32,"value":437},{"type":27,"tag":101,"props":2231,"children":2232},{},[2233,2234],{"type":32,"value":501},{"type":27,"tag":399,"props":2235,"children":2236},{"href":476},[2237],{"type":32,"value":479},{"type":27,"tag":101,"props":2239,"children":2240},{},[2241,2242],{"type":32,"value":510},{"type":27,"tag":399,"props":2243,"children":2244},{"href":427},[2245],{"type":32,"value":430},{"type":27,"tag":28,"props":2247,"children":2248},{},[2249],{"type":32,"value":519},{"type":27,"tag":97,"props":2251,"children":2252},{},[2253,2260,2267,2274],{"type":27,"tag":101,"props":2254,"children":2255},{},[2256],{"type":27,"tag":399,"props":2257,"children":2258},{"href":528},[2259],{"type":32,"value":531},{"type":27,"tag":101,"props":2261,"children":2262},{},[2263],{"type":27,"tag":399,"props":2264,"children":2265},{"href":537},[2266],{"type":32,"value":540},{"type":27,"tag":101,"props":2268,"children":2269},{},[2270],{"type":27,"tag":399,"props":2271,"children":2272},{"href":434},[2273],{"type":32,"value":437},{"type":27,"tag":101,"props":2275,"children":2276},{},[2277],{"type":27,"tag":399,"props":2278,"children":2279},{"href":553},[2280],{"type":32,"value":556},{"title":7,"searchDepth":558,"depth":558,"links":2282},[2283,2284,2285,2286,2287,2288,2289,2290],{"id":75,"depth":561,"text":78},{"id":118,"depth":561,"text":121},{"id":157,"depth":561,"text":160},{"id":216,"depth":561,"text":219},{"id":255,"depth":561,"text":258},{"id":305,"depth":561,"text":308},{"id":344,"depth":561,"text":344},{"id":377,"depth":561,"text":377},1781081291089]