34
Author: Michael Thiessen
Translator: Frontend Xiaozhi
Source: dev

There are dreams and dry goods. WeChat search [Great Move to the World] Follow the brushing wisdom who is still doing dishes in the early morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.

Learning to become a better Vue developer is not always about big concepts that take time and effort to master. Mastering some tips and tricks can make our programming life easier-without a lot of repetitive work.

In the past few years of development with Vue, I have learned a lot of useful skills. Some are tricky, some are used almost every day, and some are more advanced-but they are all very useful.

1. Restrict a prop to a list of types

validator option in the prop definition, a prop type can be restricted to a set of specific values.

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

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

When only passing true or false to control certain conditions that cannot meet the demand, I usually use this method to do it.

The 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 points

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 to use the default slots is to use them to create extension points.

We can take any part of the component and encapsulate it in a slot, and on the outside we can cover that part of the component with whatever content we want. By default, it will still work the same way, but doing so will have more options

<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 way, or custom way.

<!-- 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 quotes to monitor nested attributes

You may not know this, we can easily monitor nested values directly by using quotes:

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, there will be higher performance.

<ComplicatedChart v-show="chartEnabled" />

When v-if is opened or closed, it will create and completely destroy the element. Instead, v-show will create the element and leave it there, hiding it display: none

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

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. Shorthand for a single scope slot (no template tag required)

Restricted slots are very 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 scoped slot.

Ordinary writing:

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

Without using template :

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

Simple, straightforward, and amazing.

6. Conditionally render slots

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

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

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

But this $slots object only has slots applicable to the component, not every defined slot.

Take this component that defines several slots, 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 apply only one slot to the component, then 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 wrapper div applied style will only be rendered when we fill this slot with something.

v-if is not used, then if there is no slot, you will get an empty and unnecessary div . According to div , this may disrupt our layout and make the interface look strange.

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

There are three main reasons for using conditional slots:

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

For example, when we are adding 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>

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">
    <!-- 槽中没有内容,但这个div 仍然被渲染。糟糕 -->
  </div>
  <button @click="$emit('click')">Click me!</button>
</div>

The solution is to judge with multiple conditions as mentioned above.

7. How to monitor the changes of a slot

Sometimes we need to know when the content of the slot has changed.

<!-- 可惜这个事件不存在 -->
<slot @change="update" />

Unfortunately, Vue does not have a built-in method for us to detect this.

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

The MutationObserver interface provides the ability to monitor changes made 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() {
    // 当有变化时调用`update`
    const observer = new MutationObserver(this.update);

    // 监听此组件的变化
    observer.observe(this.$el, {
      childList: true,
      subtree: true
    });
  }
};

There is still a lot of content involved. I will post a separate article later, remember that brush bowl wisdom <Da > Oh 161073c9169700!

8. Mix the local and global style

Normally, when dealing with styles, we want them to be divided into a single component.

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

However, if needed, you can also add a non-scoped style block to add a global style

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

<style scoped>
  /* 在该组件内有效 */
  .component {
    background: green;
  }
</style>

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

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

Scoped CSS is great at keeping the content tidy and does not introduce styles to 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 a CSS preprocessor like SCSS, you may need to use /deep/ instead.

10. Create magic with context-aware components

Context-aware components (context-aware) are "magic", they automatically adapt to what's happening around them, handle edge situations, state sharing, and so on.

There are 3 main types of context-aware , but Configuration is the one I am most interested in.

1. Status sharing

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

We can do these tasks "behind the scenes" instead of pushing them to users.

We generally decompose the Dropdown Select and Option components, which will gain more flexibility. But for ease of use, the Select and Option components share the selected state with each other.

<!-- 为简单起见,作为一个单一组件使用 -->
<Dropdown v-model="selected" :options="[]" />

<!-- 分多个组件,更灵活 -->
<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 be changed based on other parts of the application. This is usually to automatically handle edge cases, otherwise it would be annoying to handle them.

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

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

3. Style

A CSS of context-aware was created, and different styles were applied according to the parent or sibling 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 on different parts of a page.

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

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

This way, we can computed props , watch and anywhere else, and it works just 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 combination API, you can directly use ref or reactive .

import { ref } from 'vue';

// 可以完全在Vue组件之外完成
const externalVariable = getValue();
const reactiveVariable = ref(externalVariable);

console.log(reactiveVariable.value);

Use reactive instead:

import { reactive } from 'vue';

//  可以完全在Vue组件之外完成
const externalVariable = getValue();
// reactive 只对对象和数组起作用
const anotherReactiveVariable = reactive(externalVariable);

// Access directly
console.log(anotherReactiveVariable);

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

12. Deconstruction in v-for

Did you know that you can use deconstruction -vfor

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

It is more widely known that the index can be retrieved v-for

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

When using an object, you can use key like this:

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

You can also combine these two methods to get key and the attribute's index .

<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

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

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

Rendering result:

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

When we use v-for with range, it will start from 1 and end with the number we specified.

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:

  • Calculated attributes
  • props
  • Nested value

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

15. Steal prop type

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

For example, we used a Icon component in this component.

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

To make it work, we need to add the correct prop type, `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 Icon component is updated, we will definitely forget to return this component and update them. Over time, when the prop type of the component starts to deviate from Icon component, an error will be introduced.

So 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 complicated.

Except in our example, we add icon to the beginning of prop So we have to 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 prop type in the Icon component is modified, our component will remain updated.

But what if a prop type is added or deleted from the Icon To deal with these situations, we can use v-bind and a calculated prop to keep it dynamic.

16. Detect clicks outside (or inside) the element

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

window.addEventListener('mousedown', e => {
  // 获取被点击的元素
  const clickedEl = e.target;
  
  if (el.contains(clickedEl)) {
   //在 "el "里面点击了
  } else {
   //在 "el "外点击了
  }
});

17. Recursive Slots

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

<!-- VFor.vue -->
<template>
    <div>
        <!--  渲染第一项 -->
    {{ list[0] }}
        <!-- 如果我们有更多的项目,继续!但是不要使用我们刚刚渲染的项 -->
    <v-for
      v-if="list.length > 1"
            :list="list.slice(1)"
        />
    </div>
</template>

If you want to do this with scoped slots, it just needs 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>

The following is how to use this component.

<template>
  <div>
    <!-- 常规列表 -->
    <v-for :list="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 state. Sometimes we need to add some metadata to provide more information to other components.

For example, if you are working on an analysis instrument like Google Analytics:

image.png

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',
  // 👇 只需将其作为一个额外的属性添加
  columns: 3,
  props: {
    // ...
  },
  data() {
    return {
      //...
    };
  },
};
export default {
  name: 'LiveUsersWidget',
  // 👇  只需将其作为一个额外的属性添加
  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 $options

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 logo used to build tools to treat components differently
  • Add custom functions to components in addition to calculated attributes, data, watch, etc.
  • other

19. Multi-file single-file component

This is a known feature 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>

If you need to share styles, files, or anything else, this can be very convenient.

20. Reusable components are not what you think

Reusable components are not necessarily large or complex things.

I often make small and short components reusable.

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

<!-- OverflowMenu.vue -->
<template>
  <Menu>
    <!-- 添加一个自定义按钮来触发我们的菜单   -->
    <template #button v-slot="bind">
      <!-- 使用bind来传递click处理程序、a11y 属性等 -->
      <Button v-bind="bind">
        <template #icon>
          <svg src="./ellipsis.svg" />
        </template>
      </Button>
    </template>
  </Menu>
</template>

Here, we used a menu component, but added a ellipsis icon on the button that triggered it. (Ellipsis) icon to trigger its opening.

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

But this OverflowMenu will be used dozens of times, now if we want to update the icon or its behavior, we can do it very easily. Moreover, it is easier to use it.

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 this question again.

Sometimes, "best practices" do not apply to what you are doing, and you need an escape port like this one.

Normally, we use props and events to communicate between components. Props are sent to the child component, and events are sent 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 fine, but it can only be used the first time it is called. If you need to trigger this action multiple times, you must clean up and reset the state. The logic is like this

  • The parent component passes true to the trigger prop
  • Watch is triggered, and then the Child component calls the method
  • The child component emits an event to tell the parent component that the method has been successfully triggered
  • The Parent component resets trigger false , so we can start from the beginning again

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

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

Yes, we broke the "props down, events up"" rule, we broke the encapsulation, but this is clearer and easier to understand, so it’s worth it

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

22. Monitor arrays and objects

The watcher part of using 061073c917bf35 is that sometimes it doesn't seem to trigger properly.

Usually, this is because we tried to listen to the array or object, but did not set deep to true

export default {
  name: 'ColourChange',
  props: {
    colours: {
      type: Array,
      required: true,
    },
  },
  watch: {
    // 使用对象语法,而不仅仅是方法
    colours: {
      // 这将让Vue知道要在数组内部寻找
      deep: true,

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

Using Vue 3's API will 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 point) state in the URL, allowing us to jump directly to a specific state on the page.

For example, if you load a page that has a date range filter selected:

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

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

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

Using vue-router 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 the template

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

I like to use it to simplify the v-if , and sometimes use 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>

It's a bit clumsy, and it's not obvious at first, a bunch of such elements are shown and hidden together. On a larger and more complex component, this may be a worse situation

But we can optimize it.

We can use the template tag to group these elements and 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. A better way 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 log 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, let the user refresh or try other things.

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

~ Finished, I'm wise to wash the bowls, wash the bowls, sleep, and play Lol.


possible bugs of 161073c917c6c1 code after deployment cannot be known in real time. In order to solve these bugs afterwards, a lot of time was spent on log debugging. By the way, I would like to recommend a useful BUG monitoring tool Fundebug .

Original: https://dev.to/michaelthiessen/25-vue-tips-you-ned-to-know-2h70

comminicate

If you have dreams and dry goods, search on [Daily Move to the World] still doing dishes in the early morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line factory interview complete test sites, materials and my series of articles.


王大冶
68k 声望104.9k 粉丝