Skip to content

Commit 3001454

Browse files
committed
add: reactivity
1 parent 172deed commit 3001454

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed

src/guide/reactivity.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,178 @@ type: guide
44
order: 15
55
---
66

7+
大部分的基础内容我们已经讲到了,现在讲点底层内容。Vue.js 最显著的一个功能是响应系统 —— 模型只是普通对象,修改它则更新视图。这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避免一些常见问题。下面我们开始深挖 Vue.js 响应系统的底层细节。
8+
9+
## 如何追踪变化
10+
11+
把一个普通对象传给 Vue 实例作为它的 `data` 选项,Vue.js 将遍历它的属性,用 [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 将它们转为 `getter/setter`。这是 ES5 特性,不能打补丁实现,这便是为什么 Vue.js 不支持 IE8 及更低版本。
12+
13+
用户看不到 getter/setters,但是在内部它们让 Vue.js 追踪依赖,在属性被访问和修改时通知变化。一个问题是在浏览器控制台打印数据对象时 `getter/setter` 的格式化不同,使用 `vm.$log()` 实例方法可以得到更友好的输出。
14+
15+
模板中每个指令/数据绑定都有一个对应的 **watcher** 对象,在计算过程中它把属性记录为依赖。之后当依赖的 `setter` 被调用时,会触发 **watcher** 重新计算 ,也就会导致它的关联指令更新 DOM。
16+
17+
> !!TODO: 因为vdom层的引入,这里可能会更新。
18+
19+
![data](/images/data.png)
20+
21+
## 变化检测问题
22+
23+
受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 `getter/setter`,所以属性必须在 `data` 对象上才能让 Vue.js 转换它,才能让它是响应的。例如:
24+
25+
``` js
26+
var data = { a: 1 }
27+
var vm = new Vue({
28+
data: data
29+
})
30+
// `vm.a` and `data.a` 现在是响应的
31+
32+
vm.b = 2
33+
// `vm.b` 不是响应的
34+
35+
data.b = 2
36+
// `data.b` 不是响应的
37+
```
38+
39+
不过,有办法在实例创建之后**添加属性并且让它是响应的**
40+
41+
对于 Vue 实例,可以使用 `Vue.$set(key, value)` 实例方法:
42+
43+
``` js
44+
Vue.set(data, 'c', 3)
45+
// `vm.c` and `data.c` 现在是响应的
46+
```
47+
48+
有时你想向已有对象上添加一些属性,例如使用 `Object.assign()``_.extend()`添加属性。但是,添加到对象上的新属性不会触发更新。这时可以创建一个新的对象,包含原对象的属性和新的属性:
49+
50+
``` js
51+
// 不使用 `Object.assign(this.someObject, { a: 1, b: 2 })`
52+
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
53+
```
54+
55+
也有一些数组相关的问题,之前已经在[列表渲染](/guide/list.html#Caveats)中讲过。
56+
57+
## 初始化数据
58+
59+
尽管 Vue.js 提供了 API 动态地添加响应属性,还是推荐在 `data` 对象上声明所有的响应属性。
60+
61+
不这么做:
62+
63+
不这么做:
64+
65+
``` js
66+
var vm = new Vue({
67+
template: '<div>{{ message }}</div>'
68+
})
69+
// 之后设置 `message`
70+
Vue.set(vm.$data, 'message', 'Hello!')
71+
```
72+
73+
这么做:
74+
75+
``` js
76+
var vm = new Vue({
77+
data: {
78+
// 声明 message 为一个空值字符串
79+
message: ''
80+
},
81+
template: '<div>{{ message }}</div>'
82+
})
83+
// 之后设置 `message`
84+
vm.message = 'Hello!'
85+
```
86+
87+
这么做有两个原因:
88+
89+
1. `data` 对象就像组件状态的模式(schema)。在它上面声明所有的属性让组件代码更易于理解。
90+
91+
2.添加一个顶级响应属性会强制所有的 watcher 重新计算,因为它之前不存在,没有 watcher 追踪它。这么做性能通常是可以接受的(特别是对比 Angular 的脏检查),但是可以在初始化时避免
92+
93+
## 异步更新队列
94+
95+
> !!TODO: 我认为这里很大部分已经失效,需要尤小右来重写
96+
97+
By default, Vue performs DOM updates **asynchronously**. Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop. If the same watcher is triggered multiple times, it will be pushed into the queue only once. Then, in the next event loop "tick", Vue flushes the queue and performs only the necessary DOM updates. Internally Vue uses `MutationObserver` if available for the asynchronous queuing and falls back to `setTimeout(fn, 0)`.
98+
99+
For example, when you set `vm.someData = 'new value'`, the DOM will not update immediately. It will update in the next "tick", when the queue is flushed. Most of the time we don't need to care about this, but it can be tricky when you want to do something that depends on the post-update DOM state. Although Vue.js generally encourages developers to think in a "data-driven" fashion and avoid touching the DOM directly, sometimes it might be necessary to get your hands dirty. In order to wait until Vue.js has finished updating the DOM after a data change, you can use `Vue.nextTick(callback)` immediately after the data is changed. The callback will be called after the DOM has been updated. For example:
100+
101+
``` html
102+
<div id="example">{{ message }}</div>
103+
```
104+
105+
``` js
106+
var vm = new Vue({
107+
el: '#example',
108+
data: {
109+
message: '123'
110+
}
111+
})
112+
vm.message = 'new message' // change data
113+
vm.$el.textContent === 'new message' // false
114+
Vue.nextTick(function () {
115+
vm.$el.textContent === 'new message' // true
116+
})
117+
```
118+
119+
There is also the `vm.$nextTick()` instance method, which is especially handy inside components, because it doesn't need global `Vue` and its callback's `this` context will be automatically bound to the current Vue instance:
120+
121+
``` js
122+
Vue.component('example', {
123+
template: '<span>{{ message }}</span>',
124+
data: function () {
125+
return {
126+
message: 'not updated'
127+
}
128+
},
129+
methods: {
130+
updateMessage: function () {
131+
this.message = 'updated'
132+
console.log(this.$el.textContent) // => 'not updated'
133+
this.$nextTick(function () {
134+
console.log(this.$el.textContent) // => 'updated'
135+
})
136+
}
137+
}
138+
})
139+
```
140+
141+
## Inside Computed Properties
142+
143+
It should be noted that Vue's computed properties are **not** simple getters. Each computed property keeps track of its own reactive dependencies. When a computed property is evaluated, Vue updates its dependency list and caches the returned value. The cached value is only invalidated when one of the tracked dependencies have changed. Therefore, as long as the dependencies did not change, accessing the computed property will directly return the cached value instead of calling the getter.
144+
145+
Why do we need caching? Imagine we have an expensive computed property **A**, which requires looping through a huge Array and doing a lot of computations. Then we may have other computed properties that in turn depend on **A**. Without caching, we would be executing **A**’s getter many more times than necessary!
146+
147+
Because of computed property caching, the getter function is not always called when you access a computed property. Consider the following example:
148+
149+
``` js
150+
var vm = new Vue({
151+
data: {
152+
message: 'hi'
153+
},
154+
computed: {
155+
example: function () {
156+
return Date.now() + this.message
157+
}
158+
}
159+
})
160+
```
161+
162+
The computed property `example` has only one dependency: `vm.message`. `Date.now()` is **not** a reactive dependency, because it has nothing to do with Vue's data observation system. Therefore, when you programmatically access `vm.example`, you will find the timestamp remains the same unless `vm.message` triggers a re-evaluation.
163+
164+
In some use cases, you may want to preserve the simple getter-like behavior, where every time you access `vm.example` it simply calls the getter again. You can do that by turning off caching for a specific computed property:
165+
166+
``` js
167+
computed: {
168+
example: {
169+
cache: false,
170+
get: function () {
171+
return Date.now() + this.message
172+
}
173+
}
174+
}
175+
```
176+
177+
Now, every time you access `vm.example`, the timestamp will be up-to-date. **However, note this only affects programmatic access inside JavaScript; data-bindings are still dependency-driven.** When you bind to a computed property in the template as `{% raw %}{{ example }}{% endraw %}`, the DOM will only be updated when a reactive dependency has changed.
178+
7179
We've covered most of the basics - now it's time to take a deep dive! One of Vue's most distinct features is the unobtrusive reactivity system. Models are just plain JavaScript objects. When you modify them, the view updates. It makes state management very simple and intuitive, but it's also important to understand how it works to avoid some common gotchas. In this section, we are going to dig into some of the lower-level details of Vue's reactivity system.
8180

9181
## How Changes Are Tracked

0 commit comments

Comments
 (0)