I have a strange vuejs effect, when I add new object data, v-for re-renders all objects, even if it has already been rendered.
I am implementing infinite scrolling like face book.
To explain this code, I get new data from firebase and then push it into a data object when it reaches the bottom of the screen
var vueApp = new Vue({ el: '#root', data: { postList: [], isOkaytoLoad: false, isRoomPostEmpty: false, }, mounted: function() { // Everytime user scroll, call handleScroll() method window.addEventLis tener('scroll', this.handleScroll); }, methods: { handleScroll: function() { var d = document.documentElement; var offset = d.scrollTop + window.innerHeight; var height = d.offsetHeight - 200; // If the user is near the bottom and it's okay to load new data, get new data from firebase if (this.isOkaytoLoad && offset >= height) { this.isOkaytoLoad = false; (async()=>{ const lastPost = this.postList[this.postList.length - 1]; const room_id = PARAMETERS.get('room'); const q = query(collection(db, 'posts'), where('room', '==', room_id), orderBy("time", "desc"), limit(5), startAfter(lastPost)); const roomSnapshot = await getDocs(q); roomSnapshot.forEach((doc) => { const postID = doc.id; (async()=>{ // Put the new data at the postList object this.postList = [...this.postList, doc]; const q = query(collection(db, 'comments'), where('post_id', '==', postID)); const commentSnapshot = await getDocs(q); doc['commentCount'] = commentSnapshot.size; //this.postList.push(doc); console.log(this.postList); setTimeout(()=>{ this.isOkaytoLoad = true }, 1000); })(); }); })(); } } } })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div v-if="postList.length > 0" class="card-containers"> <!-- I have a component `Postcard` in my js file and it works well --> <Postcard v-for="post in postList" :key="post.id" :post-id="post.id" :owner-name="post.data().owner_displayName" :owner-uid="post.data().owner_id" :post-type="post.data().post_type" :image-url="post.data().image_url" :post-content="truncateString(linkify(post.data().post_content))" :room="post.data().room" :time="post.data().time.toDate()" :likers="post.data().likers" :comment-count="post.commentCount" :file-url="post.data().file_url" :file-name="post.data().file_name" :downloads="post.data().downloads"> </Postcard> </div>
Look at this screen recording, focused mouse, it's so laggy, I can't even click the buttons while vuejs is adding and loading new data
This is the code I use
I suspect that every time new data is added, VueJS re-renders all the data, causing this effect. How to force vueJS not to re-render data that has already been rendered on the screen?
P粉5671123912023-12-29 16:34:20
You have two unnecessary asyncIIFE; The second one in forEach
is particularly problematic because the asynchronous code in it will execute simultaneously on every loop iteration, This has the following effects:
getDocs()
will fire immediately on every loop iteration, possibly spamming the server (assuming this is performing a network request). Is this your intention? It looks like you can only get a maximum of 5 new posts, so that's probably fine. Also do not use var
; use const
or let
instead. There are almost no good reasons to use var
anymore, let it die.
I can't say this will significantly improve your performance, but I recommend the following changes (untested):
async handleScroll() { const d = document.documentElement; const offset = d.scrollTop + window.innerHeight; const height = d.offsetHeight - 200; // If the user is near the bottom and it's okay to load new data, get new data from firebase if (this.isOkaytoLoad && offset >= height) { // Prevent loading while we load more posts this.isOkaytoLoad = false; try { // Get new posts const lastPost = this.postList[this.postList.length - 1]; const room_id = PARAMETERS.get('room'); const q = query(collection(db, 'posts'), where('room', '==', room_id), orderBy("time", "desc"), limit(5), startAfter(lastPost)); const roomSnapshot = await getDocs(q); // Fetch comments of each post. Do this all at once for each post. // TODO: This can probably be optimized into a single query // for all the posts, instead of one query per post. await Promise.all(roomSnapshot.docs.map(async doc => { const postID = doc.id; const q = query(collection(db, 'comments'), where('post_id', '==', postID)); const commentSnapshot = await getDocs(q); doc.commentCount = commentSnapshot.size; })); // Append the new posts to the list this.postList.push(...roomSnapshot.docs); } catch (ex) { // TODO: Handle error } finally { // Wait a bit to re-enable loading setTimeout(() => { this.isOkaytoLoad = true }, 1000); } } }
Executed in the template :post-content="truncateString(linkify(post.data().post_content))"
means linkify
will be executed during every re-render . I suspect linkify
might be slow for long lists? Can it be pre-calculated for each post in advance?
When the component is installed, you are registering a window scroll event listener. If the component is destroyed, you need to unregister the event listener, otherwise it will still fire every time the window is scrolled. This may not be a problem in your case, but for reusable components it is necessary.