- The Vue Instance
- Template Syntax
- Computed Properties and Watchers
- Class and Style Bindings
- Conditional Rendering
- List Rendering
- Event Handling
- Form Input Bindings
- Components Basics
- Component Registration
- Custom Events
- Dynamic & Async Components
- Handling Edge Cases
Transitions & Animation
- Enter/Leave & List Transitions
- State Transitions
Reusability & Composition
- Custom Directives
- Render Functions & JSX
- Single File Components
- TypeScript Support
- Production Deployment
- State Management
- Server-Side Rendering
- Reactivity in Depth
- Migration from Vue 1.x
- Migration from Vue Router 0.7.x
- Migration from Vuex 0.6.x to 1.0
- Migration to Vue 2.7
- Comparison with Other Frameworks
- Join the Vue.js Community!
- Meet the Team
Render Functions & JSX
Let’s dive into a simple example where a
render function would be practical. Say you want to generate anchored headings:
For the HTML above, you decide you want this component interface:
When you get started with a component that only generates a heading based on the
level prop, you quickly arrive at this:
That template doesn’t feel great. It’s not only verbose, but we’re duplicating
<slot></slot> for every heading level and will have to do the same when we add the anchor element.
While templates work great for most components, it’s clear that this isn’t one of them. So let’s try rewriting it with a
Much simpler! Sort of. The code is shorter, but also requires greater familiarity with Vue instance properties. In this case, you have to know that when you pass children without a
v-slot directive into a component, like the
Hello world! inside of
anchored-heading, those children are stored on the component instance at
$slots.default. If you haven’t already, it’s recommended to read through the instance properties API before diving into render functions.
Before we dive into render functions, it’s important to know a little about how browsers work. Take this HTML for example:
When a browser reads this code, it builds a tree of “DOM nodes” to help it keep track of everything, just as you might build a family tree to keep track of your extended family.
The tree of DOM nodes for the HTML above looks like this:
Every element is a node. Every piece of text is a node. Even comments are nodes! A node is just a piece of the page. And as in a family tree, each node can have children (i.e. each piece can contain other pieces).
Updating all these nodes efficiently can be difficult, but thankfully, you never have to do it manually. Instead, you tell Vue what HTML you want on the page, in a template:
Or a render function:
And in both cases, Vue automatically keeps the page updated, even when
Vue accomplishes this by building a virtual DOM to keep track of the changes it needs to make to the real DOM. Taking a closer look at this line:
createElement actually returning? It’s not exactly a real DOM element. It could perhaps more accurately be named
createNodeDescription, as it contains information describing to Vue what kind of node it should render on the page, including descriptions of any child nodes. We call this node description a “virtual node”, usually abbreviated to VNode. “Virtual DOM” is what we call the entire tree of VNodes, built by a tree of Vue components.
The next thing you’ll have to become familiar with is how to use template features in the
createElement function. Here are the arguments that
One thing to note: similar to how
v-bind:style have special treatment in templates, they have their own top-level fields in VNode data objects. This object also allows you to bind normal HTML attributes as well as DOM properties such as
innerHTML (this would replace the
With this knowledge, we can now finish the component we started:
All VNodes in the component tree must be unique. That means the following render function is invalid:
If you really want to duplicate the same element/component many times, you can do so with a factory function. For example, the following render function is a perfectly valid way of rendering 20 identical paragraphs:
map in a render function:
There is no direct
v-model counterpart in render functions - you will have to implement the logic yourself:
This is the cost of going lower-level, but it also gives you much more control over the interaction details compared to
.once event modifiers, Vue offers prefixes that can be used with
For all other event and key modifiers, no proprietary prefix is necessary, because you can use event methods in the handler:
|Modifier(s)||Equivalent in Handler|
Here’s an example with all of these modifiers used together:
You can access static slot contents as Arrays of VNodes from
And access scoped slots as functions that return VNodes from
To pass scoped slots to a child component using render functions, use the
scopedSlots field in VNode data:
If you’re writing a lot of
render functions, it might feel painful to write something like this:
Especially when the template version is so simple in comparison:
That’s why there’s a Babel plugin to use JSX with Vue, getting us back to a syntax that’s closer to templates:
h is a common convention you’ll see in the Vue ecosystem and is actually required for JSX. Starting with version 3.4.0 of the Babel plugin for Vue, we automatically inject
const h = this.$createElement in any method and getter (not functions or arrow functions), declared in ES2015 syntax that has JSX, so you can drop the
(h) parameter. With prior versions of the plugin, your app would throw an error if
h was not available in the scope.
The anchored heading component we created earlier is relatively simple. It doesn’t manage any state, watch any state passed to it, and it has no lifecycle methods. Really, it’s only a function with some props.
In cases like this, we can mark components as
functional, which means that they’re stateless (no reactive data) and instanceless (no
this context). A functional component looks like this:
Note: in versions before 2.3.0, the
propsoption is required if you wish to accept props in a functional component. In 2.3.0+ you can omit the
propsoption and all attributes found on the component node will be implicitly extracted as props.
The reference will be HTMLElement when used with functional components because they’re stateless and instanceless.
In 2.5.0+, if you are using single-file components, template-based functional components can be declared with:
Everything the component needs is passed through
context, which is an object containing:
props: An object of the provided props
children: An array of the VNode children
slots: A function returning a slots object
scopedSlots: (2.6.0+) An object that exposes passed-in scoped slots. Also exposes normal slots as functions.
data: The entire data object, passed to the component as the 2nd argument of
parent: A reference to the parent component
listeners: (2.3.0+) An object containing parent-registered event listeners. This is an alias to
injections: (2.3.0+) if using the
injectoption, this will contain resolved injections.
functional: true, updating the render function of our anchored heading component would require adding the
context argument, updating
context.children, then updating
Since functional components are just functions, they’re much cheaper to render.
They’re also very useful as wrapper components. For example, when you need to:
- Programmatically choose one of several other components to delegate to
- Manipulate children, props, or data before passing them on to a child component
Here’s an example of a
smart-list component that delegates to more specific components, depending on the props passed to it:
On normal components, attributes not defined as props are automatically added to the root element of the component, replacing or intelligently merging with any existing attributes of the same name.
Functional components, however, require you to explicitly define this behavior:
context.data as the second argument to
createElement, we are passing down any attributes or event listeners used on
my-functional-button. It’s so transparent, in fact, that events don’t even require the
If you are using template-based functional components, you will also have to manually add attributes and listeners. Since we have access to the individual context contents, we can use
data.attrs to pass along any HTML attributes and
listeners (the alias for
data.on) to pass along any event listeners.
You may wonder why we need both
slots().default be the same as
children? In some cases, yes - but what if you have a functional component with the following children?
For this component,
children will give you both paragraphs,
slots().default will give you only the second, and
slots().foo will give you only the first. Having both
slots() therefore allows you to choose whether this component knows about a slot system or perhaps delegates that responsibility to another component by passing along
You may be interested to know that Vue’s templates actually compile to render functions. This is an implementation detail you usually don’t need to know about, but if you’d like to see how specific template features are compiled, you may find it interesting. Below is a little demo using
Vue.compile to live-compile a template string: