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)
Related
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.
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);
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],
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.
This works, but I need to use mounted(){} to initiate the function which I think can be avoided but not sure how.
<script>
export default {
data () {
return {
domains: [],
}
},
methods: {
fetchDomains() {
let _this = this;
api._get({url: 'api/domains'})
.then(function (response) {
_this.domains = response.data;
})
}
},
mounted() {
this.fetchDomains()
}
}
</script>
This code doesn't work, but I like to do something like this. Initiating the function in data(){} itself.
<script>
export default {
data () {
return {
domains: this.fetchDomains(),
}
},
methods: {
fetchDomains() {
let data = [];
api._get({url: 'api/domains'})
.then(function (response) {
data = response.data;
})
return data
}
}
}
</script>
Thanks in advance.
Your first code snippet is the correct way to do it.
You can't initialize domains with the data from the API response because it is an async operation which may or may not be resolved successfully at some point in the future, well after the component is mounted. You might also want to do other things like keeping track of the async operation with a loading property which you set to true for the duration of the request.
Your component will initially be in a loading state which does not yet have any domains data, and you need to account for this. Show a loading spinner or something during this time.
I agree with Decade Moon that your first approach is the better way to do it (though you could use created instead of mounted).
The reason your second approach doesn't work is that you return an array and then replace the local variable's value with a different array. What you need to do is populate the array you returned.
new Vue({
el: '#app',
data() {
return {item: this.getItem()}
},
methods: {
getItem() {
let val = [];
setTimeout(() => {
const result = ['first','second','third'];
val.push(...result);
}, 800);
return val;
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">{{item}}</div>
I might be deviating slightly from the question (since it explicitly mentions the data property), but I think this might be helpful. Personally, if I want to provide some data with more complex logic I use the computed property. This is great in my opinion and you can read more about it in the docs. The problem in this case is that it doesn't work entirely as expected with asynchronous operations...
However, there is a lovely little module called vue-async-computed which can be found here. It solves this specific problem by providing an asyncComputed property and keeps the code really clean!