These 25 Vue tips you need to know

Posted by Domcsore on Thu, 06 Jan 2022 01:12:18 +0100

1. Limit prop to type list

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

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

The validator function accepts a prop and returns true or false. You can also use it when you need more options than Boolean values allow. Button types or alarm types (information, success, danger, warning) are some of the more common uses.

2. Default content and extension point

Slots in Vue can have default content, which allows you to make easier to use components:

<button class="button" @click="$emit('click')">
  <slot>
    <!-- If not provided slot Then use -->
    Click me
  </slot>
</button>
Copy code

Basically, you can get any part of the component, wrap it in a slot, and then you can overwrite that part of the component with whatever you want. By default, it will still work as usual, and you have more options:

<template>
  <button class="button" @click="$emit('click')">
    <!-- At first slot Add nothing to the tag -->
    <!-- We can do this by slot Provide content to override it -->
    <slot>
      <div class="formatting">
        {{ text }}
      </div>
    </slot>
  </button>
</template>
Copy code

Now you can use this component in many different ways. Simple default method or your own custom method:

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

<!-- Create custom behavior using extension points -->
<ButtonWithExtensionPoint>
  <div class="different-formatting">
    Do something different here
  </div>
</ButtonWithExtensionPoint>
Copy code

3. Use quotation marks to observe nested values

You may not know this: you can easily view nested values directly by using quotation marks:

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

This is useful for working with deeply nested objects

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

Sometimes it is more efficient to use v-show instead of v-if:

<ComplicatedChart v-show="chartEnabled" />
Copy code

When v-if is turned on and off, it will completely create and destroy elements. The difference with v-show is that it creates an element and leaves it there, hiding it by setting its style to display: none.

If the components you need to switch are more expensive to render, it will be more efficient. On the other hand, if you don't need to use that component immediately, you can use v-if so that it skips rendering it and loads the page faster.

5. Abbreviation of single scope slot (no template label required!)

Scoped slot s are interesting, but in order to use them, you must also use many template tags.

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

You don't have to write this:

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

We can write this:

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

This is simpler and more direct.

6. Conditionally render the slot

Each Vue component has a special $slots object that contains all slots. The default slot has a default key. All named slots use their names as keys:

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

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

Take this component with several slots defined as an example, including several named slots:

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

If we only apply one slot to the component, only that slot will appear in our $slots object:

<template>
  <Slots>
    <template #second>
      This applies to the second slot
    </template>
  </Slots>
</template>
Copy code
$slots = { second: <vnode> }
Copy code

We can use it in our component to detect which slots have been applied to the component, for example, by hiding the wrapper element of the slot:\

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

Now div, the styled wrapper will only appear when we actually fill the slot with something.

If we don't use v-if, div, if we don't have a slot, we end up with an empty and unnecessary one. Depending on the style of the div, this may mess up our layout and make things look strange.

Why do we want to render slot s conditionally?

There are three main reasons for using the condition slot:

  1. When using wrapper div to add a default style
  2. slot is empty
  3. When we combine default content with nested slot s

For example, when we add a default style, we will 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>
Copy code

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

<div>
  <h2>This is a great component</h2>
  <div class="default-styling">
    <!-- slot There is no content in, but this will still be rendered div-->
  </div>
  <button @click="$emit('click')">Click me!</button>
</div>
Copy code

v-if can solve the problem by adding div to the package. No content applied to the slot? Like this:\

<div>
  <h2>This is a great component</h2>
  <button @click="$emit('click')">Click me!</button>
</div>
Copy code

7. How to observe the change of slot

Sometimes we need to know when the content in the slot has changed:

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

Unfortunately, Vue does not have a built-in method to detect this. Using the mutation observer is a very concise method:

export default {
  mounted() {
    // Call ` update when things change`
    const observer = new MutationObserver(this.update);

    // Observe the change of this component
    observer.observe(this.$el, {
      childList: true,
      subtree: true
    });
  }
};
Copy code

8. Mix local and global styles

Generally, when using styles, we want them to be limited to a single component:

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

If you need to, you can also add a non scope style block to add global styles:

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

<style scoped>
  /*The scope is limited to this specific component*/
  .component {
    background: green;
  }
</style>
Copy code

9. Style of cover assembly - correct method

Scoped CSS is easier to keep clean and tidy and does not accidentally infiltrate styles into other parts of the application. But sometimes you need to cover the style of the cover assembly and break through that range. Vue has a deep selector dedicated to this:

<style scoped>
/* Cover the CSS of the cover component while maintaining the style range*/
.my-component >>> .child-component {
  font-size: 24px;
}
</style>
Copy code

Note: if you are using CSS preprocessors such as SCSS, you may need to use / deep / instead.

10. Create magic with context aware components

Context aware components are "magical" - they can automatically adapt to what is happening around them, deal with edge situations, state sharing, and so on. There are three main types of context aware components, but I think configuration is the most interesting one.

1. Status sharing

When you decompose a large component into multiple widgets, they usually still need to share state. You can do this "behind the scenes" rather than pushing the work to the people who use the components.

A Dropdown component can be decomposed into Select and Option components to provide greater flexibility. However, for ease of use, the Select and Option components share the selected state with each other:

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

<!-- Split for greater flexibility -->
<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>
Copy code

2. Configuration

Sometimes you need to change the behavior of components based on the rest of the application. This is usually done to automatically handle edge conditions, otherwise it will be troublesome. Popup or Tooltip should reposition itself so that it does not overflow the page. However, if the component is inside a modal, it should reposition itself to avoid overflowing the modal. This can be done automatically if the Tooltip knows when it is in mode.

3. Modeling

When you create context aware CSS, apply different styles according to what happens in the parent element or sibling element.

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

/* Some separation is made between adjacent statistics*/
.statistic + .statistic {
  margin-left: 10px;
}
Copy code

Variables in CSS let us go a step further, allowing us to set different values in different parts of the page.

11. How do I make variables created outside Vue responsive?

If you get a variable from outside Vue, it's good to make it responsive. So you can use it in computing props, observers and anywhere else, and it works like any other state in Vue.

When you are using the options API, you just need to put it in the data component:

const externalVariable = getValue();

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

When you use the composite API in Vue 3, you can use ref or reactive to:

import { ref } from 'vue';

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

// use. value access
console.log(reactiveVariable.value);
Copy code

Use reactive instead of:\

import { reactive } from 'vue';

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

// Direct access
console.log(anotherReactiveVariable);
Copy code

If you are still using Vue 2 (like many of us), you can use observable instead of reactive to get exactly the same results.

12. Deconstruction in v-for

Did you know that you can deconstruct in v-for?

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

As we all know, you can use such tuples to get indexes from v-for:

<li v-for="(value, key) in [
  'Hai Yong',
  'Frozen',
  'Web Beginner'
]">
  {{ index + 1 }} - {{ value }}
</li>
Copy code

When using objects, you can also grasp the key:

<li v-for="(value, key) in {
  name: 'Hai Yong',
  released: 2021,
  director: 'A blogger',
}">
  {{ key }}: {{ value }}
</li>
Copy code

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

<li v-for="(value, key, index) in {
  name: 'Hai Yong',
  released: 2021,
  director: 'A blogger',
}">
  #{{ index + 1 }}. {{ key }}: {{ value }}
</li>
Copy code

13. Cycle through a range in Vue

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">project#{{ n }}</li>
  </ul>
</template>
Copy code

Display effect:

  • Project #1
  • Project #2
  • Item #3
  • Project #4
  • Project #5

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

14. Observe anything in the assembly

Any response in your component can be observed:

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

You can see:

  • Calculation props
  • prop
  • Nested values

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

15. Types of stolen props

prop types are copied from child components just to use them in the parent component. But stealing these types of props is much better than just copying them.

For example, Icon uses a component in this component:

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

To make it work, we need to add the correct prop type and copy it from 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,
    },
  },
};
Copy code

When the prop type of Icon component is updated, you are sure you will forget to go back to the component and update them. Over time, the error Icon will be introduced as the prop type of the component begins to deviate from the prop type in the component.

So that's why we steal them:

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

Except in our example, we added "icon" at the beginning of each prop name. So we have to do some extra work to achieve this:

import Icon from './Icon';

const iconProps = {};

// Do some processing beforehand
Object.entries(Icon.props).forEach((key, val) => {
  iconProps[`icon${key[0].toUpperCase()}${key.substring(1)}`] = val;
});

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

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? To cover these situations, we can use v-bind calculation props to keep dynamic.

16. Detect external (or internal) clicks of elements

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

window.addEventListener('mousedown', e => {
  // Get the clicked element
  const clickedEl = e.target;

  // `el ` is the element you are detecting external clicks
  if (el.contains(clickedEl)) {
    // Click "el" inside
  } else {
    // Click outside ` el '
  }
});
Copy code

17. Recursive slot

Can we v-for just use templates to make a component? In the process, I discovered how to use slot recursively.

This is what the component looks like:

<!-- VFor.vue -->
<template>
    <div>
        <!-- Render first item -->
    {{ list[0] }}
        <!-- If we have more projects to continue, we need to leave the project we just rendered -->
    <v-for
      v-if="list.length > 1"
            :list="list.slice(1)"
        />
    </div>
</template>
Copy code

If you want to do this with the scope slot - why not Only some adjustments are needed:

<template>
  <div>
    <!-- Transfer the project to the to render slot in -->
    <slot v-bind:item="list[0]">
      <!-- Default -->
      {{ list[0] }}
    </slot>

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

The following is how to use this component:

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

    <!-- List with bold items -->
    <v-for :list="list">
      <template v-slot="{ item }">
        <strong>{{ item }}</strong>
      </template>
    </v-for>
  </div>
</template>
Copy code

18. Component metadata

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

For example, if you want to build a bunch of different widgets for analysis dashboards such as Google Analytics:

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

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

You will find that this metadata is a property on the component:

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

You can also access metadata from within the component through the special $options attribute:

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

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

Other uses include (but are not limited to):

  • Keep the version number of each component
  • Custom flags for building tools to differentiate components
  • Add custom functions to components beyond calculating props, data, observers, etc.

19. Multi document single document component

This is a little-known function of SFC. You can import a file like a regular HTML file:

<!-- "single" File component -->
<template src="./template.html"></template>
<script src="./script.js"></script>
<style scoped src="./styles.css"></style>
Copy code

This is convenient if you need to share styles, documents, or anything else. It is also very suitable for ultra long component files that wear fingers due to rolling

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 won't rewrite this code everywhere, it's much 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 Pass Click handler a11y Properties, etc. -->
      <Button v-bind="bind">
        <!-- Use our own“..."Icon, this button has no text -->
        <template #icon>
          <svg src="./ellipsis.svg" />
        </template>
      </Button>
    </template>
  </Menu>
</template>
Copy code

Here we use a Menu component, but add a "..." on the button that triggers it to open (ellipsis) icon. It may not be worth using it to make reusable components because it only has a few lines. Every time we want to use Menu, can't we just add icons? 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 easily. And it's much easier to use!

<template>
  <OverflowMenu
    :menu-items="items"
    @click="handleMenuClick"
  />
</template>
Copy code

21. Calling methods from outside the component

You can call the method ref from outside the component by giving it:

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

Typically, we use props and events to communicate between components. The prop is sent to the child component, and the event is sent back to the parent component.

<template>
  <ChildComponent
    :tell-me-what-to-do="someInstructions"
    @something-happened="hereIWillHelpYouWithThat"
  />
</template>
Copy code

But sometimes you may encounter situations where you need a parent component to trigger a method in a child component. This is where only passing down props doesn't work. You can pass a Boolean value down and let the subcomponent monitor it:

<!-- Parent.vue -->
<template>
  <ChildComponent :trigger="shouldCallMethod" />
</template>
Copy code
// Child.vue
export default {
  props: ['trigger'],
  watch: {
    shouldCallMethod(newVal) {
      if (newVal) {
        // This method is called when the trigger is set to 'true'
        this.method();
      }
    }
  }
}
Copy code

This works, but only for the first call. If you need to trigger this action multiple times, you must clean up and reset the state. Then the logic looks like this:

  1. The Parent component passes true to triggerprop
  2. The Watch is triggered and the Child component calls the method
  3. The Child component sends an event to tell the Parent component that the method has been successfully triggered
  4. The Parent component resets the trigger back to false, so we can do this again

Ah.

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

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

We broke the "props down, events up" rule and broke the encapsulation, but it is clearer and easier to understand, which is worth doing!

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

22. Observe arrays and objects

The trickiest part of using an observer is that sometimes it doesn't seem to trigger correctly. Generally, it is because you try to view an array or an object, but do not 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,

      // We must move our method to the handler field
      handler()
        console.log('Color list changed!');
      }
    }
  }
}
Copy code

The reactive API using Vue 3 looks like this:

watch(
  colours,
  () => {
    console.log('Color list changed!');
  },
  {
    deep: true,
  }
);
Copy code

23. Deep link with Vue Router

You can store (some) states in the URL, allowing you to jump directly to a specific state on the page.

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

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

This is useful for users who may share a large number of linked application parts, server rendered applications, or pass more information between two independent applications than conventional links usually provide.

You can store filters, search values, whether the mode is on or off, or where we scroll to in the list - perfect for unlimited paging.

Use Vue router to obtain queries (this also applies to most Vue frameworks such as Nuxt and Vuepress):

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

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

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

24. Another use of template labels

The template tag can be used anywhere within 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>
Copy code

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

But we can solve this problem.

We can use the template tag to group these elements and promote v-if them to the template tag 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>
Copy code

25. Better ways to handle errors (and warnings)

You can provide custom handlers 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);
};
Copy code

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

For example, if the error is not handled, the application will not only crash, but also display a full page of the error screen and let the user refresh or try other operations.

In Vue 3, the error handler applies only to template and watcher errors, but the Vue 2 error handler captures almost everything. The warning handlers in both versions are for development only

I hope it can help you!

For those who need to get free materials, add a little assistant vx: soxwv # to get materials for free!

Topics: Java Back-end Programmer architecture