This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

🌊 author’s home page: Haichong 🌊 Author Profile: πŸ₯‡HDZ core group member, πŸ† full stack quality creator, 🌊 retained the top ten of the weekly list of STATION C fan welfare: fans send four books every week and various small gifts every month (enamel cup, pillow, mouse pad, mugs, etc.)

Today I bring you 25 Vue tips that I hope will help you

1. Restrict prop to a type list

Using the Validator option in the prop definition, you can restrict 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 the code

This validator function accepts a prop and returns true or false. It can also be used when you need more options than booleans allow. Button types or alert types (information, success, danger, warning) are some of the more common uses.

2. Default content and extension points

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

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

Basically you can take any part of the component, wrap it in a slot, and then you can overlay 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')">
    <! -- Add do nothing to slot tag at first -->
    <! -- We can override the slot by supplying it with content -->
    <slot>
      <div class="formatting">
        {{ text }}
      </div>
    </slot>
  </button>
</template>
Copy the code

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

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

<! Create custom behavior with extension points -->
<ButtonWithExtensionPoint>
  <div class="different-formatting">Let's do something different here</div>
</ButtonWithExtensionPoint>
Copy the code

3. Use quotation marks to observe nested values

You may not know this: it’s easy to view nested values directly by using quotes:

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

This is useful for dealing 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 the code

When v-IF is on and off, it completely creates and destroys elements. V-show is different in that it creates the element and leaves it there, hiding it by setting its style to display: None.

This is more efficient if you need to switch components that are expensive to render. On the other hand, if you don’t need that component right away, use V-if so that it skips rendering it and loads the page faster.

5. Short for single-scoped slot (no template tag required!)

Scoped Slot is interesting, but in order to use them, you also have to use a lot of template tags.

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

Instead of writing:

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

We can write this:

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

It’s simpler and more direct.

6. Conditionally render slot

Each Vue component has a special $slots object that contains all slots. Default slots have default keys, and named slots all use their names as keys:

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

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

Take the component that defines several slots, including several named slots:

<! -- Slots.vue -->
<template>
  <div>
    <h2>So here's some slots</h2>
    <slot />
    <slot name="second" />
    <slot name="third" />
  </div>
</template>
Copy the code

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

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

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

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

Now div, the wrapper that applies the style is only rendered if we actually fill that slot with something.

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

Why do we want to conditionally render slots?

Conditional slot is used for three reasons:

  1. Use the wrapperdivTo add a default style
  2. Slot is empty
  3. When we combine default content with nested slot

For example, when we add the 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>
Copy the code

However, if the parent component does not apply content to that slot, we end up rendering an empty: \ on the page div

<div>
  <h2>This is a great component</h2>
  <div class="default-styling">
    <! -- No content in slot, but still render div-->
  </div>
  <button @click="$emit('click')">Click me!</button>
</div>
Copy the code

V-if adds its div to the wrapper to solve the problem. Content not applied to slot? Like this: \

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

7. How do I observe slot changes

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

<! -- Too bad the event doesn't exist -->
<slot @change="update" />
Copy the code

Unfortunately, Vue doesn’t have a built-in way to detect this. A very neat way to do this is to use mutagepviewer:

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

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

8. Mix local and global styles

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

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

You can also add a non-scoped style block to add global styles if you want:

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

<style scoped>
  /* Scope is limited to this particular component */
  .component {
    background: green;
  }
</style>
Copy the code

9. Override the styles of child components — the right way

Scoped CSS is easier to keep clean and does not accidentally seep styles into other parts of your application. But sometimes you need to override the styles of child components and go beyond that range. Vue has a selector for deep specifically for this:

<style scoped>
/* Overrides the CSS of the child components while preserving the style range */
.my-component >>> .child-component {
  font-size: 24px;
}
</style>
Copy the 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 “magic” — they can automatically adapt to what’s going on around them, handle edge situations, state sharing, and so on. There are three main types of context-aware components, but I find configuration to be the most interesting.

1. Status sharing

When you break up a large component into multiple components, they often still need to share state. You can do this “behind the scenes” rather than pushing the job off to the people who use the components.

You can split a Dropdown component into Select and Option components to provide more flexibility. But 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 the code

2. The configuration

Sometimes you need to change the behavior of a component based on the rest of the application. This is usually done to automatically handle edge cases that would otherwise be cumbersome. Popup or Tooltip should reposition itself so that it doesn’t overflow the page. However, if the component is inside Modal, it should reposition itself so as not to overflow Modal. This can be done automatically if the Tooltip knows when it is in mode.

Modelling of 3.

When you create context-aware CSS, apply different styles depending on what happens in parent or sibling elements.

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

/* Do some separation between adjacent statistics */
.statistic + .statistic {
  margin-left: 10px;
}
Copy the code

CSS variables take us a step further and allow us to set different values in different parts of the page.

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

If you get a variable outside of Vue, it’s nice to be able to make it responsive. This way you can use it for calculating items, observers, and anywhere else, and it works just like any other state in Vue.

When you’re using the Options API, you just put it in the data component section:

const externalVariable = getValue();

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

When you use composite apis in Vue 3, you can use ref or Reactive:

import { ref } from 'vue';

// This can be done entirely outside of the Vue component
const externalVariable = getValue();
const reactiveVariable = ref(externalVariable);

// Access with.value
console.log(reactiveVariable.value);
Copy the code

Use reactive instead of: \

import { reactive } from 'vue';

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

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

If you’re still using Vue 2 (as many of us are), you can get exactly the same results using Observable instead of Reactive.

12. Deconstruct in V-for

Did you know you can deconstruct it in V-for?

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

As you know, you can get indexes from v-for using tuples like this:

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

When using objects, you can also grab keys:

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

You can also combine the two methods to get the key and index of an attribute:

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

13. Loop a range in Vue

The V-for directive allows us to iterate over an array, but it also lets us iterate over a range:

<template>
  <ul>
    <li v-for="n in 5">Project #{{n}}</li>
  </ul>
</template>
Copy the code

Display effect:

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

When we use the V-for range, it will start at 1 and end with the number we specify.

14. Observe anything in the component

Any response in your component can be observed:

export default {
  computed: {
    someComputedProperty() {
      // Update compute items}},watch: {
    someComputedProperty() {
      // Do something when calculating prop updates}}};Copy the code

You can see:

  • Calculation of props
  • The props
  • Nested values

If you use the composite API, you can monitor any value as long as it’s a REF or Reactive object.

15. Steal item types

Copy prop types from child components only to use them in parent components. But it’s much better to steal these item types than just copy them.

For example, we Icon use a component in this component:

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

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

When the Prop type of the Icon component is updated, you’re sure you’ll forget to go back to the component and update them. Over time, error ICONS are introduced as the prop type of this 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 the code

Except in our example, we added “icon” at the beginning of each item 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 the code

Now, if the prop type in the Icon component is changed, our component will be kept up to date.

But what if a prop type is added or removed from the Icon component? To cover these cases, we can use v-bind to calculate items to stay dynamic.

16. Detect clicks outside (or inside) the element

Sometimes we need to detect whether the click is inside or outside of a particular 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 for external clicks
  if (el.contains(clickedEl)) {
    // Click inside el
  } else {
    // Click outside of 'el'}});Copy the code

17. The recursive slots

Can we v-for make a component using only templates? Along the way, I discovered how to use slot recursively.

Here’s what the component looks like:

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

If you want to do this with scoped slot — why not? ! – Just make some adjustments:

<template>
  <div>
    <! -- Pass the item to 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)"
    >
      <! -- recursive pass down scope slot -->
      <template v-slot="{ item }">
        <slot v-bind:item="item" />
      </template>
    </v-for>
  </div>
</template>
Copy the code

Here’s how to use this component:

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

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

18. Component metadata

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

For example, if you’re building a bunch of different widgets for an Analytics dashboard like Google Analytics:

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

export default {
  name: 'LiveUsersWidget'.// πŸ‘‡ simply add it as an additional attribute
  columns: 3.props: {
    // ...
  },
  data() {
    return {
      / /...}; }};Copy the code

You’ll notice that this metadata is a property on the component:

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

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

export default {
  name: 'LiveUsersWidget'.columns: 3.created() {
    // πŸ‘‡ '$options' contains all metadata for the component
    console.log(`Using The ${this.$options.metadata} columns`); }};Copy the code

Remember that this metadata is the same for each instance of the component and is not reactive.

Other uses include (but are not limited to) :

  • Keep the version numbers of each component
  • Custom flags for building tools to treat components differently
  • Add custom functionality to components beyond counting items, data, observers, and so on.

19. Multi-file single-file component

This is a little-known feature of SFC. You can import the file as you would a regular HTML file:

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

This is handy if you need to share styles, documents, or anything else. It’s also great for super-long component files that wear out fingers from scrolling

Reusable components are not what you think

Reusable components don’t have to be big or complex things, and I often make small, short components reusable. Because I’m not rewriting this code around, it’s much easier to update it, and I can make sure that each OverflowMenu looks exactly as it works — because they’re the same!

<! -- OverflowMenu.vue -->
<template>
  <Menu>
    <! Add custom button to trigger our menu -->
    <template #button v-slot="bind">
      <! Use bind to pass click handlers, 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 the code

Here we use a Menu component, but add a “… “to the button that triggers it to open. (Ellipsis) icon. It’s probably not worth it to make reusable components because it’s only a few lines long. Can’t we just add ICONS every time we want to use something like Menu? But this OverflowMenu will be used dozens of times, and 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 the code

21. Call methods from outside the component

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

<! -- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
Copy the code
This.$refs.child.method(); // Parent.Copy the code

Typically, we use items and events to communicate between components. Items are sent to the child component and events are sent back to the parent component.

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

But sometimes you might run into situations where a parent component needs to trigger methods in a child component. This is where only downpass items don’t work. We can pass a Boolean down and have a child component monitor it:

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

This works fine, but only for the first call. If you need to trigger this operation more than once, you must clean up and reset the state. Then the logic looks like this:

  1. Parent component passingtruetotriggerprop
  2. The Watch is triggered, and the Child component calls the method
  3. The Child component emits an event to tell the Parent component that the method fired successfully
  4. Parent Component resettriggerBack to thefalse, so we can do it again

Ah.

Instead, if we set ref on the child component, we can call this method directly:

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

We broke the “props down, events up” rule, broke the encapsulation, but it was clearer and easier to understand and worth it!

Sometimes the “best” solution turns out to be the worst solution.

Look at arrays and objects

The trickiest part about using an observer is that sometimes it doesn’t seem to trigger correctly. This is usually because you are trying to view an array or an object and do not set deep to true:

export default {
  name: 'ColourChange'.props: {
    colours: {
      type: Array.required: true,}},watch: {
    // Use object syntax instead of 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 has changed! '); }}}}Copy the code

The reaction API using Vue 3 looks like this:

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

For more information, see the documentation for Vue 3 and Vue 2.

23. Deep link with Vue Router

You can store (some) states in urls that allow you to jump directly to specific states on a page.

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

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

This is useful for parts of an application where users may share a large number of links, applications rendered by the server, or for passing more information between two independent applications than is typically provided by regular links.

You can store filters, search values, whether the mode is on or off, or the location in the list we scroll to — great for unlimited paging.

Get the query using vue-router (this also works with most VUE frameworks like Nuxt and Vuepress) :

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

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

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

24. Another use for template tags

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 all 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 the code

It’s a little awkward and not obvious at first, a bunch of these elements are shown and hidden together. On larger, more complex components, this could be worse!

But we can work it out.

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

Bugsnag and Rollbar bug tracking services hook into these handlers to log errors, but you can also use them to handle errors more elegantly for a better user experience.

For example, if an error is not handled, the application not only crashes, it can display a full-page error screen and let the user refresh or try something else.

In Vue 3, the error handler applies only to templates and observing program errors, but the Vue 2 error handler catches almost everything. The warning handlers in both versions are development only.

Write it at the end

I’ve been writing tech blogs for a long time, mostly through nuggets, and this is my 25 Tips for Vue. I like to share technology and happiness through articles. You can visit my blog at juejin.cn/user/204034… For more information. Hope you like it! 😊

πŸ’Œ welcomes your comments and suggestions in the comments section! πŸ’Œ

The nuggets will be drawing 100 nuggets in the comments section after project Digital. see the event article for details