Skip to content
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

Add the expose option and expose function #1251

Merged
merged 1 commit into from
Sep 25, 2021
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
28 changes: 27 additions & 1 deletion src/api/composition-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>) => void
}

function setup(props: Data, context: SetupContext): Data
Expand Down Expand Up @@ -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
Expand Down
25 changes: 24 additions & 1 deletion src/api/options-composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,18 +294,21 @@ 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 = {
setup(props, context) {
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
Expand All @@ -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`.
Expand Down
36 changes: 36 additions & 0 deletions src/api/options-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,39 @@
:::

* **See also:** [Attribute Inheritance](../guide/component-attrs.html#attribute-inheritance)

## expose <Badge text="3.2+" />

- **Type:** `Array<string>`

- **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)
42 changes: 35 additions & 7 deletions src/guide/composition-api-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
```
Expand All @@ -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

Expand Down Expand Up @@ -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.
1 change: 1 addition & 0 deletions src/style-guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down