How to creatin Vue wrapper components with type support #15161
|
Hi. In my web apps, I create a lot of wrapper composables and components. I've done this in React, but I have a challenge doing it in Vue. When I use this component named "FormInputMasked", I can add a "label" attribute with TypeScript support, and it will go to UFormField. With inputProps, I can also add props to UInput. (This is a sample; maybe the code is wrong) It's like I add a wrapper but don't touch any nested components' behaviour, just adding some functionality to them. I know I can use "useAttrs" and get UFormField props from there, but there is no way to define the type of fallthrough attributes for "FormInputMasked ", and also this does not solve the other prop, which is "inputProps". |
Replies: 4 comments 1 reply
|
Here For the wrapper, keep |
|
Vue isn't filling in your optional props — but there's a detail that makes it look exactly like it is. On 3.5.40, a wrapper declaring Every omitted prop is The Wrapper passed Which is good news for the pattern you want, because The one piece that genuinely doesn't work the way you'd like is typing fallthrough attributes. import type { FormFieldProps, InputProps } from '@nuxt/ui'
interface Props extends /* @vue-ignore */ Omit<FormFieldProps, 'name'> {
name: string
inputDir?: 'ltr' | 'rtl'
inputProps?: InputProps
mask?: string
}
// without /* @vue-ignore */
props: {
name: { type: String, required: true },
inputProps: { type: Object, required: false },
label: { type: String, required: false }, // from FormFieldProps
help: { type: String, required: false },
required: { type: Boolean, required: false }
}
// with /* @vue-ignore */
props: {
name: { type: String, required: true },
inputProps: { type: Object, required: false }
}So you keep TypeScript completion for I wouldn't reach for JSX for this. The friction you hit was a diagnosis problem rather than a limitation of SFCs, and you'd give up the template type-checking that's doing real work in a wrapper like this. |
|
Thank you for contributing to the discussion. |
|
That's the missing piece, and it's worth spelling out because it explains why the Boolean is the only type Vue does this for. Same component, four optional props, nothing passed: That's Vue's boolean casting: a prop declared And that's exactly your error-text symptom. A wrapper that declares the boolean forwards a real In the first case Which is what // without /* @vue-ignore */
{ name: { type: String, required: true },
label: { type: String, required: false },
required: { type: Boolean, required: false } } // <-- becomes false when absent
// with /* @vue-ignore */
{ name: { type: String, required: true } }The extended interface stays visible to TypeScript for completion, and If you ever do need a boolean declared on the wrapper — say you want it in the prop types for documentation — give it an explicit const props = withDefaults(defineProps<Props>(), { required: undefined })Then absent stays absent and the child's default survives. Worth knowing about, because it's the one case where adding a |
Vue isn't filling in your optional props — but there's a detail that makes it look exactly like it is.
On 3.5.40, a wrapper declaring
name,inputDir?,inputProps?andmask?, rendered with onlynamepassed:Every omitted prop is
undefined. Nothing was invented from the TypeScript types. But the key is present on the props object, becausedefineProps<Props>()declares all four props at runtime — declared and unset, not absent. So'inputProps' in propsistrue,Object.keys(props)include…