简体   繁体   中英

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.

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://v2.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? */
}

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 Unhandled error during execution of scheduler flush. This is likely a Vue internals bug

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM