前面汇总过 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
![ce76ab5c27ff54181f74e20b8f681409.png](https://i-blog.csdnimg.cn/blog_migrate/ec4ca6d67dcdb6b9c756ee97edc7cd90.png)
每次 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>`)
![2cbc9495289e668328bcbb2759a0fe0f.png](https://i-blog.csdnimg.cn/blog_migrate/3575b106023fc00f6b93f91b8c5f503b.jpeg)
虚拟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
关于视图更新的其他说明
![f87f620e963b7b12eab50c698d47b20d.png](https://i-blog.csdnimg.cn/blog_migrate/de5c7f948157deef18becfe5304561a0.jpeg)
- 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
});
}
}
}
2. forceUpdate()
// 全局
import Vue from 'vue';
Vue.forceUpdate();
// Using the component instance
export default {
methods: {
methodThatForcesUpdate() {
// 组件
this.$forceUpdate()
// ...
}
}
}
注意:$forceUpdate
只会重新渲染视图,不会重新计算属性 -- forceUpdate does not update computed fields
3. 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
欢迎关注 「 Super 前端 」微信公众号
![2418379cd51c735c39f1de295b0efca7.png](https://i-blog.csdnimg.cn/blog_migrate/e1b1c5093a1799429cbb7d1f0726e185.png)
版权声明: 本文原创自我的博客:李刚的学习专栏