I have a class Thing
, whose constructor starts an asynchronous fetch
operation. When the fetch
completes, the result is assigned to a field on the Thing
object:
class Thing {
constructor() {
this.image = null
this.load()
}
async load() {
const response = await fetch('https://placekitten.com/200/300')
const blob = await response.blob()
this.image = await createImageBitmap(blob)
}
}
I'm using thing.image
in a Vue
component. The problem is that Vue doesn't pick up on the change in image
when the Promise
resolves. I think I understand why this happens: in the constructor, this
refers to the raw Thing
, and not Vue's reactive proxy wrapper. So the assignment to this.image
ends up bypassing the proxy.
It works if I move the load
call out of the constructor, so that this
inside the load
function refers to the reactive proxy. But that makes my Thing
class harder to use.
Is there a better way to handle this issue?
Minimal example (Vue playground link):
<script setup>
import { reactive } from 'vue'
class Thing {
constructor() {
this.image = null
this.load() // This does not trigger reactivity.
}
async load() {
const response = await fetch('https://placekitten.com/200/300')
const blob = await response.blob()
this.image = await createImageBitmap(blob)
}
}
const thing = reactive(new Thing())
// thing.load() // This triggers reactivity as expected.
</script>
<template>
<p v-if="thing.image">
Image size is {{thing.image.width}}×{{thing.image.height}}
</p>
<p v-if="!thing.image">
Loading...
</p>
</template>
CodePudding user response:
define your class attribute as a ref
<script setup>
import { reactive, ref } from 'vue'
class Thing {
constructor() {
this.image = ref(null)
this.load()
}
async load() {
const response = await fetch('https://placekitten.com/200/300')
const blob = await response.blob()
this.image.value = await createImageBitmap(blob)
}
}
const thing = reactive(new Thing())
// thing.load() // This triggers reactivity as expected.
</script>
<template>
<p v-if="thing.image">
Image size is {{thing.image.width}}×{{thing.image.height}}
</p>
<p v-if="!thing.image">
Loading...
</p>
</template>