Subscribe on changes!

`defineComponent` : `composableFunction` references a type with a cyclic structure error

avatar
Dec 11th 2023

Vue version

3.3.8

Link to minimal reproduction

https://play.vuejs.org/#eNqVVF9P2zAQ/yqWXwAJtQjeSpqpLVRiwJhG9zRPU5pcW1PHjmynK6ry3Xe2SUjabhpvyf35/TndeUdHRdHblEAHNDKp5oUlBmxZEJHI5ZBRaxiNmeR5obQlO5Iq/DLJXMCYVGShVU5Oev1W9OSaSSbtawFknBieztzXkOyYJGTBtbG/ZJLDgBiruVxiMUGqw2jFZKqkaSglSOsYh20FUcMQn54xGfWDA9SLPxbyQiQW8C9qQTge9PUuhVHSdzX9VgM9R+PIv+DL3otREqfjDTDqkLgA/VRYjvoYHQRrLmcTvQTrQozePl9eXF4wel4nSwM3sOASpkpP0LGZchCZB7C6hKYuV1kpUFQA+QJbBGySgs9d5kcLH6M3T4/NR+/OgnbDYfRn02bWvHjg88kK0vUBYQapSHTi7PxFzDcwSpR1AaPzUmY4gpYu7So28BlH9djo7yBxLMDZZiF9aFuq25z72XXCL2YbOAukAL1B4FbLd4lTzR5UmohjiCH9Fc3lgFM5VjJNhLArrcrlapIYMHfy+Te36eqg1C1m2uhz0crnGMXjmfzXUlz1rnwfLjfu1/saj3rW4II1J5b5NXGYfmXrM/NMjHbOC6IZXkQ4mjhcGbPhkmYEVwdkZt7S5BOGBkTCBnS4LyZh6ynDobUEIVJ0jydFhnGAfKvIC8zsyXM3CNEaXtWC3Mfx6a468xKZ1fiOaOm6HN91x/P4o56PPECjow/Q6OQfzsbO2eyIs5rYWd/TUjvaL3VYLVoP6+s67j2J660JcGs6gbEPVGFE1R8BE+an

Steps to reproduce

I create two composable for creating component with defineComponent.

composableA: It creates component which will works as a child. I pass the types for the props

type Base<T = string> = {
    name: T extends string ? string : never;
}

export const composableA = <K>() => {
    const cmp = defineComponent<Base<keyof K>>({});

    return cmp;
};

composableB: This creates a parent and this composable will give back the child and parent together:

export const composableB = <T>() => {
    const componentA = defineComponent({});
    const componentB = composableA<T>();
    

    return {
        componentA,
        componentB,
    };
};

On code level this is working as expected. If I have type then then name prop will be type safe. But the lint is not so happy about it and I get this error: The inferred type of 'composableB' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.

I noticed the problem with the occured with the <Base<keyof K>>. If I not using generic with Base or just using the K like this:

const cmp = defineComponent<K>({});
or
const cmp = defineComponent<keyof K>({});

the error is gone but when I pass it to the Base like this:

const cmp = defineComponent<Base<keyof K>>({});

then it fails

(I'm not sure this is a Vue or a Typescript related issue or mine of course)

What is expected?

Not getting this error

What is actually happening?

Getting this error

System Info

No response

Any additional comments?

No response

avatar
Dec 11th 2023

This issue is caused mainly because of the generic than the defineComponent, even is not a valid type as for vue props, because the name: never will not make the type valid at runtime, since name will always be a prop of the component.

That said there's a change on the type from passed props to the defineComponent declared props, you can mitigate that with declaring the prop and overriding it's runtime type. example:

import { PropType, defineComponent } from "vue";

export const composableA = <K>() => {
    const cmp = defineComponent({
        props: {
            name: {
                type: String as unknown as PropType<K extends string ? K : never>
            }
        }
    });

    return cmp;
};

playground

Based on the example provided composableA expects the argument to be a string to work as expected, but the type passed to composableB is an object, which is passed directly to composableA, not sure exactly what is the intention.

Please remember typed only props do not work in vue at runtime! Vue requires actual props to be passed to the component, for validation, otherwise there's no difference between props or attributes.

Based on that there's a workaround, I personally think passing props as argument should only be when using compiler helper defineProps, other usages must be done with care.