Vue关于data数据的深度响应原理

次访问 收藏
文章目录
  1. 1. 发现问题并产生疑问
    1. 1.1. 问题描述
    2. 1.2. 代码分析
    3. 1.3. 列出疑问
  2. 2. 探索既是发现
    1. 2.1. 针对问题寻找答案
    2. 2.2. 更改问题代码
    3. 2.3. 解决方法不唯一
  3. 3. 参考文档

前些日子在写Vue的时候遇到了一个问题,虽然最后修改好了。但是浅尝辄止不是我的性格。所以在工作之余,自己找寻了一下问题的原因,顺便研究了一下Vue的深度响应原理。

发现问题并产生疑问

问题描述

任务需求:创建一个用户填写信息的表单页面,用户需要输入身份信息、手机号和验证码等。

出现问题:页面已经写好了,可是在表单中使用v-model时出现了问题。应该响应用户输入的表单却不随着输入更新数据,导致提交时的表单信息没有更新。

代码分析

在每个表单中都引用相应的v-model值,并在用户点击提交后,存放在cookie里,用户需要重复输入的时候读取cookie中已有的值,可以避免用户重复输入信息。

1
2
3
4
5
6
7
<input type="tel" placeholder="请输入您的手机号" v-model="order.mobile" />
<input type="number" placeholder="请输入验证码" v-model="order.verify_code" />
<input type="text" placeholder="请输入您的姓名" v-model="order.real_name" />
.
.
.
<input type="text" placeholder="请输入详细地址" v-model="order.add_detail"/>

表单代码如上,表单信息存在名为order的数据中,用户点击提交按钮后,把order存储在cookie中。现在展示vue的代码部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
data(){
return {
order:{}
}
},
methods: {
reload() {
this.order = getCookie('order')
if(!this.order) this.order = {}
this.order.verify_code = ''
...
}
},
route: {
data() {
this.reload()
}
}
</script>

意思是,从cookie中取出order数据,如果cookie中没有order数据,就创建一个空的order,把验证码这一数据置空。路由切换的时候就执行reload()函数。

大致思路是没有问题的,可是测试时order.verify_code的值并不是表单中输入的值,而且也不随着表单输入的更新而更新数据。

更难以理解的bug是点击输入验证码后,点击其他表单进行输入时,刚才输入的验证码就会自动清空,有时会直接切换显示出上次输入的数据。

通过打印了一堆日志发现,order的值只在点击提交才会存储变更,这并不是我想要的结果。而且order的值并没有通过Vue生成对应的 getter/setter 属性。

列出疑问

  1. 为什么输入的数据会清空?
  2. 为什么order.verify_code的数据不是动态响应用户输入的?
  3. 为什么刷新页面后order.verify_code的值是刷新之前填写的值?
  4. 为什么Vue不自动生成属性相对应的 getter/setter ?

探索既是发现

针对问题寻找答案

我很清楚,问题肯定是出在reload()函数的代码中,但是究竟要怎么修改呢?为了找到出现这种状况的原因,我仔细研读了Vue的官方文档。下面就针对这次的问题科普下Vue的知识:

关于深入响应式原理,官方文档是这样写的:

  1. 如何追踪变化:把一个普通对象传给Vue实例作为它的data选项,Vue.js将遍历它的属性,用object.defineProperty将它们转为 getter/setter。

  2. 变化检测问题:受ES5的限制,Vue.js不能检测到对象属性的添加或删除。因为Vue.js在初始化实例时将属性转为 getter/setter,所以属性必须在data对象上才能让Vue.js转换它,才能让它是响应的。


例如:
1
2
3
4
5
6
7
8
var data = {a:1}
var vm = new Vue({
data:data
}) // vm.a 和 data.a 是响应的
vm.b = 2 // 不响应
data.b = 2 //不响应

看到这里,我的疑惑就已经清晰了,原来文档中写的很清楚。

并且在data的API解释时,文档中也说明了:

Vue实例的数据对象,Vue.js会递归地将它全部属性转换为 getter/setter,从而让它能响应数据变化。这个对象必须是普通对象,原生对象 getter/setter 及原型属性会被忽略。

所以原因出在,我没有在data中事先声明order中的各个属性,导致Vue在创建实例时没有遍历到这些参数从而转换成相应的 getter/setter ,也就无法检测到这些属性的变化。

更改问题代码

找到了问题的根本,也理解的问题的发生原因,我需要修改我的代码。正确的代码应为是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script>
data(){
return {
order: {
verify_code: '',
phone: '',
real_name: '',
...
add_detail: ''
}
}
},
methods: {
data(){
reload() {
let order_cookie = getCookie('order')
if (order_cookie) {
this.order.phone = order_cookie.phone
...
}
this.order.verify_code = ''
...
}
}
},
route: {
data() {
this.reload()
}
}
</script>

问题解决了,归根结底好像是我没有好好阅读官方文档的缘故,好尴尬呀~不过在这之后,我准备仔细看一遍官方文档,了解Vue深层运作的原理,分析一下源代码。

作为一个Vue初学者,差的还远呢。

解决方法不唯一

同样,在浏览官方文档的时候,也发现了针对这次遇到的问题的另一个解决办法。俗话说嘛,条条大路通罗马,我们Vue的大神也留了另一条路让我们过坑。

还是引用官方文档的描述:

有办法在实例创建之后添加属性并且让它是响应的
对于Vue实例,可以使用$set(key, value)实例方法:

1
2
vm.$set(‘b’, 2)
// vm.b 和 data.b 现在是响应的

对于普通数据对象,可以使用全局方法Vue.set(object, key, value)
1
2
Vue.set(data, ‘c’, 3)
// vm.c 和 data.c 现在是响应的

有时你想向已有对象上添加一些属性,例如使用Object.assigh()_.extend()添加属性。但是添加到对象上的新属性不会触发更新。
这时,可以创建一个新对象,包含原对象的属性和新的属性:
1
2
this.someObject = Object.assign({}, this.someObject, {a:1, b:2})
// 不使用 Object.assign(this.someObject, {a:1, b:2})

参考文档


分享到