This is an excerpt from my upcoming course Reusable Components

We all want to write less code, but get more done.

To make this happen, we build our components so they can be reused more than just once.

Some components only need basic reusability.

Others need far more complex techniques to get the most out of them.

I've identified 6 different levels of reusability, but there may be more that I've missed.

Here is a basic overview of the levels. My upcoming course, Reusable Components, explores each one and how to get the most out of them.

1. Templating

Instead of copy + pasting code around everywhere, with this technique we wrap it up inside it's own component.

When we reuse the component โ€”ย and not the code directly โ€”ย it gives us two benefits:

  1. Making changes in the future is much easier to do, since we only have to do it one place
  2. We don't have to remember the dozens (or hundreds) of places we've copied that code to

This is the most basic, and most often talked about form of reusability.

But the higher levels get a lot more interesting...

2. Configuration

With some components we'll need to have variations on how they work.

A Button component might have a primary version, as well as an icon-only version. But instead of creating entirely new components for each of these versions, we use props to switch between the different types.

Adding in these props doesn't usually add much to a component, but gives us far more flexibility in how that component can be used. Neat-o.

Note: This is different than using props for state or data, such as a loading prop or a disabled prop.

3. Adaptability

The biggest problem with configuration is lack of foresight. You need to anticipate future needs and build them into the component by putting in those props.

But if you make your component adaptable, it can allow for use cases that were never even thought of โ€” without needing to change the component.

We do this by passing a chunk of markup from the parent to the component using a slot.

For example, instead of using a text prop in a Button component, we can use the default slot:

<!-- Button.vue -->
<template>
  <button
    class="btn btn--default"
    @click="$emit('click')"
  >
    <slot />
  </button>
</template>

Now we're not limited to just passing a string or number in.

If we wanted to add in a loading spinner without having to modify our Button component, we can do that:

<template>
  <Button>
    <img
      v-if="loading"
      src="spinner.svg"
    />
    Click Me
  </Button>
</template>

4. Inversion

Instead of passing a complete chunk of markup to our child component, we can pass a set of instructions for how to render.

This is like following a recipe instead of ordering takeout. When you follow a recipe it's a little more work, but you have total control over what you're making. You can tweak things as you go, or throw out the recipe altogether.

We use scoped slots to add even more flexibility to our components.

5. Extension

With Adaptability and Inversion we have the necessary techniques to maximize the reusability of our components.

The next step is to apply these techniques all throughout our component so we can more easily extend it's behaviour.

We use named slots to add in one or more extension points in our component. Where Adaptability and Inversion on their own only give us one option to extend behaviour, having multiple extension points gives us many different options.

Here we have a Modal component with a header, default, and a footer:

<template>
  <div class="modal">
    <slot name="header">
      <h2>{{ title }}</h2>
    </slot>

    <!-- Default slot for main content -->
    <slot />

    <slot name="footer">
      <Button @click="closeModal">
        Close
      </Button>
    </slot>
  </div>
</template>

This is a fairly simple example of Extension, but we already have several options for extending this component:

  1. Just override the default slot to add in our content
  2. Add in content but also override the header slot
  3. Content and just the footer slot to add different buttons
  4. Content and both header and footer slots for something more custom

(We could add more if we didn't use the default slot, but that's unlikely here)

You don't have to extend the behaviour of this component, or you can extend parts of it. Either way, you get a lot of flexibility and a lot of code reuse.

6. Nesting

We'll take Extension to it's conclusion by passing these extension points through one or more layers of components.

It may sound crazy at first, but it's extremely useful, especially in medium to large applications.

You start with a base component that's fairly general in what it does. The next component is a little more specific, extending the base component in a few ways. Then on and on until you have the final component that does the real work.

It's exactly how we go from a very general Animal to a more specific Mammal and then Dog and eventually land on a Poodle. If all we need is a Poodle component, we're wasting our time here, but in large applications we need lots of variations on the same basic idea.

We can extend the Dog component to get a Corgi and a Beagle component. Or extend the Mammal component to get a Cat component, which lets us then add in Tiger and Lion components.

This is the most advanced application of reusability that I have come across. I use this technique a lot in my own work.

Conclusion

These are the 6 levels of reusability that I've come across.

I might have missed some, and I certainly wouldn't say that this is an exhaustive list, but it's complete enough to be useful.

A short article like this doesn't do them justice, but I go into them in much more depth in my upcoming course, Reusable Components.

Subscribe to my email list in order to get weekly updates and previews of the course!