Reputation: 31
I have a problem with typescript in Vue 3.
When I call function (this function will filter objects to simplify code) in fetch, it throws an Error:
Property 'filterData' does not exist on type 'void'.
My Code:
<template>
<div>
</div>
</template>
<script lang="ts">
import { defineComponent, onUpdated } from 'vue';
interface Song {
id: number;
name: string;
nameT: string;
cover?: string;
preview: string;
}
interface SongFromITunes {
artistId: number;
artistName: string;
trackName: string;
previewUrl: string;
artworkUrl100?: string;
}
export default defineComponent({
data() {
return {
list: [] as Array<Song>
};
},
props: {
query: String
},
methods: {
filterData({
artistId: id,
artistName: name,
trackName: nameT,
artworkUrl100: cover,
previewUrl: preview
}: SongFromITunes): Song {
return { id, name, nameT, cover, preview };
}
},
setup(props) {
onUpdated(() => {
const { query } = props;
if (query != null) {
fetch(
`https://itunes.apple.com/search?term=${encodeURI(
query
)}&limit=10&entity=musicTrack`
)
.then((resolve) => resolve.json())
.then((data) => data.results.map((item: SongFromITunes) => this.filterData(item));
}
});
}
});
</script>
<style scoped></style>
Upvotes: 3
Views: 1720
Reputation: 37803
I found your question really interesting.
From the docs it's clear this
cannot be used inside setup()
as the Vue instance doesn't exist yet.
But in the case of lifecycle hooks you are passing the callback which must be sometime later attached to the new Vue instance
Docs : These lifecycle hook registration functions can only be used synchronously during setup()
, since they rely on internal global state to locate the current active instance (the component instance whose setup()
is being called right now).
So it seems the right way to do would be it would be easy if Vue just used bind
and bound the callback to the instance. However it's not the case it seems...
Solution for you is to move filterData
from methods
into setup()
setup(props) {
let filterData = function({
artistId: id,
artistName: name,
trackName: nameT,
artworkUrl100: cover,
previewUrl: preview
}: SongFromITunes): Song {
return { id, name, nameT, cover, preview };
}
onUpdated(() => {
const { query } = props;
if (query != null) {
fetch(
`https://itunes.apple.com/search?term=${encodeURI(
query
)}&limit=10&entity=musicTrack`
)
.then((resolve) => resolve.json())
.then((data) => data.results.map((item: SongFromITunes) => filterData(item));
}
});
return { filterData }
}
I was really curious about the reasons behind this because I feel hooks are inherently bound to the instance and probably many people will expect this
is available in callbacks. So I asked on Vue discord and received following explanation (thanks Vaage#9161):
If we bind the lifecycle hooks in setup to the instance, it would only cause confusion and encourage antipatterns.
You make a choice, options api, or composition api. If you choose composition api, there is nothing interesting for you on this
. Everything is contained in the setup
closure.
If we added it, typescript inference would be harder to implement, and people will start using the options api in combination with it.
Don't mix composition api and options api
Upvotes: 6
Reputation: 937
this
is not accessible from the setup function because the component has not been created yet. You need to define filterData
inside setup as well.
Here's the relevant documentation
Upvotes: 1