diff --git a/src/.vuepress/config.js b/src/.vuepress/config.js index 65240ca34f..b6bf6318bb 100644 --- a/src/.vuepress/config.js +++ b/src/.vuepress/config.js @@ -31,15 +31,6 @@ const sidebar = { '/guide/component-edge-cases' ] }, - { - title: 'Internals', - collapsable: false, - children: [ - '/guide/reactivity', - '/guide/optimizations', - '/guide/change-detection' - ] - }, { title: 'Reusability & Composition', collapsable: false, @@ -48,18 +39,36 @@ const sidebar = { '/guide/custom-directive', '/guide/teleport', '/guide/render-function', - '/guide/plugins', - '/guide/composition-api-introduction' + '/guide/plugins' ] }, { - title: 'Tooling', + title: 'Advanced Guides', collapsable: false, children: [ - '/guide/single-file-component', - '/guide/testing' + { + title: 'Reactivity', + children: ['/guide/reactivity', '/guide/reactivity-fundamentals', '/guide/reactivity-computed-watchers'] + }, + { + title: 'Composition API', + children: [ + '/guide/composition-api-introduction', + '/guide/composition-api-setup', + '/guide/composition-api-lifecycle-hooks', + '/guide/composition-api-provide-inject', + '/guide/composition-api-template-refs' + ] + }, + '/guide/optimizations', + '/guide/change-detection' ] }, + { + title: 'Tooling', + collapsable: false, + children: ['/guide/single-file-component', '/guide/testing'] + }, { title: 'Scaling Up', collapsable: false, diff --git a/src/.vuepress/styles/index.styl b/src/.vuepress/styles/index.styl index 852aef14f2..fd40ef0fd5 100644 --- a/src/.vuepress/styles/index.styl +++ b/src/.vuepress/styles/index.styl @@ -149,3 +149,10 @@ vertical-align: middle; } } + +.sidebar-group.is-sub-group > .sidebar-heading:not(.clickable) { + font-size: inherit; + cursor: pointer!important; + font-weight: bold; + opacity: 1!important; +} diff --git a/src/api/basic-reactivity.md b/src/api/basic-reactivity.md index 2ee701dd9d..8fed58a1dc 100644 --- a/src/api/basic-reactivity.md +++ b/src/api/basic-reactivity.md @@ -12,7 +12,7 @@ const obj = reactive({ count: 0 }) The reactive conversion is "deep"—it affects all nested properties. In the [ES2015 Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) based implementation, the returned proxy is **not** equal to the original object. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object. -### Typing +**Typing:** ```ts function reactive(target: T): UnwrapNestedRefs diff --git a/src/api/composition-api.md b/src/api/composition-api.md index e67c108db0..97150b6da2 100644 --- a/src/api/composition-api.md +++ b/src/api/composition-api.md @@ -4,174 +4,79 @@ ## `setup` -The `setup` function is a new component option. It serves as the entry point for using the Composition API inside components. +A component option that is executed **before** the component is created, once the `props` are resolved, and serves as the entry point for composition API's -### Invocation Timing +- **Arguments:** -`setup` is called right after the initial props resolution when a component instance is created. [Lifecycle-wise](../guide/instance.html#instance-lifecycle-hooks), it is called before the `beforeCreate` hook. + - `{Data} props` + - `{SetupContext} context` -### Arguments +- **Typing**: -The function receives the resolved [props](../guide/component-props.html) as its first argument: - -```js -export default { - props: { - name: String - }, - setup(props) { - console.log(props.name) - } +```ts +interface Data { + [key: string]: unknown } -``` -Note that this `props` object is reactive - i.e. it is updated when new props are passed in, and can be observed and reacted upon using [watchEffect](./computed-watch-api.html#watcheffect) or [watch](./computed-watch-api.html#watch): - -```js -export default { - props: { - name: String - }, - setup(props) { - watchEffect(() => { - console.log(`name is: ` + props.name) - }) - } +interface SetupContext { + attrs: Data + slots: Slots + emit: (event: string, ...args: unknown[]) => void } -``` -However, **do NOT destructure** the `props` object! If you do so, the unpacked values won't have reactivity: - -```js -export default { - props: { - name: String - }, - setup({ name }) { - watchEffect(() => { - console.log(`name is: ` + name) // Will not be reactive! - }) - } -} +function setup(props: Data, context: SetupContext): Data ``` -The `props` object is immutable during development. Vue will emit a warning if there's an attempt to mutate it. +::: tip +To get type inference for the arguments passed to `setup()`, the use of [defineComponent](global-api.html#definecomponent) is needed. +::: -The second argument of `setup` provides a context object which exposes a selective list of the properties that were previously exposed on `this` in 2.x APIs: +- **Example** -```js -const MyComponent = { - setup(props, context) { - context.attrs - context.slots - context.emit - } -} -``` + With the template: -Unlike `props`, `context` argument can be destructured safely so [attrs](./instance-properties.html#attrs) and [slots](./instance-properties.html#slots) would always expose the latest values even after updates: + ```vue-html + + -```js -const MyComponent = { - setup(props, { attrs }) { - // a function that may get called at a later stage - function onClick() { - console.log(attrs.foo) // guaranteed to be the latest reference - } - } -} -``` + + ``` -### Usage with Templates + With render function: -If `setup` returns an object, the properties on the object will be merged into the render context of the component's template: + ```js + // MyBook.vue -```html - - - -``` - -Note that [refs](./refs-api.html#ref) returned from `setup` are [automatically unwrapped](./refs-api.html#access-in-templates) when accessed in the template so you shouldn't use `.value` in templates. + ``` -### Usage with Render Functions - -`setup` can also return a render function which can directly make use of the reactive state declared in the same scope: - -```js -import { h, ref, reactive } from 'vue' - -export default { - setup() { - const count = ref(0) - const object = reactive({ foo: 'bar' }) - - return () => h('div', [count.value, object.foo]) - } -} -``` - -### Usage of `this` - -**Inside `setup()`, `this` won't be a reference to Vue instance** Since `setup()` is called before other component options are resolved, `this` inside `setup()` will behave quite differently from `this` in other options. This might cause confusions when using `setup()` along other Options API. Another reason for avoiding `this` in `setup()` is a very common pitfall for beginners: - -```js -setup() { - const that = this - function onClick() { - console.log(this !== that) // not the `this` you'd expect! - } -} -``` - -### Typing - -```ts -interface Data { - [key: string]: unknown -} - -interface SetupContext { - attrs: Data - slots: Slots - emit: (event: string, ...args: unknown[]) => void -} - -function setup(props: Data, context: SetupContext): Data -``` - -::: tip -To get type inference for the arguments passed to `setup()`, the use of [defineComponent](global-api.html#definecomponent) is needed. -::: +- **See also**: [Composition API `setup`](,,/guide/composition-api-setup.html) ## Lifecycle Hooks @@ -213,52 +118,13 @@ The component instance context is also set during the synchronous execution of l - `renderTracked` -> `onRenderTracked` - `renderTriggered` -> `onRenderTriggered` -## Dependency Injection +- **See also**: [Composition API lifecycle hooks](../guide/composition-api-lifecycle-hooks.html) -`provide` and `inject` enables dependency injection. Both can only be called during [`setup()`](#setup) with a current active instance. +## Provide / Inject -```js -import { provide, inject } from 'vue' - -const ThemeSymbol = Symbol() - -const Ancestor = { - setup() { - provide(ThemeSymbol, 'dark') - } -} - -const Descendent = { - setup() { - const theme = inject(ThemeSymbol, 'light' /* optional default value */) - return { - theme - } - } -} -``` - -`inject` accepts an optional default value as the 2nd argument. If a default value is not provided and the property is not found on the provide context, `inject` returns `undefined`. - -### Injection Reactivity - -To retain reactivity between provided and injected values, we can use a [ref](./refs-api.html#ref): - -```js -// in provider -const themeRef = ref('dark') -provide(ThemeSymbol, themeRef) - -// in consumer -const theme = inject(ThemeSymbol, ref('light')) -watchEffect(() => { - console.log(`theme set to: ${theme.value}`) -}) -``` - -If a reactive object is injected, it can also be reactively observed. +`provide` and `inject` enables dependency injection. Both can only be called during [`setup()`](#setup) with a current active instance. -### Typing +- **Typing**: ```ts interface InjectionKey extends Symbol {} @@ -289,86 +155,6 @@ If using string keys or non-typed symbols, the type of the injected value will n const foo = inject('foo') // string | undefined ``` -## Template Refs - -When using the Composition API, the concept of [reactive refs](./refs-api.html#ref) and [template refs](../guide/component-template-refs.html) are unified. In order to obtain a reference to an in-template element or component instance, we can declare a ref as usual and return it from `setup()`: - -```html - - - -``` - -Here we are exposing `root` on the render context and binding it to the div as its ref via `ref="root"`. In the Virtual DOM patching algorithm, if a VNode's `ref` key corresponds to a ref on the render context, the VNode's corresponding element or component instance will be assigned to the value of that ref. This is performed during the Virtual DOM mount / patch process, so template refs will only get assigned values after the initial render. - -Refs used as templates refs behave just like any other refs: they are reactive and can be passed into (or returned from) composition functions. - -### Usage with JSX - -```js -export default { - setup() { - const root = ref(null) - - return () => - h('div', { - ref: root - }) - - // with JSX - return () =>
- } -} -``` - -### Usage inside `v-for` - -Composition API template refs do not have special handling when used inside `v-for`. Instead, use function refs to perform custom handling: - -```html - - - -``` +- **See also**: + - [Provide / Inject](../guide/component-provide-inject.html) + - [Composition API Provide / Inject](../guide/composition-api-provide-inject.html) diff --git a/src/api/computed-watch-api.md b/src/api/computed-watch-api.md index 753e092cd1..b403e15324 100644 --- a/src/api/computed-watch-api.md +++ b/src/api/computed-watch-api.md @@ -30,7 +30,7 @@ plusOne.value = 1 console.log(count.value) // 0 ``` -### Typing +**Typing:** ```ts // read-only @@ -56,141 +56,7 @@ setTimeout(() => { }, 100) ``` -### Stopping the Watcher - -When `watchEffect` is called during a component's [setup()](./composition-api.html#setup) function or [lifecycle hooks](./composition-api.html#lifecycle-hooks), the watcher is linked to the component's lifecycle and will be automatically stopped when the component is unmounted. - -In other cases, it returns a stop handle which can be called to explicitly stop the watcher: - -```js -const stop = watchEffect(() => { - /* ... */ -}) - -// later -stop() -``` - -### Side Effect Invalidation - -Sometimes the watched effect function will perform asynchronous side effects that need to be cleaned up when it is invalidated (i.e state changed before the effects can be completed). The effect function receives an `onInvalidate` function that can be used to register an invalidation callback. This invalidation callback is called when: - -- the effect is about to re-run -- the watcher is stopped (i.e. when the component is unmounted if `watchEffect` is used inside `setup()` or lifecycle hooks) - -```js -watchEffect(onInvalidate => { - const token = performAsyncOperation(id.value) - onInvalidate(() => { - // id has changed or watcher is stopped. - // invalidate previously pending async operation - token.cancel() - }) -}) -``` - -We are registering the invalidation callback via a passed-in function instead of returning it from the callback because the return value is important for async error handling. It is very common for the effect function to be an async function when performing data fetching: - -```js -const data = ref(null) -watchEffect(async onInvalidate => { - onInvalidate(() => {...}) // we register cleanup function before Promise resolves - data.value = await fetchData(props.id) -}) -``` - -An async function implicitly returns a Promise, but the cleanup function needs to be registered immediately before the Promise resolves. In addition, Vue relies on the returned Promise to automatically handle potential errors in the Promise chain. - -### Effect Flush Timing - -Vue's reactivity system buffers invalidated effects and flushes them asynchronously to avoid unnecessary duplicate invocation when there are many state mutations happening in the same "tick". Internally, a component's `update` function is also a watched effect. When a user effect is queued, it is always invoked after all component `update` effects: - -```html - - - -``` - -In this example: - -- The count will be logged synchronously on initial run. -- When `count` is mutated, the callback will be called **after** the component has updated. - -Note the first run is executed before the component is mounted. So if you wish to access the DOM (or template refs) in a watched effect, do it in the mounted hook: - -```js -onMounted(() => { - watchEffect(() => { - // access the DOM or template refs - }) -}) -``` - -In cases where a watcher effect needs to be re-run synchronously or before component updates, we can pass an additional `options` object with the `flush` option (default is `'post'`): - -```js -// fire synchronously -watchEffect( - () => { - /* ... */ - }, - { - flush: 'sync' - } -) - -// fire before component updates -watchEffect( - () => { - /* ... */ - }, - { - flush: 'pre' - } -) -``` - -### Watcher Debugging - -The `onTrack` and `onTrigger` options can be used to debug a watcher's behavior. - -- `onTrack` will be called when a reactive property or ref is tracked as a dependency -- `onTrigger` will be called when the watcher callback is triggered by the mutation of a dependency - -Both callbacks will receive a debugger event which contains information on the dependency in question. It is recommended to place a `debugger` statement in these callbacks to interactively inspect the dependency: - -```js -watchEffect( - () => { - /* side effect */ - }, - { - onTrigger(e) { - debugger - } - } -) -``` - -`onTrack` and `onTrigger` only work in development mode. - -### Typing +**Typing:** ```ts function watchEffect( @@ -216,9 +82,11 @@ type InvalidateCbRegistrator = (invalidate: () => void) => void type StopHandle = () => void ``` +**See also**: [`watchEffect` guide](../guide/reactivity-computed-watchers.html#watcheffect) + ## `watch` -The `watch` API is the exact equivalent of the Options API [this.\$watch](./instance-methods.html#watch) (and the corresponding [watch](./options-data.html#watch) option). `watch` requires watching a specific data source and applies side effects in a separate callback function. It also is lazy by default - i.e. the callback is only called when the watched source has changed. +The `watch` API is the exact equivalent of the Options API [this.$watch](./instance-methods.html#watch) (and the corresponding [watch](./options-data.html#watch) option). `watch` requires watching a specific data source and applies side effects in a separate callback function. It also is lazy by default - i.e. the callback is only called when the watched source has changed. - Compared to [watchEffect](#watcheffect), `watch` allows us to: @@ -261,7 +129,7 @@ watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { `watch` shares behavior with [`watchEffect`](#watcheffect) in terms of [manual stoppage](#stopping-the-watcher), [side effect invalidation](#side-effect-invalidation) (with `onInvalidate` passed to the callback as the 3rd argument instead), [flush timing](#effect-flush-timing) and [debugging](#watcher-debugging). -### Typing +**Typing:** ```ts // wacthing single source @@ -298,3 +166,5 @@ interface WatchOptions extends WatchEffectOptions { deep?: boolean } ``` + +**See also**: [`watch` guide](../guide/reactivity-computed-watchers.html#watch) diff --git a/src/api/refs-api.md b/src/api/refs-api.md index f0040d213a..8b3ee695a9 100644 --- a/src/api/refs-api.md +++ b/src/api/refs-api.md @@ -6,6 +6,8 @@ Takes an inner value and returns a reactive and mutable ref object. The ref object has a single property `.value` that points to the inner value. +**Example:** + ```js const count = ref(0) console.log(count.value) // 0 @@ -16,97 +18,7 @@ console.log(count.value) // 1 If an object is assigned as a ref's value, the object is made deeply reactive by the [reactive](./proxy-api.html#reactive) method. -### Access in Templates - -When a ref is returned as a property on the render context (the object returned from [setup()](./composition-api.html#setup)) and accessed in the template, it automatically unwraps to the inner value. There is no need to append `.value` in the template: - -```html - - - -``` - -However, if we decide to change the inline event handler on button click to the component method declared in `setup`, we need to remember that `ref` is not unwrapped there: - -```html - - - -``` - -### Access in Reactive Objects - -When a ref is accessed or mutated as a property of a reactive object, it automatically unwraps to the inner value so it behaves like a normal property: - -```js -const count = ref(0) -const state = reactive({ - count -}) - -console.log(state.count) // 0 - -state.count = 1 -console.log(count.value) // 1 -``` - -If a new ref is assigned to a property linked to an existing ref, it will replace the old ref: - -```js -const otherCount = ref(2) - -state.count = otherCount -console.log(state.count) // 2 -console.log(count.value) // 1 -``` - -Ref unwrapping only happens when nested inside a reactive `Object`. There is no unwrapping performed when the ref is accessed from an `Array` or a native collection type like [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map): - -```js -const arr = reactive([ref(0)]) -// need .value here -console.log(arr[0].value) - -const map = reactive(new Map([['foo', ref(0)]])) -// need .value here -console.log(map.get('foo').value) -``` - -### Typing +**Typing:** ```ts interface Ref { @@ -271,7 +183,7 @@ Creates a customized ref with explicit control over its dependency tracking and } ``` -### Typing +**Typing:** ```ts function customRef(factory: CustomRefFactory): Ref @@ -296,3 +208,5 @@ foo.value = {} // but the value will not be converted. isReactive(foo.value) // false ``` + +**See also**: [Creating Standalone Reactive Values as `refs`](http://localhost:8080/guide/reactivity-fundamentals.html#creating-standalone-reactive-values-as-refs) diff --git a/src/guide/component-provide-inject.md b/src/guide/component-provide-inject.md index ec30c98d57..2a9a3887cb 100644 --- a/src/guide/component-provide-inject.md +++ b/src/guide/component-provide-inject.md @@ -96,7 +96,7 @@ In fact, you can think of dependency injection as sort of “long-range props” ## Working with reactivity -In the example above, if we change the list of `todos`, this change won't be reflected in the injected `todoLength` property. This is because `provide/inject` bindings are _not_ reactive by default. We can change this behavior by passing a `ref` property or `reactive` object to `provide`. In our case, if we want to react to changes in the ancestor component, we need to assign a Composition API `computed` property to our provided `todoLength`: +In the example above, if we change the list of `todos`, this change won't be reflected in the injected `todoLength` property. This is because `provide/inject` bindings are _not_ reactive by default. We can change this behavior by passing a `ref` property or `reactive` object to `provide`. In our case, if we wanted to react to changes in the ancestor component, we would need to assign a Composition API `computed` property to our provided `todoLength`: ```js app.component('todo-list', { @@ -109,4 +109,4 @@ app.component('todo-list', { }) ``` -In this, any change to `todos.length` will be reflected correctly in the components, where `todoLength` is injected. Read more about `reactive` provide/inject in the [Composition API section](TODO) +In this, any change to `todos.length` will be reflected correctly in the components, where `todoLength` is injected. Read more about `reactive` provide/inject in the [Composition API section](composition-api-provide-inject.html#injection-reactivity) diff --git a/src/guide/composition-api-introduction.md b/src/guide/composition-api-introduction.md index 733e5e7798..0dd50161c9 100644 --- a/src/guide/composition-api-introduction.md +++ b/src/guide/composition-api-introduction.md @@ -1,4 +1,4 @@ -# Composition API: Introduction +# Introduction ## Why Composition API? @@ -72,7 +72,7 @@ The new `setup` component option is executed **before** the component is created Because the component instance is not yet created when `setup` is executed, there is no `this` inside a `setup` option. This means, with the exception of `props`, you won't be able to access any properties declared in the component – **local state**, **computed properties** or **methods**. ::: -The `setup` option should be a function that accepts `props` and `context` which we will talk about later. Additionally, everything that we return from `setup` will be exposed to the rest of our component (computed properties, methods, lifecycle hooks and so on) as well as to the component's template. +The `setup` option should be a function that accepts `props` and `context` which we will talk about [later](composition-api-setup.html#arguments). Additionally, everything that we return from `setup` will be exposed to the rest of our component (computed properties, methods, lifecycle hooks and so on) as well as to the component's template. Let’s add `setup` to our component: @@ -84,11 +84,11 @@ export default { props: { user: { type: String } }, - setup (props) { + setup(props) { console.log(props) // { user: '' } return {} // anything returned here will be available for the rest of the component - }, + } // the "rest" of the component } ``` @@ -229,20 +229,7 @@ We will start with the lifecycle hook. ### Lifecycle Hook Registration Inside `setup` -To make Composition API feature-complete compared to Options API, we also need a way to register lifecycle hooks inside `setup`. This is possible thanks to several new functions exported from Vue. Below is a list of Options API's functions and how they're mapped into the Composition API. - - - -- ~~`beforeCreate`~~ -> use `setup()` -- ~~`created`~~ -> use `setup()` -- `beforeMount` -> `onBeforeMount` -- `mounted` -> `onMounted` -- `beforeUpdate` -> `onBeforeUpdate` -- `updated` -> `onUpdated` -- `beforeDestroy` -> `onBeforeUnmount` -- `destroyed` -> `onUnmounted` -- `errorCaptured` -> `onErrorCaptured` - +To make Composition API feature-complete compared to Options API, we also need a way to register lifecycle hooks inside `setup`. This is possible thanks to several new functions exported from Vue. Lifecycle hooks on composition API have the same name as for Options API but are prefixed with `on`: i.e. `mounted` would look like `onMounted`. These functions accept a callback that will be executed when the hook is called by the component. @@ -296,13 +283,13 @@ Whenever `counter` is modified, for example `counter.value = 5`, the watch will ```js export default { - data () { + data() { return { counter: 0 } }, watch: { - counter (newValue, oldValue) { + counter(newValue, oldValue) { console.log('The new counter value is: ' + this.counter) } } @@ -401,7 +388,7 @@ setup (props) { } ``` -We could do the same for other **logical concerns** but you might be already asking the question – *Isn’t this just moving the code to the `setup` option and making it extremely big?* Well, that’s true. That’s why before moving on with the other responsibilities, we will first extract the above code into a standalone **composition function**. Let's start with creating `useUserRepositories`: +We could do the same for other **logical concerns** but you might be already asking the question – _Isn’t this just moving the code to the `setup` option and making it extremely big?_ Well, that’s true. That’s why before moving on with the other responsibilities, we will first extract the above code into a standalone **composition function**. Let's start with creating `useUserRepositories`: ```js // src/composables/useUserRepositories.js @@ -409,7 +396,7 @@ We could do the same for other **logical concerns** but you might be already ask import { fetchUserRepositories } from '@/api/repositories' import { ref, onMounted, watch, toRefs } from 'vue' -export default function useUserRepositories (user) { +export default function useUserRepositories(user) { const repositories = ref([]) const getUserRepositories = async () => { repositories.value = await fetchUserRepositories(user.value) @@ -432,7 +419,7 @@ And then the searching functionality: import { ref, onMounted, watch, toRefs } from 'vue' -export default function useRepositoryNameSearch (repositories) { +export default function useRepositoryNameSearch(repositories) { const searchQuery = ref('') const repositoriesMatchingSearchQuery = computed(() => { return repositories.value.filter(repository => { @@ -506,7 +493,7 @@ export default { props: { user: { type: String } }, - setup (props) { + setup(props) { const { user } = toRefs(props) const { repositories, getUserRepositories } = useUserRepositories(user) @@ -529,7 +516,7 @@ export default { getUserRepositories, searchQuery, filters, - updateFilters, + updateFilters } } } diff --git a/src/guide/composition-api-lifecycle-hooks.md b/src/guide/composition-api-lifecycle-hooks.md new file mode 100644 index 0000000000..de5bf47e7e --- /dev/null +++ b/src/guide/composition-api-lifecycle-hooks.md @@ -0,0 +1,40 @@ +# Lifecycle Hooks + +> This guide assumes that you have already read the [Composition API Introduction](composition-api-introduction.html) and [Reactivity Fundamentals](reactivity-fundamentals.html). Read that first if you are new to Composition API. + +You can access a component's lifecycle hook by prefixing the lifecycle hook with "on". + +The following table contains how the lifecycle hooks are invoked inside of [setup()](composition-api-setup.html): + +| Options API | Hook inside inside `setup` | +| ----------------- | -------------------------- | +| `beforeCreate` | Not needed\* | +| `created` | Not needed\* | +| `beforeMount` | `onBeforeMount` | +| `mounted` | `onMounted` | +| `beforeUpdate` | `onBeforeUpdate` | +| `updated` | `onUpdated` | +| `beforeUnmount` | `onBeforeUnmount` | +| `unmounted` | `onUnmounted` | +| `errorCaptured` | `onErrorCaptured` | +| `renderTracked` | `onRenderTracked` | +| `renderTriggered` | `onRenderTriggered` | + +:::tip +Because `setup` is run around the `beforeCreate` and `created` lifecycle hooks, you do not need to explicitly define the,. In other words, any code that would be written inside those hooks should be written directly in the `setup` function. +::: + +These functions accept a callback that will be executed when the hook is called by the component: + +```js +// MyBook.vue + +export default { + setup() { + // mounted + onMounted(() => { + console.log('Component is mounted!) + }) + } +} +``` diff --git a/src/guide/composition-api-provide-inject.md b/src/guide/composition-api-provide-inject.md new file mode 100644 index 0000000000..fbb6b5b2c2 --- /dev/null +++ b/src/guide/composition-api-provide-inject.md @@ -0,0 +1,121 @@ +# Provide / Inject + +> This guide assumes that you have already read the [Composition API Introduction](composition-api-introduction.html) and [Reactivity Fundamentals](reactivity-fundamentals.html). Read that first if you are new to Composition API. + +We can use [provide / inject](component-provide-inject.html) with the Composition API as well. Both can only be called during [`setup()`](composition-api-setup.html) with a current active instance. + +For example, if we want to provide a book name on the root component and inject it on the child component: + +```js +import { provide, inject } from 'vue' + +const RootComponent = { + setup() { + provide('book', 'Vue 3 guide') + } +} + +const MyBook = { + setup() { + const book = inject( + 'book', + 'Eloquent Javasctipt' /* optional default value */ + ) + return { + book + } + } +} +``` + +`inject` accepts an optional default value as the 2nd argument. If a default value is not provided and the property is not found on the provide context, `inject` returns `undefined`. + +If we need to provide or inject multiple values, we can do this with a subsequent call of `provide` or `inject` respectively: + +```js{5-6,12-16} +import { provide, inject } from 'vue' + +const RootComponent = { + setup() { + provide('book', 'Vue 3 guide') + provide('year', '2020') + } +} + +const MyBook = { + setup() { + const book = inject( + 'book', + 'Eloquent Javasctipt' /* optional default value */ + ) + const year = inject('year') + + return { + book, + year + } + } +} +``` + +## Injection Reactivity + +To retain reactivity between provided and injected values, we can use a [ref](reactivity-fundamentals.html#creating-standalone-reactive-values-as-refs) or [reactive](reactivity-fundamentals.html#declaring-reactive-state) when providing a value: + +```js +import { ref, reactive } from 'vue' + +// in provider +setup() { + const book = reactive({ + title: 'Vue 3 Guide', + author: 'Vue Team' + }) + const year = ref('2020') + + provide('book', book) + provide('year', book) +} + +// in consumer +setup() { + const book = inject('book') + const year = inject('year') + + return { book, year } +} +``` + +Now, when either `book` or `year` are changed on the _provider_ component, we can observe them changing on the component where they are injected. + +::: warning +We don't recommend mutating a reactive property where it's injected as it's breaking Vue one-direction data flow. Instead, try to either mutate values where they are _provided_ or provide a method to mutate them + +```js +import { ref, reactive } from 'vue' + +// in provider +setup() { + const book = reactive({ + title: 'Vue 3 Guide', + author: 'Vue Team' + }) + + function changeBookName() { + book.title = 'Vue 3 Advanced Guide' + } + + provide('book', book) + provide('changeBookName', changeBookName) +} + +// in consumer +setup() { + const book = inject('book') + const changeBookName = inject('changeBookName') + + return { book, changeBookName } +} +``` + +::: diff --git a/src/guide/composition-api-setup.md b/src/guide/composition-api-setup.md new file mode 100644 index 0000000000..4623090c45 --- /dev/null +++ b/src/guide/composition-api-setup.md @@ -0,0 +1,164 @@ +# Setup + +> This section uses [single-file component](single-file-component.html) syntax for code examples + +> This guide assumes that you have already read the [Composition API Introduction](composition-api-introduction.html) and [Reactivity Fundamentals](reactivity-fundamentals.html). Read that first if you are new to Composition API. + +## Arguments + +When using the `setup` function, it will take two arguments: + +1. `props` +2. `context` + +Let's dive deeper into how each argument can be used. + +### Props + +The first argument in the `setup` function is the `props` argument. Just as you would expect in a standard component, `props` inside of a `setup` function are reactive and will be updated when new props are passed in. + +```js +// MyBook.vue + +export default { + props: { + title: String + }, + setup(props) { + console.log(props.title) + } +} +``` + +:::warning +However, because `props` are reactive, you **cannot use ES6 destructuring** because it will remove props reactivity. +::: + +If you need to destructure your props, you can do this safely by utilizing the [toRefs](reactivity-fundamentals.html#destructuring-reactive-state) inside of the `setup` function. + +```js +// MyBook.vue + +import { toRefs } from 'vue' + +setup(props) { + const { title } = toRefs(props) + + console.log(title.value) +} +``` + +### Context + +The second argument passed to the `setup` function is the `context`. The `context` is a normal JavaScript object that exposes three component properties: + +```js +// MyBook.vue + +export default { + setup(props, context) { + // Attributes (Reactive Property) + console.log(context.attrs) + + // Slots (Reactive Property) + console.log(context.slots) + + // Emit Events (Method) + console.log(context.emit) + } +} +``` + +Because it is a normal JavaScript object, i.e., it is not reactive, this means you can safely use ES6 destructuring on `context`. + +```js +// MyBook.vue +export default { + setup(props, { attrs, slots, emit }) { + ... + } +} +``` + +As a result, similar to `props`, if you need to destructure either of these properties, you can utilize the `toRefs` method to create a similar effects. + +```jsx +// MyBook.vue + +import { toRefs } from 'vue' + +export default { + setup(props, { attrs }) { + const { id } = toRefs(attrs) + console.log(id.value) + } +) +``` + +## Accessing Component Properties + +When `setup` is executed, the component instance has not been created yet. As a result, you will only be able to access the following properties: + +- `props` +- `attrs` +- `slots` +- `emit` + +In other words, you **will not have access** to the following component options: + +- `data` +- `computed` +- `methods` + +## Usage with Templates + +If `setup` returns an object, the properties on the object can be accessed in the component's template: + +```vue-html + + + + +``` + +Note that [refs](../api/refs-api.html#ref) returned from `setup` are [automatically unwrapped](../api/refs-api.html#access-in-templates) when accessed in the template so you shouldn't use `.value` in templates. + +## Usage with Render Functions + +`setup` can also return a render function which can directly make use of the reactive state declared in the same scope: + +```js +// MyBook.vue + +import { h, ref, reactive } from 'vue' + +export default { + setup() { + const readersNumber = ref(0) + const book = reactive({ title: 'Vue 3 Guide' }) + // Please note that we need to explicitly expose ref value here + return () => h('div', [readersNumber.value, book.title]) + } +} +``` + +## Usage of `this` + +**Inside `setup()`, `this` won't be a reference to Vue instance** Since `setup()` is called before other component options are resolved, `this` inside `setup()` will behave quite differently from `this` in other options. This might cause confusions when using `setup()` along other Options API. diff --git a/src/guide/composition-api-template-refs.md b/src/guide/composition-api-template-refs.md new file mode 100644 index 0000000000..4b9c965139 --- /dev/null +++ b/src/guide/composition-api-template-refs.md @@ -0,0 +1,87 @@ +## Template Refs + +> This section uses [single-file component](single-file-component.html) syntax for code examples + +> This guide assumes that you have already read the [Composition API Introduction](composition-api-introduction.html) and [Reactivity Fundamentals](reactivity-fundamentals.html). Read that first if you are new to Composition API. + +When using the Composition API, the concept of [reactive refs](reactivity-fundamentals.html#creating-standalone-reactive-values-as-refs) and [template refs](component-template-refs.html) are unified. In order to obtain a reference to an in-template element or component instance, we can declare a ref as usual and return it from [setup()](composition-api-setup.html): + +```html + + + +``` + +Here we are exposing `root` on the render context and binding it to the div as its ref via `ref="root"`. In the Virtual DOM patching algorithm, if a VNode's `ref` key corresponds to a ref on the render context, the VNode's corresponding element or component instance will be assigned to the value of that ref. This is performed during the Virtual DOM mount / patch process, so template refs will only get assigned values after the initial render. + +Refs used as templates refs behave just like any other refs: they are reactive and can be passed into (or returned from) composition functions. + +### Usage with JSX + +```js +export default { + setup() { + const root = ref(null) + + return () => + h('div', { + ref: root + }) + + // with JSX + return () =>
+ } +} +``` + +### Usage inside `v-for` + +Composition API template refs do not have special handling when used inside `v-for`. Instead, use function refs to perform custom handling: + +```html + + + +``` diff --git a/src/guide/reactivity-computed-watchers.md b/src/guide/reactivity-computed-watchers.md new file mode 100644 index 0000000000..864c05654e --- /dev/null +++ b/src/guide/reactivity-computed-watchers.md @@ -0,0 +1,226 @@ +# Computed and Watch + +> This section uses [single-file component](single-file-component.html) syntax for code examples + +## Computed values + +Sometimes we need state that depends on other state - in Vue this is handled with component [computed properties](computed.html#computed-properties). To directly create a computed value, we can use the `computed` method: it takes a getter function and returns an immutable reactive [ref](reactivity-fundamentals.html#creating-standalone-reactive-values-as-refs) object for the returned value from the getter. + +```js +const count = ref(1) +const plusOne = computed(() => count.value++) + +console.log(plusOne.value) // 2 + +plusOne.value++ // error +``` + +Alternatively, it can take an object with `get` and `set` functions to create a writable ref object. + +```js +const count = ref(1) +const plusOne = computed({ + get: () => count.value + 1, + set: val => { + count.value = val - 1 + } +}) + +plusOne.value = 1 +console.log(count.value) // 0 +``` + +## `watchEffect` + +To apply and _automatically re-apply_ a side effect based on reactive state, we can use the `watchEffect` method. It runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed. + +```js +const count = ref(0) + +watchEffect(() => console.log(count.value)) +// -> logs 0 + +setTimeout(() => { + count.value++ + // -> logs 1 +}, 100) +``` + +### Stopping the Watcher + +When `watchEffect` is called during a component's [setup()](composition-api-setup.html) function or [lifecycle hooks](composition-api-lifecycle-hooks.html), the watcher is linked to the component's lifecycle and will be automatically stopped when the component is unmounted. + +In other cases, it returns a stop handle which can be called to explicitly stop the watcher: + +```js +const stop = watchEffect(() => { + /* ... */ +}) + +// later +stop() +``` + +### Side Effect Invalidation + +Sometimes the watched effect function will perform asynchronous side effects that need to be cleaned up when it is invalidated (i.e state changed before the effects can be completed). The effect function receives an `onInvalidate` function that can be used to register an invalidation callback. This invalidation callback is called when: + +- the effect is about to re-run +- the watcher is stopped (i.e. when the component is unmounted if `watchEffect` is used inside `setup()` or lifecycle hooks) + +```js +watchEffect(onInvalidate => { + const token = performAsyncOperation(id.value) + onInvalidate(() => { + // id has changed or watcher is stopped. + // invalidate previously pending async operation + token.cancel() + }) +}) +``` + +We are registering the invalidation callback via a passed-in function instead of returning it from the callback because the return value is important for async error handling. It is very common for the effect function to be an async function when performing data fetching: + +```js +const data = ref(null) +watchEffect(async onInvalidate => { + onInvalidate(() => {...}) // we register cleanup function before Promise resolves + data.value = await fetchData(props.id) +}) +``` + +An async function implicitly returns a Promise, but the cleanup function needs to be registered immediately before the Promise resolves. In addition, Vue relies on the returned Promise to automatically handle potential errors in the Promise chain. + +### Effect Flush Timing + +Vue's reactivity system buffers invalidated effects and flushes them asynchronously to avoid unnecessary duplicate invocation when there are many state mutations happening in the same "tick". Internally, a component's `update` function is also a watched effect. When a user effect is queued, it is always invoked after all component `update` effects: + +```html + + + +``` + +In this example: + +- The count will be logged synchronously on initial run. +- When `count` is mutated, the callback will be called **after** the component has updated. + +Note the first run is executed before the component is mounted. So if you wish to access the DOM (or template refs) in a watched effect, do it in the mounted hook: + +```js +onMounted(() => { + watchEffect(() => { + // access the DOM or template refs + }) +}) +``` + +In cases where a watcher effect needs to be re-run synchronously or before component updates, we can pass an additional `options` object with the `flush` option (default is `'post'`): + +```js +// fire synchronously +watchEffect( + () => { + /* ... */ + }, + { + flush: 'sync' + } +) + +// fire before component updates +watchEffect( + () => { + /* ... */ + }, + { + flush: 'pre' + } +) +``` + +### Watcher Debugging + +The `onTrack` and `onTrigger` options can be used to debug a watcher's behavior. + +- `onTrack` will be called when a reactive property or ref is tracked as a dependency +- `onTrigger` will be called when the watcher callback is triggered by the mutation of a dependency + +Both callbacks will receive a debugger event which contains information on the dependency in question. It is recommended to place a `debugger` statement in these callbacks to interactively inspect the dependency: + +```js +watchEffect( + () => { + /* side effect */ + }, + { + onTrigger(e) { + debugger + } + } +) +``` + +`onTrack` and `onTrigger` only work in development mode. + +## `watch` + +The `watch` API is the exact equivalent of the component [watch](computed.html#watchers) property. `watch` requires watching a specific data source and applies side effects in a separate callback function. It also is lazy by default - i.e. the callback is only called when the watched source has changed. + +- Compared to [watchEffect](#watcheffect), `watch` allows us to: + + - Perform the side effect lazily; + - Be more specific about what state should trigger the watcher to re-run; + - Access both the previous and current value of the watched state. + +### Watching a Single Source + +A watcher data source can either be a getter function that returns a value, or directly a `ref`: + +```js +// watching a getter +const state = reactive({ count: 0 }) +watch( + () => state.count, + (count, prevCount) => { + /* ... */ + } +) + +// directly watching a ref +const count = ref(0) +watch(count, (count, prevCount) => { + /* ... */ +}) +``` + +### Watching Multiple Sources + +A watcher can also watch multiple sources at the same time using an array: + +```js +watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { + /* ... */ +}) +``` + +### Shared Behavior with `watchEffect` + +`watch` shares behavior with [`watchEffect`](#watcheffect) in terms of [manual stoppage](#stopping-the-watcher), [side effect invalidation](#side-effect-invalidation) (with `onInvalidate` passed to the callback as the 3rd argument instead), [flush timing](#effect-flush-timing) and [debugging](#watcher-debugging). diff --git a/src/guide/reactivity-fundamentals.md b/src/guide/reactivity-fundamentals.md new file mode 100644 index 0000000000..4595feedd5 --- /dev/null +++ b/src/guide/reactivity-fundamentals.md @@ -0,0 +1,162 @@ +# Reactivity Fundamentals + +## Declaring Reactive State + +To create a reactive state from a JavaScript object, we can use a `reactive` method: + +```js +import { reactive } from 'vue' + +// reactive state +const state = reactive({ + count: 0 +}) +``` + +`reactive` is the equivalent of the `Vue.observable()` API in Vue 2.x, renamed to avoid confusion with RxJS observables. Here, the returned state is a reactive object. The reactive conversion is "deep" - it affects all nested properties of the passed object. + +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. + +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](render-function.html) that makes use of these reactive properties. + +You can learn more about `reactive` in the [Basic Reactivity API's](../api/basic-reactivity.html) section + +## Creating Standalone Reactive Values as `refs` + +Imagine the case where we have a standalone primitive value (for example, a string) and we want to make it reactive. Of course, we could make am object with a single property equal to our string, and pass it to `reactive`. Vue has a method that will do the same for us - it's a `ref`: + +```js +import { ref } from 'vue' + +const count = ref(0) +``` + +`ref` will return a reactive and mutable object that serves as a reactive **ref**erence to the internal value it is holding - that's where the name comes from. This object contains the only one property named `value`: + +```js +import { ref } from 'vue' + +const count = ref(0) +console.log(count.value) // 0 + +count.value++ +console.log(count.value) // 1 +``` + +### Ref Unwrapping + +When a ref is returned as a property on the render context (the object returned from [setup()](composition-api-setup.html)) and accessed in the template, it automatically unwraps to the inner value. There is no need to append `.value` in the template: + +```vue-html + + + +``` + +### Access in Reactive Objects + +When a `ref` is accessed or mutated as a property of a reactive object, it automatically unwraps to the inner value so it behaves like a normal property: + +```js +const count = ref(0) +const state = reactive({ + count +}) + +console.log(state.count) // 0 + +state.count = 1 +console.log(count.value) // 1 +``` + +If a new ref is assigned to a property linked to an existing ref, it will replace the old ref: + +```js +const otherCount = ref(2) + +state.count = otherCount +console.log(state.count) // 2 +console.log(count.value) // 1 +``` + +Ref unwrapping only happens when nested inside a reactive `Object`. There is no unwrapping performed when the ref is accessed from an `Array` or a native collection type like [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map): + +```js +const books = reactive([ref('Vue 3 Guide')]) +// need .value here +console.log(books[0].value) + +const map = reactive(new Map([['count', ref(0)]])) +// need .value here +console.log(map.get('count').value) +``` + +## Destructuring Reactive State + +When we want to use a few properties of the large reactive object, it could be tempting to use [ES6 destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to get properties we want: + +```js +import { reactive } from 'vue' + +const book = reactive({ + author: 'Vue Team', + year: '2020', + title: 'Vue 3 Guide', + description: 'You are reading this book right now ;)', + price: 'free' +}) + +let { author, title } = book +``` + +Unfortunately, with such a destructuring the reactivity for both properties would be lost. For such case, we need to convert our reactive object to a set of `refs`. These refs will retaining the reactive connection to the source object: + +```js +import { reactive, toRefs } from 'vue' + +const book = reactive({ + author: 'Vue Team', + year: '2020', + title: 'Vue 3 Guide', + description: 'You are reading this book right now ;)', + price: 'free' +}) + +let { author, title } = toRefs(book) + +title.value = 'Vue 3 Detailed Guide' // we need to use .value as title is a ref now +console.log(book.title) // 'Vue 3 Detailed Guide' +``` + +You can learn more about `refs` in the [Refs API](../api/refs-api.html#ref) section + +## Prevent Mutating Reactive Objects with `readonly` + +Sometimes we want to track changes of the reactive object (`ref` or `reactive`) but we also want prevent changing it from a certain place of the application. For example, when we have a [provided](component-provide-inject.html) reactive object, we want to prevent mutating it where it's injected. To do so, we can create a readonly proxy to the original object: + +```js +const original = reactive({ count: 0 }) + +const copy = readonly(original) + +// mutating original will trigger watchers relying on the copy +original.count++ + +// mutating the copy will fail and result in a warning +copy.count++ // warning: "Set operation on key 'count' failed: target is readonly." +```