A case study to understand the scope slot of Vue.js

Scope slot is a very useful feature in Vue.js, which can significantly improve the universality and reusability of components. The problem is that it's really not easy to understand. It is no less painful to try to understand the intricate relationship between father and son scopes than to solve a difficult mathematical equation.

When you can't understand something, the best way is to experience its application in the process of solving problems. This article will show you how to use domain slots to build a reusable list component.

Note: The complete code can go Codepen See

The most basic components

The component we are going to build is called my-list, which is used to show a series of projects. What's special about it is that you can customize the rendering of list items every time you use components.

Let's start with the simplest single list: an array of geometric names and edges.

app.js

Vue.component('my-list', {
  template: '#my-list',
  data() {
    return {
      title: 'Shapes',
      shapes: [ 
        { name: 'Square', sides: 4 }, 
        { name: 'Hexagon', sides: 6 }, 
        { name: 'Triangle', sides: 3 }
      ]
    };
  }
});

new Vue({
  el: '#app'
});

index.html

<div id="app">
  <my-list></my-list>
</div>

<script type="text/x-template" id="my-list">
  <div class="my-list">
    <div class="title">{{ title }}</div>
    <div class="list">
      <div class="list-item" v-for="shape in shapes">
        <div>{{ shape.name }} <small>({{ shape.sides }} sides)</small></div>
      </div>
    </div>
  </div>
</script>

With a little style added, it will probably look like the following:

More general my-list

Now we want to make my-list more generic and render lists of any type. This time we're showing a bunch of color names and corresponding color squares.

To do this, we need to abstract the data unique to the above list. Since items in the list may have different structures, we will give my-list a slot where the parent component defines how the list is displayed.

app.js

Vue.component('my-list', {
  template: '#my-list',
  props: [ 'title' ]
});

index.html

<script type="text/x-template" id="my-list">
  <div class="my-list">
    <div class="title">{{ title }}</div>
    <div class="list">
      <slot></slot>
    </div>
  </div>
</script>

Now, we create two instances of my-list component in the root instance, showing two test case lists: lists:

app.js

new Vue({
  el: '#app',
  data: {
    shapes: [ 
      { name: 'Square', sides: 4 }, 
      { name: 'Hexagon', sides: 6 }, 
      { name: 'Triangle', sides: 3 }
    ],
    colors: [
      { name: 'Yellow', hex: '#F4D03F', },
      { name: 'Green', hex: '#229954' },
      { name: 'Purple', hex: '#9B59B6' }
    ]
  }
});
<div id="app">
  <my-list :title="Shapes">
    <div class="list-item" v-for="item in shapes">
      <div>{{ shape.name }} <small>({{ shape.sides }} sides)</small></div>
    </div>
  </my-list>
  <my-list :title="Colors">
    <div class="list-item" v-for="color in colors">
      <div>
        <div class="swatch" :style="{ background: color.hex }"></div>
        {{ color.name }}
      </div>
    </div>
  </my-list>
</div>

The effect is as follows:

Components for big and small use

The components we just created do meet the requirements, but that piece of code is not very good. my-list is supposed to be a component for displaying lists, but we abstract the logical part needed to render lists into the parent component, so that sub-components are just used to wrap lists here, which seems to be a waste of time.

Worse still, there is a lot of duplicate code in the declaration of the two components (for example, <div class= "list-item" v-for= "item in...">). If we can write these codes in subcomponents, then subcomponents will no longer be the "role of soy sauce".

Scope slot

Ordinary sockets can not meet our needs, then scope sockets will come into use. Scope slots allow you to pass a template instead of rendered elements to the slot. Scope slots are called because templates, although rendered in parent scopes, can get data from child components.

For example, a component child with a scoped slot might look like the following:

<div>
  <slot my-prop="Hello from child"></slot>
</div>

The parent component that uses this component will declare a template element in the slot. The template element will have a scope attribute pointing to an object, and any attribute added to the slot (located in the sub-component template) will be the attribute of the object.

<child>
  <template scope="props">
    <span>Hello from parent</span>
    <span>{{ props.my-prop }}</span>
  </template>
</child>

It will be rendered as:

<div>
  <span>Hello from parent</span>
  <span>Hello from child</span>
</div>

Use scope slots in my-list

We pass two list arrays through props to my-list. Then replace the general slot with the scope slot, so that my-list can be responsible for iterating the list items, while the parent component can still define the specific presentation of each item.

index.html

<div id="app">
  <my-list title="Shapes" :items="shapes">
    <!--Write here template-->
  </my-list>
  <my-list title="Colors" :items="colors">
    <!--Write here template-->
  </my-list>   
</div>

Then let's have my-list iterate over the project. In the v-for loop, item is an alias for the current iteration project. We can create a slot and bind that project to the slot through v-bind="item".

app.js

Vue.component('my-list', {
  template: '#my-list',
  props: [ 'title', 'items' ]
});

index.html

<script type="text/x-template" id="my-list">
  <div class="my-list">
    <div class="title">{{ title }}</div>
    <div class="list">
      <div v-for="item in items">
        <slot v-bind="item"></slot>
      </div>
    </div>
  </div>
</script>

Note: Maybe you haven't seen the use of v-bind without parameters before. This usage will bind all attributes of the entire object to the current element. This usage is common when it comes to scoped slots, because the bound objects may have many attributes, and it's obviously too cumbersome to list them one by one and bind them manually.

Now, back to the root instance, declare a template in my-list slot. Let's first look at the list of geometries (the list in the first example). The template we declare must have a scope attribute, which is assigned shape here. The alias shape allows us to access scoped slots. In the template, we can continue to use the markup in the original example to show the project.

<my-list title="Shapes" :items="shapes">
  <template scope="shape">
    <div>{{ shape.name }} <small>({{ shape.sides }} sides)</small></div>
  </template>
</my-list>

The whole template is roughly as follows:

<div id="app">
  <my-list title="Shapes" :items="shapes">
    <template scope="shape">
      <div>{{ shape.name }} <small>({{ shape.sides }} sides)</small></div>
    </template>
  </my-list>
  <my-list title="Colors" :items="colors">
    <template scope="color">
      <div>
        <div class="swatch" :style="{ background: color.hex }"></div>
        {{ color.name }}
      </div>
    </template>
  </my-list>   
</div>

conclusion

Although the amount of code does not decrease with the use of scoped slots, we leave the common functions to the sub-components, which significantly improves the robustness of the code.

Codepen with complete code is here:

https://codepen.io/anthonygor...

Translator's Note: Change slot-scope to v-slot after Vue.js 2.6.0

Tags: Javascript Vue Attribute less

Posted on Thu, 10 Oct 2019 06:08:41 -0700 by konky