Translator: Lara

The original link

There’s a popular saying about Vue: When React and Angular got together and had a baby, it was Vue. I’ve always felt that way. Since Vue has such a small learning curve, it’s not surprising that so many people like it. Vue has always tried to give developers as much control as possible over components and their implementations, which brings us to today’s topic.

The term renderless Components refers to components that do not render anything. In this article, we’ll show you how Vue handles rendering of a component.

We’ll also learn how to use the render() function to build non-render components.

To better understand this article, you might want to know a little bit about Vue. If you’re new to this, check out Sarah Drasner’s Got Your Back (https://css tricks.com/guides/vue/), and the official documentation is also a great resource.

Demystifying how Vue renders components

Vue has a number of ways to define component labels. There are:

  • Single-file components let us define components and their tags just as we would write a normal HTML file.

  • The template property of a component allows us to define the tag of the component using JavaScript template literals.

  • The component’s EL property lets Vue query the DOM for the label to use as a template.

You’ve probably heard (perhaps annoyingly) : _ At the end of the day, Vue and all its components are just JavaScript. Given the amount of HTML and CSS we write, I can see why you might think this is wrong. Case and Point: single-file components.

Using single-file components, we can define a Vue component as follows:

<template> <div class=""mood""> {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }} </div> </template> <script> export default { data: () => ({ todayIsSunny: true }) } </script> <style> .mood:after { content: '&#x1f389; &#x1f389; '; } </style>Copy the code

From the official language above, how can we say Vue is “just JavaScript”? But, in the end, Vue is. Vue does try to make it easier for our view to manage its style and other resource files, but Vue doesn’t do that directly — it hands it off to build processes like Webpack.

When Webpack encounters a.vue file, it runs it through a transformation process. During this process, CSS is extracted from the component and placed in its own file, and the rest of the file is converted to Javascript. Like this:

export default {
  template: 
    <div class=""mood"">
      {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
    </div>,
  data: () => ({ todayIsSunny: true })
}

Copy the code

Well… Not exactly what we said above. To understand what happens next, we need to say a little bit about the template compiler.

Template compiler and Render function

This part of the build process for Vue components is necessary to compile and run each optimization technique currently implemented by Vue.

When the template compiler encounters the following:

{ template: <div class=""mood"">... </div>, data: () => ({ todayIsSunny: true }) }Copy the code

… It extracts the template attribute and compiles its contents to JavaScript. Then add a Render function to the component object. This Render function, in turn, returns the content of the extracted template properties that have been converted to JavaScript.

This is what the template above looks like in the render function:

. render(h) { return h( 'div', { class: 'mood' }, this.todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' ) } ...Copy the code

See the official documentation for more information on the Render function.

Now, when the component object is passed to the Vue, the component’s render function is transformed into a VNode (virtual node) with some optimization. A VNode is the content passed into the SNabbDOM (the library inside the Vue that manages the virtual DOM). Sarah Drasner has a good explanation of the “h” in the render function mentioned above.

VNnode is the way Vue renders components. By the way, the Render function also allows us to use JSX in Vue!

We don’t have to wait for Vue to add the render function for us either — we can define the render function and it should take precedence over the EL or template attributes. Read here [to learn about the Render function and its options](vuejs.org/v2/guide/re… Function. HTML).

By generating Vue components through the Vue CLI or some custom build process, you don’t need to introduce a template compiler that increases the size of the build file. Your components will also be pre-optimized for superior performance and truly lightweight JavaScript files.

So… Vue components without rendering

As I mentioned, the term Renderless Components refers to a component that doesn’t render anything. So why would we want a component that doesn’t render anything?

We can boil it down to creating an abstraction of common component functionality as our own component, and extending the component to create better, even more robust components. _ also complies with the S.O.L.I.D principle.

According to the S.O.L.I.D. principle of single liability:

A class should have only one purpose.

By having only one responsibility per component, we can carry this concept all the way into Vue development.

You might say, like Nicky, “Yeah, yeah, I know.” Well, of course! You might write a component called “password-input” and actually render a password entry box. The problem with this approach, however, is that when you want to reuse this component in another project, you may have to modify the styles or labels in the component’s source code to be consistent with the new project’s style guide.

This breaks one of S.O.L.I.D. ‘s rules, the open and closed principle, which states that:

Under the open closed principle, a class or component should be extensible, but not modified.

That is, you should be able to extend components, not edit their source code.

Because Vue understands the S.O.L.I.D. principle, it allows components to have [props](vuejs.org/v2/guide/co… Props. HTML), events, [slots](vuejs.org/v2/guide/co… Slots. HTML). And Scoped slots to make component communication and extension easy. We can then build components that have all the functionality, without any style or markup. This is great for code reusability and efficiency.

Build the “Toggle” non-render component

This is simple and does not require setting up a Vue CLI project.

The Toggle component allows you to Toggle between on and off states. It also provides helper functions to switch between states. It is useful for building components like on/off (such as custom checkboxes) and anything that requires an ON /off state.

Let’s quickly stub our component: go to the JavaScript area of the CodePen Pen and continue.

// toggle.js const toggle = { props: { on: { type: Boolean, default: false } }, render() { return [] }, data() { return { currentState: this.on } }, methods: { setOn() { this.currentState = true }, setOff() { this.currentState = false }, toggle() { this.currentState = ! this.currentState } } }Copy the code

This component is very small and not yet complete. It requires a template, and since we don’t want this component to render anything, we have to make sure it works with any component that can render.

You can use slots!

Slots in the no render component

Slots allows us to place content between the start tag and the close tag of a Vue component. Like this:

<toggle>
  This entire area is a slot.
</toggle>

Copy the code

In the single-file component of Vue, we can define a slot like this:

<template>
  <div>
    <slot/>
  </div>
</template>

Copy the code

Well, if we use the render function to implement it, we can:

// toggle.js
render() {
  return this.$slots.default
}

Copy the code

We can automatically put things into our Toggle component, no tags, nothing.

Use Scoped Slots to send data up

In toggle.js, we have some on states and helper functions in the methods object. It would be great if we could make them available to developers. The slots we’re using now doesn’t allow us to expose anything in the child component.

What we want is Scoped slots. Scoped slots work exactly the same way as slots, with the added benefit that components with scoped slots can expose data without triggering events.

We can do this:

<toggle>
  <div slot-scope=""{ on }"">
    {{ on ? 'On' : 'Off' }}
  </div>
</toggle>

Copy the code

The slot-Scope attribute on the div you can see is deconstructing the object exposed from the Toggle component.

Going back to the render() function, we can do:

render() {
  return this.$scopedSlots.default({})
}

Copy the code

This time, we call the default attribute of the $scopedSlots object as a method, because the scope slot is a method that takes a parameter. In this case, the method name is the default because no name is specified for the scope slot and it is the only scope slot that exists. The parameters we pass to the scope slot can be exposed from the component. In our example, let’s expose the current state of ON and helper functions to help control that state.

render() {
  return this.$scopedSlots.default({
    on: this.currentState,
    setOn: this.setOn,
    setOff: this.setOff,
    toggle: this.toggle,
  })
}

Copy the code

Use the Toggle component

We can do this in CodePen. Here’s what we’re building:

Check out the code template written by Samuel Oloruntoba (@kayandrae07) on CodePen.

Here are the tags in action:

<div id=""app"">
  <toggle>
    <div slot-scope=""{ on, setOn, setOff }"" class=""container"">
      <button @click=""click(setOn)"" class=""button"">Blue pill</button>
      <button @click=""click(setOff)"" class=""button isRed"">Red pill</button>
      <div v-if=""buttonPressed"" class=""message"">
        <span v-if=""on"">It's all a dream, go back to sleep.</span>
        <span v-else>I don't know how far the rabbit hole goes, I'm not a rabbit, neither do I measure holes.</span>
      </div>
    </div>
  </toggle>
</div>

Copy the code
  1. First, we are deconstructing the state and helper functions in the scope slot.

  2. Then, in the scope slot, we create two buttons, one to switch the state to ON and one to switch the state to off.

  3. The click method is used only to ensure that the button is actually pressed before the result is displayed. You can see the click method below.

new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})

Copy the code

We can still pass properties and invoke events through the Toggle component. Using a scoped slot does not change anything.

new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})

Copy the code

This is a simple example, but we can see how powerful it can be when we start building components like Date Picker or AutoComplete. We can reuse these components across multiple projects without having to worry about pesky stylesheets getting in the way.

Another thing we can do is expose the properties needed for accessibility from Scoped Slot without worrying about making accessible components that extend that component.

From what has been discussed above

  • The render function of a component is extremely powerful.

  • Build faster running Vue components

  • Components’ EL, template, and even single-file components are compiled into render functions.

  • Try to build smaller components to get more reusable code.

  • Your code doesn’t have to follow S.O.L.I.D., but it’s a particularly good coding specification.

reference

  • Demystifying Vue.js internals

  • Vue component slots

  • The Trick to Understanding Scoped Slots in Vue.js by Adam Wathan

  • Renderless Components in Vue.js by Adam Wathan

  • WIP: a collection of Renderless Vue Components by Samuel Oloruntoba