+
+
+
diff --git a/src/.vuepress/config.js b/src/.vuepress/config.js
index e6feb5592e..78f610d547 100644
--- a/src/.vuepress/config.js
+++ b/src/.vuepress/config.js
@@ -25,7 +25,14 @@ module.exports = {
],
sidebarDepth: 2,
sidebar: {
- '/guide/': ['installation', 'introduction', 'instance', 'template-syntax']
+ '/guide/': [
+ 'installation',
+ 'introduction',
+ 'instance',
+ 'template-syntax',
+ 'computed',
+ 'class-and-style'
+ ]
}
},
plugins: {
@@ -39,4 +46,4 @@ module.exports = {
}
}
}
-};
+}
diff --git a/src/guide/class-and-style.md b/src/guide/class-and-style.md
new file mode 100644
index 0000000000..e5ea4952d5
--- /dev/null
+++ b/src/guide/class-and-style.md
@@ -0,0 +1,223 @@
+# Class and Style Bindings
+
+A common need for data binding is manipulating an element's class list and its inline styles. Since they are both attributes, we can use `v-bind` to handle them: we only need to calculate a final string with our expressions. However, meddling with string concatenation is annoying and error-prone. For this reason, Vue provides special enhancements when `v-bind` is used with `class` and `style`. In addition to strings, the expressions can also evaluate to objects or arrays.
+
+## Binding HTML Classes
+
+[Watch a free video lesson on Vue School](https://vueschool.io/lessons/vuejs-dynamic-classes?friend=vuejs)
+
+### Object Syntax
+
+We can pass an object to `v-bind:class` to dynamically toggle classes:
+
+```html
+
+```
+
+The above syntax means the presence of the `active` class will be determined by the [truthiness](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) of the data property `isActive`.
+
+You can have multiple classes toggled by having more fields in the object. In addition, the `v-bind:class` directive can also co-exist with the plain `class` attribute. So given the following template:
+
+```html
+
+```
+
+And the following data:
+
+```js
+data: {
+ isActive: true,
+ hasError: false
+}
+```
+
+It will render:
+
+```html
+
+```
+
+When `isActive` or `hasError` changes, the class list will be updated accordingly. For example, if `hasError` becomes `true`, the class list will become `"static active text-danger"`.
+
+The bound object doesn't have to be inline:
+
+```html
+
+```
+
+```js
+data: {
+ classObject: {
+ active: true,
+ 'text-danger': false
+ }
+}
+```
+
+This will render the same result. We can also bind to a [computed property](computed.md) that returns an object. This is a common and powerful pattern:
+
+```html
+
+```
+
+```js
+data: {
+ isActive: true,
+ error: null
+},
+computed: {
+ classObject() {
+ return {
+ active: this.isActive && !this.error,
+ 'text-danger': this.error && this.error.type === 'fatal'
+ }
+ }
+}
+```
+
+### Array Syntax
+
+We can pass an array to `v-bind:class` to apply a list of classes:
+
+```html
+
+```
+
+```js
+data: {
+ activeClass: 'active',
+ errorClass: 'text-danger'
+}
+```
+
+Which will render:
+
+```html
+
+```
+
+If you would like to also toggle a class in the list conditionally, you can do it with a ternary expression:
+
+```html
+
+```
+
+This will always apply `errorClass`, but will only apply `activeClass` when `isActive` is truthy.
+
+However, this can be a bit verbose if you have multiple conditional classes. That's why it's also possible to use the object syntax inside array syntax:
+
+```html
+
+```
+
+### With Components
+
+> This section assumes knowledge of [Vue Components](TODO:components.html). Feel free to skip it and come back later.
+
+When you use the `class` attribute on a custom component, those classes will be added to the component's root element. Existing classes on this element will not be overwritten.
+
+For example, if you declare this component:
+
+```js
+const MyComponent = {
+ template: '
Hi!
'
+}
+```
+
+Then add some classes when using it:
+
+```html
+
+```
+
+> TODO: needs a check after https://github.com/vuejs/rfcs/blob/attr-fallthrough/active-rfcs/0000-attr-fallthrough.md is merged
+
+The same is true for class bindings:
+
+```html
+
+```
+
+When `isActive` is truthy, the rendered HTML will be:
+
+```html
+
Hi
+```
+
+## Binding Inline Styles
+
+### Object Syntax
+
+The object syntax for `v-bind:style` is pretty straightforward - it looks almost like CSS, except it's a JavaScript object. You can use either camelCase or kebab-case (use quotes with kebab-case) for the CSS property names:
+
+```html
+
+```
+
+```js
+data: {
+ activeColor: 'red',
+ fontSize: 30
+}
+```
+
+It is often a good idea to bind to a style object directly so that the template is cleaner:
+
+```html
+
+```
+
+```js
+data: {
+ styleObject: {
+ color: 'red',
+ fontSize: '13px'
+ }
+}
+```
+
+Again, the object syntax is often used in conjunction with computed properties that return objects.
+
+### Array Syntax
+
+The array syntax for `v-bind:style` allows you to apply multiple style objects to the same element:
+
+```html
+
+```
+
+### Auto-prefixing
+
+When you use a CSS property that requires [vendor prefixes](https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix) in `v-bind:style`, for example `transform`, Vue will automatically detect and add appropriate prefixes to the applied styles.
+
+### Multiple Values
+
+You can provide an array of multiple (prefixed) values to a style property, for example:
+
+```html
+
+```
+
+This will only render the last value in the array which the browser supports. In this example, it will render `display: flex` for browsers that support the unprefixed version of flexbox.
diff --git a/src/guide/computed.md b/src/guide/computed.md
new file mode 100644
index 0000000000..0c6beba719
--- /dev/null
+++ b/src/guide/computed.md
@@ -0,0 +1,243 @@
+# Computed Properties and Watchers
+
+## Computed Properties
+
+[Learn how computed properties work with a free lesson on Vue School](https://vueschool.io/lessons/vuejs-computed-properties?friend=vuejs)
+
+In-template expressions are very convenient, but they are meant for simple operations. Putting too much logic in your templates can make them bloated and hard to maintain. For example:
+
+```html
+
+ {{ message.split('').reverse().join('') }}
+
+```
+
+At this point, the template is no longer simple and declarative. You have to look at it for a second before realizing that it displays `message` in reverse. The problem is made worse when you want to include the reversed message in your template more than once.
+
+That's why for any complex logic, you should use a **computed property**.
+
+### Basic Example
+
+```html
+
+```
+
+```js
+const vm = Vue.createApp().mount(
+ {
+ data() {
+ return {
+ message: 'Hello'
+ }
+ },
+ computed: {
+ // a computed getter
+ reversedMessage() {
+ // `this` points to the vm instance
+ return this.message
+ .split('')
+ .reverse()
+ .join('')
+ }
+ }
+ },
+ '#example'
+)
+```
+
+Result:
+
+
+
+Here we have declared a computed property `reversedMessage`. The function we provided will be used as the getter function for the property `vm.reversedMessage`:
+
+```js
+console.log(vm.reversedMessage) // => 'olleH'
+vm.message = 'Goodbye'
+console.log(vm.reversedMessage) // => 'eybdooG'
+```
+
+You can open the sandbox(TODO) and play with the example vm yourself. The value of `vm.reversedMessage` is always dependent on the value of `vm.message`.
+
+You can data-bind to computed properties in templates just like a normal property. Vue is aware that `vm.reversedMessage` depends on `vm.message`, so it will update any bindings that depend on `vm.reversedMessage` when `vm.message` changes. And the best part is that we've created this dependency relationship declaratively: the computed getter function has no side effects, which makes it easier to test and understand.
+
+### Computed Caching vs Methods
+
+You may have noticed we can achieve the same result by invoking a method in the expression:
+
+```html
+
Reversed message: "{{ reverseMessage() }}"
+```
+
+```js
+// in component
+methods: {
+ reverseMessage() {
+ return this.message.split('').reverse().join('')
+ }
+}
+```
+
+Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that **computed properties are cached based on their reactive dependencies.** A computed property will only re-evaluate when some of its reactive dependencies have changed. This means as long as `message` has not changed, multiple access to the `reversedMessage` computed property will immediately return the previously computed result without having to run the function again.
+
+This also means the following computed property will never update, because `Date.now()` is not a reactive dependency:
+
+```js
+computed: {
+ now() {
+ return Date.now()
+ }
+}
+```
+
+In comparison, a method invocation will **always** run the function whenever a re-render happens.
+
+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! In cases where you do not want caching, use a method instead.
+
+### Computed vs Watched Property
+
+Vue does provide a more generic way to observe and react to data changes on a Vue instance: **watch properties**. When you have some data that needs to change based on some other data, it is tempting to overuse `watch` - especially if you are coming from an AngularJS background. However, it is often a better idea to use a computed property rather than an imperative `watch` callback. Consider this example:
+
+```html
+
{{ fullName }}
+```
+
+```js
+const vm = Vue.createApp().mount(
+ {
+ data() {
+ return {
+ firstName: 'Foo',
+ lastName: 'Bar',
+ fullName: 'Foo Bar'
+ }
+ },
+ watch: {
+ firstName(val) {
+ this.fullName = val + ' ' + this.lastName
+ },
+ lastName(val) {
+ this.fullName = this.firstName + ' ' + val
+ }
+ }
+ },
+ '#demo'
+)
+```
+
+The above code is imperative and repetitive. Compare it with a computed property version:
+
+```js
+const vm = Vue.createApp().mount(
+ {
+ data() {
+ return {
+ firstName: 'Foo',
+ lastName: 'Bar'
+ }
+ },
+ computed: {
+ fullName() {
+ return this.firstName + ' ' + this.lastName
+ }
+ }
+ },
+ '#demo'
+)
+```
+
+Much better, isn't it?
+
+### Computed Setter
+
+Computed properties are by default getter-only, but you can also provide a setter when you need it:
+
+```js
+// ...
+computed: {
+ fullName: {
+ // getter
+ get() {
+ return this.firstName + ' ' + this.lastName
+ },
+ // setter
+ set(newValue) {
+ const names = newValue.split(' ')
+ this.firstName = names[0]
+ this.lastName = names[names.length - 1]
+ }
+ }
+}
+// ...
+```
+
+Now when you run `vm.fullName = 'John Doe'`, the setter will be invoked and `vm.firstName` and `vm.lastName` will be updated accordingly.
+
+## Watchers
+
+While computed properties are more appropriate in most cases, there are times when a custom watcher is necessary. That's why Vue provides a more generic way to react to data changes through the `watch` option. This is most useful when you want to perform asynchronous or expensive operations in response to changing data.
+
+For example:
+
+```html
+
+
+ Ask a yes/no question:
+
+
+
{{ answer }}
+
+```
+
+```html
+
+
+
+
+
+
+```
+
+Result:
+
+
+
+In this case, using the `watch` option allows us to perform an asynchronous operation (accessing an API) and sets a condition for performing this operation. None of that would be possible with a computed property.
+
+In addition to the `watch` option, you can also use the imperative [vm.\$watch API](TODO:../api/#vm-watch).
diff --git a/yarn.lock b/yarn.lock
index 24237d2aa4..b955fe45ab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1364,6 +1364,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c"
integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==
+axios@^0.19.1:
+ version "0.19.1"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.1.tgz#8a6a04eed23dfe72747e1dd43c604b8f1677b5aa"
+ integrity sha512-Yl+7nfreYKaLRvAvjNPkvfjnQHJM1yLBY3zhqAwcJSwR/6ETkanUgylgtIvkvz0xJ+p/vZuNw8X7Hnb7Whsbpw==
+ dependencies:
+ follow-redirects "1.5.10"
+
babel-loader@^8.0.4:
version "8.0.6"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
@@ -2338,6 +2345,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"
+debug@=3.1.0, debug@~3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+ dependencies:
+ ms "2.0.0"
+
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@@ -2352,13 +2366,6 @@ debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "^2.1.1"
-debug@~3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
- integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
- dependencies:
- ms "2.0.0"
-
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -3032,6 +3039,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
+follow-redirects@1.5.10:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+ integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
+ dependencies:
+ debug "=3.1.0"
+
follow-redirects@^1.0.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.9.0.tgz#8d5bcdc65b7108fe1508649c79c12d732dcedb4f"