Skip to content

Commit 70210b3

Browse files
sdrasyyx990803NataliaTepluhinabencodezen
authored
Rough Draft of New Reactivity Section (#62)
* add a bit about scoping down PRs into our writing guide * add reactivity into sidebar * add in first reactivity section * start to get in the next section * add in older section * add in codepen examples * embed the video * Fix typo, 'cell' vs 'cel' Co-Authored-By: Evan You <yyx990803@gmail.com> * Add space Co-Authored-By: Evan You <yyx990803@gmail.com> * Move proxies up a paragragh Co-Authored-By: Natalia Tepluhina <tarya.se@gmail.com> * Fix quotations Co-Authored-By: Natalia Tepluhina <tarya.se@gmail.com> * small tone updates and change detection page * move virtual dom to a new dedicated page * add new pages to the sidebar * add in more information and original vs proxied caveats * small copy adjustments: strikethrough and array method formatting * Add space Co-Authored-By: Ben Hong <ben@bencodezen.io> * Add space Co-Authored-By: Ben Hong <ben@bencodezen.io> * Add space Co-Authored-By: Ben Hong <ben@bencodezen.io> Co-authored-by: Evan You <yyx990803@gmail.com> Co-authored-by: Natalia Tepluhina <tarya.se@gmail.com> Co-authored-by: Ben Hong <ben@bencodezen.io>
1 parent 5ab628e commit 70210b3

File tree

6 files changed

+444
-24
lines changed

6 files changed

+444
-24
lines changed

src/.vuepress/config.js

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ const sidebar = {
1414
'/guide/list',
1515
'/guide/events',
1616
'/guide/forms',
17-
'/guide/component-basics',
18-
],
17+
'/guide/component-basics'
18+
]
1919
},
2020
{
2121
title: 'Components In-Depth',
@@ -25,9 +25,18 @@ const sidebar = {
2525
'/guide/component-props',
2626
'/guide/component-custom-events',
2727
'/guide/component-slots',
28-
'/guide/component-provide-inject',
28+
'/guide/component-provide-inject'
29+
]
30+
},
31+
{
32+
title: 'Internals',
33+
collapsable: false,
34+
children: [
35+
'/guide/reactivity',
2936
'/guide/component-dynamic-async',
30-
],
37+
'/guide/optimizations',
38+
'/guide/change-detection'
39+
]
3140
},
3241
{
3342
title: 'Reusability & Composition',
@@ -37,14 +46,14 @@ const sidebar = {
3746
{
3847
title: 'Migration to Vue 3',
3948
collapsable: true,
40-
children: ['migration'],
49+
children: ['migration']
4150
},
4251
{
4352
title: 'Contribute to the Docs',
4453
collapsable: true,
45-
children: ['writing-guide'],
46-
},
47-
],
54+
children: ['writing-guide']
55+
}
56+
]
4857
}
4958

5059
module.exports = {
@@ -80,17 +89,17 @@ module.exports = {
8089
items: [
8190
{ text: 'Guide', link: '/guide/introduction' },
8291
{ text: 'Style Guide', link: '/style-guide/' },
83-
{ text: 'Tooling', link: '/tooling/' },
84-
],
92+
{ text: 'Tooling', link: '/tooling/' }
93+
]
8594
},
8695
{ text: 'API Reference', link: '/api/' },
8796
{
8897
text: 'Examples',
8998
ariaLabel: 'Examples Menu',
9099
items: [
91100
{ text: 'Examples', link: '/examples/' },
92-
{ text: 'Cookbook', link: '/cookbook/' },
93-
],
101+
{ text: 'Cookbook', link: '/cookbook/' }
102+
]
94103
},
95104
{
96105
text: 'Community',
@@ -99,34 +108,34 @@ module.exports = {
99108
{ text: 'Team', link: '/community/team/' },
100109
{ text: 'Partners', link: '/community/partners/' },
101110
{ text: 'Join', link: '/community/join/' },
102-
{ text: 'Themes', link: '/community/themes/' },
103-
],
104-
},
111+
{ text: 'Themes', link: '/community/themes/' }
112+
]
113+
}
105114
],
106115
sidebarDepth: 2,
107116
sidebar: {
108117
'/guide/': sidebar.guide,
109-
'/community/': sidebar.guide,
118+
'/community/': sidebar.guide
110119
},
111-
smoothScroll: false,
120+
smoothScroll: false
112121
},
113122
plugins: {
114123
'@vuepress/pwa': {
115124
serviceWorker: true,
116125
updatePopup: {
117126
'/': {
118127
message: 'New content is available.',
119-
buttonText: 'Refresh',
120-
},
121-
},
122-
},
128+
buttonText: 'Refresh'
129+
}
130+
}
131+
}
123132
},
124133
markdown: {
125134
/** @param {import('markdown-it')} md */
126-
extendMarkdown: (md) => {
135+
extendMarkdown: md => {
127136
md.options.highlight = require('./markdown/highlight')(
128137
md.options.highlight
129138
)
130-
},
131-
},
139+
}
140+
}
132141
}
Binary file not shown.

src/guide/change-detection.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Change Detection Caveats in Vue 2
2+
3+
> This page applies only to Vue 2.x and below, and assumes you've already read the [Reactivity Section](reactivity.md). Please read that section first.
4+
5+
Due to limitations in JavaScript, there are types of changes that Vue **cannot detect**. However, there are ways to circumvent them to preserve reactivity.
6+
7+
### For Objects
8+
9+
Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the `data` object in order for Vue to convert it and make it reactive. For example:
10+
11+
```js
12+
var vm = new Vue({
13+
data: {
14+
a: 1
15+
}
16+
})
17+
// `vm.a` is now reactive
18+
19+
vm.b = 2
20+
// `vm.b` is NOT reactive
21+
```
22+
23+
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, propertyName, value)` method:
24+
25+
```js
26+
Vue.set(vm.someObject, 'b', 2)
27+
```
28+
29+
You can also use the `vm.$set` instance method, which is an alias to the global `Vue.set`:
30+
31+
```js
32+
this.$set(this.someObject, 'b', 2)
33+
```
34+
35+
Sometimes you may want to assign a number of properties to an existing object, for example using `Object.assign()` or `_.extend()`. However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object:
36+
37+
```js
38+
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
39+
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
40+
```
41+
42+
### For Arrays
43+
44+
Vue cannot detect the following changes to an array:
45+
46+
1. When you directly set an item with the index, e.g. `vm.items[indexOfItem] = newValue`
47+
2. When you modify the length of the array, e.g. `vm.items.length = newLength`
48+
49+
For example:
50+
51+
```js
52+
var vm = new Vue({
53+
data: {
54+
items: ['a', 'b', 'c']
55+
}
56+
})
57+
vm.items[1] = 'x' // is NOT reactive
58+
vm.items.length = 2 // is NOT reactive
59+
```
60+
61+
To overcome caveat 1, both of the following will accomplish the same as `vm.items[indexOfItem] = newValue`, but will also trigger state updates in the reactivity system:
62+
63+
```js
64+
// Vue.set
65+
Vue.set(vm.items, indexOfItem, newValue)
66+
```
67+
68+
```js
69+
// Array.prototype.splice
70+
vm.items.splice(indexOfItem, 1, newValue)
71+
```
72+
73+
You can also use the [`vm.$set`](https://vuejs.org/v2/api/#vm-set) instance method, which is an alias for the global `Vue.set`:
74+
75+
```js
76+
vm.$set(vm.items, indexOfItem, newValue)
77+
```
78+
79+
To deal with caveat 2, you can use `splice`:
80+
81+
```js
82+
vm.items.splice(newLength)
83+
```
84+
85+
## Declaring Reactive Properties
86+
87+
Since Vue doesn't allow dynamically adding root-level reactive properties, you have to initialize Vue instances by declaring all root-level reactive data properties upfront, even with an empty value:
88+
89+
```js
90+
var vm = new Vue({
91+
data: {
92+
// declare message with an empty value
93+
message: ''
94+
},
95+
template: '<div>{{ message }}</div>'
96+
})
97+
// set `message` later
98+
vm.message = 'Hello!'
99+
```
100+
101+
If you don't declare `message` in the data option, Vue will warn you that the render function is trying to access a property that doesn't exist.
102+
103+
There are technical reasons behind this restriction - it eliminates a class of edge cases in the dependency tracking system, and also makes Vue instances play nicer with type checking systems. But there is also an important consideration in terms of code maintainability: the `data` object is like the schema for your component's state. Declaring all reactive properties upfront makes the component code easier to understand when revisited later or read by another developer.
104+
105+
## Async Update Queue
106+
107+
In case you haven't noticed yet, 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. This buffered de-duplication is important in avoiding unnecessary calculations and DOM manipulations. Then, in the next event loop "tick", Vue flushes the queue and performs the actual (already de-duped) work. Internally Vue tries native `Promise.then`, `MutationObserver`, and `setImmediate` for the asynchronous queuing and falls back to `setTimeout(fn, 0)`.
108+
109+
For example, when you set `vm.someData = 'new value'`, the component will not re-render 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:
110+
111+
```html
112+
<div id="example">{{ message }}</div>
113+
```
114+
115+
```js
116+
var vm = new Vue({
117+
el: '#example',
118+
data: {
119+
message: '123'
120+
}
121+
})
122+
vm.message = 'new message' // change data
123+
vm.$el.textContent === 'new message' // false
124+
Vue.nextTick(function() {
125+
vm.$el.textContent === 'new message' // true
126+
})
127+
```
128+
129+
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:
130+
131+
```js
132+
Vue.component('example', {
133+
template: '<span>{{ message }}</span>',
134+
data: function() {
135+
return {
136+
message: 'not updated'
137+
}
138+
},
139+
methods: {
140+
updateMessage: function() {
141+
this.message = 'updated'
142+
console.log(this.$el.textContent) // => 'not updated'
143+
this.$nextTick(function() {
144+
console.log(this.$el.textContent) // => 'updated'
145+
})
146+
}
147+
}
148+
})
149+
```
150+
151+
Since `$nextTick()` returns a promise, you can achieve the same as the above using the new [ES2017 async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) syntax:
152+
153+
```js
154+
methods: {
155+
updateMessage: async function () {
156+
this.message = 'updated'
157+
console.log(this.$el.textContent) // => 'not updated'
158+
await this.$nextTick()
159+
console.log(this.$el.textContent) // => 'updated'
160+
}
161+
}
162+
```

src/guide/optimizations.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Rendering Mechanisms and Optimizations
2+
3+
> This page is not required reading in order to learn how to use Vue well, but it provides more information, should you be curious how rendering works under the hood.
4+
5+
## Virtual DOM
6+
7+
Now that we know how watchers are updating the components, you might ask how those changes eventually make it to the DOM! Perhaps you’ve heard of the Virtual DOM before, many frameworks including Vue use this paradigm to make sure our interfaces reflect the changes we’re updating in JavaScript effectively
8+
9+
<iframe height="500" style="width: 100%;" scrolling="no" title="How does the Virtual DOM work?" src="https://codepen.io/sdras/embed/RwwQapa?height=500&theme-id=light&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true">
10+
See the Pen <a href='https://codepen.io/sdras/pen/RwwQapa'>How does the Virtual DOM work?</a> by Sarah Drasner
11+
(<a href='https://codepen.io/sdras'>@sdras</a>) on <a href='https://codepen.io'>CodePen</a>.
12+
</iframe>
13+
14+
We make a copy of the DOM in JavaScript called the Virtual DOM, we do this because touching the DOM with JavaScript is computationally expensive. While performing updates in JavaScript is cheap, finding the required DOM nodes and updating them with JS is expensive. So we batch calls, and change the DOM all at once.
15+
16+
The Virtual DOM in is a lightweight JavaScript object, created by a render function. It takes three arguments: the element, an object with data, props, attrs and more, and an array. The array is where we pass in the children, which have all these arguments too, and then they can have children and so on, until we build a full tree of elements.
17+
18+
If we need to update the list items, we do so in JavaScript, using the reactivity we mentioned earlier. We then make all the changes to the JavaScript copy, the virtual DOM, and perform a diff between this and the actual DOM. Only then do we make our updates to just what has changed. The Virtual DOM allows us to make performant updates to our UIs!

0 commit comments

Comments
 (0)