VueJS - Render Function
Vue 3 - Render Functions & JSX
Section titled “Vue 3 - Render Functions & JSX”While Vue’s templates are powerful and cover most use cases, sometimes you need the full programmatic power of JavaScript to create dynamic components. This is where render functions come in. Vue 3 provides the h function (short for hyperscript) for creating virtual DOM nodes within render functions or the setup function.
Render functions are typically used in advanced scenarios or when authoring highly reusable components that need more flexibility than templates offer.
Why Use Render Functions?
Section titled “Why Use Render Functions?”- Full programmatic control over component structure.
- Creating components where the exact HTML tag needs to be dynamic based on props.
- Performance optimizations in very specific, rare cases (though Vue’s template compiler is highly optimized).
- Integrating with libraries that expect JavaScript functions for rendering.
For most applications, templates using Single File Components (SFCs) are the recommended and more maintainable approach.
The h Function
Section titled “The h Function”The h function is the core of render functions. It’s typically imported from ‘vue’.
import { h } from 'vue'
// h(type, props, children)
// type: String (HTML tag), Object (Component), Function (Async Component)// props: Object with attributes, props, event listeners etc.// children: String (text) or Array of VNodes (other h() calls) or Slot functionExample: Dynamic Heading Component
Section titled “Example: Dynamic Heading Component”Let’s recreate a component similar to the original example, but using Vue 3’s h function. This component will render a heading (h1 to h6) with dynamic text content and styling.
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Vue 3 Render Function Example</title> <script src="https://unpkg.com/vue@3"></script></head><body> <div id="app"> <dynamic-heading :level="1" style="color: blue;">Main Title</dynamic-heading> <dynamic-heading :level="3" class="sub-title">Subtitle</dynamic-heading> <dynamic-heading :level="currentLevel" :style="dynamicStyle">Dynamic Level Heading</dynamic-heading> <button @click="increaseLevel">Increase Level (Current: {{ currentLevel }})</button> </div>
<script> const { createApp, h, ref, computed } = Vue;
const app = createApp({ setup() { const currentLevel = ref(4); const dynamicStyle = computed(() => ({ color: currentLevel.value > 3 ? 'red' : 'green', borderBottom: '1px solid black' }));
function increaseLevel() { if (currentLevel.value < 6) { currentLevel.value++; } else { currentLevel.value = 1; // Cycle back } } return { currentLevel, dynamicStyle, increaseLevel }; } });
// Dynamic Heading Component using Render Function app.component('dynamic-heading', { props: { level: { // Heading level (1-6) type: Number, required: true, validator: (value) => value >= 1 && value <= 6 } // Other attributes like 'style', 'class' are passed automatically // via $attrs in render function context }, setup(props, { slots, attrs }) { // Access props, slots, attrs // Define the render function within setup return () => { // Return the render function itself const tag = `h${props.level}`; // Calculate the tag name
// Use h() to create the VNode return h( tag, // Element type attrs, // Pass down non-prop attributes (like style, class) slots.default ? slots.default() : [] // Render default slot content ); }; } // --- Options API Equivalent --- /* props: { level: { type: Number, required: true } }, render() { const tag = `h${this.level}`; return h( tag, this.$attrs, // Pass attributes this.$slots.default ? this.$slots.default() : [] // Access slots ); } */ });
app.mount('#app'); </script> <style> .sub-title { font-style: italic; color: gray; } </style></body></html>Explanation
Section titled “Explanation”- We import
hfrom Vue (or access it via the globalVue.hin CDN examples). - The
dynamic-headingcomponent accepts alevelprop (1-6). - In the Composition API
setupfunction, we return another function. This returned function is the actual render function. - Inside the render function, we dynamically determine the tag name (e.g.,
'h1','h2') based on theprops.level. - We call
h(tag, attrs, slots.default()): tag: The dynamically determined HTML tag string.attrs: This object (provided by the second argumentcontextinsetup, destructured as{ attrs }) contains any attributes passed to the component that are not declared as props (likestyleandclassin this example). Passing these down ensures styles and classes applied on the component tag work as expected.slots.default(): This accesses the default slot content passed between the component’s tags (e.g., ‘Main Title’). We call it as a function to get the array of VNodes for the slot content. We add a check (slots.default ? ... : []) in case no slot content is provided.- The Options API equivalent uses the
render()method directly, accessing props viathis.level, attributes viathis.$attrs, and slots viathis.$slots.
JSX Alternative
Section titled “JSX Alternative”Vue also supports using JSX (JavaScript XML) within render functions, which can feel more like writing templates. This requires build tool configuration (like adding @vitejs/plugin-vue-jsx for Vite).
// Example using JSX (requires build setup)import { defineComponent } from 'vue';
export default defineComponent({ props: { level: { type: Number, required: true } }, setup(props, { slots, attrs }) { const Tag = `h${props.level}`; // Note: Capitalized variable for JSX tag return () => ( <Tag {...attrs}> { slots.default ? slots.default() : '' } </Tag> ); }});JSX allows writing HTML-like syntax directly in JavaScript, which is compiled into h function calls. Notice the dynamic tag name needs to be stored in a capitalized variable (Tag) for JSX to recognize it as a dynamic component/tag.
Conclusion
Section titled “Conclusion”Render functions and JSX offer maximum flexibility when templates fall short. However, they are more verbose and less declarative than templates. Use them judiciously for complex dynamic rendering scenarios or when building highly configurable low-level components.
For more details, see the official documentation on Render Functions & JSX.