Subscribe on changes!

template ref can not get exposed value from a top level await component

avatar
Nov 10th 2021

Version

3.2.21

Reproduction link

sfc.vuejs.org/

Steps to reproduce

click the button

What is expected?

without error

What is actually happening?

got an error

avatar
Nov 10th 2021

see https://github.com/vuejs/vue-next/issues/4891#issuecomment-964762807 the component is not resolved when setRef. image

avatar
Nov 11th 2021

as a workaround, from @sodatea defineExpose should run before the first await. see demo

avatar
Nov 11th 2021

@edison1105 I tried that, but I do not think it's a good workaround.

As code below, expose method need invoke a var returned by a async method.

const props = defineProps<id: string>()

const recorder = await getRecorder(props.id)
const save = () => {
    // post recorder to server
}
defineExpose({ record })

If I move defineExpose({ record }) to the front of async function, then I have to change recorder from const to var.

这种写法存在代码上的逻辑撕裂,如果可能的话请加以增强。

avatar
Nov 11th 2021

Currently, for template refs Vue relies on the presence of instance.exposed to decide wether this is a closed instance – requiring an exposeProxy– or a normal, open instance which exposes the normal instance.proxy.

This is usually fine, but becomes a problem with an async component like the one in this issue, because here, instance.exposed will be set after the instance was assigned to the parent's template ref, so the parent''s ref doesn't point to the exposeProxy.

So what we would need is a way to recognize that a component was created with script setup and is therefore closed by default right when the component instance is being created, not only once exposed() has been called, maybe a option flag we sneak in during compilation.

But that would still leave us with a challenge: While the async component is still pending, instance.exposeProxy would exist, but still be unaware of the sayHi function as expose() hasn't been called yet. That would mean that comp.sayHi would still be unavailable until the async component has resolved. The example of OP would still fail, in my understanding.

So in summary:

  • We could improve this scenario a bit so that the exposed properties will be available once the component has resolved.
  • But this would still leave room for bugs and confusion as it they will be missing while it's still pending.

So fix or not, the general recommendation should be to call expose() before any top level await calls in setup.

avatar
Nov 12th 2021

@LinusBorg Thank you for your reply.

Sometimes call expose() before any top level await calls will make code logic strange as I replied before. The exposed fuction invkoe var which defined in setup is very normal.

Consider exposed function need invoke a var which created by an async function, then we only have tow choises:

  1. define the var after the exposed function
const save = () => {
   // where is the declare code for recorder?
  console.log(recorder)
}
const recorder = await getRecorder()
  1. declare the var from const to let whithout init value before the exposed function
// delcare the recorder without init, so we need special the type, and we need change const to let
let recorder: RecorderType
const save = () => {
  console.log(recorder)
}
recorder = await getRecorder()

The first option violates the logic of writing code, we always invoke a var after we declared it. Here we change the code order, only because we need let the defineExpose work as expected.

The second option seems tobe a good choise than the first one, but still make the code reader confusion why we need split declare and init, where is the init code.

So I think we should find a way to improve the defineExposed macro to work better.