Click to jump to personal blog to view previous articles

preface

This article introduces some tips for daily project development, which can not only improve work efficiency, but also improve application performance. Here are some of the techniques I use on a daily basis.

Minxin makes component reuse flexible

Vue provides a method for inserting component properties into a component. I recommend using minxin as little as possible, but there is one scenario that strongly recommends using Minxin: When a piece of code is repeated in multiple components and the repeated block is large, using it as a minxin is often a great convenience for later maintenance.

For example, we have packaged a list function in the project, such as pull-down refresh, load automatic request data, pull-up load next page data, etc. The code is as follows:

export default {
    data() {
        return {
            page: 1.limit: 10.busy: false.// Request interception to prevent multiple loads
            finish: false.// Whether the request is completed is used to display the effect of the page
            pageList: [].// Page data
            reqParams: {}, // Page request parameters can be changed
            defaultParams: {}, // Page request parameters, drop-down refresh will not be reset changes
            routeName: ' '.// In special cases, the page needs to reuse someone else's list
            autoReq: true.// whether the onload request itself
            lodingText: ' '.// The copy shown at the bottom of the request
            noDataText: 'No data at present'.// Customize no data copy
            lastText: '- I draw the 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 = 'Data loading'
            if (saveParams) {
                const { page, limit } = this.reqParams
                page ? this.page = page : ' '
                limit ? this.limit = limit : ' '
            } else {
                this.reqParams = {}
            }
            this.getCommonList(refresh)
        },
        // Pull down the refresh function
        pullDownRefreshFn() {
            this.initData()
            this.initPage(false.true)},// load the function
        reachBottomFn() {
            this.getCommonList()
        },
        // Reset data, easy to call (generally outside the custom empty some data)
        initData() { // Reset variables in data, so that external references to the mixin can be pulled down to refresh the reset 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/ / compatible nvue
            try {
                constquery = { ... this.defaultParams, ... this.reqParams,page: this.page,
                    limit: this.limit
                }
                const { data } = await httpFn(this.reqName, query)
                if (this.page === 1) this.pageList = []
                / * * * [using concat Node. JS and push the performance comparison of two or more array] (http://ourjs.com/detail/5cb3fe1c44b4031138b4a1e2) * that both in the Node. JS performance? We did a set of tests, one million tests each. * Push is about 3 times faster than concat methods. Because push is just modifying the original array, it's faster. * Push returns the length of the array, So there is no to redefine the variables to determine the * [Array. Prototype. Push. Apply (arr1, Arr2) does not trigger the DOM updates automatically] (https://www.imooc.com/wenda/detail/494323) * because this. PageList. Push! == array.prototype. push, this.pagelist. Push refers to the */ method that vue overwrites
                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) { // There is data
                    this.busy = false
                    this.page = Math.ceil(listLen / this.limit) + 1
                }
                this.resSuccess(data, refresh)
            } catch (e) {
                // Prevent the interface from locking incorrectly
                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 (so that the external imported file can reference itself)
        finishInit(data) { // What do I do when the request completes
            // console.log(' List request completed ');}}}Copy the code

Many of you might look at this scenario and wonder why not encapsulate it as a component? Because many lists have different styles, encapsulation as a component does not scale well. 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 && Load medium prompt -->
    <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.// Enter the page to automatically request data
      reqParams: {}, // Request parameters
      reqName: 'userCompanyList' // Request an address}}}</script>

<style></style>
Copy the code

We can implement a nice list function by defining the request parameters, the address of the request, and the style of the list.

Save the messy template–render function

Sometimes there is a multi-value judgment in the template of a project, and 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>
Copy the code

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

Once and for all component registration

Before using a component, import it and then register it:

import BaseButton from './baseButton'
import BaseIcon from './baseIcon'
import BaseInput from './baseInput'

export default {
  components: {
    BaseButton,
    BaseIcon,
    BaseInput
  }
}
Copy the code

BaseButton, BaseIcon, and BaseInput can now be used in templates:

<BaseInput
  v-model="searchText"
  @keydown.enter="search"
/>
<BaseButton @click="search">
  <BaseIcon name="search"/>
</BaseButton>
Copy the code

But if there are many components, each time you have to import each component you want to use, and then register the components, it will add a lot of code! How do we optimize?

At this point, we need to use webpack’s require.context() method to create our own (module) context to implement the automatic dynamic require component. This method takes three parameters: the folder directory to search, whether its subdirectories should also be searched, and a regular expression to match the files.

We’ll start by adding a file called global.js to the Components folder (which contains high-frequency components) and using require.context to dynamically package all high-frequency components we need. Then import the global.js file in the main.js file.

/ / global. Js file
import Vue from 'vue'
function changeStr (str) {
  return str.charAt(0).toUpperCase() + str.slice(1)}const requireComponent = require.context('/'.false./\.vue$/)
// Find components that end in vue in the same directory
const install = () = > {
  requireComponent.keys().forEach(fileName= > {
    let config = requireComponent(fileName)
    console.log(config) //./child1.vue then gets child1 with the re
    let componentName = changeStr(
      fileName.replace(/ ^ \ \ / /.' ').replace(/\.\w+$/.' ')
    )
    Vue.component(componentName, config.default || config)
  })
}
export default {
  install // Expose the install method externally
}
Copy the code
// main.js
import index from './components/global.js'
Vue.use(index)
Copy the code

As a result, we can use these high-frequency components anywhere and anytime on the page without having to manually import them one by one.

Hidden big trick –hook

Sometimes during development we will create a timer that will be destroyed before the component is destroyed. The code is as follows:

mounted() {
  // Create a timer
    this.timer = setInterval(() = > {
      / /...
    }, 500);
  },
  // Destroy the timer.
  beforeDestroy() {
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null; }}Copy the code

There is an obvious drawback to this: the timer is created and cleaned in different places, so it’s easy to forget to clean!

We can hook the code so that it is easier to maintain:

mounted() {
    let timer = setInterval(() = > {
      / /...
    }, 500);
    this.$once("hook:beforeDestroy".function() {
      if (timer) {
        clearInterval(timer);
        timer = null; }}); }Copy the code

$on(‘hook:updated’, () => {})

In addition to the above application, hook can also listen to the component’s life cycle function externally. In some cases, we need to know in the parent when a child component is created, mounted, or updated.

For example, if you want to listen for a third-party component CustomSelect’s updated hook when rendering, you can do so with @hook:updated:

<template> <! Listen to the component's updated hook function --> <! All lifecycle hooks of a component can be listened for 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("Custom -select component's updated hook function is triggered"); }}};</script>
Copy the code

Simple and violent router key

When we were developing a project, we might encounter a problem like this: when the page switches to the same route but with different parameter addresses, for example, /detail/1, and goes to /detail/2, the data is not updated after the page jumps? The route configuration is as follows:

 {
     path: "/detail/:id".name:"detail".component: Detail
 }
Copy the code

This is because vue-Router recognizes that the two routes use the same component for reuse and does not recreate the component, and the component’s lifecycle hooks are not triggered, resulting in no data updates after the jump. So how do we solve this problem? We can add a property key to the router-view component as shown in the following example:

<router-view :key="$route.fullpath"></router-view>
Copy the code

This method mainly uses the virtual DOM to compare whether two nodes are the same by key during rendering. If the key is not the same, the router-View component will be judged as a new node, so the component will be destroyed first, and then a new component will be created, so that the life cycle within the component will be triggered again.

High precision permission control – custom directive

We usually add v-if/V-show to an element to determine whether the user has permission or not, but if the criteria are verbose and multiple places need to be determined, this approach is not only inelegant but redundant code. In this case, we can encapsulate an instruction authority, which can realize the button level authority judgment easily and quickly. Let’s start by creating a new array.js file to store permiss-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 / / without permission}}Copy the code

Then mount the array file globally

// main.js
import { checkArray } from "./common/array";
Vue.config.productionTip = false;
Vue.directive("permission", {
  inserted (el, binding) {
    let permission = binding.value; // Obtain the value of v-permission
    if (permission) {
      let hasPermission = checkArray(permission);
      if(! hasPermission) {// Do not have permission to remove Dom elementsel.parentNode && el.parentNode.removeChild(el); }}}});Copy the code

Finally, we can judge the page through the custom command V-permission:

 <div class="btns">
    <button v-permission="'admin'">Permission button 1</button>  / / will be displayed
    <button v-permission="'visitor'">Permission button 2</button> / / no show
    <button v-permission="'editor'">Permission button 3</button> / / will be displayed
  </div>
Copy the code

Dynamic instruction parameter

One of the coolest features of Vue 2.6 is the ability to dynamically pass instruction parameters to components. We can use a JavaScript expression enclosed in square brackets as an argument to an instruction:

<a v-bind:[attributeName]="url"> This is a link </a>Copy the code

The attributeName is evaluated dynamically as a JavaScript expression, and the resulting value is used as the final parameter. Similarly, you can bind handlers to a dynamic event name with dynamic parameters:

<a v-on:[eventName]="doSomething"> This is a link </a>Copy the code

Let’s take a look at an example: Suppose you have a button that in some cases wants to listen for click events and in some cases for double-click events. This is where the dynamic command argument comes in handy:

<template>
  <div>
    <aButton@ [someEvent] ="handleSomeEvent()" />
  </div>
</template>
<script>
export default {
  data () {
    return {
      someEvent: someCondition ? "click" : "dbclick"}},methods: {
    handleSomeEvent () {
      // handle some event}}}</script>
Copy the code

Filters facilitate data processing

Vue. Js allows you to customize filters. It is very simple to use, but maybe some friends have not used it.

1. Understand filters

  • Function: To display the data after a specific format.
  • Note: The filter does not change the original data, so you need to wrap the presented data.
  • Usage scenarios: double curly brace interpolation and v-bind expressions (the latter supported as of 2.1.0+).

2. Define a 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) } }Copy the code

You can also define filters globally before creating Vue instances:

Vue.filter('capitalize', function (value) { if (! value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) })Copy the code

3. Use a filter

Use method is simple, that is used in the double brace pipe (pipeline) |

<! -- In double braces --><div>{{ myData| filterName}}</div>
<div>{{ myData| filterName(arg)}}</div>
<!-- 在 v-bind 中 -->
<div v-bind:id="rawId | formatId"></div>
Copy the code

Filters can be connected in series:

{{ message | filterA | filterB }}
Copy the code

In this example, filterA is defined as a filter function that receives a single parameter, and the value of the expression Message is passed into the function as a parameter. It then proceeds to call filterB, which is also defined to receive a single argument, passing the result of filterA to filterB. Let’s look at an example of how to format dates using filters:

 <div>
    <h2>Displays the formatted date and time</h2>
    <p>{{ date }}</p>
    <p>{{ date | filterDate }}</p>
    <p>(date) (month) (year) : {{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
      returnmoment(value).format(format); }},data() {
    return {
      date: new Date()
    };
  }
Copy the code

Refer to articles and books

  • Vue official document
  • Vue2.6 check and fill gaps
  • Vue.js Best Practices (5 Ways to Become a Vue.js master)
  • Actual combat skills, Vue can also be written like this
  • Use vue.Observable () for state management
  • Introduction to Vue project construction and development
  • 12 Vue.js development tips and tricks for 2020