Skip to content

Rough Draft of New Reactivity Section #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 33 additions & 24 deletions src/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const sidebar = {
'/guide/list',
'/guide/events',
'/guide/forms',
'/guide/component-basics',
],
'/guide/component-basics'
]
},
{
title: 'Components In-Depth',
Expand All @@ -25,9 +25,18 @@ const sidebar = {
'/guide/component-props',
'/guide/component-custom-events',
'/guide/component-slots',
'/guide/component-provide-inject',
'/guide/component-provide-inject'
]
},
{
title: 'Internals',
collapsable: false,
children: [
'/guide/reactivity',
'/guide/component-dynamic-async',
],
'/guide/optimizations',
'/guide/change-detection'
]
},
{
title: 'Reusability & Composition',
Expand All @@ -37,14 +46,14 @@ const sidebar = {
{
title: 'Migration to Vue 3',
collapsable: true,
children: ['migration'],
children: ['migration']
},
{
title: 'Contribute to the Docs',
collapsable: true,
children: ['writing-guide'],
},
],
children: ['writing-guide']
}
]
}

module.exports = {
Expand Down Expand Up @@ -80,17 +89,17 @@ module.exports = {
items: [
{ text: 'Guide', link: '/guide/introduction' },
{ text: 'Style Guide', link: '/style-guide/' },
{ text: 'Tooling', link: '/tooling/' },
],
{ text: 'Tooling', link: '/tooling/' }
]
},
{ text: 'API Reference', link: '/api/' },
{
text: 'Examples',
ariaLabel: 'Examples Menu',
items: [
{ text: 'Examples', link: '/examples/' },
{ text: 'Cookbook', link: '/cookbook/' },
],
{ text: 'Cookbook', link: '/cookbook/' }
]
},
{
text: 'Community',
Expand All @@ -99,34 +108,34 @@ module.exports = {
{ text: 'Team', link: '/community/team/' },
{ text: 'Partners', link: '/community/partners/' },
{ text: 'Join', link: '/community/join/' },
{ text: 'Themes', link: '/community/themes/' },
],
},
{ text: 'Themes', link: '/community/themes/' }
]
}
],
sidebarDepth: 2,
sidebar: {
'/guide/': sidebar.guide,
'/community/': sidebar.guide,
'/community/': sidebar.guide
},
smoothScroll: false,
smoothScroll: false
},
plugins: {
'@vuepress/pwa': {
serviceWorker: true,
updatePopup: {
'/': {
message: 'New content is available.',
buttonText: 'Refresh',
},
},
},
buttonText: 'Refresh'
}
}
}
},
markdown: {
/** @param {import('markdown-it')} md */
extendMarkdown: (md) => {
extendMarkdown: md => {
md.options.highlight = require('./markdown/highlight')(
md.options.highlight
)
},
},
}
}
}
Binary file not shown.
162 changes: 162 additions & 0 deletions src/guide/change-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Change Detection Caveats in Vue 2

> 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.

Due to limitations in JavaScript, there are types of changes that Vue **cannot detect**. However, there are ways to circumvent them to preserve reactivity.

### For Objects

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:

```js
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive

vm.b = 2
// `vm.b` is NOT reactive
```

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:

```js
Vue.set(vm.someObject, 'b', 2)
```

You can also use the `vm.$set` instance method, which is an alias to the global `Vue.set`:

```js
this.$set(this.someObject, 'b', 2)
```

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:

```js
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
```

### For Arrays

Vue cannot detect the following changes to an array:

1. When you directly set an item with the index, e.g. `vm.items[indexOfItem] = newValue`
2. When you modify the length of the array, e.g. `vm.items.length = newLength`

For example:

```js
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // is NOT reactive
vm.items.length = 2 // is NOT reactive
```

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:

```js
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
```

```js
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
```

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`:

```js
vm.$set(vm.items, indexOfItem, newValue)
```

To deal with caveat 2, you can use `splice`:

```js
vm.items.splice(newLength)
```

## Declaring Reactive Properties

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:

```js
var vm = new Vue({
data: {
// declare message with an empty value
message: ''
},
template: '<div>{{ message }}</div>'
})
// set `message` later
vm.message = 'Hello!'
```

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.

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.

## Async Update Queue

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)`.

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:

```html
<div id="example">{{ message }}</div>
```

```js
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // change data
vm.$el.textContent === 'new message' // false
Vue.nextTick(function() {
vm.$el.textContent === 'new message' // true
})
```

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:

```js
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function() {
return {
message: 'not updated'
}
},
methods: {
updateMessage: function() {
this.message = 'updated'
console.log(this.$el.textContent) // => 'not updated'
this.$nextTick(function() {
console.log(this.$el.textContent) // => 'updated'
})
}
}
})
```

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:

```js
methods: {
updateMessage: async function () {
this.message = 'updated'
console.log(this.$el.textContent) // => 'not updated'
await this.$nextTick()
console.log(this.$el.textContent) // => 'updated'
}
}
```
18 changes: 18 additions & 0 deletions src/guide/optimizations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Rendering Mechanisms and Optimizations

> 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.

## Virtual DOM

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

<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">
See the Pen <a href='https://codepen.io/sdras/pen/RwwQapa'>How does the Virtual DOM work?</a> by Sarah Drasner
(<a href='https://codepen.io/sdras'>@sdras</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>

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.

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.

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!
Loading