[{"data":1,"prerenderedAt":239},["ShallowReactive",2],{"content:\u002Fposts\u002F2025\u002Fhow-to-use-the-variant-props-pattern-in-vue":3,"surround:\u002Fposts\u002F2025\u002Fhow-to-use-the-variant-props-pattern-in-vue":227},{"id":4,"title":5,"body":6,"canonical":19,"categories":187,"date":189,"description":190,"draft":191,"extension":192,"image":193,"meta":194,"navigation":211,"path":212,"permalink":190,"readingTime":213,"recommend":218,"references":190,"seo":219,"sitemap":220,"stem":221,"tags":222,"type":190,"updated":225,"__hash__":226},"content\u002Fposts\u002F2025\u002FHow-to-Use-the-Variant-Props-Pattern-in-Vue.md","【翻译】 如何在 Vue 中使用Props变体",{"type":7,"value":8,"toc":177},"minimark",[9,23,26,29,33,36,47,50,54,57,81,84,92,95,98,104,107,111,131,134,140,143,146,153,156,165,168,171],[10,11,12],"auths-alex",{},[13,14,15,16],"p",{},"原文地址：",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Falexop.dev\u002Fposts\u002Fvue-typescript-variant-props-type-safe-props\u002F",[21],"nofollow","How to Use the Variant Props Pattern in Vue",[24,25],"hr",{},[13,27,28],{},"构建在保持类型安全的同时处理多重变化的 Vue 组件可能很棘手。让我们深入研究如何使用变体 Props 模式（VPP） - 一种强大的方法，它使用 TypeScript 的可区分联合类型与 Vue 的组合式 API 来创建真正类型安全的组件变体。",[30,31,32],"h2",{"id":32},"长话短说",[13,34,35],{},"Vue 中的 Variant Props 模式将 TypeScript 的可区分联合与 Vue 的 prop 系统相结合，以创建类型安全的组件变体。我们不使用复杂类型实用程序，而是将不兼容的 props 显式标记为 never，以防止在编译时混合 props：",[37,38,44],"pre",{"className":39,"code":41,"language":42,"meta":43},[40],"language-ts","interface BaseProps {\n    title: string\n}\n\ntype SuccessProps = BaseProps & {\n    variant: \"success\"\n    message: string\n    errorCode?: never\n}\n\ntype ErrorProps = BaseProps & {\n    variant: \"error\"\n    errorCode: string\n    message?: never\n}\n\ntype Props = SuccessProps | ErrorProps\n","ts","",[45,46,41],"code",{"__ignoreMap":43},[13,48,49],{},"该模式提供了编译时安全性、出色的 IDE 支持和可靠的 vue-tsc 兼容性。非常适合需要多个互斥道具组合的组件。",[30,51,53],{"id":52},"问题混合-props-噩梦","问题：混合 Props 噩梦",[13,55,56],{},"想象一下：您正在构建一个需要处理成功和错误状态的通知组件。每个类型都有自己特定的属性：",[58,59,60,71],"ul",{},[61,62,63,64,67,68],"li",{},"成功需要 ",[45,65,66],{"code":66},"message"," 和 ",[45,69,70],{"code":70},"duration",[61,72,73,74,67,77,80],{},"错误需要 ",[45,75,76],{"code":76},"errorCode",[45,78,79],{"code":79},"retryable"," 标志",[13,82,83],{},"如果没有适当的安全类型，开发人员可能会意外的混用这些 props",[37,85,90],{"className":86,"code":88,"language":89,"meta":43},[87],"language-html","\u003C!-- 🚨 Mixing success and error props -->\n\u003CNotificationAlert\n  variant=\"primary\"\n  title=\"Data Saved\"\n  message=\"Success!\"\n  errorCode=\"UPLOAD_001\"\n  :duration=\"5000\"\n  @close=\"handleClose\"\n\u002F>\n","html",[45,91,88],{"__ignoreMap":43},[30,93,94],{"id":94},"简单的解决方案不起作用",[13,96,97],{},"您的第一直觉可能是定义单独的接口：",[37,99,102],{"className":100,"code":101,"language":42,"meta":43},[40],"interface SuccessProps {\n    title: string\n    variant: \"primary\" | \"secondary\"\n    message: string\n    duration: number\n}\n\ninterface ErrorProps {\n    title: string\n    variant: \"danger\" | \"warning\"\n    errorCode: string\n    retryable: boolean\n}\n\n\u002F\u002F 🚨 This allows mixing both types!\ntype Props = SuccessProps & ErrorProps\n",[45,103,101],{"__ignoreMap":43},[13,105,106],{},"有什么问题？这种方法允许开发人员同时使用 success 和 error props - 绝对不是我们想要的！",[30,108,110],{"id":109},"使用-never","使用 never",[112,113,114,117],"blockquote",{},[13,115,116],{},"TypeScript 提示： never 类型是 TypeScript 中的一种特殊类型，表示从未出现的值。当属性被标记为 never 时，TypeScript 确保永远不能将值分配给该属性。这使得它非常适合创建互斥的 props，因为它可以防止开发人员意外地使用不应该同时存在的 props。\nnever 类型通常出现在 TypeScript 中的几种场景中：",[58,118,119,122,125,128],{},[61,120,121],{},"永不返回的函数（抛出错误或具有无限循环）",[61,123,124],{},"switch 语句中的详尽类型检查",[61,126,127],{},"不可能的类型交叉（例如 string & number）",[61,129,130],{},"使属性互斥，就像我们在这种模式中所做的那样",[13,132,133],{},"使其与当前的 defineProps 实现兼容的主要技巧是使用 never 来显式标记未使用的变体道具。",[37,135,138],{"className":136,"code":137,"language":42,"meta":43},[40],"\u002F\u002F Base props shared between variants\ninterface BaseProps {\n    title: string\n}\n\n\u002F\u002F Success variant\ntype SuccessProps = BaseProps & {\n    variant: \"primary\" | \"secondary\"\n    message: string\n    duration: number\n    \u002F\u002F Explicitly mark error props as never\n    errorCode?: never\n    retryable?: never\n}\n\n\u002F\u002F Error variant\ntype ErrorProps = BaseProps & {\n    variant: \"danger\" | \"warning\"\n    errorCode: string\n    retryable: boolean\n    \u002F\u002F Explicitly mark success props as never\n    message?: never\n    duration?: never\n}\n\n\u002F\u002F Final props type - only one variant allowed!\ntype Props = SuccessProps | ErrorProps\n",[45,139,137],{"__ignoreMap":43},[30,141,142],{"id":142},"把它们放在一起",[13,144,145],{},"这是使用 Variant Props 模式的完整通知组件：",[13,147,148],{},[17,149,152],{"href":150,"rel":151},"https:\u002F\u002Fplay.vuejs.org\u002F#eNqlWN1u2zYUfhXOAWYHiGXFabpUc7KlaYptGNqi6S6GuhhoibLZSKJAUnay1M+xmz3A3mNvsifZ4Y8oyrLSDmuLIOI5\u002FHj++bEPg8uyDNYVGUSDmYg5LSXKcLE8nw+kmA+QILIqL+YFzUvGJXpAnKRoi1LOcjSEbcNvnewVkzSlMZaUFZcZgRWjFUw6EnWg2jkvYlYIicSKbW6qOCZC+LroXB03krwih6Dd6F5zzvgjmrXuChdJRt4Sye9BY3SIzi\u002FQw7xASIlZRoKMLUdDLafFErGScA0XBMFQ4Ww7WFcZE8Sa2obs8SFY46wioJriTJB+TO1SF7Hj6V682cSkDhIFH5LkZYYlgS+EZgldozjDQkBK4VSJaUH4fKCFIF5NL1qBvL7DsJuI2QQkRsdqdrJo1hFaj2kK4D0BmA+cHuYUFxJUS05zzO8bkaQyIyB4gSVGN3hNkkaWAyBeKumvrOIohqAtiYDYrQlaEFIgofSRMEenVZZ5wFFSmZTC9tMwDBvJ97EKOyx3E1srTf57ADr52ud+ojyAHOx6\u002F0uZMZygl5hmfgCIwrxiiVZ58\u002FPryxe\u002FheGx5yNXBYwXGkO1gOekFjkndaU\u002FHgLtQE8A\u002FFJaVFJCDbtCArFZUqg0vu0vCKhdY6TbidANqKI6+jXexAA+fsC+UbAf3ni2H3w2Ad9Mv9jfZhOvjeBTyPuMIBHDjEhgJXCtZHq1xEkCIyRCU05y6EgoW3w33tBEriL0NAzLO7vIl7SIUIhwJZnqXgW+mhoQIx0vGFiWOyitEth4txQlK70DEyrA3vsIpRkxpy0xyI89FBvAtsVhcAoqtR5CC8YTwsccJ7QSSjxVck8EmOUdgvlJE3RApuQsDY0Ux7dLzqoiidBmRaWaTjBqKy4Y7CkZLSThbVOiFVvXIfS3H6RnKU5jowzTTQX\u002FYnAEdxLEPaXL4KNgBVxZeqOaa3kJPcNfl6oCoCgjA6lkOMvY5ie9puriqF6PVyS+3bP+UdyptfngDSeC8DVUkpNJiDuRRnx984rcwe9OmLOkykD7EeFbAlGrzFhQas\u002FBWTDb09PW\u002FqgvVEjOO3F9J0khaqd0YYPmVuvPB3CNXj3iemPuSfBE74N4QhT33sefIQDyviToORbkDWeluvr0KXpyRUhIDta6y20yqXsZFX5nlrAVTqMEelzjWa0WpB2TERraW2KIPqGhIJD4RH2ZPjJ3gncy1L+d9BEqqnyhK82bnd\u002FBMoFa06tuYHqrznRzEz9uuNbpMdtMd231BvMCzDM2O1NaVjtTIuguoCS48B1sWV07uM\u002Fol7TAmbZTIG3hGLEiu4cfpDYN6doiyVfonz\u002F\u002Fsn7ULjSJ\u002FRqNWln55PnqUytz1DlKSApTUItn+ufFqOFqJKfS6VzDh5jpYI3A26H2fHgYoTWjiXZQL+s7yVveGkDt5SUMzfGKJgSlkCJ747dSBQmiKRpp64I6YIeWTxH5juaEVXLksSxIDBg2qs\u002FVhmyPjH8NQj22wIgfSAbFgNKqiHVxSAYOwmTLwcd22Zg7Q9+YdUSgH\u002F3uu1Iyxfpc+egYvh\u002Fa7+EHjw5uqIxXTtU6BQMWctc0S+QvNl1jl3W9VbwAkQmeKc1a35ZuC6Ou4Q6CrmezX82VL6ChFtYyCD9UMGiMMKql7\u002FdFyua1jkCdJfv9wdCW+kr32Yp\u002F1lhd3aSAudgwi9XJxcODRdMzDW23wIBPnIbTrL2qSZ\u002FZY9u1RTpmZYNp5RpVTdOahLRi9AXnuBmyc5JzVCuAh1rDTDIzcpwpDmLHmIZfNSuO2pqtblY1FNJPqBaPDUhbwzE202q2830dzwyEDE1t7OoQwd24NfTNcdUdX9wLSHV5x8Yd++woqOXu3L\u002F\u002FqI\u002FzDPpSwtiaDG0G1k+9HPPaoYZuR8kENZceJ3A2XRvS1eWCHysB59\u002FXxQ93UIljiASRG3hDaRWc0WUxBt6Ww9lq41gAg5D7qWTLnxrV+KW2grLTtKMmsCNql+3B5kwRxAMSp0l62kc0j8PFs7MuqBtxvbCOSu6FffYEnyzOHKzujsDMwX5Ekk7TaS8jTp\u002FAnx1EO0X7IdN0QRZ9kOnpMxIqqXkunPivAPWYgL+GxBsinkI2xoL+Dm1\u002FHLhS0asbQpcryD48SWq4cgetbbmeJTavCiDFOc2gsHJWMF1CHerPlws8Co+Q\u002FReEp+ZS9R4c+kXhl\u002Fcjbw5XyrTI4IYdLzIW3zob\u002FZnTfRuFwTcO5\u002F+8dwqgUR0\u002FD6BupulT88gxafzsq6dlb+\u002FjZ3r69EQXg9njz6yutuS4gERwaL\u002F9NreqwbmmY7my1aDayo9QjwsI\u002FocMx1RCMiCIe+3zfXLKtmvdO277L6Hl430=",[21],"vue playground preview",[30,154,155],{"id":155},"结论",[13,157,158,159,164],{},"Variant Props Pattern (VPP) 提供了一种构建类型安全的 Vue 组件的强大方法。虽然 Vue 团队正在努力改进 ",[17,160,163],{"href":161,"rel":162},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F8952",[21],"vuejs\u002Fcore#8952"," 中对受歧视联合体的本机支持，但这种模式今天提供了一个实用的解决方案：",[13,166,167],{},"不幸的是，目前不起作用的是使用像 Xor 这样的辅助实用程序类型，这样我们就不必手动将未使用的变体属性标记为从不。当你这样做时，你会从 vue-tsc 收到错误。",[13,169,170],{},"Xor 等辅助类型的示例：",[37,172,175],{"className":173,"code":174,"language":42,"meta":43},[40],"type Without\u003CT, U> = { [P in Exclude\u003Ckeyof T, keyof U>]?: never }\ntype XOR\u003CT, U> = T | U extends object ? (Without\u003CT, U> & U) | (Without\u003CU, T> & T) : T | U\n\n\u002F\u002F Success notification properties\ninterface SuccessProps {\n    title: string\n    variant: \"primary\" | \"secondary\"\n    message: string\n    duration: number\n}\n\n\u002F\u002F Error notification properties\ninterface ErrorProps {\n    title: string\n    variant: \"danger\" | \"warning\"\n    errorCode: string\n    retryable: boolean\n}\n\n\u002F\u002F Final props type - only one variant allowed! ✨\ntype Props = XOR\u003CSuccessProps, ErrorProps>\n",[45,176,174],{"__ignoreMap":43},{"title":43,"searchDepth":178,"depth":178,"links":179},4,[180,182,183,184,185,186],{"id":32,"depth":181,"text":32},2,{"id":52,"depth":181,"text":53},{"id":94,"depth":181,"text":94},{"id":109,"depth":181,"text":110},{"id":142,"depth":181,"text":142},{"id":155,"depth":181,"text":155},[188],"译","2025-01-06 11:24:05",null,false,"md","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002FHow-to-Use-the-Variant-Props-Pattern-in-Vue.jpg",{"aside":195,"excerpt":198},[196,197],"toc","auth-alex",{"type":7,"value":199},[200,207,209],[10,201,202],{},[13,203,15,204],{},[17,205,22],{"href":19,"rel":206},[21],[24,208],{},[13,210,28],{},true,"\u002Fposts\u002F2025\u002Fhow-to-use-the-variant-props-pattern-in-vue",{"text":214,"minutes":215,"time":216,"words":217},"5 min read",4.98,298800,996,1,{"title":5,"description":190},{"loc":212},"posts\u002F2025\u002FHow-to-Use-the-Variant-Props-Pattern-in-Vue",[223,224],"Props","Vue","2026-02-07 23:23:50","R6eD59P4MyOhjfiV80uOC1Ig-q183Nd2N6_gIZmwVzM",[228,234],{"title":229,"path":230,"stem":231,"date":232,"type":233,"children":-1},"给未来的一封信——24周岁生日前","\u002Fposts\u002F2023\u002Fa-messy-24","posts\u002F2023\u002FA messy 24","2023-12-17 21:05:11","story",{"title":235,"path":236,"stem":237,"date":238,"type":190,"children":-1},"【翻译】Vue3 组合式API 完全指南","\u002Fposts\u002F2025\u002Fvue3-composables-guide","posts\u002F2025\u002Fvue3-composables-guide","2025-01-06 12:00:00",1789642585773]