The back-end gives you 100000 pieces of data at a time. How to show it gracefully? What do you examine me?

Posted by Seraph on Tue, 23 Nov 2021 07:36:32 +0100

preface

Hello, I'm Lin Sanxin. In the most popular words, the most difficult knowledge point is my motto. The foundation is advanced, and the premise is my initial intention. Today, I'll talk to you. If the back end really returns 100000 data to the front end, how can we show it gracefully in the front end? (haha, assuming that the back end can really transmit 100000 data to the front end)

Front work

Do the pre work well before testing

Back end construction

Create a new server.js file, simply start a service, return 10w pieces of data to the front end, and start the service through nodemon server.js

Students who have not installed nodemon can install npm i nodemon -g globally first

// server.js

const http = require('http')
const port = 8000;

http.createServer(function (req, res) {
  // Turn on Cors
  res.writeHead(200, {
    //Set the domain name that allows cross domain, or * allow all domain names
    'Access-Control-Allow-Origin': '*',
    //Cross domain allowed request methods, or set * to allow all methods
    "Access-Control-Allow-Methods": "DELETE,PUT,POST,GET,OPTIONS",
    //Allowed header types
    'Access-Control-Allow-Headers': 'Content-Type'
  })
  let list = []
  let num = 0

  // Generate a list of 100000 data
  for (let i = 0; i < 1000000; i++) {
    num++
    list.push({
      src: 'https://p3-passport.byteacctimg.com/img/user-avatar/d71c38d1682c543b33f8d716b3b734ca~300x300.image',
      text: `I am ${num}No. jiabinlin three heart`,
      tid: num
    })
  }
  res.end(JSON.stringify(list));
}).listen(port, function () {
  console.log('server is listening on port ' + port);
})

Front page

First create a new index.html

// index.html

// style
<style>
    * {
      padding: 0;
      margin: 0;
    }
    #container {
      height: 100vh;
      overflow: auto;
    }
    .sunshine {
      display: flex;
      padding: 10px;
    }
    img {
      width: 150px;
      height: 150px;
    }
  </style>

// html part
<body>
  <div id="container">
  </div>
  <script src="./index.js"></script>
</body>

Then create a new index.js file to encapsulate an AJAX function to request the 10w data

// index.js

// Request function
const getList = () => {
    return new Promise((resolve, reject) => {
        //Step 1: create an asynchronous object
        var ajax = new XMLHttpRequest();
        //Step 2: set the url parameters of the request. The first parameter is the type of the request, and the second parameter is the url of the request, which can take parameters
        ajax.open('get', 'http://127.0.0.1:8000');
        //Step 3: send request
        ajax.send();
        //Step 4: the registration event onreadystatechange will be called when the state changes
        ajax.onreadystatechange = function () {
            if (ajax.readyState == 4 && ajax.status == 200) {
                //Step 5: if you can enter this judgment, the data has come back perfectly, and the requested page exists
                resolve(JSON.parse(ajax.responseText))
            }
        }
    })
}

// Get container object
const container = document.getElementById('container')

Direct Render

The most direct way is to render directly, but this is certainly not advisable, because rendering 10w nodes at one time is very time-consuming. Let's take a look at the time-consuming. It takes almost 12 seconds, which is very time-consuming

const renderList = async () => {
    console.time('List time')
    const list = await getList()
    list.forEach(item => {
        const div = document.createElement('div')
        div.className = 'sunshine'
        div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
        container.appendChild(div)
    })
    console.timeEnd('List time')
}
renderList()

setTimeout paging rendering

This method is to divide 10w into a total of Math.ceil(total / limit) pages according to the limit of the number of pages per page, and then use setTimeout to render 1 page of data each time. In this way, the time to render the home page data is greatly reduced

const renderList = async () => {
    console.time('List time')
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total / limit)

    const render = (page) => {
        if (page >= totalPage) return
        setTimeout(() => {
            for (let i = page * limit; i < page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
                container.appendChild(div)
            }
            render(page + 1)
        }, 0)
    }
    render(page)
    console.timeEnd('List time')
}

requestAnimationFrame

Using requestAnimationFrame instead of setTimeout reduces the number of rearrangements and greatly improves the performance. It is recommended that you use requestAnimationFrame more in rendering

const renderList = async () => {
    console.time('List time')
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total / limit)

    const render = (page) => {
        if (page >= totalPage) return
        // Use requestAnimationFrame instead of setTimeout
        requestAnimationFrame(() => {
            for (let i = page * limit; i < page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
                container.appendChild(div)
            }
            render(page + 1)
        }, 0)
    }
    render(page)
    console.timeEnd('List time')
}

Document fragment + requestAnimationFrame

Benefits of document fragmentation

  • 1. In the past, every time a div tag was created, it was appendChild. However, with document fragments, you can first put the div tag of one page into the document fragment, and then appendChild into the container at one time, which reduces the number of appendChild and greatly improves the performance
  • 2. The page renders only the elements wrapped in document fragments, not document fragments
const renderList = async () => {
    console.time('List time')
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total / limit)

    const render = (page) => {
        if (page >= totalPage) return
        requestAnimationFrame(() => {
            // Create a document fragment
            const fragment = document.createDocumentFragment()
            for (let i = page * limit; i < page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
                // Insert the document fragment first
                fragment.appendChild(div)
            }
            // Disposable appendChild
            container.appendChild(fragment)
            render(page + 1)
        }, 0)
    }
    render(page)
    console.timeEnd('List time')
}

Lazy loading

For a more popular explanation, let's start a vue front-end project, and the back-end service is still on

In fact, the implementation principle is very simple. Let's show it through a figure, that is, put an empty node blank at the end of the list, render the data on page 1 first, scroll up, and wait until blank appears in the view to explain the end. At this time, load the second page and push it later.

As for how to judge whether blank appears on the view, you can use the getBoundingClientRect method to obtain the top attribute

<script setup lang="ts">
import { onMounted, ref, computed } from 'vue'
const getList = () => {
  // Same code as above
}

const container = ref<HTMLElement>() // container node
const blank = ref<HTMLElement>() // blank node
const list = ref<any>([]) // list
const page = ref(1) // Current number of pages
const limit = 200 // One page display
// Maximum pages
const maxPage = computed(() => Math.ceil(list.value.length / limit))
// Real show list
const showList = computed(() => list.value.slice(0, page.value * limit))
const handleScroll = () => {
  // Comparison of current and maximum pages
  if (page.value > maxPage.value) return
  const clientHeight = container.value?.clientHeight
  const blankTop = blank.value?.getBoundingClientRect().top
  if (clientHeight === blankTop) {
    // If blank appears in the view, the current number of pages is increased by 1
    page.value++
  }
}

onMounted(async () => {
  const res = await getList()
  list.value = res
})
</script>

<template>
  <div class="container" @scroll="handleScroll" ref="container">
    <div class="sunshine" v-for="(item) in showList" :key="item.tid">
      <img :src="item.src" />
      <span>{{ item.text }}</span>
    </div>
    <div ref="blank"></div>
  </div>
</template>

Virtual list

There are a lot of virtual lists to explain. Here I share my article on virtual lists. Haha, I think it's good

In combination with the "Kangxi draft", let's talk about "virtual list"

epilogue

If you think this article is of little help to you, give a praise and encourage Lin Sanxin, ha ha. Or you can join my fishing group. If you want to join the learning group, please click here loaf on a job , I will regularly broadcast simulated interviews, resume guidance, and answer questions

Topics: Javascript Front-end ECMAScript Vue.js Interview