Vue. No rendering behavior slot in JS

In this article, we discuss what problems can be solved by the no rendering slot mode in vue.

In Vue The scope slot introduced in JS 2.3.0 significantly improves the reusability of components. The rendering free component pattern came into being, which solves the problem of providing reusable behavior and pluggable representation.

Here, we will see how to solve the opposite problem: how to provide reusable appearance and pluggable behavior.

 

No rendering components

This pattern is suitable for components with complex behavior and customizable representation.

It meets the following functions:

  1. This component implements all behaviors
  2. The slot of the scope is responsible for rendering
  3. Backup content ensures that components can be used directly.

For example: a component of a slot that executes an Ajax request and displays the results. The component handles Ajax requests and loads status, while the default slot provides a demonstration.

This is a simplified implementation:

<template>
  <div>
    <slot v-if="loading" name="loading">
        <div>Loading ...</div>
    </slot>
    <slot v-else v-bind={data}>
    </slot>
  </div>
</template>

<script>
export default {
  props: ["url"],
  data: () => ({
    loading: true,
    data: null
  }),
  async created() {
    this.data = await fetch(this.url);
    this.loading = false;
  }
};
</script>

Usage:

<lazy-loading url="https://server/api/data">
  <template #default="{ data }">
    <div>{{ data }}</div>
  </template>
</lazy-loading>

See the original article on this pattern here.

 

An opposite question

What if the problem is reversed: imagine if the main feature of a component is its representation, and its behavior should be customizable.

Suppose you want to create a tree component based on SVG. You want to provide the display and behavior of SVG, such as retracting nodes and highlighting text when clicking.

Problems arise when you plan to not hard code these behaviors and let the users of the component freely override them.

A simple solution to exposing these behaviors is to add methods and events to components.

You might do this:

<script>
export default {
  mounted() {
    // pseudo code
    nodes.on('click',(node) => this.$emit('click', node));
  },
  methods: {
    expandNode(node) {
      //...
    },
    retractNode(node) {
      //...
    },
    highlightText(node) {
      //...
    },
  }
};
</script>

If the user of the component wants to add behavior to the component, you need to use ref in the parent component, for example:

<template>
  <tree ref="tree" @click="onClick"></tree>
</template>

<script>
export default {
  methods: {
    onClick(node) {
      this.$refs.tree.retractNode(node);
    }
  }
};
</script>

This method has several disadvantages:

  1. Default behavior can no longer be provided
  2. Behavior code will eventually be copied and pasted frequently
  3. Behavior is not reusable

Let's look at how no rendering slots solve these problems.

Resource search website Encyclopedia http://www.szhdn.com Guangzhou VI design companyhttps://www.houdianzi.com

No rendering slots

Behavior basically involves demonstrating a response to an event. So let's create a slot to receive access to events and component methods:

<template>
  <div>
    <slot name="behavior" :on="on" :actions="actions">
    </slot>
  </div>
</template>

<script>
export default {
  methods: {
    expandNode(node) { },
    retractNode(node) { },
   //...
  },
  computed:{
    actions() {
      const {expandNode, retractNode} = this;
      return {expandNode, retractNode};
    },
    on() {
      return this.$on.bind(this);
    }
  }
};
</script>

The on attribute is the parent component's $on method, so you can listen for all events.

You can implement the behavior as a non rendering component. Next, write the click extension component:

export default {
  props: ['on','action']

  render: () => null,

  created() {
    this.on("click", (node) => {
      this.actions.expandNode(node);
    });
  }
};

Usage:

<tree>
  <template #behavior="{ on, actions }">
    <expand-on-click v-bind="{ on, actions }"/>
  </template>
</tree>

The main advantages of this solution are:

  • The possibility of providing default behavior through alternative content:

For example, by declaring a graphic component as:

<template>
  <div>
    <slot name="behavior" :on="on" :actions="actions">
      <expand-on-click v-bind="{ on, actions }"/>
    </slot>
  </div>
</template>
  • It can create reusable components and implement the standard behavior that users using this component can choose

Consider a hover highlight component:

export default {
  props: ['on','action']

  render: () => null,

  created() {
    this.on("hover", (node) => {
      this.actions.highlight(node);
    });
  }
};

Override standard behavior:

<tree>
  <template #behavior="{ on, actions }">
    <highlight-on-hover v-bind="{ on, actions }"/>
  </template>
</tree>
  • Behavior slots are composable

Add two predefined behaviors:

<tree>
  <template #behavior="{ on, actions }">
    <expand-on-click v-bind="{ on, actions }"/>
    <highlight-on-hover v-bind="{ on, actions }"/>
  </template>
</tree>
  • Readability of solution

As a component of behavior, it can be self described.

  • Scalability

The on property can access all component events. By default, new events are available for this slot.

Tags: Framework

Posted by purpendicular on Sat, 07 May 2022 03:19:28 +0300