工作中遇到一种场景,一个页面要加载多个组件,而且组件中逻辑较为复杂或者元素很多,占用了较多渲染或逻辑运算时间,给用户造成较长时间的白屏,这时候可以用到延迟装载的方式,让组件挨个加载.

虽然总的渲染时间没变,甚至更长一些,但是对用户更加友好.


延迟装载其实就是利用了浏览器的 requestAnimationFrame() 然后累计帧数,通过判断当前累计帧数和期望渲染帧数是否符合,再用v-if进行控制组件的装载


requestAnimationFrame() 告诉浏览器——你希望执行一个函数,并且要求浏览器在下次重绘之前调用指定的回调函数。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 requestAnimationFrame()。requestAnimationFrame() 是一次性的。


这里使用了mixin(混入)实现延迟装载的功能,mixin代码如下


混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

mixin.js

/**
 * @param {Number} maxFrameCount最大帧数,浏览器渲染总帧数超过此变量时,就不在运行
 * @returns 
 */
export default function(maxFrameCount){
    return {
        data(){
            return {
                //当前累计帧数
                frameCount: 0
            };
        },
        mounted(){
            // 每次渲染都累加帧数,当累加帧数超过设定的最大帧数后,就不在调用了
            const refreshFrameCount = () => {
                requestAnimationFrame(()=>{
                    console.log("帧数",this.frameCount)
                    this.frameCount++;
                    if(this.frameCount < maxFrameCount){
                        refreshFrameCount();
                    }
                })
            }
            refreshFrameCount();
        },
        methods:{
            /**
             * 判断是否达到了指定的渲染帧数
             * 当前累计帧数大于等于设定帧时,就返回true
             * @param {Number} showInFrameCount 指定帧数
             * @returns 
             */
            isShow(showInFrameCount){
                return this.frameCount >= showInFrameCount;
            }
        }
    };
}

再创建一个渲染耗时的组件,用来测试效果

这个组件没啥好说的,就是接受一个count,然后里面渲染对应数量的小div,用来增加渲染时间

ItemCom.vue

<template>
  <div class="box">
    <div class="item" v-for="(item,index) in count" :key="index"></div>
  </div>
</template>

<script>
export default {
    name:'ItemCom',
    props:{
        count:{
            type: Number,
            default: 100
        }
    },
    data(){
        return {
            
        };
    }
}
</script>

<style scoped>

.box{
    border: 1px solid pink;
    padding: 5px;
    width: 300px;
    min-height: 300px;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
}

.item{
    width: 10px;
    height: 5px;
    background-color: #367fd3;
    margin: 3px;
}

</style>

然后是调用

FunctionCom这个组件是我测试函数式组件用的,可以忽略不管

函数式组件没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。

实际上,它只是一个接受一些 prop 的函数。在这样的场景下,我们可以将组件标记为 functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。

home.vue

<template>
  <div class="hello">
    <span>{{msg}}</span>
    <function-com msg="大家好"></function-com>
    <div class="box">
      <!-- isShow 就是mixin中的isShow 用来判断当前累计帧数是否达到了预定的渲染帧数 -->
      <item-com :count="800" v-for="(item,index) in 4" :key="index" v-if="isShow(index*10)"></item-com>
    </div>
  </div>
</template>

<script>
// 引入组件
import ItemCom from './ItemCom';
//这个函数式组件可以不引入,与此文章无关
//import FunctionCom from './FunctionCom'
// 引入mixin
import mix from '../mixin/mixin'
export default {  
  name: 'HelloWorld',
  // 注册组件
  components:{ItemCom},//,FunctionCom
  // 注册mixin
  // mix(XX)就是设定最多处理多少帧,超过就不在累计了
  mixins: [mix(500)],
  data () {
    return {
      msg: '我是标题'
    }
  },
  methods:{
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
a {
  color: #42b983;
}
.box{
  display:flex;
  flex-wrap: wrap;
}
</style>

运行后的效果就是组件一个个肉眼可见的渲染出来