- Restrict prop to a list of types
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.
- Default content and extension points
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
- 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
- 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.
- Short for 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.
- Conditionally render slot s
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:
When using wrapper div to add a default style
slot is empty
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
- How to observe the change of slot
Sometimes we need to know when the content in the slot has changed:
<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
- 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
- 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.
-
Creating 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. -
State 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
-
to configure
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. -
modelling
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.
- 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); // Access using. value 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.
- 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
- 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.
- Observe anything in the component
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,.
- Type 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.
- Detect clicks outside (or inside) the element
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
- 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
- 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
- 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 "..." (ellipsis) on the button that triggers it to open Icons. 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 icons or their behavior, we can do it easily. And it's much easier to use!
<template> <OverflowMenu :menu-items="items" @click="handleMenuClick" /> </template> Copy code
- Calling a method from outside a component
You can call the method ref from outside the component by giving it:
<!-- Parent.vue --> <template> <ChildComponent ref="child" /> </template> Copy code
// Somewhere in Parent.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:
The Parent component passes true to triggerprop
The Watch is triggered and the Child component calls the method
The Child component sends an event to tell the Parent component that the method has been successfully triggered
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
// Somewhere in Parent.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.
- 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
If you want more information, you can refer to the documentation for Vue 3 and Vue 2.
- 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
- 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
- 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.
last
If you think this article is a little helpful to you, give it a compliment. Or you can join my development exchange group: 1025263163 learn from each other, and we will have professional technical Q & A to solve doubts
If you think this article is useful to you, please click star: http://github.crmeb.net/u/defu esteem it a favor!