0
点赞
收藏
分享

微信扫一扫

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


前面汇总过 ​​「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​​

vue组件对象字面量传值的注意啦!_视图更新

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

App.vue

<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

<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

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 限制。

import {compile} from 'vue-template-compiler'
compile(`<hello-world :person="{name: 'ligang'}"></hello-world>`)
compile(`<hello-world2 :person="p"></hello-world2>`)

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

虚拟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 值也可以使用时间戳(每次变更时间戳)

<my-component v-if="renderComponent" />

export default {
data() {
return {
renderComponent: true,
};
},
methods: {
forceRerender() {
// 移除 my-component DOM
this.renderComponent = false

this.$nextTick(() => {
// 追加 DOM
this.renderComponent = true
});
}
}
}

  1. forceUpdate()

// 全局
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值发生了变化)。

<li v-for="(person, index) in people" :key="index">
{{ person.name }} - {{ index }}
</li>

正确示例:

<li v-for="(person, index) in people" :key="person.id">
{{ person.name }} - {{ index }}
</li>

参考地址

  • ​​So What Actually is Vue.set?​​
  • ​​Vue to re-render a component​​
  • ​​https://stackoverflow.com/questions/56380383/vue-js-child-component-with-object-literals-as-props-unexpectedly-updates-when-i​​


举报

相关推荐

0 条评论