前言

主要记录vue项目的一些优化


一、渲染优化

v-for

避免v-if和v-for的同级使用,v-for的优先级比v-if高,会导致数据渲染错误

v-for设置key的值,尽量不适用index,使用数据中唯一的标识,有利于dom的定位与diff。


v-show和v-if的选择

经常复用的组件用v-show来渲染(v-show是隐藏不销毁)
相反则用v-if(直接判断是否创建)


长列表优化

纯粹做数据展示,不需要热更新

处于data中的数据会被监视,发生变化时数据就发生变化
所以采用object.freeze(数据)方法冻结数据。

长列表

采用虚拟滚动,只渲染少部分区域的内容
只渲染视口部分的数据,也就是说渲染的DOM节点个数是固定的

页面:

<template>
  <div>
    <div>
      <span v-if="list.length===0">暂无数据!!!</span>
      <div v-else class="box" :style="`height:${viewH}px;overflow-y:scroll;`" @scroll="handleScroll">
        <ul>
          <li :style="`transform:translateY(${offsetY}px); height:${itemH}px;`" v-for='i in clist' :key="i">{{ i }}</li>
        </ul>
      </div>
    </div>
    <button @click="gotoCSSLongList" class="but">CssLongList</button>
  </div>
</template>

逻辑:

<script>
export default {
  name: "LongListOne",
  data() {
    return {
      list: [],//上万条总数据
      clist: [],// 页面展示数据
      viewH: 500, // 可视box高度
      itemH: 60, // 单项高度
      scrollH: '', // 整个滚动列表高度
      showNum: '',//可视化高度一次能装几个列表
      offsetY: 0// 动态偏移量
    }
  },
  mounted() {
    for (let index = 0; index < 100000; index++) {// 10万条数据
      this.list.push(index)
    }
    this.scrollH = this.list.length * this.itemH;// 计算总高度
    // 计算可视化高度一次能装几个列表, 多设置几个防止滚动时候直接替换
    this.showNum = Math.floor(this.viewH / this.itemH) + 4
    // 默认展示几个
    this.clist = this.list.slice(0, this.showNum);
    this.lastTime = new Date().getTime()
  },
  computed: {},
  methods: {
    // 滚动监视器
    handleScroll(e) {
      let {lastTime,itemH,list}=this
      if (new Date().getTime() - lastTime > 10) {
        let scrollTop = e.target.scrollTop // 滚去的高度
        // 每一次滚动后根据scrollTop值获取一个可以整除itemH结果进行偏移
        // 例如: 滚动的scrllTop = 1220  1220 % this.itemH = 20  offsetY = 1200
        this.offsetY = scrollTop - (scrollTop % itemH)
        //上面卷掉了多少,就要往下平移多少,不然showNum滚出可视区外了
        this.clist = list.slice(
          Math.floor(scrollTop / itemH),  // 计算卷入了多少条
          Math.floor(scrollTop / itemH) + this.showNum
        )
        this.lastTime = new Date().getTime()
      }
    },

    // 跳转
    gotoCSSLongList(){
      this.$router.replace('/CssLongList')
    }
  }
}
</script>

样式:

<style scoped>
* {
  padding: 0;
  margin: 0;
  list-style: none;
  box-sizing: border-box;
}
li {
  text-align: center;
  line-height: 60px;
  border-bottom: 1px solid red;
}
.but{
  height: 40px;
  width: 100px;
  margin-top: 10%;
}
</style>
插件

vue-virtual-scroller或者vue-virtual-scroll-list

自己封装

1. 数据一次性给:

  • 元素监听scroll事件(滚动事件)
  • 计算可视化高度一次能装几个列表,然后从总数据中进行slice截取
  • 每一次滚动后根据scrollTop值获取一个可以整除itemH结果进行偏移(scrollTop:滚动条移动的距离)

2. 数据通过请求:

代码实现存在点问题修改后再上传


事件销毁

Vue组件在销毁时,会自动解绑它的全部指令事件及监听器,但仅限于组件本身。
比如定时器等最好在销毁阶段手动销毁,避免内存泄漏

created(){
  this.timer = setInterval(this.refresh,)
}

beforeDestroy(){
  clearInterval(this.timer)
}

无状态的组件标记为函数式组件(静态组件)

只接收父组件传过来的值,自己不做处理,无状态,不创建实例。

函数组件没有this

<template functional> // 函数组件
 <div class="cell">
    <div v-if="props.value" class="on"></div>
    <section v-else class="off"></section>
 <div>
</template >
<script>
 export default {
  props: ['value']
}
</script>

子组件分割

将子组件中耗时的任务交给组件自己管理,不影响整体页面的加载

<template>
 <div>
   <HelloWorld/>
 </div>
</template>
<script>
export default {
  // 子组件自己管自己
  HelloWorld:{
    methods:{
      heavy(){/* 耗时任务 */}
    },
    render(h) {
      return h('div', this.heavy());
    }
  }
}

变量本地化

减少使用 this.数据 的形式获取数据,减少一些不必要的处理
用一个变量先获取数据,在这个变量上处理数据。

在用this.数据 时会有数据劫持,去调用get/set做处理,浪费了时间。


模块化、组件化

  • 封装高度复用的模块
  • 拆分高度复用的组件
  • 组件可配置性
<template>
// 可配置
 <button
   :class="['mybtn',`btn-${btnStyle}`]"
   @click="myBtnClick($event)"
   v-show="btnShow"
 >
   <slot></slot>
 </button>
</template>
<script>

export default {
  name: 'MyBtn',
  props: {
    btnStyle: String,
    btnShow: Boolean
  },
  setup (props,ctx){
    const myBtnClick = (e)=>{
      ctx.emit('my-btn-click', e);
    }
    return{
      myBtnClick
    }
  }
}
</script>

<style lang="scss" scoped>
.mybtn {
  border: none;
  outline: none;
  height:34px;
  padding: 0 15px;
  background-color: #fff;
  border: 1px solid #fff;
}
&.btn-default{
  color: #333;
  background-color: #fff;
  border-color: #ccc;
}
&.btn-primary{
  color: #fff;
  background-color: #317DEF;
  border-color: #317DEF;
}
</style>

二、懒加载

路由懒加载

const router = new VueRouter({
  routes:[
    {path:'.foo', component: () =>import('./Foo.vue')}
 ]
})

图片懒加载

运用插件

vue-lazyload

<img v-lazy="图片地址">

自己封装

代码完善后上传


三、keep-alive缓存页面

原理

在created周期函数(init初始化后)调用时。将需要缓存VNode节点保存 在this.cache中,在渲染页面前调用render函数时。如果VNodedename符合缓存条件(用include和exclude控制),则会从this.cache中取出缓存的VNode实例进行渲染。

参数

  • include:字符或者正则表达式。在名称匹配的组件会被渲染。
  • exclude:字符或者正则表达式。在名称匹配的组件不会被渲染。
  • max:数字。最多可以缓存多少个实例。

被keep-alive包裹时具有的周期函数

activated

  • keep-alive组件激活时调用
  • 服务器端渲染不被调用

deactivated

  • keep-alive组件停用时调用
  • 服务器渲染期间不调用

注意

  • keep-alive会将数据保留在内存中,如果要获取最新数据,则要在activated钩子函数中获取。
  • include需要的name是组件的name
  • 在多级嵌套兄弟页面时,缓存其中一个页面时会将父级都缓存了,导致所有子页面都缓存了,这时需要手动销毁其它兄弟页面。
<keep-alive>    
<router-view v-if="$route.meta.keepAlive"></router-view></keep-alive>
// 销毁不需要的兄弟组件
<router-view v-if="!$route.meta.keepAlive"></router-view>

使用场景

如需要在不同组件切换时某个组件需要不改变
如:搜索框在输入文字后,切换组件时,输入框文字不消失,返回也不消失。

使用方法

使用属性include和exclude

// 只缓存名字为home的组件
<keep-alive include="home">
   <router-view />
</keep-alive>
// 缓存整个组件
<keep-alive>
   <router-view />
</keep-alive>

路由中meta属性来控缓存

// meta属性中keepAlive值设为true
{
      path: '/',
      name: 'home',
      meta:{
        keepAlive:true
      },
      component: Home
    }
// 配合 keep-alive进行控制缓存
<keep-alive>
      <router-view v-if="$route.meta.keepAlive" />
</keep-alive>

存在的问题

组件被缓存后,不存在销毁阶段,在组件切换时,不会调用created等生命周期函数,导致数据不能及时更新。

解决方法:

使用提供的activated与deactivated函数来获取当前组件是否活跃。
在activated函数中获取数据,更新数据(被缓存的组件才会有这两个函数)

四、打包优化

1.引入库

按需引入,避免体积过大

import {Button , selet}from 'element-ui';

2.引入资源

插件cdn、图片cdn、使用CSS图标
减少项目体积


3.打包配置

位置:build/config/index.js
属性:productionGzip: true
作用:打包压缩

位置:truebuild/config/index.js
属性:productionSourceMap: false
作用:减小打包体积 定位源码的会生成map文件 可能造成源码泄露


五、首屏优化

数据加载使用loding骨架屏

在数据加载阶段擦用骨架屏可以给用户带来更好的体验


首屏采用SSR服务端渲染

  • 客户端发送请求给服务器
  • 服务器读取模板,解析成dom节点,返回一个完整的首屏html结构
  • 客户端进行首屏激活(把用户写的交互的代码,在前端激活,重新变成一个spa应用)
  • 这样后续,用户再点击超链接、跳转时,不会再向服务器发送请求了,而是使用前端路由跳转,只会发送一些ajax请求数据

使用web worker

作用

让JS实现多线程

给JS创造多线程运行环境,允许主线程创建worker线程,分配任务给后者,主线程运行的同时worker线程也在运行,相互不干扰,在worker线程运行结束后把结果返回给主线程。

这并不意味着JS语言本身支持了多线程能力,而是浏览器作为宿主环境提供了JS一个多线程运行的环境。

使用

Worker()构造函数,第一个参数是脚本的网址(必须遵守同源政策),该参数是必需的,且只能加载 JS 脚本,否则报错。第二个参数是配置对象,该对象可选。它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程。

var myWorker = new Worker(jsUrl, options)

使用场景

  1. 加密数据
    有些加解密的算法比较复杂,或者在加解密很多数据的时候,这会非常耗费计算资源,导致UI线程无响应,因此这是使用Web Worker的好时机,使用Worker线程可以让用户更加无缝的操作UI。

  2. 预取数据
    有时候为了提升数据加载速度,可以提前使用Worker线程获取数据,因为Worker线程是可以是用 XMLHttpRequest 的。

  3. 预渲染
    在某些渲染场景下,比如渲染复杂的canvas的时候需要计算的效果比如反射、折射、光影、材料等,这些计算的逻辑可以使用Worker线程来执行,也可以使用多个Worker线程,这里有个射线追踪的示例。

  4. 复杂数据处理场景
    某些检索、排序、过滤、分析会非常耗费时间,这时可以使用Web Worker来进行,不占用主线程。

  5. 预加载图片
    有时候一个页面有很多图片,或者有几个很大的图片的时候,如果业务限制不考虑懒加载,也可以使用Web Worker来加载图片,可以参考一下这篇文章的探索,这里简单提要一下。


优化webpack配置

  • webpack的code-split结合vue-router做懒加载
  • webpack的conthash模式针对文件级别更改做缓存

总结

以上记录了vue在各个方面的优化,对于webpackp的优化在学完webpackp后完善

Logo

前往低代码交流专区

更多推荐