search

Home  >  Q&A  >  body text

Vuejs v-for is very laggy on infinite scroll

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.

Code

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>


Now, here’s the problem…

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

What do I suspect

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粉301523298P粉301523298330 days ago381

reply all(1)I'll reply

  • P粉567112391

    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:

    1. 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.
    2. The asynchronous function updates some state, which will trigger Vue to re-render for each document. This should be batched together at the end so that Vue updates as little as possible.

    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.

    reply
    0
  • Cancelreply