前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue组件对象字面量传值的注意啦!

vue组件对象字面量传值的注意啦!

作者头像
奋飛
发布2020-05-28 17:09:16
2.2K0
发布2020-05-28 17:09:16
举报
文章被收录于专栏:Super 前端Super 前端

前面汇总过 「vue组件引用传值的最佳实践」,对于 vue2 版本存在一个严重的性能问题,需要格外注意:对象字面量的传递

vue-props-传入一个对象 <!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue --> <!-- 这是一个 JavaScript 表达式而不是一个字符串。--> <blog-post v-bind:author="{ name: 'Veronica', company: 'Veridian Dynamics' }" ></blog-post> <!-- 用一个变量进行动态赋值。--> <blog-post v-bind:author="post.author"></blog-post>

问题描述

前提:

  • 字面量形式传参
  • 触发虚拟DOM重绘或patch(模板使用的响应数据修改;向模板中动态调整响应数据set/ delete)

官方允许对象字面量的方式进行属性传递,如上述。会产生这样一个问题:组件外部响应式变量(组件内并没有使用)发生变化,也会引起组件的 updated(vue 生命周期一环),如果我们在组件内部 watch/computed 了相关传递的属性值(如上述的 author,虽然 author 使用的值没有发生变化),也会导致 watch/computed 逻辑被执行

示例:

演示地址:https://8x6mx.csb.app/

代码地址:https://codesandbox.io/s/code-8x6mx?file=/src/components/HelloWorld2.js:0-108

每次 test 变量修改,都会引起 HelloWorld 组件的 updated,从而导致 watch 的执行。

App.vue

代码语言:javascript
复制
<template>
  <div id="app">
    <hello-world :person="{name: 'ligang'}"></hello-world>
    <hello-world2 :person="p"></hello-world2>
    <input type="text" v-model="test" placeholder="请输入">
  </div>
</template>

<script>
import HelloWorld from "./components/HelloWorld.vue"
import HelloWorld2 from "./components/HelloWorld2.js"

export default {
  name: "App",
  components: {
    HelloWorld,
    HelloWorld2
  },
  data() {
    return {
      p: { name: "ligang" },
      test: ""
    }
  }
}
</script>

HelloWorld.vue

代码语言:javascript
复制
<template>
  <div>
    <div>{{$options._componentTag}}</div>
    <div>Hello {{person.name}}</div>
    <div>Updated次数:{{count}}</div>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  props: {
    person: Object
  },
  data() {
    return {
      count: 0
    };
  },
  watch: {
    person: {
      handler(val, oldVal) {
        this.count++;
      },
      immediate: true
    }
  },
  updated() {
    console.log("HelloWorld updated")
  }
}
</script>

HelloWorld2.js

代码语言:javascript
复制
import HelloWorld from "./HelloWorld.vue"
export default {
  name: "HelloWorld2",
  extends: HelloWorld
}

原因分析

模板 ==> AST ==> render函数 ==> vnode对象(virtual dom) ==> 真实Dom

模板解析为 AST,预编译为渲染函数。通过 vue-template-compiler,可以查看生成 render 函数的不同。

模板中使用了响应式数据 test,修改该数据,vue 追踪到变化,修改 vnode,通过对比算法确定需要更新的节点,进行 patch 操作,渲染视图。

每次执行 render,虽然 person 对象属性未发生变化,但 hello-world 组件中其为字面量,所以导致每次的引用值不同,因此触发组件内的 watch;hello-world2 为同一引用,因此不会触发组件内的 watch。

render 函数

vue-template-compiler:该模块可用于将 Vue 2.0 模板预编译为渲染函数(template => ast => render),以避免运行时编译开销和 CSP 限制。

代码语言:javascript
复制
import {compile} from 'vue-template-compiler'
compile(`<hello-world :person="{name: 'ligang'}"></hello-world>`)
compile(`<hello-world2 :person="p"></hello-world2>`)
虚拟DOM
  • Vue1.0,当状态发生变化时,在一定程度上知道哪些节点使用了这个状态,从而对这些节点进行更新操作,无需对比。但这种细粒度的处理方式,每一个绑定都会有一个对应的 watcher 来观察状态的变化,这样就会有一些内存开销以及一些追踪依赖的开销,当状态值被越多的节点使用时,开销就越大。对于一个中大型项目,这个开销是巨大的。
  • vue2.0,选择了一种”中粒度“解决方案,引入虚拟DOM,组件级别 watcher,一个组件内有 10 个节点使用了某一状态值,其也只会有一个 watcher 在观察这个状态的变化。组件得到变化通知后,通过虚拟 DOM 进行对比,最后渲染。
  • vue3.0,该问题不复存在。现阶段可以通过 vite 尝试。
响应属性

vue 内置响应式的属性:props、data、computed、watch。

  • 由于 javascript 并没有提供元编程的能力,无法侦测 object 什么时间添加或减少属性。所以 vue 新增了 vm.set 和 vm. delete ,通过此来转换成响应式的
  • 关于数组,是通过拦截原型方法 if (Array.isArray(value)) { value.__proto__ = arrayMethods },来实现的。其中包括 push、pop、shift、unshift、splice、sort、reverse

关于视图更新的其他说明

方式

结论

【horrible】重新加载整个页面

无语…

【terrible】使用 v-if

频繁重排,组件生命周期都会触发一遍

【better】使用Vue的内置forceUpdate方法

官方 Api,即使响应数据没有更新,也会重新渲染

【best】改变组件的 key 属性

  1. v-if 示例 key 值也可以使用时间戳(每次变更时间戳)
代码语言:javascript
复制
 <my-component v-if="renderComponent" />
代码语言:javascript
复制
export default {
  data() {
    return {
      renderComponent: true,
    };
  },
  methods: {
    forceRerender() {
      // 移除 my-component DOM
      this.renderComponent = false

      this.$nextTick(() => {
        // 追加 DOM
        this.renderComponent = true
      });
    }
  }
}
  1. forceUpdate()
代码语言:javascript
复制
// 全局
import Vue from 'vue';
Vue.forceUpdate();

// Using the component instance
export default {
  methods: {
    methodThatForcesUpdate() {
      // 组件
      this.$forceUpdate()
      // ...
    }
  }
}

注意$forceUpdate 只会重新渲染视图,不会重新计算属性 – forceUpdate does not update computed fields

  1. key

错误示例: 在过滤或者删除某一person时,列表会被重新渲染(key值发生了变化)。

代码语言:javascript
复制
<li v-for="(person, index) in people" :key="index">
  {{ person.name }} - {{ index }}
</li>

正确示例:

代码语言:javascript
复制
<li v-for="(person, index) in people" :key="person.id">
  {{ person.name }} - {{ index }}
</li>

参考地址

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题描述
  • 原因分析
    • render 函数
      • 虚拟DOM
        • 响应属性
        • 关于视图更新的其他说明
        • 参考地址
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档