构建在保持类型安全的同时处理多重变化的 Vue 组件可能很棘手。让我们深入研究如何使用变体 Props 模式(VPP) - 一种强大的方法,它使用 TypeScript 的可区分联合类型与 Vue 的组合式 API 来创建真正类型安全的组件变体。
长话短说
Vue 中的 Variant Props 模式将 TypeScript 的可区分联合与 Vue 的 prop 系统相结合,以创建类型安全的组件变体。我们不使用复杂类型实用程序,而是将不兼容的 props 显式标记为 never,以防止在编译时混合 props:
interface BaseProps { title: string } type SuccessProps = BaseProps & { variant: "success" message: string errorCode?: never } type ErrorProps = BaseProps & { variant: "error" errorCode: string message?: never } type Props = SuccessProps | ErrorProps
该模式提供了编译时安全性、出色的 IDE 支持和可靠的 vue-tsc 兼容性。非常适合需要多个互斥道具组合的组件。
问题:混合 Props 噩梦
想象一下:您正在构建一个需要处理成功和错误状态的通知组件。每个类型都有自己特定的属性:
- 成功需要
message
和duration
- 错误需要
errorCode
和retryable
标志
如果没有适当的安全类型,开发人员可能会意外的混用这些 props
<!-- 🚨 Mixing success and error props --> <NotificationAlert variant="primary" title="Data Saved" message="Success!" errorCode="UPLOAD_001" :duration="5000" @close="handleClose" />
简单的解决方案不起作用
您的第一直觉可能是定义单独的接口:
interface SuccessProps { title: string variant: "primary" | "secondary" message: string duration: number } interface ErrorProps { title: string variant: "danger" | "warning" errorCode: string retryable: boolean } // 🚨 This allows mixing both types! type Props = SuccessProps & ErrorProps
有什么问题?这种方法允许开发人员同时使用 success 和 error props - 绝对不是我们想要的!
使用 never
TypeScript 提示: never 类型是 TypeScript 中的一种特殊类型,表示从未出现的值。当属性被标记为 never 时,TypeScript 确保永远不能将值分配给该属性。这使得它非常适合创建互斥的 props,因为它可以防止开发人员意外地使用不应该同时存在的 props。 never 类型通常出现在 TypeScript 中的几种场景中:
- 永不返回的函数(抛出错误或具有无限循环)
- switch 语句中的详尽类型检查
- 不可能的类型交叉(例如 string & number)
- 使属性互斥,就像我们在这种模式中所做的那样
使其与当前的 defineProps 实现兼容的主要技巧是使用 never 来显式标记未使用的变体道具。
// Base props shared between variants interface BaseProps { title: string } // Success variant type SuccessProps = BaseProps & { variant: "primary" | "secondary" message: string duration: number // Explicitly mark error props as never errorCode?: never retryable?: never } // Error variant type ErrorProps = BaseProps & { variant: "danger" | "warning" errorCode: string retryable: boolean // Explicitly mark success props as never message?: never duration?: never } // Final props type - only one variant allowed! type Props = SuccessProps | ErrorProps
把它们放在一起
这是使用 Variant Props 模式的完整通知组件:
结论
Variant Props Pattern (VPP) 提供了一种构建类型安全的 Vue 组件的强大方法。虽然 Vue 团队正在努力改进 vuejs/core#8952 中对受歧视联合体的本机支持,但这种模式今天提供了一个实用的解决方案:
不幸的是,目前不起作用的是使用像 Xor 这样的辅助实用程序类型,这样我们就不必手动将未使用的变体属性标记为从不。当你这样做时,你会从 vue-tsc 收到错误。
Xor 等辅助类型的示例:
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never } type XOR<T, U> = T | U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U // Success notification properties interface SuccessProps { title: string variant: "primary" | "secondary" message: string duration: number } // Error notification properties interface ErrorProps { title: string variant: "danger" | "warning" errorCode: string retryable: boolean } // Final props type - only one variant allowed! ✨ type Props = XOR<SuccessProps, ErrorProps>