Shook Lyngs
Shook Lyngs

Reputation: 1334

Why can't I get ref if the target is an AsyncComponent in Vue3?

If i'm importing component with import SearchBarPopper from './xxx':

import SearchBarPopper from './search-bar-popper';
export default {
  components: {
    SearchBarPopper,
  },
};

Define ref name in template:

<search-bar-popper ref="popper" />

Then call its function when some other component triggers @click:

methods: {
  onClick(index) {
    console.log(this.$refs.popper);
    if (this.$refs.popper) {
      this.$refs.popper.setSearchListIndex(index);
    }
  },
}

And it works. I got Proxy like this:

[[Target]]: Object
  searchBar: Proxy {setPopperRelativeIndex: ƒ, onInputFocus: ƒ, onInputBlur: ƒ, onInputKeyUp: ƒ, 
  onInputKeyDown: ƒ, …}
  setSearchListIndex: ƒ ()
  show: (...)
  $: (...)
  $attrs: (...)
  $data: (...)
  $el: (...)
  $emit: (...)
  $forceUpdate: (...)
  $nextTick: (...)
  $options: (...)
  $parent: (...)
  $props: (...)
  $refs: (...)
  $root: (...)
  $router: (...)
  $slots: (...)
  $store: (...)
  $watch: (...)
  _: (...)

It has the function I need.

Now let's retry this, let's import component with defineAsyncComponent():

export default {
  components: {
    SearchBarPopper: defineAsyncComponent(() => import('./search-bar-popper'))
  },
};

Still triggred in manual event @click, not from mounted() or something.

This time I can't get that ref correctly:

[[Target]]: Object
  $: (...)
  $attrs: (...)
  $data: (...)
  $el: (...)
  $emit: (...)
  $forceUpdate: (...)
  $nextTick: (...)
  $options: (...)
  $parent: (...)
  $props: (...)
  $refs: (...)
  $root: (...)
  $router: (...)
  $slots: (...)
  $store: (...)
  $watch: (...)
  _: (...)

Ref content still printed, but inside I can't find that funciton setSearchListIndex().

by the way, component SearchBarPopper looks like this:

<template>
  <div class="ls-view-home-search__popper">
    <ls-collapse class="ls-view-home-search__popper__wrap" :show="show">
      <search-bar-list ref="search-bar-list" />
    </ls-collapse>
  </div>
</template>

What is going on here?

Upvotes: 3

Views: 2315

Answers (2)

trickyedecay
trickyedecay

Reputation: 31

In vue 3.0.4+

they fix that problem when you bind ref to async component (using defineAsyncComponent).

the ref will bind to the actual component inside the <AsyncComponentWrapper>.

btw, if you using <script setup> in your custom component. you should use defineExpose to expose methods or other things to be used in the parent context.

// child component
import { defineExpose } from 'vue'

// cannot be accessed from outside
function hello(){
    console.log('hello')
}

// can be accessed from outside cause defineExpose
function hello2(){
    console.log('hello2')
}

defineExpose({ hello2 })

Upvotes: 3

Xinchao
Xinchao

Reputation: 3543

That is because when you define a component as async (using defineAsyncComponent), what you get is a wrapper component. The wrapper component is mounted as the direct child component of the parent, and it manages the async loading of your SeachBarPopper component.

If you look at the component tree in your vue devtools, you will see there is an extra AsyncComponentWrapper component around your true SearchBarPopper. Your ref in this case is pointing to the AsyncComponentWrapper, not the SearhBarPopper within.

Upvotes: 3

Related Questions