vue优化小技巧

?实际场景中的一些细节优化

函数式组件来加快组件渲染

函数式组件(functional):利用组件传值的关系,加快template模板的渲染,效率最快是render,但修改比较麻烦,考虑这点建议还是使用函数式组件。

使用场景:静态页面不需要修改值变化的场景。

<template functional>
    <ul>
        <li v-for="item in props.data" :key="item.id">{{item.value}}</li>
    </ul>
</template>
<script>
export default {
    name: 'Functional',
    props: {
        data: {
            require: true
        }
    }
}
</script>
<template>
   <Functional :data="list"/> 
</template>
<script>
import Functional from '@/components/Functional.vue'
export default {
  name: 'App',
  data() {
    return {
      list: [
        {id: 1,value: '111'},
        {id: 2,value: '333'},
        {id: 3,value: '333'}
      ]
    }
  },
  components: {Functional}
}
</script>

关于v-for和DOM-DIFF的一些细节处理

v-for中key为啥最好不要设置index?设置key为啥能提高dom-diff查找效率?

<template>
    <div class="dom">
        <div>v-for 和 dom-diff之间的关系</div>
        <div class="btn">
            <button @click="delList">del(0)</button>
            <button @click="addList">add</button>
        </div>
        <ul>
            <li v-for="item in list" :key="item.id">
                {{item.id}}:{{item.value}}
            </li>
        </ul>
        <div>.....................................................</div>
        <button @click="delList2">del2(0)</button>
        <button @click="addList2">add2</button>
        <ul>
            <li v-for="(item,index) in list2" :key="index">{{index}}:{{item.value}}</li>
        </ul>
    </div>
</template>
<script>
export default {
    name: 'ForAndDiff',
    data() {
        return {
            list: [
                {id: 1,value: '111'},
                {id: 2,value: '333'},
            ],
            list2: [
                {id: 1,value: '111'},
                {id: 2,value: '333'},
            ]
        }
    },
    methods: {
        delList() {
            this.list.splice(0,1);
        },
        delList2() {
            this.list2.splice(0,1);
        },
        addList() {
            this.list.splice(2, 0, {
                id: 545,
                value: "互联网",
            });
        },
        addList2() {
            this.list2.splice(2, 0, {
                value: "互联网",
            });
        },
    }
}
</script>

根据上面案例,可以发现key设置index,key值是动态变化,而绑定id,key是唯一固定,添加相同id,能添加成功,但vue会给报错信息。这样的好处:有利于diff算法查询,而key为index,移除之前后又有重新的值,这样容易导致diff算法出现问题,为了减少不必要的情况,所以最好用id。

computed计算属性的优化

<template>
    <div>
        {{ subtotal }}
        {{ subtotal2 }}
    </div>
</template>
<script>
export default {
    name: 'Compoueduse',
    data() {
        return {
            n: 10
        }
    },
    computed: {
        m() {
            return 20;
        },
        subtotal() {
            console.time("AAA");
            let result = this.n;
            for (let index = 0; index < 9999999; index++) {
                result += this.m;
            }
            console.timeEnd("AAA"); //第一次217.31494140625 ms
            return result;
        },
        subtotal2() {
            console.time("BBB");
            let {m, n}= this;
            let result = n;
            for (let index = 0; index < 9999999; index++) {
                result += m;
            }
            console.timeEnd("BBB"); //BBB: 20.25390625 ms
            return result;
        }
    },
}
</script>

一下子优化10倍,这效率很明显吧。

v-show和v-if的合理使用技巧

区别:

  1. v-if控制元素的创建销毁,会导致重绘;v-show控制元素的display,导致回流。
  2. v-show 有更高的首次渲染开销,而 v-if 的首次渲染开销要小的多;
  3. v-if 有更高的切换开销,v-show 切换开销小;
  4. v-if 有配套的 v-else-if 和 v-else,而 v-show 没有

使用场景具体分析:

  1. 切换频繁考虑v-if,不频繁v-show
  2. 切换后是相同组件,直接v-show

关于vue-lozyload图片懒加载的研究

  1. 使用第三方插件
  2. 利用img.onload,结合下面长列表实现。

长列表和无限列表的性能优化

监听离可视窗口的距离: IntersectionObserver(不兼容Safari, 有polyfill版)

Object.freeze()方法可以冻结一个对象

一个被冻结的对象再也不能被修改

  • 不能添加新属性
  • 不能删除已有属性
  • 不能修改已有属性的可枚举性、可配置性、可写性
  • 不能修改已有属性的值
  • 不能修改原型
<template>
  <div class="news-list">
      <template v-if="list.length">
        <div class="item" v-for="(item, index) in list" :key="index">
            <div class="con">
                <h4 class="title">{{item.title}}</h4>
                <p class="hint">{{item.public_abbr}}</p>
            </div>
            <div class="pic" ref="pics">
                <img
                    v-if="item.list_image_url"
                    :data-src="item.list_image_url"
                    alt=""
                />
            </div>
        </div>
      </template>
       <div class="loading" ref="loading" v-show="visible">加载更多数据...</div>
  </div>
</template>

<script>
export default {
    name: 'newList',
    data() {
        return {
            list: [],
            visible: false
        }
    },
    methods: {
        async query() {
            let result = await this.$api.trending();
            result = result.map((item) => {
                return Object.freeze(item.object.data)
                // return item.object.data;
            })
            this.list.push(...result);
            this.visible = true;
            // 获取PIC并且进行监听
            this.$nextTick(() => {
                this.$refs["pics"].forEach((item) => {
                    this.ob.observe(item);
                });
            });
        }
    },
    created() { 
        this.query();
    },
    mounted() {
        this.ob = new IntersectionObserver(
            (changes) => {
                changes.forEach(({isIntersecting, target}) => {
                    if (!isIntersecting) return;
                     // 出现在视口中:加载真实图片
                    const img = target.querySelector("img");
                    if (!img) return;
                    img.src = img.getAttribute("data-src");
                    img.onload = () => (img.style.opacity = 1);
                    this.ob.unobserve(target);
                });
            },
            { threshold: [1] }
        );
        // 滚动到底部加载更多数据
        this.obLoading = new IntersectionObserver(([item]) => {
            if (item.isIntersecting) {
                // 滚动到底部了
                this.query();
            }
        });
        this.obLoading.observe(this.$refs["loading"]);
    }
}
</script>

<style lang="scss" scoped>
.news-list {
  .item {
    position: relative;
    padding: 10px 0;
    min-height: 120px;
    border-bottom: 1px dashed #eee;

    .con {
      margin-right: 170px;

      .title {
        line-height: 30px;
        font-size: 16px;
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow: hidden;
      }

      .hint {
        font-size: 14px;
        line-height: 20px;
      }
    }

    .pic {
      position: absolute;
      top: 10px;
      right: 10px;
      box-sizing: border-box;
      width: 150px;
      height: 120px;
      overflow: hidden;
      background: url("../assets/defaultbg.webp") no-repeat;
      background-size: 100% 100%;

      img {
        display: block;
        width: 100%;
        height: 100%;
        opacity: 0;
        transition: opacity 0.3s;
      }
    }
  }
  .loading {
    height: 50px;
    line-height: 50px;
    text-align: center;
  }
}
</style>

使用自定义指令(directive)来优化项目代码

direactive.js
import Vue from 'vue';
import store from '@/store/index';
Vue.directive('permission', {
    inserted(el, binding){
        let permission = store.state.permission,permissionList = [];
        if (!permission) permission = "";
        permissionList = permission.split('|');
        let passText = binding.value;
        // 循环校验是否有权限
        let flag = permissionList.includes(passText);
        // 控制元素显示隐藏
        if (!flag) el.parentNode && el.parentNode.removeChild(el);
    }
})
demo.veu
<template>
  <div class="permission-demo">
    <el-menu
      default-active="1"
      text-color="#fff"
      active-text-color="#409eff"
      background-color="#222832"
    >
      <el-menu-item index="1" v-permission="'ControlPanel'">
        <i class="el-icon-menu"></i>
        <span slot="title">控制面板</span>
      </el-menu-item>

      <el-submenu index="2">
        <template #title>
          <i class="el-icon-s-tools"></i>
          <span>主页配置</span>
        </template>
        <el-menu-item index="2-1" v-permission="'BannerSetting'">
          <i class="el-icon-picture-outline"></i>
          <span slot="title">轮播图</span>
        </el-menu-item>
        <el-menu-item index="2-2" v-permission="'GoodsSetting'">
          <i class="el-icon-wallet"></i>
          <span slot="title">热销商品</span>
        </el-menu-item>
        <el-menu-item index="2-3" v-permission="'ProductSetting'">
          <i class="el-icon-position"></i>
          <span slot="title">新品上线</span>
        </el-menu-item>
        <el-menu-item index="2-4" v-permission="'RecommendSetting'">
          <i class="el-icon-thumb"></i>
          <span slot="title">为你推荐</span>
        </el-menu-item>
      </el-submenu>

      <el-menu-item index="3" v-permission="'ClassiFication'">
        <i class="el-icon-tickets"></i>
        <span slot="title">分类管理</span>
      </el-menu-item>

      <el-menu-item index="4" v-permission="'GoodsManager'">
        <i class="el-icon-shopping-cart-full"></i>
        <span slot="title">商品管理</span>
      </el-menu-item>

      <el-menu-item index="5" v-permission="'MemberManager'">
        <i class="el-icon-user"></i>
        <span slot="title">会员管理</span>
      </el-menu-item>

      <el-menu-item index="6" v-permission="'OrderManager'">
        <i class="el-icon-s-order"></i>
        <span slot="title">订单管理</span>
      </el-menu-item>

      <el-menu-item index="7" v-permission="'SystemSetting'">
        <i class="el-icon-setting"></i>
        <span slot="title">系统设置</span>
      </el-menu-item>
    </el-menu>
  </div>
</template>

<script>
export default {
  name: "permission-demo",
};
</script>

<style lang="scss" scoped>
.el-menu {
  border-right: none;
}
</style>
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import {createLogger} from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    permission: "ControlPanel"
  },
  plugins: [createLogger()]
});

/* 
全部权限代码
  控制面板 ControlPanel
  首页设置
    轮播图 BannerSetting
    热销商品 GoodsSetting
    新品上线 ProductSetting
    为你推荐 RecommendSetting
  分类管理 ClassiFication
  商品管理 GoodsManager
  会员管理 MemberManager
  订单管理 OrderManager
  系统设置 SystemSetting
*/


版权声明:本文为qq_39189369原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。