2

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?

1

1 Answer 1

1

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"
}
Sign up to request clarification or add additional context in comments.

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.