I have a small web application written in Vue.js and I am trying to show a loading indicator on the page while a list of items is being filtered and re-rendered. The list is not fetched asynchronously, it is just a list of items from a computed property, based on some filter fields and is updated as the filters are changed. There is a delay of a few seconds before the list is updated in the UI because the list has about 1400 items and each item is a card-style component with an image, a button, icons, and some text. I have attempted to show a "Loading..." message between filter changes and new list rendering, but can't seem to get it to show to the user.
The sample shows a list of numbers 1-100000, and when you click the "even ?" checkbox filter, it causes the list to update with only even numbers. 100000 was used to show the slight delay in the rendering of the new list. If you inspect the DOM, you can see that after checking/unchecking the checkbox, the <div id="loading"> pops into the DOM for a short bit, then changes over to the new list inside <div id="loaded">. Although the loading <div> is shown in the DOM, it is never updated in the UI.
I have code I hoped would show "loading" when the computed property is called, and when the list is done rendering, hide it and show the list. Why is this not working? Why does the DOM change, but the UI is never updated? Is the UI not updating because all the dynamic elements are in the same component and it is not flushed until the list is done rendering? Is there a better way to achieve this?
Thanks!
解决方案
You are doing a long synchronous process. These types of processes tie up the UI thread, ie you cannot do anything within the tab nor can the DOM be updated during that time.
To do long processes like you are doing you should put it into a Web Worker. Using the Messaging api you would then send a message to the worker telling it to make the list, and it would send a message back when it is done. This would make your process asynchronous and not tie up the UI.
Side note: you might want to think about using pagination for displaying such a large list. As the hiding/showing of that many elements will also cause the browser to lock up.
importScripts("lodash.js");
self.addEventListener('message', function(e) {
var data = e.data;
if(data.command == "start"){
let list = data.even ? _.range(0,100000,2) : _.range(0,100000);
self.postMessage({"list":list});
}
}, false);
app.js
var app,msgBus;
var worker = new Worker('worker.js');
//listen for messages coming back from the worker
worker.addEventListener('message', function(e) {
var data = e.data;
if(data.list){
//emit a message to your vue app that the list
//was made
msgBus.$emit('listGenerated',data.list);
}
}, false);
//separate empty vue for events
msgBus = new Vue();
app = new Vue({
el: '#app',
mounted:function(){
//Listen for a listGenerated event
//set numbers to the passed list
msgBus.$on("listGenerated",(list)=>{
this.numbers = list;
this.loading = false;
});
},
methods:{
onEvenChanged:function(){
this.loading = true;
//send the start command to the worker
//passing also the even property
this.$nextTick(()=>{
worker.postMessage({command:"start",even:this.even});
});
}
}
});