Reputation: 31
My team is building a Vue 3 component library. As the project goes larger in scale, we refactored it with typescript to get a better developing experience.
But here's the thing - there are teams using our lib in different environments, with or without typescript and bundler, which means for almost every component we need to validate prop types both in compile-time and runtime.
props: {
someProp: {
type: String as PropType<'enum1' | 'enum2' | 'enum3'>,
validator: (v: string) => {
return ['enum1', 'enum2', 'enum3'].includes(v)
}
}
}
The code runs well. Teams using typescript are happy - they got their code completion and can spot potential errors ahead of time. Teams using javascript are happy - they can discover any type-related errors at runtime thanks to the validator
. But we are not happy, since the code is duplicated. Technically <'enum1' | ...>
is a type def and ['enum1', ...]
is an array, they are not the same thing - but we literally write the same string multiple times.
Is there any way to implement something that can define a prop with PropType
and validator
without having to reeeeeeepeat myself?
Upvotes: 2
Views: 940
Reputation: 31
Well, currently I'm doing something like this:
export const buildProp = <
T = any,
R extends boolean = boolean,
D extends T = T
>({
type?: any,
/* all the values allowed for this prop */
values?: readonly T[],
required?: R,
/* if a prop is required, it shouldn't default to any value */
/* if a prop has a default value, it shouldn't be marked as required */
defaultValue?: R extends true
? never
: D extends Record<string, unknown> | Array<any>
? () => D
: D,
/* any other validations */
validator?: (val: any) => boolean
} = {}) => {
return {
type: type as PropType<T | never>,
required: !!required,
default: defaultValue,
validator: (val: any) => {
let valid = false
// we see if the prop value passed in is in the union
// or is the defaultValue
if (values) {
valid =|| [...values, defaultValue].includes(val)
}
if (validator) {
valid =|| validator(val)
}
return valid
}
}
}
As a result,
someProp: buildProp({
type: String,
values: ['enum1', 'enum2', 'enum3']
})
will produce
someProp: {
type: String as PropType<'enum1' | 'enum2' | 'enum3'>,
validator: (val) => {
return ['enum1', 'enum2', 'enum3'].includes(val)
}
}
if (props.someProp === 'illegalValue') {
// ERR: will always return false since type "'enum1' | 'enum2' | 'enum3'" has no overlap with "illegalValue"
}
Upvotes: 1