Development tips commonly used in Vue - make development more convenient and fast - Summary

1. Limit a prop to a list of types

Using the {validator} option in the prop definition, you can limit a prop type to a specific set of values.

export default {
  name: 'Image',
  props: {
    src: {
      type: String,
    },
    style: {
      type: String,
      validator: s => ['square', 'rounded'].includes(s)
    }
  }
};

This validation function accepts a prop and returns true or false if the prop is valid or invalid.

I usually use this method when only passing in true or false to control that some conditions cannot meet the requirements.

Button type or warning type (information, success, danger, warning) is the most common usage. Color is also a good use.

2. Default content and extension point

Slots in Vue can have default content, which allows us to make components that are easier to use.

<button class="button" @click="$emit('click')">
  <slot>
    <!-- Used if no slot is provided -->
    Click me
  </slot>
</button>

My favorite way to use default slots is to use them to create extension points.

We can take any part of the component and package it in a slot. On the outside, we can cover that part of the component with whatever we want. By default, it will still work the same way, but there will be more options to do so

<template>
  <button class="button" @click="$emit('click')">
    <slot>
      <div class="formatting">
        {{ text }}
      </div>
    </slot>
  </button>
</template>

Now we can use this component in many different ways. Simple, default, or custom.

<!-- Uses default functionality of the component -->
<ButtonWithExtensionPoint text="Formatted text" />

<ButtonWithExtensionPoint>
  <div class="different-formatting">
    Do something a little different here
  </div>
</ButtonWithExtensionPoint>

3. Use quotation marks to listen for nested attributes

You may not know this. We can easily listen directly to nested values by using quotation marks:

watch {
  '$route.query.id'() {
    // ...
  }
}

4. Know when to use v-if (and when to avoid it)

Instead of using v-if, sometimes using v-show instead will have higher performance.

<ComplicatedChart v-show="chartEnabled" />

When v-if is turned on or off, it creates and completely destroys the element. Instead, v-show creates the element and leaves it there, hiding it by setting its style to display: none.

If the rendering cost of the component you want to switch is high, it will be more efficient.

Conversely, if you don't need to execute expensive components immediately, you can use v-if so that it will skip rendering it and make the page load faster.

5. Abbreviation of single scope slot (template label is not required)

Delimited slots are interesting, but in order to use them, you must also use many template tags.

Fortunately, there is a shorthand that allows us to get rid of it, but only if we use a single scope slot.

Common writing:

<DataTable>
  <template #header="tableAttributes">
    <TableHeader v-bind="tableAttributes" />
  </template>
</DataTable>

Do not use template:

<DataTable #header="tableAttributes">
  <TableHeader v-bind="tableAttributes" />
</DataTable>

Simple, straightforward and amazing.

6. Conditionally render slots

Let's look at how to do it first, and then discuss why we want to hide the slot.

Each Vue component has a special $slots object with all your slots. The key of the default slot is default, and any named slot uses its name as the key.

const $slots = {
  default: <default slot>,
  icon: <icon slot>,
  button: <button slot>,
};

However, the $slots object only applies to the slots of the component, not each defined slot.

Take this component with several slots defined, including several named slots.

<!-- Slots.vue -->
<template>
  <div>
    <h2>Here are some slots</h2>
    <slot />
    <slot name="second" />
    <slot name="third" />
  </div>
</template>

If we only apply one slot to the component, only that slot will be displayed in our $slots object.

<template>
  <Slots>
    <template #second>
      This will be applied to the second slot.
    </template>
  </Slots>
</template>
$slots = { second: <vnode> }

We can use this in our component to detect which slots have been applied to the component, for example, by hiding the packaging element of the slot.

<template>
  <div>
    <h2>A wrapped slot</h2>
    <div v-if="$slots.default" class="styles">
      <slot />
    </div>
  </div>
</template>

Now, the styled wrapper div is rendered only when we fill the slot with something.

If you do not use v-if, you will get an empty unnecessary div if there is no slot. Depending on the div style, this may disrupt our layout and make the interface look strange.

So why do we want to be able to render slots conditionally?

There are three main reasons for using conditional slots:

  • When using encapsulated div to add default style
  • The slot is empty
  • If we combine the default content with nested slots

For example, when we are adding a default style, we add a div around the slot:

<template>
  <div>
    <h2>This is a pretty great component, amirite?</h2>
    <div class="default-styling">
      <slot >
    </div>
    <button @click="$emit('click')">Click me!</button>
  </div>
</template>

However, if the parent component does not apply the content to the slot, we will eventually render an empty div on the page.

<div>
  <h2>This is a pretty great component, amirite?</h2>
  <div class="default-styling">
    <!-- There is nothing in the slot, but this div Still rendered. too bad -->
  </div>
  <button @click="$emit('click')">Click me!</button>
</div>

The solution is to judge multiple conditions as mentioned above.

7. How to monitor the change of a slot

Sometimes we need to know when the contents of the slot have changed.

<!-- Unfortunately, this event does not exist -->
<slot @change="update" />

Unfortunately, Vue has no built-in method for us to detect this.

However, my friend Austin came up with a very clean way to use MutationObserver to do this.

The MutationObserver interface provides the ability to monitor changes to the DOM tree. It is designed as a replacement for the old Mutation Events feature, which is part of the DOM3 Events specification.

export default {
  mounted() {
    // Call ` update when there are changes`
    const observer = new MutationObserver(this.update);

    // Listen for changes to this component
    observer.observe(this.$el, {
      childList: true,
      subtree: true
    });
  }
};

8. Mix local and global style s

In general, when dealing with styles, we want them to be divided into a separate component.

<style scoped>
  .component {
    background: green;
  }
</style>

However, you can also add a non scoped style block to add global styles if necessary

<style>
  /* overall situation */
  .component p {
    margin-bottom: 16px;
  }
</style>

<style scoped>
  /* Valid within this component */
  .component {
    background: green;
  }
</style>

But be careful, global styles are dangerous and difficult to track. But sometimes they are a perfect escape hatch, just what you need.

9. Rewrite the style of sub components -- the correct method

Scoped CSS is great at keeping content clean and tidy, and does not introduce styles into other components of the application.

But sometimes you need to override the style of a child component and jump out of this scope.

Vue has a {deep} selector:

<style scoped>
.my-component >>> .child-component {
  font-size: 24px;
}
</style>

Note: if you use CSS preprocessors like SCSS, you may need to use / deep / instead.

10. Create magic with context aware components

Context aware components (context aware) * are "magical". They automatically adapt to what happens around them, handle edge situations, state sharing, etc.

There are three main , context aware, but , Configuration , is the one I am most interested in.

1. Status sharing

When you decompose a large component into several small components, they often still need to share state.

We can do this "behind the scenes" instead of pushing it to users.

Generally, we will decompose the "drop down" component into "Select" and "Option" components, which will gain more flexibility. However, for ease of use, the Select and Option components share the selected state with each other.

<!-- For simplicity, use as a single component -->
<Dropdown v-model="selected" :options="[]" />

<!-- Multiple components, more flexible -->
<Select v-model="selected">
  <Option value="mustard">Mustard</Option>
  <Option value="ketchup">Ketchup</Option>
  <div class="relish-wrapper">
    <Option value="relish">Relish</Option>
  </div>
</Select>

2. Configuration

Sometimes, the behavior of a component needs to change depending on the rest of the application. This is usually to automatically handle edge situations, otherwise it will be annoying.

A Popup or Tooltip should be relocated so that it does not overflow the page. However, if the component is within a modal, it should be relocated so that it does not overflow the modal.

If the Tooltip knows that it is in a mode, this can be done automatically.

3. Style

CSS of {context aware is created, and different styles are applied according to the parent or peer elements.

.statistic {
  color: black;
  font-size: 24px;
  font-weight: bold;
}

.statistic + .statistic {
  margin-left: 10px;
}

CSS variables take us one step further, allowing us to set different values in different parts of a page.

11. How to create a responsive variable outside Vue (Vue2 and 3)

If you get a variable from outside Vue, it's good to make it reactive.

In this way, we can use it in computed props, watch and anywhere else, and it works like any other state in Vue.

If we use the option API, all we need is to put it in the data part of the component:

const externalVariable = getValue();

export default {
  data() {
    return {
      reactiveVariable: externalVariable,
    };
  }
};

If you use Vue3's composite API, you can use ref or reactive directly.

import { ref } from 'vue';

// It can be done completely outside the Vue component
const externalVariable = getValue();
const reactiveVariable = ref(externalVariable);

console.log(reactiveVariable.value);

Use "reactive" instead of:

import { reactive } from 'vue';

//  It can be done completely outside the Vue component
const externalVariable = getValue();
// reactive works only on objects and arrays
const anotherReactiveVariable = reactive(externalVariable);

// Access directly
console.log(anotherReactiveVariable);

If you are still using Vue2, you can use observable instead of reactive to achieve exactly the same results.

12. Deconstruction in V-for

Did you know that deconstruction can be used in - vfor?

<li
  v-for="{ name, id } in users"
  :key="id"
>
  {{ name }}
</li>

It is better known that indexes can be extracted from v-for by using such tuples.

<li v-for="(movie, index) in [
  'Lion King',
  'Frozen',
  'The Princess Bride'
]">
  {{ index + 1 }} - {{ movie }}
</li>

When using an object, you can use {key as follows:

<li v-for="(value, key) in {
  name: 'Lion King',
  released: 2019,
  director: 'Jon Favreau',
}">
  {{ key }}: {{ value }}
</li>

You can also combine the two methods to obtain the key and the index of the attribute.

<li v-for="(value, key, index) in {
  name: 'Lion King',
  released: 2019,
  director: 'Jon Favreau',
}">
  #{{ index + 1 }}. {{ key }}: {{ value }}
</li>

13. Cycle within the specified range

The v-for instruction allows us to traverse an array, but it also allows us to traverse a range

<template>
  <ul>
    <li v-for="n in 5">Item #{{ n }}</li>
  </ul>
</template>

Render results:

Item #1
Item #2
Item #3
Item #4
Item #5

When we use v-for with a range, it starts with 1 and ends with the number we specify.

14. Listen to anything in your component

export default {
  computed: {
    someComputedProperty() {
      // Update the computed prop
    },
  },
  watch: {
    someComputedProperty() {
      // Do something when the computed prop is updated
    }
  }
};

We can monitor:

  • Calculation properties
  • props
  • Nested values

If you use the composite API, any value can be monitored as long as it is a ref or reactive object.

15. Steal prop type

I copy prop types from a child component just to use them in a parent component. But I've found that stealing these prop types is much better than just copying them.

For example, we use an Icon component in this component.

<template>
  <div>
    <h2>{{ heading }}</h2>
    <Icon
      :type="iconType"
      :size="iconSize"
      :colour="iconColour"
    />
  </div>
</template>

In order for it to work, we need to add the correct prop type and copy it from the ` ` Icon 'component.

import Icon from './Icon';
export default {
  components: { Icon },
  props: {
    iconType: {
      type: String,
      required: true,
    },
    iconSize: {
      type: String,
      default: 'medium',
      validator: size => [
        'small',
        'medium',
        'large',
        'x-large'
      ].includes(size),
    },
    iconColour: {
      type: String,
      default: 'black',
    },
    heading: {
      type: String,
      required: true,
    },
  },
};

How painful.

When the {prop type of the} Icon} component is updated, we will certainly forget to return the component and update them. Over time, errors are introduced when the} prop type of the component begins to deviate from the prop type in the Icon component.

Therefore, this is why we want to steal the {prop} type of the component:

import Icon from './Icon';
export default {
  components: { Icon },
  props: {
    ...Icon.props,
    heading: {
      type: String,
      required: true,
    },
  },
};

It doesn't need to be any more complicated.

Except in our example, we add , icon , at the beginning of each , prop , name. So we must do some extra work to achieve this.

import Icon from './Icon';

const iconProps = {};

Object.entries(Icon.props).forEach((key, val) => {
  iconProps[`icon${key.toUpperCase()}`] = val;
});

export default {
  components: { Icon },
  props: {
    ...iconProps,
    heading: {
      type: String,
      required: true,
    },
  },
};

Now, if the {prop} type in the Icon component is modified, our component will remain up-to-date.

But what if a {prop} type is added or removed from the} Icon} component? In order to deal with these situations, we can use v-bind and a calculated , prop , to keep dynamic.

16. Detect external (or internal) clicks of elements

Sometimes I need to detect whether a click occurs inside or outside a specific element el. This is the method I usually use.

window.addEventListener('mousedown', e => {
  // Get the clicked element
  const clickedEl = e.target;
  
  if (el.contains(clickedEl)) {
   //Click in "el"
  } else {
   //Click outside "el"
  }
});

17. Recursive slot

Once, I decided to see if I could make a v-for component using only templates. In the process, I also found out how to use slots recursively.

<!-- VFor.vue -->
<template>
    <div>
        <!--  Render first item -->
    {{ list[0] }}
        <!-- If we have more projects, continue!But don't use the item we just rendered -->
    <v-for
      v-if="list.length > 1"
            :list="list.slice(1)"
        />
    </div>
</template>

If you want to do this with a scope slot, you just need some adjustments

<template>
  <div>
    <!-- Pass the item into the slot to be rendered -->
    <slot v-bind:item="list[0]">
      <!-- Default -->
      {{ list[0] }}
    </slot>

    <v-for
      v-if="list.length > 1"
      :list="list.slice(1)"
    >
      <!-- Recursively pass down scoped slot -->
      <template v-slot="{ item }">
        <slot v-bind:item="item" />
      </template>
    </v-for>
  </div>
</template>

Here is how to use this component.

<template>
  <div>
    <!-- General list -->
    <v-for :list="list" />

    <!-- Bold item list -->
    <v-for :list="list">
      <template v-slot="{ item }">
        <strong>{{ item }}</strong>
      </template>
    </v-for>
  </div>
</template>

18. Component metadata

Not every bit of information added to a component is a state. Sometimes we need to add some metadata to provide more information to other components.

For example, if you are working on an analytical instrument such as Google analytics:

If you want the layout to know how many columns each widget should occupy, you can add metadata directly to the component.

export default {
  name: 'LiveUsersWidget',
  //  Just add it as an additional attribute
  columns: 3,
  props: {
    // ...
  },
  data() {
    return {
      //...
    };
  },
};
export default {
  name: 'LiveUsersWidget',
  //   Just add it as an additional attribute
  columns: 3,
  props: {
    // ...
  },
  data() {
    return {
      //...
    };
  },
};

You will find that this metadata is an attribute on the component.

import LiveUsersWidget from './LiveUsersWidget.vue';
const { columns } = LiveUsersWidget;

We can also access metadata from within the component through the special $options attribute.

export default {
  name: 'LiveUsersWidget',
  columns: 3,
  created() {
    //  `$options` contains all the metadata for a component
    console.log(`Using ${this.$options.metadata} columns`);
  },
};

Just remember that this metadata is the same for every instance of the component and is not responsive.

Other uses in this regard include (but are not limited to):

  • Keep the version number of a single component
  • Custom flags for building tools to differentiate components
  • Add custom functions for components in addition to calculation properties, data, watch, etc
  • other

19. Multi document single document component

This is a known feature of SFC (single file component).

You can import files like regular HTML files:

<template src="./template.html"></template>
<script src="./script.js"></script>
<style scoped src="./styles.css"></style>

This can be very convenient if you need to share styles, files, or anything else.

20. Reusable components are not what you think

Reusable components are not necessarily large or complex.

I often make small and short components reusable.

Because I didn't rewrite this code everywhere, it's easier to update it, and I can make sure that each overflow menu looks and works exactly the same -- because they're the same! ".

<!-- OverflowMenu.vue -->
<template>
  <Menu>
    <!-- Add a custom button to trigger our menu   -->
    <template #button v-slot="bind">
      <!-- use bind To deliver click Handler a11y Properties, etc -->
      <Button v-bind="bind">
        <template #icon>
          <svg src="./ellipsis.svg" />
        </template>
      </Button>
    </template>
  </Menu>
</template>

Here, we take a menu component, but add an "ellipsis" icon on the button that triggers it. (ellipsis) to trigger its opening.

It doesn't seem worth making it a reusable component because it has only a few lines. Can't we add icons every time we want to use such a menu?

But this overflow menu will be used dozens of times. Now if we want to update the icon or its behavior, we can do it very easily. And it's easier to use.

21. Call a method from outside the component

We can call a method from outside a component by giving it a ref.

<!-- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
// Somewhere in Parent.vue
this.$refs.child.method();

Explain the problem again.

Sometimes "best practices" don't apply to what you're doing. You need an escape exit like this.

Typically, we use props and events to communicate between components. Props is distributed to the child component, and events are uploaded to the parent component.

<template>
  <ChildComponent
    :tell-me-what-to-do="someInstructions"
    @something-happened="hereIWillHelpYouWithThat"
  />
</template>
// Child.vue
export default {
  props: ['trigger'],
  watch: {
    shouldCallMethod(newVal) {
      if (newVal) {
        // Call the method when the trigger is set to `true`
        this.method();
      }
    }
  }
}

This works, but can only be used on the first call. If you need to trigger this operation multiple times, you must clean up and reset the status. Here's the logic

  • The parent component passes {true} to the trigger} prop
  • The Watch is triggered, and the Child component calls the method
  • The child component issues an event to tell the parent component that the method has been successfully triggered
  • The Parent component resets "trigger" to "false", so we can do it all over again

On the contrary, if we set a ref on the sub component, we can call the method directly:

<!-- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
// Somewhere in Parent.vue
this.$refs.child.method();

Yes, we broke the rule of "props down, events up". We broke the encapsulation, but it was clearer and easier to understand, so it was worth it

Sometimes the "best" solution will eventually become the worst solution.

22. Listen for arrays and objects

The trickiest part of using , watcher , is that sometimes it doesn't seem to trigger correctly.

Usually, this is because we try to listen to arrays or objects, but we don't set deep to true

export default {
  name: 'ColourChange',
  props: {
    colours: {
      type: Array,
      required: true,
    },
  },
  watch: {
    // Use object syntax, not just methods
    colours: {
      // This will let Vue know to look inside the array
      deep: true,

      handler()
        console.log('The list of colours has changed!');
      }
    }
  }
}

The API using Vue 3 would look like this:

watch(
  colours,
  () => {
    console.log('The list of colours has changed!');
  },
  {
    deep: true,
  }
);

23. Deep linking with Vue Router

We can store (a little) state in the URL, allowing us to jump directly to a specific state on the page.

For example, you load a page with a date range filter selected:

someurl.com/edit?date-range=last-week

This is great for the part of the application where users may share a large number of links. For the application rendered by the server, or the communication between two independent applications, more information is usually provided than ordinary links.

We can store filters, search values, whether the modal box is on or off, or where in the list to scroll to achieve infinite paging.

Using Vue router to get query parameters works like this (this also applies to most Vue frameworks, such as Nuxt and Vuepress):

const dateRange = this.$route.query.dateRange;

To change it, we use the} RouterLink} component and update} query.

<RouterLink :to="{
  query: {
    dateRange: newDateRange
  }
}">

24. Another use of template tag

The template tag can be used anywhere in the template to better organize the code.

I like to use it to simplify v-if logic, and sometimes v-for.

In this example, we have several elements that use the same v-if condition.

<template>
  <div class="card">
    <img src="imgPath" />
    <h3>
      {{ title }}
    </h3>
    <h4 v-if="expanded">
      {{ subheading }}
    </h4>
    <div
      v-if="expanded"
      class="card-content"
    >
      <slot />
    </div>
    <SocialShare v-if="expanded" />
  </div>
</template>

This is a bit clumsy and not obvious at first. A pile of such elements are displayed and hidden together. On a larger, more complex component, this could be a worse situation

But we can optimize it.

We can use the 'template' tag to group these elements and promote 'v-if' to the template 'template' itself.

<template>
  <div class="card">
    <img src="imgPath" />
    <h3>
      {{ title }}
    </h3>
    <template v-if="expanded">
      <h4>
        {{ subheading }}
      </h4>
      <div class="card-content">
        <slot />
      </div>
      <SocialShare />
    </template>
  </div>
</template>

Now it seems easier to understand, and what it is doing is clear at a glance.

25. Better ways to handle errors (and warnings)

We can provide a custom handler for errors and warnings in Vue.

// Vue 3
const app = createApp(App);
app.config.errorHandler = (err) => {
  alert(err);
};

// Vue 2
Vue.config.errorHandler = (err) => {
  alert(err);
};

Error tracking services like Bugsnag and Rollbar can hook these handlers to record errors, but you can also use them to handle errors more gracefully for a better user experience.

For example, if an error is not handled, the application will not crash directly. You can display a complete error screen and let the user refresh or try something else.

In Vue3, the error handler can only handle , template , and , watcher , errors, but Vue2's error handler can catch almost all errors. The warning handlers in both versions are only valid during the development phase.

Keywords: Vue

Added by kobmat on Tue, 21 Dec 2021 17:40:03 +0200