Skip to content

Commit bbcc027

Browse files
authored
Update Composition API: separate watchEffect and watch
1 parent f49274c commit bbcc027

File tree

1 file changed

+26
-24
lines changed

1 file changed

+26
-24
lines changed

active-rfcs/0013-composition-api.md

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ In comparison, the APIs proposed in this RFC utilize mostly plain variables and
6565

6666
### API Introduction
6767

68-
Instead of bringing in new concepts, the APIs being proposed here are more about exposing Vue's core capabilities - such as creating and observing reactive state - as standalone functions. Here we will introduce a number of the most fundamental APIs and how they can be used in place of 2.x options to express in-component logic. Note this section focuses on introducing the basic ideas so it does not goes into full details for each API. Full API specs can be found in the [API Reference](https://vue-composition-api-rfc.netlify.com/api.html) section.
68+
Instead of bringing in new concepts, the APIs being proposed here are more about exposing Vue's core capabilities - such as creating and observing reactive state - as standalone functions. Here we will introduce a number of the most fundamental APIs and how they can be used in place of 2.x options to express in-component logic. Note this section focuses on introducing the basic ideas so it does not go into full details for each API. Full API specs can be found in the [API Reference](./api) section.
6969

7070
#### Reactive State and Side Effects
7171

@@ -82,24 +82,26 @@ const state = reactive({
8282

8383
`reactive` is the equivalent of the current `Vue.observable()` API in 2.x, renamed to avoid confusion with RxJS observables. Here, the returned `state` is a reactive object that all Vue users should be familiar with.
8484

85-
The essential use case for reactive state in Vue is that we can use it during render. Thanks to dependency tracking, the view automatically updates when reactive state changes. Rendering something in the DOM is considered a "side effect": our program is modifying state external to the program itself (the DOM). To apply and *automatically re-apply* a side effect based on reactive state, we can use the `watch` API:
85+
The essential use case for reactive state in Vue is that we can use it during render. Thanks to dependency tracking, the view automatically updates when reactive state changes. Rendering something in the DOM is considered a "side effect": our program is modifying state external to the program itself (the DOM). To apply and *automatically re-apply* a side effect based on reactive state, we can use the `watchEffect` API:
8686

8787
``` js
88-
import { reactive, watch } from 'vue'
88+
import { reactive, watchEffect } from 'vue'
8989

9090
const state = reactive({
9191
count: 0
9292
})
9393

94-
watch(() => {
94+
watchEffect(() => {
9595
document.body.innerHTML = `count is ${state.count}`
9696
})
9797
```
9898

99-
`watch` expects a function that applies the desired side effect (in this case, setting `innerHTML`). It executes the function immediately, and tracks all the reactive state properties it used during the execution as dependencies. Here, `state.count` would be tracked as a dependency for this watcher after the initial execution. When `state.count` is mutated at a future time, the inner function will be executed again.
99+
`watchEffect` expects a function that applies the desired side effect (in this case, setting `innerHTML`). It executes the function immediately, and tracks all the reactive state properties it used during the execution as dependencies. Here, `state.count` would be tracked as a dependency for this watcher after the initial execution. When `state.count` is mutated at a future time, the inner function will be executed again.
100100

101101
This is the very essence of Vue's reactivity system. When you return an object from `data()` in a component, it is internally made reactive by `reactive()`. The template is compiled into a render function (think of it as a more efficient `innerHTML`) that makes use of these reactive properties.
102102

103+
> `watchEffect` is similar to the 2.x `watch` option, but it doesn't require separating the watched data source and the side effect callback. Composition API also provides a `watch` function that behaves exactly the same as the 2.x option.
104+
103105
Continuing the above example, this is how we would handle user input:
104106

105107
``` js
@@ -113,7 +115,7 @@ document.body.addEventListener('click', increment)
113115
But with Vue's templating system we don't need to wrangle with `innerHTML` or manually attaching event listeners. Let's simplify the example with a hypothetical `renderTemplate` method so we can focus on the reactivity side:
114116

115117
``` js
116-
import { reactive, watch } from 'vue'
118+
import { reactive, watchEffect } from 'vue'
117119

118120
const state = reactive({
119121
count: 0
@@ -128,7 +130,7 @@ const renderContext = {
128130
increment
129131
}
130132

131-
watch(() => {
133+
watchEffect(() => {
132134
// hypothetical internal code, NOT actual API
133135
renderTemplate(
134136
`<button @click="increment">{{ state.count }}</button>`,
@@ -157,7 +159,7 @@ What is `computed` returning here? If we take a guess at how `computed` is imple
157159
// simplified pseudo code
158160
function computed(getter) {
159161
let value
160-
watch(() => {
162+
watchEffect(() => {
161163
value = getter()
162164
})
163165
return value
@@ -176,7 +178,7 @@ function computed(getter) {
176178
const ref = {
177179
value: null
178180
}
179-
watch(() => {
181+
watchEffect(() => {
180182
ref.value = getter()
181183
})
182184
return ref
@@ -188,7 +190,7 @@ In addition, we also need to intercept read / write operations to the object's `
188190
``` js
189191
const double = computed(() => state.count * 2)
190192

191-
watch(() => {
193+
watchEffect(() => {
192194
console.log(double.value)
193195
}) // -> 0
194196

@@ -197,7 +199,7 @@ state.count++ // -> 2
197199

198200
**Here `double` is an object that we call a "ref", as it serves as a reactive reference to the internal value it is holding.**
199201

200-
> You might be aware that Vue already has the concept of "refs", but only for referencing DOM elements or component instances in templates ("template refs"). Check out [this](https://vue-composition-api-rfc.netlify.com/api.html#template-refs) to see how the new refs system can be used for both logical state and template refs.
202+
> You might be aware that Vue already has the concept of "refs", but only for referencing DOM elements or component instances in templates ("template refs"). Check out [this](./api.html#template-refs) to see how the new refs system can be used for both logical state and template refs.
201203
202204
In addition to computed refs, we can also directly create plain mutable refs using the `ref` API:
203205

@@ -231,7 +233,7 @@ const renderContext = {
231233
increment
232234
}
233235

234-
watch(() => {
236+
watchEffect(() => {
235237
renderTemplate(
236238
`<button @click="increment">{{ count }}</button>`,
237239
renderContext
@@ -256,7 +258,7 @@ console.log(state.double)
256258
Our code so far already provides a working UI that can update based on user input - but the code runs only once and is not reusable. If we want to reuse the logic, a reasonable next step seems to be refactoring it into a function:
257259

258260
``` js
259-
import { reactive, computed, watch } from 'vue'
261+
import { reactive, computed, watchEffect } from 'vue'
260262

261263
function setup() {
262264
const state = reactive({
@@ -276,7 +278,7 @@ function setup() {
276278

277279
const renderContext = setup()
278280

279-
watch(() => {
281+
watchEffect(() => {
280282
renderTemplate(
281283
`<button @click="increment">
282284
Count is: {{ state.count }}, double is: {{ state.double }}
@@ -330,7 +332,7 @@ So far we have covered the pure state aspect of a component: reactive state, com
330332
- When some state changes;
331333
- When the component is mounted, updated or unmounted (lifecycle hooks).
332334

333-
We know that we can use the `watch` API to apply side effects based on state changes. As for performing side effects in different lifecycle hooks, we can use the dedicated `onXXX` APIs (which directly mirror the existing lifecycle options):
335+
We know that we can use the `watchEffect` and `watch` APIs to apply side effects based on state changes. As for performing side effects in different lifecycle hooks, we can use the dedicated `onXXX` APIs (which directly mirror the existing lifecycle options):
334336

335337
``` js
336338
import { onMounted } from 'vue'
@@ -346,7 +348,7 @@ export default {
346348

347349
These lifecycle registration methods can only be used during the invocation of a `setup` hook. It automatically figures out the current instance calling the `setup` hook using internal global state. It is intentionally designed this way to reduce friction when extracting logic into external functions.
348350

349-
> More details about these APIs can be found in the [API Reference](https://vue-composition-api-rfc.netlify.com/api.html). However, we recommend finishing the following sections before digging into the design details.
351+
> More details about these APIs can be found in the [API Reference](./api). However, we recommend finishing the following sections before digging into the design details.
350352
351353
### Code Organization
352354

@@ -356,13 +358,13 @@ This is an understandable first impression. But as mentioned in the Motivations
356358

357359
#### What is "Organized Code"?
358360

359-
Let's take a step back and consider what we really mean when we talk about "organized code". The end goal of keeping code organized should be making the code easier to read and understand. And what do we mean by "understanding" the code? Can we really claim that we "understand" a component just because we know what options it contains? Have you ever run into a big component authored by another developer (for example [this one](https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L198-L404)), and have a hard time wrapping your head around it?
361+
Let's take a step back and consider what we really mean when we talk about "organized code". The end goal of keeping code organized should be making the code easier to read and understand. And what do we mean by "understanding" the code? Can we really claim that we "understand" a component just because we know what options it contains? Have you ever run into a big component authored by another developer (for example [this one](https://github.com/vuejs/vue-cli/blob/a09407dd5b9f18ace7501ddb603b95e31d6d93c0/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L198-L404)), and have a hard time wrapping your head around it?
360362

361363
Think about how we would walk a fellow developer through a big component like the one linked above. You will most likely start with "this component is dealing with X, Y and Z" instead of "this component has these data properties, these computed properties and these methods". When it comes to understanding a component, we care more about "what the component is trying to do" (i.e. the intentions behind the code) rather than "what options the component happens to use". While code written with options-based API naturally answers the latter, it does a rather poor job at expressing the former.
362364

363365
#### Logical Concerns vs. Option Types
364366

365-
Let's define the "X, Y and Z" the component is dealing with as **logical concerns**. The readability problem is typically non-present in small, single purpose components because the entire component deals with a single logical concern. However, the issue becomes much more prominent in advanced use cases. Take the [Vue CLI UI file explorer](https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L198-L404) as an example. The component has to deal with many different logical concerns:
367+
Let's define the "X, Y and Z" the component is dealing with as **logical concerns**. The readability problem is typically non-present in small, single purpose components because the entire component deals with a single logical concern. However, the issue becomes much more prominent in advanced use cases. Take the [Vue CLI UI file explorer](https://github.com/vuejs/vue-cli/blob/a09407dd5b9f18ace7501ddb603b95e31d6d93c0/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L198-L404) as an example. The component has to deal with many different logical concerns:
366368

367369
- Tracking current folder state and displaying its content
368370
- Handling folder navigation (opening, closing, refreshing...)
@@ -371,7 +373,7 @@ Let's define the "X, Y and Z" the component is dealing with as **logical concern
371373
- Toggling show hidden folders
372374
- Handling current working directory changes
373375

374-
Can you instantly recognize and tell these logical concerns apart by reading the options-based code? It surely is difficult. You will notice that code related to a specific logical concern is often fragmented and scattered all over the place. For example, the "create new folder" feature makes use of [two data properties](https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L221-L222), [one computed property](https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L240), and [a method](https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L387) - where the method is defined in a location more than a hundred lines away from the data properties.
376+
Can you instantly recognize and tell these logical concerns apart by reading the options-based code? It surely is difficult. You will notice that code related to a specific logical concern is often fragmented and scattered all over the place. For example, the "create new folder" feature makes use of [two data properties](https://github.com/vuejs/vue-cli/blob/a09407dd5b9f18ace7501ddb603b95e31d6d93c0/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L221-L222), [one computed property](https://github.com/vuejs/vue-cli/blob/a09407dd5b9f18ace7501ddb603b95e31d6d93c0/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L240), and [a method](https://github.com/vuejs/vue-cli/blob/a09407dd5b9f18ace7501ddb603b95e31d6d93c0/packages/@vue/cli-ui/src/components/folder/FolderExplorer.vue#L387) - where the method is defined in a location more than a hundred lines away from the data properties.
375377

376378
If we color-code each of these logical concerns, we notice how fragmented they are when expressed with component options:
377379

@@ -565,7 +567,7 @@ The Composition API can be used alongside the existing options-based API.
565567

566568
Many Vue plugins today inject properties onto `this`. For example, Vue Router injects `this.$route` and `this.$router`, and Vuex injects `this.$store`. This has made type inference tricky since each plugin requires the user to augment the Vue typing for injected properties.
567569

568-
When using the Composition API, there is no `this`. Instead, plugins will leverage [`provide` and `inject`](https://vue-composition-api-rfc.netlify.com/api.html#provide-inject) internally and expose a composition function. The following is hypothetical code for a plugin:
570+
When using the Composition API, there is no `this`. Instead, plugins will leverage [`provide` and `inject`](./api.html#provide-inject) internally and expose a composition function. The following is hypothetical code for a plugin:
569571

570572
``` js
571573
const StoreSymbol = Symbol()
@@ -697,7 +699,7 @@ export default {
697699
}
698700
```
699701

700-
The [`toRefs`](https://vue-composition-api-rfc.netlify.com/api.html#torefs) API is provided to deal with this constraint - it converts each property on a reactive object to a corresponding ref:
702+
The [`toRefs`](./api.html#torefs) API is provided to deal with this constraint - it converts each property on a reactive object to a corresponding ref:
701703

702704
``` js
703705
function useMousePosition() {
@@ -750,7 +752,7 @@ Any JavaScript program starts with an entry file (think of it as the `setup()` f
750752

751753
## Adoption strategy
752754

753-
The Composition API is purely additive and does not affect / deprecate any existing 2.x APIs. It has been made available as a 2.x plugin via the [`@vue/composition` library](https://github.com/vuejs/composition-api/). The library's primary goal is to provide a way to experiment with the API and to collect feedback. The current implementation is up-to-date with this proposal, but may contain minor inconsistencies due to technical constraints of being a plugin. It may also receive breaking changes as this proposal is updated, so we do not recommend using it in production at this stage.
755+
The Composition API is purely additive and does not affect / deprecate any existing 2.x APIs. It has been made available as a 2.x plugin via the [`@vue/composition` library](https://github.com/vuejs/composition-api/). The library's primary goal is to provide a way to experiment with the API and to collect feedback. The current implementation is up-to-date with this proposal, but may contain minor inconsistencies due to technical constraints of being a plugin. It may also receive braking changes as this proposal is updated, so we do not recommend using it in production at this stage.
754756

755757
We intend to ship the API as built-in in 3.0. It will be usable alongside existing 2.x options.
756758

@@ -814,7 +816,7 @@ Although taking very different routes, the Composition API and Svelte 3's compil
814816

815817
``` html
816818
<script>
817-
import { ref, watch, onMounted } from 'vue'
819+
import { ref, watchEffect, onMounted } from 'vue'
818820
819821
export default {
820822
setup() {
@@ -824,7 +826,7 @@ export default {
824826
count.value++
825827
}
826828
827-
watch(() => console.log(count.value))
829+
watchEffect(() => console.log(count.value))
828830
829831
onMounted(() => console.log('mounted!'))
830832

0 commit comments

Comments
 (0)