Env
I have a Vue 3 Application which requires a constant setInterval() to be running in the background (Game Loop).
I put that in store/index.js and call it from views/Playground.vue on mounted().
When leaving Playground i call beforeUnmount(). Making sure that not multiple setInterval() are running.
// store/index.js
startGameLoop({ commit, dispatch, getters }) {
commit(
"setIntervalId",
setInterval(() => {
dispatch("addPower", getters["ship/getFuelPerSecond"]);
}, 1000)
);
},
// Playground.vue
beforeUnmount() {
clearInterval(this.intervalId);
}
In the top section of Playground.vue there is a score displayed and updated within the setInterval(). I use a library called gsap to make the changing numbers a bit pleasant for the eye.
<h2>Points: {{ tweened.toFixed(0) }}</h2>
watch: {
points(n) {
console.log("gsap");
gsap.to(this, { duration: 0.2, tweened: Number(n) || 0 });
},
},
Problem
methods from the Playground.vue are fired differently and i'm struggling to understand why that is the case.
gsap
the watch from the gsap is fired every second like i would expect from the setInterval() but...
Image
In the center of the Playground i display and image where the src part is v-bind to a method called getEnemyShipImage. In the future i would like to change the displayed enemie ship programmatically - but the method is called 34 times per second. Why is that?
<img
:class="{ flashing: flash }"
#click="fightEnemie()"
:src="getEnemyShipImage()"
alt=""
/>
getEnemyShipImage() {
console.log("image");
return require("#/assets/ships/ship-001.png");
}
Log (Browser)
Console Log Output
moved it to a part without using a method and switch changing images to a watch.
data: () => {
return {
...
selectedImages: "",
images: [
require("#/assets/ships/ship-001.png"),
require("#/assets/ships/ship-002.png"),
require("#/assets/ships/ship-003.png"),
],
};
},
// initial value
mounted() {
this.selectedImages =
this.images[Math.floor(Math.random() * this.images.length)];
this.$store.dispatch("startGameLoop");
}
// watch score
watch: {
score() {
this.selectedImages =
this.images[Math.floor(Math.random() * this.images.length)];
},
}
it's not perfect but better as initialy.
Related
I am a vue beginner and would like to ask a question!
My English is not good but I try to describe my problem completely, thank you.
Currently I use vue to learn how to access the API, but I hope to be able to automatically touch the API again every 3 seconds to update the screen, but I really don’t know how to achieve this?
Hope to get your help, thank you again for watching my question.
My example
Add setInterval in created hook that calls the method that loads the data :
Vue.createApp({
data() {
return {
status: true,
data: [],
interval:null
};
},
methods: {
reNew() {
axios.get("https://randomuser.me/api/?results=5").then(
(response) =>
// console.log(response)
(this.data = response.data.results)
)
}
},
mounted() {
this.reNew()
},
created(){
this.interval = setInterval(() =>{
this.reNew()},3000)
},
destroyed(){
clearInterval(this.interval)
}
}).mount('#app');
You can use the setInterval method.
This would execute the function every 3 seconds.
setInterval(function(){
// fetch data...
}, 3000);
So I have this nuxt page /pages/:id.
In there, I do load the page content with:
content: function(){
return this.$store.state.pages.find(p => p.id === this.$route.params.id)
},
subcontent: function() {
return this.content.subcontent;
}
But I also have an action in this page to delete it. When the user clicks this button, I need to:
call the server and update the state with the result
redirect to the index: /pages
// 1
const serverCall = async () => {
const remainingPages = await mutateApi({
name: 'deletePage',
params: {id}
});
this.$store.dispatch('applications/updateState', remainingPages)
}
// 2
const redirect = () => {
this.$router.push({
path: '/pages'
});
}
Those two actions happen concurrently and I can't orchestrate those correctly:
I get an error TypeError: Cannot read property 'subcontent' of undefined, which means that the page properties are recalculated before the redirect actually happens.
I tried:
await server call then redirect
set a beforeUpdate() in the component hooks to handle redirect if this.content is empty.
delay of 0ms the server call and redirecting first
subcontent: function() {
if (!this.content.subcontent) return redirect();
return this.content.subcontent;
}
None of those worked. In all cases the current page components are recalculated first.
What worked is:
redirect();
setTimeout(() => {
serverCall();
}, 1000);
But it is obviously ugly.
Can anyone help on this?
As you hinted, using a timeout is not a good practice since you don't know how long it will take for the page to be destroyed, and thus you don't know which event will be executed first by the javascript event loop.
A good practice would be to dynamically register a 'destroyed' hook to your page, like so:
methods: {
deletePage() {
this.$once('hook:destroyed', serverCall)
redirect()
},
},
Note: you can also use the 'beforeDestroy' hook and it should work equally fine.
This is the sequence of events occurring:
serverCall() dispatches an update, modifying $store.state.pages.
content (which depends on $store.state.pages) recomputes, but $route.params.id is equal to the ID of the page just deleted, so Array.prototype.find() returns undefined.
subcontent (which depends on content) recomputes, and dereferences the undefined.
One solution is to check for the undefined before dereferencing:
export default {
computed: {
content() {...},
subcontent() {
return this.content?.subcontent
👆
// OR
return this.content && this.content.subcontent
}
}
}
demo
I use NuxtJs Framework. I created an action to call endpoint and set it response's to store.
I want to call an Endpoint every 30 seconds in some of my dashboard pages. If the user navigates to pages that don't need the API call, I want to disable API call.
In my first try I use to implementing Interval in all pages I need API call data (This is work but I don't want to copy these codes in many of my components those need the API call data):
data: () => {
return {
getting: null,
};
},
computed: {
...mapActions({
myAction: "***name-of-action***",
}),
},
created() {
this.getData();
},
beforeDestroy() {
clearInterval(this.getting);
},
methods: {
getData() {
this.getting = setInterval(() => {
this.myAction()
}, 30000);
},
},
In my first try, I use a NuxtJs middleware and JavaScript Interval to dispatch the action, but when I navigate to pages that don't need the API call, it still calls the API (because JavaScript Interval needs be clear but Nuxt middleware don't have access to component lifecycle component destroy to clear the Interval)
export default (props) => {
setInterval(() => {
props.store.dispatch("***name-of-action***");
}, 3000);
};
I want to do the best practice for this problem.
I believe the less bad thing is to do a mixin which exposes 3 things:
startPolling() :
method that starts polling on the particular component
stopPolling() :
method that stops polling in the component
pollingprop() //name it as you see fit
computed property that always exposes the updated data, this data is calculated every time you make the call inside the mixin
(optional) hooks beforeRouteEnter() + beforeRouteLeave() docs
which automatically calls the this.startPolling() and the this.stopPolling()
For solving this problem I used mixin and it became a fine solution.
I create a mixin like this:
// intervalMixin.js
export default {
data: () => {
return {
getting: null,
};
},
computed: {
...mapActions({
myAction: "***name-of-action***",
}),
},
created() {
this.getData();
},
beforeDestroy() {
clearInterval(this.getting);
},
methods: {
getData() {
this.getting = setInterval(() => {
this.myAction()
}, 30000);
},
},
}
So I add this mixin to each component I want like this:
mixins: [intervalMixin],
I'm using the following code to show a datetime (using momentjs) that updates every second. I've read discussion about the performance issue's that may or may not be caused by setInterval and setTimeout.
Is this the most efficient way of going about this? What is thé common way to use intervals in single page applications?
export default {
name: 'app',
data () {
return {
dateTime: null
}
},
methods: {
updateTimer () {
this.dateTime = moment().format()
}
},
mounted () {
this.interval = setInterval(this.updateTimer, 1000)
}
}
The discussion can be found here:
https://forum.vuejs.org/t/basic-problem-making-a-clock-update-in-vue/642/14 (it is 3 years old now)
The props:
props: {
delay: Number,
}
The watcher:
watch: {
q: _.debounce(function() {
console.log(this.delay); // 500; always works fine, this.delay is here
}, this.delay) // never works;
},
If hardcode a delay (set 500 instead of this.delay - it works; otherwise - function not debounce).
What am I doing wrong? Thanks.
You won't be able to accomplish setting the delay there. this is not the component in that scope. You can use $watch instead inside a lifecycle hook:
created () {
this.debounceUnwatch = this.$watch('q', _.debounce(
this.someMethod,
this.delay
))
},
destroyed () {
// Removed the watcher.
this.debounceUnwatch()
},
For more information:
https://v2.vuejs.org/v2/api/#vm-watch
Edit
This doesn't work either. It just really seemed like it should have. What I think needs to be done here is you need to debounce whatever is updating q and not q itself.