前些日子在写Vue的时候遇到了一个问题,虽然最后修改好了。但是浅尝辄止不是我的性格。所以在工作之余,自己找寻了一下问题的原因,顺便研究了一下Vue的深度响应原理。
发现问题并产生疑问
问题描述
任务需求:创建一个用户填写信息的表单页面,用户需要输入身份信息、手机号和验证码等。
出现问题:页面已经写好了,可是在表单中使用v-model
时出现了问题。应该响应用户输入的表单却不随着输入更新数据,导致提交时的表单信息没有更新。
代码分析
在每个表单中都引用相应的v-model
值,并在用户点击提交后,存放在cookie
里,用户需要重复输入的时候读取cookie
中已有的值,可以避免用户重复输入信息。
表单代码如上,表单信息存在名为order
的数据中,用户点击提交按钮后,把order
存储在cookie
中。现在展示vue的代码部分:
意思是,从cookie
中取出order
数据,如果cookie
中没有order
数据,就创建一个空的order
,把验证码这一数据置空。路由切换的时候就执行reload()
函数。
大致思路是没有问题的,可是测试时order.verify_code
的值并不是表单中输入的值,而且也不随着表单输入的更新而更新数据。
更难以理解的bug是点击输入验证码后,点击其他表单进行输入时,刚才输入的验证码就会自动清空,有时会直接切换显示出上次输入的数据。
通过打印了一堆日志发现,order
的值只在点击提交才会存储变更,这并不是我想要的结果。而且order
的值并没有通过Vue生成对应的 getter/setter 属性。
列出疑问
- 为什么输入的数据会清空?
- 为什么
order.verify_code
的数据不是动态响应用户输入的? - 为什么刷新页面后
order.verify_code
的值是刷新之前填写的值? - 为什么Vue不自动生成属性相对应的 getter/setter ?
探索既是发现
针对问题寻找答案
我很清楚,问题肯定是出在reload()
函数的代码中,但是究竟要怎么修改呢?为了找到出现这种状况的原因,我仔细研读了Vue的官方文档。下面就针对这次的问题科普下Vue的知识:
关于深入响应式原理
,官方文档是这样写的:
如何追踪变化:把一个普通对象传给Vue实例作为它的
data
选项,Vue.js将遍历它的属性,用object.defineProperty
将它们转为 getter/setter。变化检测问题:受ES5的限制,Vue.js不能检测到对象属性的添加或删除。因为Vue.js在初始化实例时将属性转为 getter/setter,所以属性必须在
data
对象上才能让Vue.js转换它,才能让它是响应的。
例如:
|
|
看到这里,我的疑惑就已经清晰了,原来文档中写的很清楚。
并且在data
的API解释时,文档中也说明了:
Vue实例的数据对象,Vue.js会递归地将它全部属性转换为 getter/setter,从而让它能响应数据变化。这个对象必须是普通对象,原生对象 getter/setter 及原型属性会被忽略。
所以原因出在,我没有在data
中事先声明order
中的各个属性,导致Vue在创建实例时没有遍历到这些参数从而转换成相应的 getter/setter ,也就无法检测到这些属性的变化。
更改问题代码
找到了问题的根本,也理解的问题的发生原因,我需要修改我的代码。正确的代码应为是:
问题解决了,归根结底好像是我没有好好阅读官方文档的缘故,好尴尬呀~不过在这之后,我准备仔细看一遍官方文档,了解Vue深层运作的原理,分析一下源代码。
作为一个Vue初学者,差的还远呢。
解决方法不唯一
同样,在浏览官方文档的时候,也发现了针对这次遇到的问题的另一个解决办法。俗话说嘛,条条大路通罗马,我们Vue的大神也留了另一条路让我们过坑。
还是引用官方文档的描述:
有办法在实例创建之后添加属性并且让它是响应的。
对于Vue实例,可以使用$set(key, value)
实例方法:
12 vm.$set(‘b’, 2)// vm.b 和 data.b 现在是响应的
对于普通数据对象,可以使用全局方法Vue.set(object, key, value)
:
12 Vue.set(data, ‘c’, 3)// vm.c 和 data.c 现在是响应的
有时你想向已有对象上添加一些属性,例如使用Object.assigh()
或_.extend()
添加属性。但是添加到对象上的新属性不会触发更新。
这时,可以创建一个新对象,包含原对象的属性和新的属性:
12 this.someObject = Object.assign({}, this.someObject, {a:1, b:2})// 不使用 Object.assign(this.someObject, {a:1, b:2})