SoFunction
Updated on 2025-04-11

Detailed explanation of Vue component reuse and extension

Overview

An important principle in software programming is (Don't Repeat Yourself), which is about trying to reuse code and logic to reduce duplication. Component extensions can avoid duplicate code and are easier to develop and maintain quickly. So, what is the best way to extend Vue components?

Vue provides a number of APIs and patterns to support component reuse and extension, and you can choose according to your purpose and preferences.

This article introduces several common methods and models, and I hope it will be helpful to you.

Is the extension component necessary

You should know that all component extension methods add complexity and extra code, and sometimes performance consumption.

Therefore, before deciding to extend components, it is best to see if there are other simpler design patterns that can achieve the goal.

The following component design patterns are usually sufficient to replace extension components:

  • props with template logic
  • slot slot
  • JavaScript tool functions

props with template logic

The easiest way is to implement the multifunctionality of components through props combined with template conditional rendering.

For example, through the type attribute:

<template>
  <div class="wrapper">
    <div v-if="type === 'a'">...</div>
    <div v-else-if="type === 'b'">...</div>
    <!--etc etc-->
  </div>
</template>
<script>
export default {
  props: { type: String },
  ...
}
</script>

When using components, you can achieve different results by passing different type values.

// **
<template>
  <MyVersatileComponent type="a" />
  <MyVersatileComponent type="b" />
</template>

If the following two situations occur, it means that this pattern is not applicable or the usage is incorrect:

  • The component combination pattern breaks down state and logic into atomic parts, making the application scalable. If there are a large number of conditional judgments in the component, readability and maintainability will become worse.
  • The original intention of props and template logic is to make components dynamic, but there is also runtime resource consumption. If you use this mechanism to solve the code combinatorial problem at runtime, it is an anti-pattern.

slot (slot)

Another way to avoid component expansion is to use slots, which is to let the parent component set custom content within the child component.

// **
<template>
  <div class="wrapper">
    <h3>Common markup</div>
    <slot />
  </div>
</template>
// **
<template>
  <MyVersatileComponent>
    <h4>Inserting into the slot</h4>
  </MyVersatileComponent>
</template>

Rendering result:

<div class="wrapper">
  <h3>Common markup</div>
  <h4>Inserting into the slot</h4>
</div>

This pattern has a potential constraint that elements within slot belong to the context of the parent component and may be less natural when splitting logic and state. scoped slot will be more flexible, and will be mentioned later in the section without rendering components.

JavaScript tool functions

If you only need to reuse independent functions between components, then you only need to extract these JavaScript modules, and you don’t need to use component extension mode at all.

JavaScript's module system is a very flexible and robust way to share code, so you should rely on it as much as possible.

export default function () {
  ...
}

import MyUtilityFunction from "./MyUtilityFunction";
export default {
  methods: {
    MyUtilityFunction
  }
}

Several modes for extending components

If you have considered the above simple modes, but these modes are not flexible enough to meet the needs. Then you can consider extending components.

There are four most popular ways to extend Vue components:

  • Composition function
  • mixin
  • Advanced Components (HOCs)
  • No rendering component

Each method has its advantages and disadvantages, and according to the usage scenario, there are more or less applicable parts.

Composition API

The latest solution to sharing state and logic among components is the Composition API. This is the API launched by Vue 3, and it can also be used as a plug-in in Vue 2.

Unlike the previous way of declaring data, computed, methods and other properties in the component definition configuration object, the Composition API declares and returns these configurations through a setup function.

For example, declare the Vue 2 configuration propertiesCounterThe component looks like this:

<template>
  <button @click="increment">
    Count is: {{ count }}, double is: {{ double }}
  </button>
<template>
<script>
export default {
  data: () => ({
    count: 0
  }),
  methods: {
    increment() {
      ++;
    }
  },
  computed: {
    double () {
      return  * 2;
    }
  }
}
</script>

Refactoring this component with the Composition API, the functions are exactly the same:

<template><!--as above--><template>
<script>
import { reactive, computed } from "vue";

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() =>  * 2)
    });

    function increment() {
      ++
    }

    return {
      count,
      double,
      increment
    }
  }
}
</script>

One of the main benefits of declaring components with the Composition API is that logical multiplexing and extraction becomes very easy.

Further refactoring, moving the counter function to the JavaScript module:

import { reactive, computed } from "vue";

export default function {
  const state = reactive({
    count: 0,
    double: computed(() =>  * 2)
  });

  function increment() {
    ++
  }

  return {
    count,
    double,
    increment
  }
}

The counter function can now be seamlessly introduced into any Vue component through the setup function:

<template><!--as above--></template>
<script>
import useCounter from "./useCounter";

export default {
  setup() {
    const { count, double, increment } = useCounter();
    return {
      count,
      double,
      increment
    }
  }
}
</script>

Composition functions make functions modular and reusable, and are the most direct and low-cost way to extend components.

Cons of Composition API

The drawbacks of the Composition API are nothing - it may be that they look a bit long-winded and the new usage is a bit strange to some Vue developers.

A discussion of the advantages and disadvantages of the Composition API, recommended reading: When To Use The New Vue Composition API (And When Not To)

mixin

If you are still using Vue 2, or just like to define component functions by configuring objects, you can use mixin mode. mixin extracts public logic and state to separate objects and merges them with internal definition objects using mixin components.

We continue to use the previous oneCounterComponent example, put public logic and state intoIn the module.

export default {
  data: () => ({
    count: 0
  }),
  methods: {
    increment() {
      ++;
    }
  },
  computed: {
    double () {
      return  * 2;
    }
  }
}

Using mixin is also very simple, just import the corresponding module and add variables to the mixins array. When component initialization, the mixin object will be merged with the internal definition object of the component.

import CounterMixin from "./CounterMixin";

export default {
  mixins: [CounterMixin],
  methods: {
    decrement() {
      --;
    }
  }
}

Option merge

What if the options within the component conflict with mixin?

For example, if you define a built-in increment method for the component, which one has higher priority?

import CounterMixin from "./CounterMixin";

export default {
  mixins: [CounterMixin],
  methods: {
    // Will the `increment` method of the included `increment` overwrite the `increment` of the mixin?    increment() { ... }
  }
}

At this time, we will talk about Vue's merge strategy. Vue has a series of rules that determine how to deal with options with the same name.

Usually, the options that come with the component will override the options from the mixin. But there are exceptions, such as the life cycle hooks of the same type, which are not directly covered, but are placed into an array and executed in order.

You can also change the default behavior by custom merge policies.

Disadvantages of mixin

As a model for extending components, mixin is quite easy to use for simple scenarios, and once the scale is expanded, the problem arises. Not only do you need to pay attention to naming conflict issues (especially third-party mixins), but using multiple mixin components, it is difficult to figure out where a certain function comes from, and it is also difficult to locate the problem.

Advanced Components

Advanced Components (HOCs) are concepts borrowed from React, and Vue can also be used.

To understand this concept, let's first look at two simple JavaScript functions, increment and double.

function increment(x) {
  return x++;
}

function double(x) {
  return x * 2;
}

Suppose we want to add a function to both functions: output the result in the console.

For this we can useAdvanced functionsMode, create a new addLogging function, accept the function as a parameter, and return a function with new functions.

function addLogging(fn) {
  return function(x) {
    const result = fn(x);
    ("The result is: ", result);
    return result;
  };
}

const incrementWithLogging = addLogging(increment);
const doubleWithLogging = addLogging(double);

How do components utilize this pattern? Similarly, we create a higher-order component to renderCounterComponent, and add a decrement method as an instance property.

The actual code is quite complicated, and here is only the pseudo-code as a schematic:

import Counter from "./Counter";

// pseudocodeconst CounterWithDecrement =&gt; ({
  render(createElement) {
    const options = {
      decrement() {
        --;
      }
    }
    return createElement(Counter, options);
  }
});

The HOC pattern is simpler and more scalable than mixin, but at the cost of adding a wrapper component, which also requires skills to implement.

No rendering component

If you need to use the same logic and state on multiple components, but the presentation method is different, then you can considerNo rendering componentmodel.

This pattern requires two types of components:logicComponents are used to declare logic and state,exhibitComponents are used to present data.

Logical Components

Or go backCounterFor example, suppose we need to reuse this component in multiple places, but the presentation method is different.

Create a logical component that defines logic, contains logic and state, but does not contain templates, but declares scoped slot through the render function.

scoped slot exposes three properties to the parent component: state count, method increment and computed attribute double.

export default {
  data: () => ({
    count: 0
  }),
  methods: {
    increment() {
      ++;
    }
  },
  computed: {
    double () {
      return  * 2;
    }
  },
  render() {
    return this.$({
      count: ,
      double: ,
      increment: ,
    })
  }
}

The scoped slot here is the key to the logical components in this pattern.

Showcase components

Next isShowcase components, as a user of rendering-free components, provides specific display methods.

All element tags are included in the scoped slot. It can be seen that these properties are no different from using them directly in logical components.

<template>
  <counter-renderless slot-scope="{ count, double, increment }">
    <div>Count is: {{ count }}</div> 
    <div>Double is: {{ double }}</div>
    <button @click="increment">Increment</button>
  </counter-renderless>
</template>
<script>
import CounterRenderless from "./CountRenderless";
export default {
  components: {
    CounterRenderless
  }
}
</script>

The render-free component mode is very flexible and easy to understand. However, it is not as general as the previous methods. There may be only one application scenario, which is to develop component libraries.

Template extension

Whether it is the API or design pattern above, there is a limitation, which is that the templates of components cannot be expanded. Vue has ways to reuse logic and state, but there is nothing to do with template tags.

There is a way to compare hacks, which is to use HTML preprocessors, such as Pug, to handle template extensions.

The first step is to create a basic template.pugFile, containing public page elements. Also include a block input as a placeholder for template extensions.


  h3 {{ myCommonProp }} <!--common markup-->
  block input <!--extended markup outlet -->

In order to expand this template, you need to install the Pug plugin for Vue Loader. Then you can introduce the basic template and use the block input syntax to replace the placeholder:

<template lang="pug">
  extends 
  block input
    h4 {{ myLocalProp }} <!--gets included in the base template-->
</template>

At first you might think that it is the same as slot concept, but there is a difference: the basic template here does not belong to any separate components. It merges with the current component at compile time, rather than being replaced at runtime like slot.

The above is a detailed explanation of the ways of Vue component reuse and extension. For more information about Vue component reuse and extension, please pay attention to my other related articles!