'Vue how to fallback to default rendering with the render function?
Looking at this simple hello world html page
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<title>Hello World</title>
</head>
<body>
<div id="app">
Message is {{ message }}
</div>
<script>
new Vue({
el: "#app",
data: {
message: "Hello World!"
}
})
</script>
</body>
</html>
When served as a static page it will show the Message is Hello World!
text on the page. If I add a render
function to the Vue declaration
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<title>Hello World</title>
</head>
<body>
<div id="app">
Message is {{ message }}
</div>
<script>
new Vue({
el: "#app",
data: {
message: "Hello World!"
},
render: function (h) {
return h("p", "Rendered " + this.message);
}
})
</script>
</body>
</html>
It will show Rendered Hello World!
inside a <p>
element, erasing whatever was present inside the <div>
element. Now my question is, what if I want the render function to perform the default rendering under certain conditions, ie. show the Message is Hello World!
text? Something like
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<title>Hello World</title>
</head>
<body>
<div id="app">
Message is {{ message }}
</div>
<script>
new Vue({
el: "#app",
data: {
message: "Hello World!"
},
render: function (h) {
if (this.message === "Hello World!") {
/* do something to make the render function fallback to default rendering? */
} else {
return h("p", "Rendered " + this.message);
}
}
})
</script>
</body>
</html>
I'm aware that I can achieve this kind of effects in different ways, I'm just wondering if it's possible to tell Vue to revert back to the default fallback rendering mechanism inside the render function, or not?
====== EDIT ======
Just to clarify some more, I'm NOT looking for solutions like this
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<title>Hello World</title>
</head>
<body>
<div id="app">
Message is {{ message }}
</div>
<script>
new Vue({
el: "#app",
data: {
message: "Hello World!"
},
render: function (h) {
if (this.message === "Hello World!") {
return h("p", "Message is " + this.message);
} else {
return h("p", "Rendered " + this.message);
}
}
})
</script>
</body>
</html>
I'm wondering if somehow you can halt the render function without returning any rendered content and make Vue to show the default rendering like there's no render function declared at all.
Solution 1:[1]
You could compile the template string yourself. It can be accessed by instance property $el.innerHTML
. Then you could call Vue.compile
to compile the template string.
More about compile: https://vuejs.org/v2/api/#Vue-compile
Somethink like this:
if (this.message === 'Hello World!') {
return Vue.compile(this.$el.outerHTML).render.call(this, h)
/* do something to make the render function fallback to default rendering? */
}
Solution 2:[2]
I put together something for a vue 2 app I'm working on by investigating how Vue internally handles rendering
Used https://github.com/vuejs/vue/blob/dev/src/core/instance/render.js as reference
created () {
const baseRender = this.$options.render;
this.$options.render = function (h) {
if (this.SOME_LOADING_CONDITION_FOR_EXAMPLE === 0) {
return h("h1", "Loading...")
}
return baseRender.call(this._renderProxy, h)
}
}
Note: Even though it is calling vm.$options.render
, the component doesn't need to have a custom render function for this to work, it works with components with a template
UPDATE: New solution for vue3 because the above solution doesn't work for vue3
I developed a replacement by modifying this.$.render
on the fly then calling $forceUpdate()
(is necessary to call this otherwise it will be stuck on the initial replacement value)
async created() {
const renderBackup = this.$.render;
this.$.render = function () {
return h("div", {key: Symbol()}, "Loading component")
}
try {
await someAsyncOperation();
this.$.render = renderBackup;
this.$forceUpdate();
} catch (error) {
this.$.render = function () {
return h("div", {key: Symbol()}, "Problem loading component")
}
this.$forceUpdate();
throw error;
}
}
Notes:
- Instead of
$forceUpdate
and replacing the function, you can also use a data property and check for the loading state inside the render function to return different elements. I tested it and it works. - With either case, {key: Symbol()} is necessary if the top level elements are the same between rendered objects otherwise there is an error saying
Unhandled error during execution of scheduler flush. This is likely a Vue internals bug
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |