前面汇总过 「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>
前提:
官方允许对象字面量的方式进行属性传递,如上述。会产生这样一个问题:组件外部响应式变量(组件内并没有使用)发生变化,也会引起组件的 updated(vue 生命周期一环),如果我们在组件内部 watch/computed 了相关传递的属性值(如上述的 author,虽然 author 使用的值没有发生变化),也会导致 watch/computed 逻辑被执行。
示例:
代码地址:https://codesandbox.io/s/code-8x6mx?file=/src/components/HelloWorld2.js:0-108
每次 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。
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 内置响应式的属性:props、data、computed、watch。
if (Array.isArray(value)) { value.__proto__ = arrayMethods }
,来实现的。其中包括 push、pop、shift、unshift、splice、sort、reverse方式 | 结论 |
---|---|
【horrible】重新加载整个页面 | 无语… |
【terrible】使用 v-if | 频繁重排,组件生命周期都会触发一遍 |
【better】使用Vue的内置forceUpdate方法 | 官方 Api,即使响应数据没有更新,也会重新渲染 |
【best】改变组件的 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
});
}
}
}
// 全局
import Vue from 'vue';
Vue.forceUpdate();
// Using the component instance
export default {
methods: {
methodThatForcesUpdate() {
// 组件
this.$forceUpdate()
// ...
}
}
}
注意:$forceUpdate
只会重新渲染视图,不会重新计算属性 – forceUpdate does not update computed fields
错误示例: 在过滤或者删除某一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>