Impressive Vue combat skills
Foreword
This article mainly introduces some techniques in the daily project development process, which can not only help improve work efficiency, but also improve the performance of applications. The following is a summary of some of my usual work skills.
mixins makes component reuse flexible
Vue provides mixins, a method of inserting component properties into components. Personally, I suggest that this product should be used sparingly, but there is a scenario where it is highly recommended to use minxin: when a certain piece of code appears repeatedly in multiple components, and this When the repeated code block is large, using it as a minxin can often bring great convenience to later maintenance.
For example, we encapsulate a list function in the project, with pull-down refresh, loading automatic request data, pull-up loading of the next page data, etc. The code is as follows:
export default {
data() {
return {
page: 1,
limit: 10,
busy: false, // request interception to prevent multiple loading
finish: false, // Whether the request is completed, used for page display effect
pageList: [], // page data
reqParams: {}, // page request parameters, can be changed
defaultParams: {}, // page request parameters, pull-down refresh will not be reset
routeName: "", // In special cases, when the page needs to reuse someone else's list
autoReq: true, // does onload request itself
lodingText: "", // Text displayed at the bottom of the request
noDataText: "No data", // custom no data text
lastText: "- I have a bottom line-",
noData: false, // the page has no data
reqName: "",
};
},
created() {
this.autoReq && this.initPage(false, true);
},
onPullDownRefresh() {
this.pullDownRefreshFn();
},
onReachBottom() {
this.reachBottomFn();
},
methods: {
// reset initialization data
initPage(saveParams = true, refresh = false) {
// initialize all variables
this.page = 1;
this.busy = false;
this.finish = false;
this.noData = false;
this.lodingText = "数据加载中";
if (saveParams) {
const { page, limit } = this.reqParams;
page ? (this.page = page) : "";
limit ? (this.limit = limit) : "";
} else {
this.reqParams = {};
}
this.getCommonList(refresh);
},
// pull down refresh function
pullDownRefreshFn() {
this.initData();
this.initPage(false, true);
}, // upload the loading function
reachBottomFn() {
this.getCommonList();
}, // Reset data, easy to call (usually clear some data by custom outside)
initData() {
// Reset the variables in the data, so that when the mixin is referenced outside, pull down to refresh to reset the variables
}, // List get data interface
async getCommonList(refresh) {
if (!this.reqName) return;
if (this.busy) return;
this.busy = true;
this.finish = false;
const httpFn = this.$http || getApp().globalData.$http; // 兼容nvue
try {
const query = {
...this.defaultParams,
...this.reqParams,
page: this.page,
limit: this.limit,
};
const { data } = await httpFn(this.reqName, query);
if (this.page === 1) this.pageList = [];
/**
* [Performance comparison of using concat and push to connect two or more arrays in Node.JS](http://ourjs.com/detail/5cb3fe1c44b4031138b4a1e2)
* So how about the performance of both in node.js? We made a set of test data, and each of them was tested 1 million times.
*push is about 3 times faster than the concat method. Because push is only modified on the basis of the original array, it will be faster.
* push returns the length of the array, so the variable is not redefined and then judged
* [Array.prototype.push.apply(arr1, arr2) cannot trigger DOM update automatically](https://www.imooc.com/wenda/detail/494323)
* Because this.pageList.push !== Array.prototype.push, this.pageList.push points to the method overridden by vue
*/
this.finish = true;
const resLen = data.list ? data.list.length : 0;
if (resLen === 0) {
this.resSuccess(data, refresh);
return;
}
const listLen = this.pageList.push.apply(this.pageList, data.list);
if (listLen < data.count && this.limit <= resLen) {
// Description and data
this.busy = false;
this.page = Math.ceil(listLen / this.limit) + 1;
}
this.resSuccess(data, refresh);
} catch (e) {
// Prevent the interface from reporting an error and locking up
this.busy = false;
this.finish = true;
}
},
resSuccess(data, refresh) {
if (this.finish && this.busy) {
if (this.pageList.length > 0) {
this.$nextTick(() => {
setTimeout(() => {
this.lodingText = this.lastText;
}, 100);
});
} else {
this.lodingText = this.noDataText;
this.noData = true;
}
}
refresh && uni.stopPullDownRefresh();
this.finishInit(data);
}, // Request completion to do something (convenient for externally imported files to reference by themselves)
finishInit(data) {
// request to complete do something
// console.log('List request completed');
},
},
};
When many people see such a scene, they should be wondering why it is not encapsulated into a component? Since many lists have different styles, encapsulation into a component is not scalable. But we can simplify the code with minxin:
<template>
<view class="c-recommend-goods">
<!-- list style -->
<view class="" v-for="item in pageList" :key="item.id">{{ item }}</view>
<!-- Empty state && loading medium tip -->
<c-no-data v-if="lodingText" :show-img="noData" :text="lodingText"></c-no-data>
</view>
</template>
<script>
import listMixins from '@/common/mixins/list.js'
export default {
mixins: [listMixins],
data() {
return {
autoReq: false, // automatically request data when entering the page
reqParams: {}, // request parameters
reqName: 'userCompanyList' // request address
}
}
}
</script>
<style>
</style>
We only need to define the request parameters and the requested address, as well as the style of the list, to achieve a good list function.
Save the messy template--render function
Sometimes there are multiple judgments of one value in the template in the project. If the business logic is written according to the code below, the code is redundant and messy.
<template>
<div>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: {
level: {
type: Number,
required: true,
},
},
}
</script>
Now rewrite the above example using the render function:
<script>
export default {
props: {
level: {
require: true,
type: Number,
}
},
render(createElement) {
return createElement('h' + this.level, this.$slots.default);
}
};
</script>
Set-and-forget component registration
Before the component is used, it needs to be imported and then registered:
import BaseButton from './baseButton'
import BaseIcon from './baseIcon'
import BaseInput from './baseInput'
export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}
Now BaseButton, BaseIcon, and BaseInput are all available in templates:
<BaseInput
v-model="searchText"
@keydown.enter="search"
/>
<BaseButton @click="search">
<BaseIcon name="search"/>
</BaseButton>
But if there are too many components, you have to import each component you want to use every time, and then register the component, which will add a lot of code! How should we optimize it?
At this time, we need to use the require.context() method of webpack to create our own (module) context, so as to achieve automatic dynamic require components. This method takes 3 arguments: the folder directory to search, whether its subdirectories should also be searched, and a regular expression to match files.
We first add a file called global.js to the components folder (which are all high-frequency components), and use require.context in this file to dynamically package all the required high-frequency components. Then import the global.js file in the main.js file.
// global.js
import Vue from 'vue'
function changeStr (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
const requireComponent = require.context('./', false, /\.vue$/)
const install = () => {
requireComponent.keys().forEach(fileName => {
let config = requireComponent(fileName)
console.log(config) // ./child1.vue
let componentName = changeStr(
fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
)
Vue.component(componentName, config.default || config)
})
}
export default {
install // Expose the install method to the outside world
}
// main.js
import index from './components/global.js'
Vue.use(index)
Finally, we can use these high-frequency components in the page anytime, anywhere, no need to manually introduce them one by one.
Hidden big move --hook
During the development process, we sometimes need to create a timer, and the timer should be destroyed before the component is destroyed. code show as below:
mounted() {
this.timer = setInterval(() => {
// ......
}, 500);
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
This way of writing has an obvious drawback: the creation and cleanup of the timer timer are not in the same place, which can easily lead to forgetting to clean up!
We can use hooks to integrate the code, so the code is easier to maintain:
mounted() {
let timer = setInterval(() => {
// ......
}, 500);
this.$once("hook:beforeDestroy", function() {
if (timer) {
clearInterval(timer);
timer = null;
}
});
}
In Vue components, you can use $on and $once to listen to all lifecycle hook functions. For example, the updated hook function of a listening component can be written as this.$on('hook:updated', () => {}).
In addition to the above applications, hooks can also externally monitor the lifecycle functions of components . In some cases, we need to know in the parent component when a child component is created, mounted or updated.
For example, if you want to listen to the updated hook of a third-party component CustomSelect when it renders, you can do it by @hook:updated
:
<template>
<!--Listen to the updated life hook function of the component through @hook:updated-->
<!--All lifecycle hooks of components can be triggered by @hook: hook function name -->
<custom-select @hook:updated="doSomething" />
</template>
<script>
import CustomSelect from "../components/custom-select";
export default {
components: {
CustomSelect
},
methods: {
doSomething() {
console.log("The updated hook function of the custom-select component is triggered");
}
}
};
</script>
Simple and violent router key
During project development, we may encounter such a problem: when the page switches to the same route but different parameter addresses, such as /detail/1, jumps to /detail/2, the data is not updated after the page jump? The routing configuration is as follows:
{
path: "/detail/:id",
name:"detail",
component: Detail
}
This is because vue-router will recognize that the two routes use the same component for reuse, and will not recreate the component, and the component's lifecycle hook will naturally not be triggered, resulting in the data not being updated after the jump. . So how do we solve this problem?
We can add a property key to the router-view component, for example:
<router-view :key="$route.fullpath"></router-view>
This method mainly uses the virtual DOM to compare whether the two nodes are the same by the key when rendering. If the key is not the same, it will determine that the router-view component is a new node, so the component is destroyed first, and then the new component is recreated. This way the lifecycle within the component will be retriggered.
High-precision permission control--custom instruction directive
We usually add v-if / v-show to an element to determine whether the user has permission, but if the judgment conditions are cumbersome and multiple places need to be judged, the code in this way is not only inelegant but also redundant. In response to this situation, we can encapsulate a command permission, which can easily and quickly implement button-level permission judgment.
We first create a new array.js file to store permissions-related global functions
// array.js
export function checkArray (key) {
let arr = ['admin', 'editor']
let index = arr.indexOf(key)
if (index > -1) {
return true // have permission
} else {
return false // no permission
}
}
Then mount the array file to the global
// main.js
import { checkArray } from "./common/array";
Vue.config.productionTip = false;
Vue.directive("permission", {
inserted (el, binding) {
let permission = binding.value; // Get the value of v-permission
if (permission) {
let hasPermission = checkArray(permission);
if (!hasPermission) { // No permission to remove Dom element
el.parentNode && el.parentNode.removeChild(el);
}
}
}
});
Finally, we can judge by the custom instruction v-permission in the page:
<div class="btns">
<button v-permission="'admin'">Permission button 1</button> // will display
<button v-permission="'visitor'">Permission button 2</button> //No display
<button v-permission="'editor'">Permission button 3</button> // will display
</div>
Dynamic command parameters
One of the coolest features of Vue 2.6 is the ability to dynamically pass directive parameters to components. We can use square bracketed JavaScript expressions as arguments to a directive:
<a v-bind:[attributeName]="url"> this is a link </a>
The attributeName here will be dynamically evaluated as a JavaScript expression, and the evaluated value will be used as the final parameter.
Likewise, you can bind handlers to a dynamic event name using dynamic parameters:
<a v-on:[eventName]="doSomething"> this is a link </a>
Let's look at an example: Suppose you have a button, and in some cases you want to listen to the click event, and in some cases you want to listen to the double click event. This is where dynamic directive parameters come in handy:
<template>
<div>
<aButton @[someEvent]="handleSomeEvent()" />
</div>
</template>
<script>
export default {
data () {
return {
someEvent: someCondition ? "click" : "dbclick"
}
},
methods: {
handleSomeEvent () {
// handle some event
}
}
}
</script>
Filters make data processing easier
Vue.js allows you to customize filters. Its usage is actually very simple, but some friends may not have used it. Next, we will introduce:
1. Understand filters
- Function: Format the data to be displayed before displaying it .
- Note: The filter does not change the original data, and the displayed data needs to be packaged .
- Usage scenarios: Double curly brace interpolation and v-bind expressions (the latter is supported since 2.1.0+).
2. Define the filter
Local filters can be defined in a component's options:
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
Filters can also be defined globally before the Vue instance is created:
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
3. Use filters
The method of use is also simple, that is, use the pipe character (pipeline) | in double curly braces to separate
<!-- in double curly braces -->
<div>{{ myData| filterName}}</div>
<div>{{ myData| filterName(arg)}}</div>
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>
Filters can be concatenated:
{{ message | filterA | filterB }}
In this example, filterA is defined as a filter function that takes a single parameter, and the value of the expression message will be passed into the function as a parameter. Then continue to call the filter function filterB, which is also defined to receive a single parameter, passing the result of filterA into filterB.
Next let's see an example of how to format a date using a filter:
<div>
<h2>Display formatted datetime</h2>
<p>{{ date }}</p>
<p>{{ date | filterDate }}</p>
<p>year month day: {{ date | filterDate("YYYY-MM-DD") }}</p>
</div>
filters: {
filterDate(value, format = "YYYY-MM-DD HH:mm:ss") {
console.log(this)//undefined filter does not point to this
return moment(value).format(format);
}
},
data() {
return {
date: new Date()
};
}