Skip to content

Commit ac5bec3

Browse files
docs: expand the page on render functions, especially their use of slots (#712)
* docs: expand the page on render functions, especially their use of slots * Update src/guide/render-function.md Co-authored-by: Ben Hong <[email protected]> * Update src/guide/render-function.md Co-authored-by: Ben Hong <[email protected]> * docs: add a note about global registration and using resolveComponent Co-authored-by: Ben Hong <[email protected]>
1 parent fa461e8 commit ac5bec3

File tree

1 file changed

+142
-10
lines changed

1 file changed

+142
-10
lines changed

src/guide/render-function.md

Lines changed: 142 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ const app = Vue.createApp({})
6262

6363
app.component('anchored-heading', {
6464
render() {
65-
const { h } = Vue
66-
67-
return h(
65+
return Vue.h(
6866
'h' + this.level, // tag name
6967
{}, // props/attributes
7068
this.$slots.default() // array of children
@@ -164,6 +162,8 @@ h(
164162
)
165163
```
166164

165+
If there are no props then the children can usually be passed as the second argument. In cases where that would be ambiguous, `null` can be passed as the second argument to keep the children as the third argument.
166+
167167
## Complete Example
168168

169169
With this knowledge, we can now finish the component we started:
@@ -240,6 +240,47 @@ render() {
240240
}
241241
```
242242

243+
## Creating Component VNodes
244+
245+
To create a VNode for a component, the first argument passed to `h` should be the component itself:
246+
247+
```js
248+
render() {
249+
return Vue.h(ButtonCounter)
250+
}
251+
```
252+
253+
If we need to resolve a component by name then we can call `resolveComponent`:
254+
255+
```js
256+
render() {
257+
const ButtonCounter = Vue.resolveComponent('ButtonCounter')
258+
return Vue.h(ButtonCounter)
259+
}
260+
```
261+
262+
`resolveComponent` is the same function that templates use internally to resolve components by name.
263+
264+
A `render` function will normally only need to use `resolveComponent` for components that are [registered globally](/guide/component-registration.html#global-registration). [Local component registration](/guide/component-registration.html#local-registration) can usually be skipped altogether. Consider the following example:
265+
266+
```js
267+
// We can simplify this
268+
components: {
269+
ButtonCounter
270+
},
271+
render() {
272+
return Vue.h(Vue.resolveComponent('ButtonCounter'))
273+
}
274+
```
275+
276+
Rather than registering a component by name and then looking it up we can use it directly instead:
277+
278+
```js
279+
render() {
280+
return Vue.h(ButtonCounter)
281+
}
282+
```
283+
243284
## Replacing Template Features with Plain JavaScript
244285

245286
### `v-if` and `v-for`
@@ -268,6 +309,8 @@ render() {
268309
}
269310
```
270311

312+
In a template it can be useful to use a `<template>` tag to hold a `v-if` or `v-for` directive. When migrating to a `render` function, the `<template>` tag is no longer required and can be discarded.
313+
271314
### `v-model`
272315

273316
The `v-model` directive is expanded to `modelValue` and `onUpdate:modelValue` props during template compilation—we will have to provide these props ourselves:
@@ -297,7 +340,7 @@ render() {
297340

298341
#### Event Modifiers
299342

300-
For the `.passive`, `.capture`, and `.once` event modifiers, they can be concatenated after event name using camel case.
343+
For the `.passive`, `.capture`, and `.once` event modifiers, they can be concatenated after the event name using camel case.
301344

302345
For example:
303346

@@ -306,7 +349,7 @@ render() {
306349
return Vue.h('input', {
307350
onClickCapture: this.doThisInCapturingMode,
308351
onKeyupOnce: this.doThisOnce,
309-
onMouseoverOnceCapture: this.doThisOnceInCapturingMode,
352+
onMouseoverOnceCapture: this.doThisOnceInCapturingMode
310353
})
311354
}
312355
```
@@ -346,34 +389,34 @@ render() {
346389

347390
### Slots
348391

349-
You can access slot contents as Arrays of VNodes from [`this.$slots`](../api/instance-properties.html#slots):
392+
We can access slot contents as Arrays of VNodes from [`this.$slots`](../api/instance-properties.html#slots):
350393

351394
```js
352395
render() {
353396
// `<div><slot></slot></div>`
354-
return Vue.h('div', {}, this.$slots.default())
397+
return Vue.h('div', this.$slots.default())
355398
}
356399
```
357400

358401
```js
359402
props: ['message'],
360403
render() {
361404
// `<div><slot :text="message"></slot></div>`
362-
return Vue.h('div', {}, this.$slots.default({
405+
return Vue.h('div', this.$slots.default({
363406
text: this.message
364407
}))
365408
}
366409
```
367410

368-
To pass slots to a child component using render functions:
411+
For component VNodes, we need to pass the children to `h` as an Object rather than an Array. Each property is used to populate the slot of the same name:
369412

370413
```js
371414
render() {
372415
// `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`
373416
return Vue.h('div', [
374417
Vue.h(
375418
Vue.resolveComponent('child'),
376-
{},
419+
null,
377420
// pass `slots` as the children object
378421
// in the form of { name: props => VNode | Array<VNode> }
379422
{
@@ -384,6 +427,95 @@ render() {
384427
}
385428
```
386429

430+
The slots are passed as functions, allowing the child component to control the creation of each slot's contents. Any reactive data should be accessed within the slot function to ensure that it's registered as a dependency of the child component and not the parent. Conversely, calls to `resolveComponent` should be made outside the slot function, otherwise they'll resolve relative to the wrong component:
431+
432+
```js
433+
// `<MyButton><MyIcon :name="icon" />{{ text }}</MyButton>`
434+
render() {
435+
// Calls to resolveComponent should be outside the slot function
436+
const Button = Vue.resolveComponent('MyButton')
437+
const Icon = Vue.resolveComponent('MyIcon')
438+
439+
return Vue.h(
440+
Button,
441+
null,
442+
{
443+
// Use an arrow function to preserve the `this` value
444+
default: (props) => {
445+
// Reactive properties should be read inside the slot function
446+
// so that they become dependencies of the child's rendering
447+
return [
448+
Vue.h(Icon, { name: this.icon }),
449+
this.text
450+
]
451+
}
452+
}
453+
)
454+
}
455+
```
456+
457+
If a component receives slots from its parent, they can be passed on directly to a child component:
458+
459+
```js
460+
render() {
461+
return Vue.h(Panel, null, this.$slots)
462+
}
463+
```
464+
465+
They can also be passed individually or wrapped as appropriate:
466+
467+
```js
468+
render() {
469+
return Vue.h(
470+
Panel,
471+
null,
472+
{
473+
// If we want to pass on a slot function we can
474+
header: this.$slots.header,
475+
476+
// If we need to manipulate the slot in some way
477+
// then we need to wrap it in a new function
478+
default: (props) => {
479+
const children = this.$slots.default ? this.$slots.default(props) : []
480+
481+
return children.concat(Vue.h('div', 'Extra child'))
482+
}
483+
}
484+
)
485+
}
486+
```
487+
488+
### `<component>` and `is`
489+
490+
Behind the scenes, templates use `resolveDynamicComponent` to implement the `is` attribute. We can use the same function if we need all the flexibility provided by `is` in our `render` function:
491+
492+
```js
493+
// `<component :is="name"></component>`
494+
render() {
495+
const Component = Vue.resolveDynamicComponent(this.name)
496+
return Vue.h(Component)
497+
}
498+
```
499+
500+
Just like `is`, `resolveDynamicComponent` supports passing a component name, an HTML element name, or a component options object.
501+
502+
However, that level of flexibility is usually not required. It's often possible to replace `resolveDynamicComponent` with a more direct alternative.
503+
504+
For example, if we only need to support component names then `resolveComponent` can be used instead.
505+
506+
If the VNode is always an HTML element then we can pass its name directly to `h`:
507+
508+
```js
509+
// `<component :is="bold ? 'strong' : 'em'"></component>`
510+
render() {
511+
return Vue.h(this.bold ? 'strong' : 'em')
512+
}
513+
```
514+
515+
Similarly, if the value passed to `is` is a component options object then there's no need to resolve anything, it can be passed directly as the first argument of `h`.
516+
517+
Much like a `<template>` tag, a `<component>` tag is only required in templates as a syntactical placeholder and should be discarded when migrating to a `render` function.
518+
387519
## JSX
388520

389521
If we're writing a lot of `render` functions, it might feel painful to write something like this:

0 commit comments

Comments
 (0)