写在开头
在业务开发中,我们经常会遇到一些不需要进行依赖收集的场景,例如:接口返回的庞大数据量、第三方库的实例对象、不需要响应化的常量等等。
下面我们来盘点一下在 Vue
中有哪些方式能避免数据被依赖收集。
在data外部定义
我们知道定义在 data
中的数据就会被 Vue
进行依赖收集,使其具有响应化的特性,那么只要我们将数据定义在 data
外部自然就不会被依赖收集了。例如:
javascript
<script>
const name = '橙某人';
export default {
data() {
return {};
}
}
</script>
当然,这是最简单的方式,但同时也会容易引起一些不必要的问题,如同一个页面内多次复用同一个组件会造成数据相互干扰。
Child.vue
文件:
javascript
<script>
const name = '橙某人';
export default {
data() {
return {};
},
methods: {
updateName(value) {
name = value;
},
showName() {
console.log(name);
}
}
}
</script>
App.vue
文件:
javascript
<template>
<div>
<child1 ref="child1" />
<child2 ref="child2" />
<button @click="handleClick1">点击1</button>
<button @click="handleClick2">点击2</button>
</div>
</template>
<script>
import Child from "./components/Child.vue";
export default {
components: { Child },
data() {
return {};
},
methods: {
handleClick1() {
this.$refs.child1.updateName("橙某人-update");
},
handleClick2() {
this.$refs.child2.showName(); // 橙某人-update
},
},
};
</script>
这也就是为什么 Vue
组件中的 data
数据必须是一个函数的原因,能起到一个互相隔绝的作用。😲
对象新属性
第二种避免数据被依赖收集是利用 Vue
不能检测对象属性的添加或删除的特性。
例如:
javascript
<template>
<div>
{{ person.name }}
<button @click="handleClick">点击</button>
</div>
</template>
<script>
export default {
data() {
return {
person: {},
};
},
methods: {
handleClick() {
this.person.name = "橙某人";
console.log(this);
},
},
};
</script>
当我们触发修改后,页面数据并不会实时响应,因为它并没有被依赖收集。通过这种方式,有时我们可以直接把第三方库的实例对象挂载在 this
身上,方便在其他方法中继续对实例对象的使用。
不止是对象,数组我们同样可以利用 Vue
不能检测通过下标添加数组项的特性。😗
_ 和 $
而第三种方式是使用特定符号,如以 $
或者 _
开头定义数据。因为 Vue2
中的属性和 API
方法都是以这些符号开头的,为了防止名称上的冲突造成异常,在 data
中定义的数据,Vue
默认是不对以这些符号开头的数据进行依赖收集的,并且如果在 template
中使用控制台将会抛出警告提示。
javascript
<script>
export default {
data() {
return {
$name: null,
};
},
methods: {
handleClick() {
this.$name = '橙某人-update';
console.log(this);
},
},
};
</script>
这种方式如果不看初始定义,本质和"对象新属性"的方式是一致的。
而如果你的初始定义不是空值,有默认值,那么初始值的访问可以通过 this.$data.xxx
或者 this._data.xxx
。
源码中的位置:vue/src/core/instance/state.ts
文件中的 initData
方法。
__ob__
和 __v_skip
而接下来两种方式来自 Vue
源码中的判断,它们比较适合对象形式的数据。
源码中的位置:vue/src/core/observer/index.ts
文件中的 observe
方法。
其一,将需要避免被依赖收集的数据对象,添加一个 __ob__
的属性,并设置它的值为一个 Observer
实例。
由于 Observer
对象并不是 Vue
的公开 API
,所以通常在业务中是不能直接访问的,为此小编也不推荐使用这种方式。但一定要玩玩看,也不是没有办法😗,例如:
Child.vue
文件:
javascript
<template>
<button @click="show">查看</button>
</template>
<script>
export default {
props: ["Observer"],
data() {
return {
person: {
age: 18,
__ob__: this.Observer,
},
};
},
methods: {
show() {
console.log(this.person);
},
}
}
</script>
App.vue
文件:
javascript
<template>
<div>
<child :Observer="person.__ob__" />
<button @click="show">展示</button>
</div>
</template>
<script>
import Child from "./components/Child.vue";
export default {
components: { Child },
data() {
return {
person: {
name: "橙某人",
},
};
},
methods: {
show() {
console.log(this.person);
},
},
};
</script>
你会发现子组件的 age
属性并没有被依赖收集,我们是通过父组件的响应函数据得到 Observer
对象,并传给了子组件使用,阻止了数据的响应化。
其二,将需要避免被依赖收集的数据对象,添加一个 __v_skip
的属性,并设置它的值为 true
。
这种方式就很简单了,推荐使用。
javascript
<script>
export default {
data() {
return {
person: {
name: "橙某人",
__v_skip: true,
},
};
},
};
</script>
Object.freeze
在 Vue
文档 中介绍数据绑定和响应时,特意标注了对于经过 Object.freeze() 方法的对象无法进行更新响应。
Object.freeze
是ES5新增的方法,可以冻结一个对象,防止对象被修改。
javascript
<template>
<button @click="handleClick">点击</button>
</template>
<script>
export default {
data() {
return {
person: null,
};
},
methods: {
handleClick() {
this.person = Object.freeze({ name: "橙某人" });
console.log(this.person);
},
},
};
</script>
这也是一个不错的方式,但是可惜就是被冻结的对象是不能被修改了。😤
class的私有属性(#)
最后一种方式,我们可以利用 class
的私有属性来完成。
javascript
<template>
<button @click="handleClick">点击</button>
</template>
<script>
export class PersonProxy {
#data;
constructor(data) {
this.#data = data;
}
get data() {
return this.#data;
}
}
export default {
data() {
return {
person: null,
};
},
methods: {
handleClick() {
this.person = new PersonProxy({ name: "橙某人" });
console.log(this.person);
console.log(this.person.data);
},
},
};
</script>
这种方式对于避免数据被依赖收集,而且还需要修改其中的数据是非常有效的,也比较推荐使用这种方式。
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。