diff --git a/src/api/composition-api.md b/src/api/composition-api.md index 14405575f7..f4ce05ad02 100644 --- a/src/api/composition-api.md +++ b/src/api/composition-api.md @@ -41,6 +41,7 @@ A component option that is executed **before** the component is created, once th attrs: Data slots: Slots emit: (event: string, ...args: unknown[]) => void + expose: (exposed?: Record) => void } function setup(props: Data, context: SetupContext): Data @@ -89,12 +90,37 @@ A component option that is executed **before** the component is created, once th setup() { const readersNumber = ref(0) const book = reactive({ title: 'Vue 3 Guide' }) - // Please note that we need to explicitly expose ref value here + // Please note that we need to explicitly use ref value here return () => h('div', [readersNumber.value, book.title]) } } ``` + If you return a render function then you can't return any other properties. If you need to expose properties so that they can be accessed externally, e.g. via a `ref` in the parent, you can use `expose`: + + ```js + // MyBook.vue + + import { h } from 'vue' + + export default { + setup(props, { expose }) { + const reset = () => { + // Some reset logic + } + + // If you need to expose multiple properties they must all + // be included in the object passed to expose. expose can + // only be called once. + expose({ + reset + }) + + return () => h('div') + } + } + ``` + - **See also**: [Composition API `setup`](../guide/composition-api-setup.html) ## Lifecycle Hooks diff --git a/src/api/options-composition.md b/src/api/options-composition.md index 602534013f..03c2f3f00f 100644 --- a/src/api/options-composition.md +++ b/src/api/options-composition.md @@ -294,7 +294,7 @@ The `setup` function is a new component option. It serves as the entry point for The `props` object is immutable for userland code during development (will emit warning if user code attempts to mutate it). - The second argument provides a context object which exposes a selective list of properties that were previously exposed on `this`: + The second argument provides a context object which exposes various objects and functions that might be useful in `setup`: ```js const MyComponent = { @@ -302,10 +302,13 @@ The `setup` function is a new component option. It serves as the entry point for context.attrs context.slots context.emit + context.expose } } ``` + `attrs`, `slots`, and `emit` are equivalent to the instance properties [`$attrs`](/api/instance-properties.html#attrs), [`$slots`](/api/instance-properties.html#slots), and [`$emit`](/api/instance-methods.html#emit) respectively. + `attrs` and `slots` are proxies to the corresponding values on the internal component instance. This ensures they always expose the latest values even after updates so that we can destructure them without worrying about accessing a stale reference: ```js @@ -319,6 +322,26 @@ The `setup` function is a new component option. It serves as the entry point for } ``` + `expose`, added in Vue 3.2, is a function that allows specific properties to be exposed via the public component instance. By default, the public instance retrieved using refs, `$parent`, or `$root` is equivalent to the internal instance used by the template. Calling `expose` will create a separate public instance with the properties specified: + + ```js + const MyComponent = { + setup(props, { expose }) { + const count = ref(0) + const reset = () => count.value = 0 + const increment = () => count.value++ + + // Only reset will be available externally, e.g. via $refs + expose({ + reset + }) + + // Internally, the template has access to count and increment + return { count, increment } + } + } + ``` + There are a number of reasons for placing `props` as a separate first argument instead of including it in the context: - It's much more common for a component to use `props` than the other properties, and very often a component uses only `props`. diff --git a/src/api/options-data.md b/src/api/options-data.md index 7c5425563f..502516b075 100644 --- a/src/api/options-data.md +++ b/src/api/options-data.md @@ -301,3 +301,39 @@ ::: * **See also:** [Attribute Inheritance](../guide/component-attrs.html#attribute-inheritance) + +## expose + +- **Type:** `Array` + +- **Details:** + + A list of properties to expose on the public component instance. + + By default, the public instance accessed via [`$refs`](/api/instance-properties.html#refs), [`$parent`](/api/instance-properties.html#parent), or [`$root`](/api/instance-properties.html#root) is the same as the internal component instance that's used by the template. The `expose` option restricts the properties that can be accessed via the public instance. + + Properties defined by Vue itself, such as `$el` and `$parent`, will always be available on the public instance and don't need to be listed. + +- **Usage:** + + ```js + export default { + // increment will be exposed but count + // will only be accessible internally + expose: ['increment'], + + data() { + return { + count: 0 + } + }, + + methods: { + increment() { + this.count++ + } + } + } + ``` + +- **See also:** [defineExpose](/api/sfc-script-setup.html#defineexpose) diff --git a/src/guide/composition-api-setup.md b/src/guide/composition-api-setup.md index e9312fae08..a9791a346b 100644 --- a/src/guide/composition-api-setup.md +++ b/src/guide/composition-api-setup.md @@ -64,21 +64,24 @@ setup(props) { ### Context -The second argument passed to the `setup` function is the `context`. The `context` is a normal JavaScript object that exposes three component properties: +The second argument passed to the `setup` function is the `context`. The `context` is a normal JavaScript object that exposes other values that may be useful inside `setup`: ```js // MyBook.vue export default { setup(props, context) { - // Attributes (Non-reactive object) + // Attributes (Non-reactive object, equivalent to $attrs) console.log(context.attrs) - // Slots (Non-reactive object) + // Slots (Non-reactive object, equivalent to $slots) console.log(context.slots) - // Emit Events (Method) + // Emit events (Function, equivalent to $emit) console.log(context.emit) + + // Expose public properties (Function) + console.log(context.expose) } } ``` @@ -88,13 +91,15 @@ The `context` object is a normal JavaScript object, i.e., it is not reactive, th ```js // MyBook.vue export default { - setup(props, { attrs, slots, emit }) { + setup(props, { attrs, slots, emit, expose }) { ... } } ``` -`attrs` and `slots` are stateful objects that are always updated when the component itself is updated. This means you should avoid destructuring them and always reference properties as `attrs.x` or `slots.x`. Also note that unlike `props`, `attrs` and `slots` are **not** reactive. If you intend to apply side effects based on `attrs` or `slots` changes, you should do so inside an `onUpdated` lifecycle hook. +`attrs` and `slots` are stateful objects that are always updated when the component itself is updated. This means you should avoid destructuring them and always reference properties as `attrs.x` or `slots.x`. Also note that, unlike `props`, the properties of `attrs` and `slots` are **not** reactive. If you intend to apply side effects based on changes to `attrs` or `slots`, you should do so inside an `onBeforeUpdate` lifecycle hook. + +We'll explain the role of `expose` shortly. ## Accessing Component Properties @@ -157,12 +162,35 @@ 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 + // Please note that we need to explicitly use ref value here return () => h('div', [readersNumber.value, book.title]) } } ``` +Returning a render function prevents us from returning anything else. Internally that shouldn't be a problem, but it can be problematic if we want to expose methods of this component to the parent component via template refs. + +We can solve this problem by calling `expose`, passing it an object that defines the properties that should be available on the external component instance: + +```js +import { h, ref } from 'vue' + +export default { + setup(props, { expose }) { + const count = ref(0) + const increment = () => ++count.value + + expose({ + increment + }) + + return () => h('div', count.value) + } +} +``` + +The `increment` method would then be available in the parent component via a template ref. + ## Usage of `this` **Inside `setup()`, `this` won't be a reference to the current active 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/style-guide/README.md b/src/style-guide/README.md index 4dd04b91f2..f0ed0158c5 100644 --- a/src/style-guide/README.md +++ b/src/style-guide/README.md @@ -1284,6 +1284,7 @@ This is the default order we recommend for component options. They're split into - `inheritAttrs` - `props` - `emits` + - `expose` 6. **Composition API** (the entry point for using the Composition API) - `setup`