热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Vue技巧】利用Proxy自动添加响应式属性

相关原理初始化Vue实例时,Vue将递归遍历data对象,通过Object.defineProperty将其中已有的属性转化为响应式的属性(gettersetter)。响应式属性的
相关原理

初始化Vue实例时,Vue将递归遍历data对象,通过Object.defineProperty将其中已有的属性转化为响应式的属性(getter/setter)。响应式属性的变化才能够被Vue观察到。
这就是为什么,Vue文档建议我们在初始化Vue实例之前,提前初始化data中所有可能用到的属性。如果想要在Vue实例创建以后添加响应式属性,需要使用Vue.set(object, key, value),而不能直接通过赋值来添加新属性(这样添加的新属性不具有响应性)。


运行时才能确定数据属性的键,这称为
动态属性。相对地,如果在
编程时就能确定属性的键,这称为
静态属性。

Vue.set的限制

注意,Vue.set的第一个参数不能是Vue实例或者Vue实例的数据对象,可以是数据对象内嵌套的对象,或者props中的对象。也就是说,不能动态添加根级响应式属性。

Vue文档: Vue does not allow dynamically adding new
root-level reactive properties to an
already created instance. However, it’s possible to add reactive properties to a
nested object using the
Vue.set(object, key, value) method.

let vm = new Vue({
data: {
nestedObj: {}
}
}); // 创建Vue实例
Vue.set(vm, 'a', 2); // not works,不能为Vue实例添加根级响应式属性
Vue.set(vm.$data, 'b', 2); // not works,不能为Vue数据对象添加根级响应式属性
Vue.set(vm.nestedObj, 'c', 2); // works,vm.nestedObj是数据对象内的一个嵌套对象
Vue.set(vm.$data.nestedObj, 'd', 2); // works,vm.$data.nestedObj是数据对象内的一个嵌套对象

Vue.set会做适当的检查并报错:set源码。

Vue.set例子
















问题背景

实际使用场景中,有时碰到这种情况:在创建Vue实例的时候,你还不确定会用到哪些属性(需要与用户进行交互之后才知道),或者有大量的属性都有可能被用到(而你不想为数据对象初始化那么多的属性)。这时候,提前初始化所有数据对象的属性就不太现实了。

解决方案

一个原始的解决方案:与用户交互的过程中,每当发现需要用到新的属性,就通过Vue.set添加响应式属性。

牢记上面讲到的
Vue.set的限制。动态添加的属性只能放在data内嵌套的对象中,或者props中的对象。实战中可以在data数据对象中专门用一个属性来存放动态属性,比如
data: { staticProp1: '', staticProp2: '', dynamicProps: {} }

在这个方法的基础上,可以扩展出一个一劳永逸的方案:使用ES6 Proxy,为data创建一个代理,拦截对data的赋值操作,如果发现这次赋值是属性添加,则使用Vue.set来动态添加响应式属性。

再进一步,我们还可以:

  1. 递归为已存在的子属性创建代理。
  2. 动态添加属性时,如果赋值的属性值是对象,那么也为这个对象创建代理。

实现如下:

import Vue from "vue";
// 已经拥有createReactiveProxy的对象拥有以下特殊属性,方便我们检测、获取reactiveProxy
const REACTIVE_PROXY = Symbol("reactiveProxy拥有的特殊标记,方便识别");
/**
* @description 拦截赋值操作,
* 如果发现这次赋值是属性添加,则使用Vue.set(object, key, value)来添加响应式属性。
*/
export function createReactiveProxy(obj) {
if (typeof obj !== "object" || obj === null) {
throw new Error(
"createReactiveProxy的参数不是object: " + JSON.stringify(obj)
);
}
if (obj[REACTIVE_PROXY]) {
// 如果传入的对象已经拥有reactiveProxy,或者它就是reactiveProxy,则直接返回已有reactiveProxy
return obj[REACTIVE_PROXY];
}
// console.log("creating reactiveProxy", obj);
const proxy = new Proxy(obj, {
set(target, property, value, receiver) {
// 如果receiver === target,表明proxy处于被赋值对象的原型链上
// https://developer.mozilla.org/en-US/docs/Web/Javascript/Reference/Global_Objects/Proxy/handler/set
// 仅仅拦截直接对proxy的赋值操作(reactiveProxy.newProperty=newValue)
if (!target.hasOwnProperty(property) && receiver === proxy) {
if (typeof value === "object" && value !== null) {
// 如果要赋的值也是对象,则也要拦截这个对象的赋值操作
value = createReactiveProxy(value);
}
// console.log("Vue.set ", target, property);
Vue.set(target, property, value);
return true;
} else {
// console.log("Reflect.set ", target, property);
return Reflect.set(...arguments);
}
}
});
// 方便以后检测、找到对象的reactiveProxy
Object.defineProperty(obj, REACTIVE_PROXY, { value: proxy });
Object.defineProperty(proxy, REACTIVE_PROXY, { value: proxy });
// 检测这个对象已有的属性,如果是对象,则也要被拦截
Object.keys(obj).forEach(key => {
if (typeof obj[key] === "object" && obj[key] !== null) {
obj[key] = createReactiveProxy(obj[key]);
}
});
return proxy;
}

createReactiveProxy例子

















关于v-model的补充
  1. Vue.set添加属性时,是通过defineProperty来添加getter和setter,并不会触发set handler,而是触发defineProperty handler。
  2. 如果v-model绑定的属性不存在对象上,那么v-model会在第一次@input事件发生时,通过Vue.set添加绑定属性,让绑定的属性拥有响应性。如上一条所说,这个过程不会触发proxy的set handler。
  3. 在后续的@input事件,v-model才会通过data.prop=$event来更新绑定,这时会触发proxy的set handler。

也就是说,v-model不仅仅是data.prop=$event这样的语法糖,它会自动添加尚不存在、但立即需要的属性(利用Vue.set)。

参考资料
  1. 深入响应式原理 – Vue文档
  2. Vue.set文档
  3. Proxy

推荐阅读
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • Skywalking系列博客1安装单机版 Skywalking的快速安装方法
    本文介绍了如何快速安装单机版的Skywalking,包括下载、环境需求和端口检查等步骤。同时提供了百度盘下载地址和查询端口是否被占用的命令。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文总结了Java中日期格式化的常用方法,并给出了示例代码。通过使用SimpleDateFormat类和jstl fmt标签库,可以实现日期的格式化和显示。在页面中添加相应的标签库引用后,可以使用不同的日期格式化样式来显示当前年份和月份。该文提供了详细的代码示例和说明。 ... [详细]
  • Python实现变声器功能(萝莉音御姐音)的方法及步骤
    本文介绍了使用Python实现变声器功能(萝莉音御姐音)的方法及步骤。首先登录百度AL开发平台,选择语音合成,创建应用并填写应用信息,获取Appid、API Key和Secret Key。然后安装pythonsdk,可以通过pip install baidu-aip或python setup.py install进行安装。最后,书写代码实现变声器功能,使用AipSpeech库进行语音合成,可以设置音量等参数。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
author-avatar
想要把迩贴上私人标签92
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有