Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dx(script-setup): add generic type to defineExpose #5035

Merged
merged 1 commit into from
Dec 6, 2021

Conversation

NoelDeMartin
Copy link
Contributor

@NoelDeMartin NoelDeMartin commented Dec 4, 2021

Currently, the public API for a component defined using defineExpose is not typed when this component is used elsewhere (as discussed in #4397).

My current workaround is to define the API as an interface and casting the object in the component:

MyComponent.d.ts

export interface IMyComponent {

    doSomething(): void;

}

MyComponent.vue

<script setup lang="ts">
    import type { IMyComponent } from './MyComponent';

    const publicAPI: IMyComponent = {
        doSomething() {
            // ...
        },
    };

    defineExpose(publicAPI);
</script>

But writing that is cumbersome, I'd prefer doing the following:

defineExpose<IMyComponent>({
    doSomething() {
        // ...
    },
});

So that's what this PR allows :). Given that it's optional, it shouldn't affect people who isn't using this approach.

I tried extracting this pattern into my own method which I called definePublicAPI, but I was getting a warning so I guess defineExpose cannot be used for composition.

@PurpleTape
Copy link

Hello!

This does not cover the case when ref values passed to defineExpose(). And as result u can't use same interface for expose and access.

MyComponent.d.ts

import type { Ref } from 'vue';

export interface IMyComponent {
    something: Ref<string>;
}

MyComponent.vue

<script setup lang="ts">
    import { ref } from 'vue';
    import type { IMyComponent } from './MyComponent';

    const something = ref('some string');

    defineExpose<IMyComponent>({
        something,
    });
</script>

As docs says: "refs are automatically unwrapped just like on normal instances"
(https://vuejs.org/api/sfc-script-setup.html#defineexpose)

Elsewhere

<tenplate>
    <MyComponent ref="component"/>
</template>

<script setup lang="ts">
    import { onMounted } from 'vue';
    import { MyComponent, IMyComponent } from './MyComponent';

    const component = ref<IMyComponent>();

    onMounted(() => {
        console.log(component.value.something) // now it's string but IMyComponent says this is Ref<string>
    })
</script>

@albertjin
Copy link

albertjin commented Mar 22, 2022

@PurpleTape

A workaround is to add the string type as follows. I hope this weakness of the <script setup> syntax should be revolved in a clean way but not this or that way of workaround.

import type { Ref } from 'vue';

export interface IMyComponent {
    something: string | Ref<string>;
}

@PurpleTape
Copy link

@albertjin, thank you!

I came to a similar decision. If anyone is interested:

// Keep types somewhere
import {
    Ref, ComputedRef, WritableComputedRef, UnwrapNestedRefs,
} from 'vue';

export type VueRef<T> = Ref<T> | ComputedRef<T> | WritableComputedRef<T>;
export type VueUnwrapRef<T> = UnwrapNestedRefs<T>;

// Component types
import { VueRef, VueUnwrapRef } from '@/types';

export interface IComponentWrapped {
    value: VueRef<string>;
}

export type IComponent = VueUnwrapRef<IComponentWrapped>;

// Component
import { ref } from 'vue';
import { IComponentWrapped } from './component-types';

const value = ref('something');

defineExpose<IComponentWrapped>({
    value,
})

// Usage
import { ref } from 'vue';
import { IComponent } from '@/component/component-types';

const myComponent = ref<IComponent>();
console.log(myComponent.value) // 'something'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants