Search code examples
typescriptvuejs3vue-composition-apivue-mixinvuejs3-composition-api

How to extend/use mixin in Vue 3 composition API with script setup


I am using Composition API with <script setup> in one component (ComponentA.vue) and want to extend a mixin (Greeting.ts) inside Component A.

Problem statement : When I am trying to access method of the mixin, it is giving me this error.

Uncaught (in promise) TypeError: Greeting.hello is not a function

ComponentA.vue :

<script setup lang="ts">
  import Greeting from '..path/Greeting';

  Greeting.hello();
</script>

Greeting.ts :

export default defineComponent({
  methods: {
    hello(): void {
      console.log('Hello');
    }
  }
});

What I tried so far ?

I tried to use both <script lang="ts"> and <script setup lang="ts"> in Component A and extends the Greeting mixin like this. But I don't know to access hello() method inside <script setup>.

ComponentA.vue :

<script lang="ts">
import Greeting from '..path/Greeting';

export default defineComponent({
  extends: Greeting
});
</script>

<script setup lang="ts">
  // Do not know how to access Greeting hello method here.
</script>

Solution

  • You can't import a component definition into another component and access its methods directly, because the component doesn't exist yet. It needs to be mounted.

    defineComponent() returns a component definition. A recipe, if you want. You can use that definition in the template (and Vue will render as many instances of that component as the template commands - and you can then access those instances through refs, in onMounted) or you could mount it programatically (without actually connecting it to the current web page's DOM). Once you have an instance, you can call its methods, because it exists.

    The code in your very first snippet is trying to taste a soup recipe. You need to make the soup first, using the recipe, and then you can taste it.

    To mount a component without placing it in DOM:

    const instance = createApp(componentDefinition)
      .use(/* stores!? */)
      .use(/* plugins!? */)
      .mount(
        document.createElement('div')
      )
    instance.hello()
    

    From the comments you made under the other answer I understand you don't want component B to be used for rendering. Which begs the question: why are you defining component B as a component? Why not define as a composable:

    hello.ts

    export const useHello = () => ({
      hello: function () {
        console.log('Hello')
      }
    })
    

    ... and now you can import it in any other component:

    import { useHello } from './path/to/hello'
    
    const myHello = useHello()
    myHello.hello()
    

    Relevant documentation.