62
头图

Click to jump to personal blog to view

Preface

This article mainly introduces some skills in the daily project development process, which can not only help improve work efficiency, but also improve application performance. The following is a summary of some of my usual work experience.

minxin makes component reuse flexible

Vue provides minxin, a method of inserting component properties into components. I personally suggest that this product can be used as little as possible. However, there is a scenario where minxin is highly recommended: 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.

This is a list function encapsulated in the project, there are pull-down refresh, load automatic request data, pull-up load the next page data, etc., it is like this

It’s okay if you don’t understand. I just gave an example in the development.

export default {
  data() {
    return {
      page: 1,
      limit: 10,
      busy: false, // 请求拦截,防止多次加载
      finish: false, // 是否请求完成,用于页面展示效果
      pageList: [], // 页面数据
      reqParams: {}, // 页面请求参数,可被改变的
      defaultParams: {}, // 页面请求参数,下拉刷新不会被重置的改变
      routeName: '', // 特殊情况,页面需要复用别人的list的时候
      autoReq: true, // onload是否自己去请求
      lodingText: '', // 请求中底部显示的文案
      noDataText: '暂无数据', // 自定义无数据文案
      lastText: '- 我是有底线的 -',
      noData: false, // 页面无数据
      reqName: '',
    }
  },
  created() {
    this.autoReq && this.initPage(false, true)
  },
  onPullDownRefresh() {
    this.pullDownRefreshFn()
  },
  onReachBottom() {
    this.reachBottomFn()
  },
  methods: {
    // 重置初始化数据
    initPage(saveParams = true, refresh = false) {
      // 初始化所有变量
      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)
    },
    // 下拉刷新函数
    pullDownRefreshFn() {
      this.initData()
      this.initPage(false, true)
    },
    // 上啦加载函数
    reachBottomFn() {
      this.getCommonList()
    },
    // 重置数据,方便调用(一般在外面自定义清空一些数据)
    initData() {
      // 重置data里面的变量,方便外面引用这个mixin的时候,下拉刷新重置变量
    },
    // 列表获取数据接口
    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 = []
        /**
         * [Node.JS中用concat和push连接两个或多个数组的性能比较](http://ourjs.com/detail/5cb3fe1c44b4031138b4a1e2)
         * 那么两者在node.js的性能如何? 我们做了一组测试数据,两种分别测试100万次。
         * push比concat方法speed 3倍左右。因为push只是在原数组的基础上进行修改,所以会快一点。
         * push返回的是数组的长度,所以没重新定义变量再判断了
         * [Array.prototype.push.apply(arr1, arr2)无法自动触发DOM更新](https://www.imooc.com/wenda/detail/494323)
         * 因为 this.pageList.push !== Array.prototype.push,,this.pageList.push指向的是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) {
          // 说明还有数据
          this.busy = false
          this.page = Math.ceil(listLen / this.limit) + 1
        }
        this.resSuccess(data, refresh)
      } catch (e) {
        // 防止接口报错锁死
        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)
    },
    // 请求完成做点什么(方便外面导入的文件自己引用)
    finishInit(data) {
      // 请求完成做点什么
      // console.log('列表请求完成');
    },
  },
}

Many people should be curious to see why they are not packaged into a component, but because many lists have different styles, the scalability of packaged into a component is not high.
Now we can use it like this.

<template>
  <view class="c-recommend-goods">
    <!-- 列表样式 -->
    <view class="" v-for="item in pageList" :key="item.id">{{item}}</view>
    <!-- 空状态&& 加载中等小提示 -->
    <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, // 进入页面自动请求数据
      reqParams: {}, // 请求参数
      reqName: 'userCompanyList' // 请求地址
    }
  }
}
</script>

<style></style>

As long as we define the request parameters and the requested address, as well as the style of the list, we can achieve a good list function.

Save the messy template--render function

  • There is one value, multiple judgments in template
  • Code is redundant and messy

Give an example of an official document

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

Component registration once and for all

Before components are used, they need 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 can all be used in the template:

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

But if there are more components, every time you have to import each component you want to use, and then register the component, a lot of code will be added! How should we optimize it?

At this time, we need to use webpack's require.context() method to create our own (module) context, so as to realize automatic dynamic require components. This method requires three parameters: the folder directory to be searched, whether it should also search its subdirectories, and a regular expression for matching files.

Let's first add a file called global.js to the components folder (there are some high-frequency components), and use require.context in this file to dynamically package all the required high-frequency components in. Then introduce 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$/)
// 查找同级目录下以vue结尾的组件
const install = () => {
  requireComponent.keys().forEach(fileName => {
    let config = requireComponent(fileName)
    console.log(config) // ./child1.vue 然后用正则拿到child1
    let componentName = changeStr(
      fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
    )
    Vue.component(componentName, config.default || config)
  })
}
export default {
  install // 对外暴露install方法
}
// main.js
import index from './components/global.js'
Vue.use(index)

Finally, we can use these high-frequency components on the page anytime, anywhere, without manually introducing them one by one.

Hidden big move--hook

During the development process, we sometimes need to create a timer, which must 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 cleaning of timers are not in one place, so it is easy to forget to clean up!

We can use hooks to integrate the code, so that the code is easier to maintain:

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

In the Vue component, you can use $on, $once to monitor all life cycle hook functions. For example, the updated hook function of the monitoring component can be written as this.$on('hook:updated', () => {}).

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

For example, if you want to listen to the updated hook of the third-party component CustomSelect when it is rendering, you can use @hook:updated to achieve:

<template>
  <!--通过@hook:updated监听组件的updated生命钩子函数-->
  <!--组件的所有生命周期钩子都可以通过@hook:钩子函数名 来监听触发-->
  <custom-select @hook:updated="doSomething" />
</template>
<script>
import CustomSelect from "../components/custom-select";
export default {
  components: {
    CustomSelect
  },
  methods: {
    doSomething() {
      console.log("custom-select组件的updated钩子函数被触发");
    }
  }
};
</script>

Simple and violent router key

When we are developing the project, 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 jumps? 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 life cycle hook of the component 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 the attribute key to the router-view component, an example is as follows:

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

This method mainly uses the virtual DOM to compare whether two nodes are the same through the key during rendering. If the keys are not the same, it will be determined that the router-view component is a new node, so that the component is destroyed first, and then the new component is recreated. In this way the life cycle within the component will be triggered again.

High-precision permission control-custom instruction directive

We usually add v-if / v-show to an element to judge 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 realize button-level permission judgment.
Let’s create a new array.js file to store global functions related to permissions

// array.js
export function checkArray(key) {
  let arr = ['admin', 'editor']
  let index = arr.indexOf(key)
  if (index > -1) {
    return true // 有权限
  } else {
    return false // 无权限
  }
}

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 // 获取到 v-permission的值
    if (permission) {
      let hasPermission = checkArray(permission)
      if (!hasPermission) {
        // 没有权限 移除Dom元素
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  },
})

Finally, we can judge by custom command v-permission on the page:

<div class="btns">
  <button v-permission="'admin'">权限按钮1</button> // 会显示
  <button v-permission="'visitor'">权限按钮2</button> //无显示
  <button v-permission="'editor'">权限按钮3</button> // 会显示
</div>

Dynamic command parameters

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 a command parameter:

<a v-bind:[attributeName]="url"> 这是个链接 </a>

The attributeName here will be dynamically evaluated as a JavaScript expression, and the obtained value will be used as the final parameter.
Similarly, you can use dynamic parameters to bind a handler function to a dynamic event name:

<a v-on:[eventName]="doSomething"> 这是个链接 </a>

Next we look at an example: suppose you have a button, in some cases you want to listen to the click event, in some cases you want to listen to the double-click event. At this time dynamic command 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 more convenient

Vue.js allows you to customize the filter, its usage is actually very simple, but some friends may not have used it, then we introduce:

1. Understand the filter

  • Function: format the data to be displayed and then display .
  • 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 expression (the latter is supported from 2.1.0+).

2. Define the filter

You can define a local filter in the options of a component:

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

You can also define filters globally before creating a Vue instance:

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) | to separate the double curly braces

<!-- 在双花括号中 -->
<div>{{ myData| filterName}}</div>
<div>{{ myData| filterName(arg)}}</div>
<!-- 在 v-bind 中 -->
<div v-bind:id="rawId | formatId"></div>

Filters can be connected in series:

{{ message | filterA | filterB }}

In this example, filterA is defined as a filter function that receives 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, and pass the result of filterA to filterB.
Next we look at an example of how to use filters to format dates:

 <div>
    <h2>显示格式化的日期时间</h2>
    <p>{{ date }}</p>
    <p>{{ date | filterDate }}</p>
    <p>年月日: {{ date | filterDate("YYYY-MM-DD") }}</p>
 </div>
 ......
  filters: {
    filterDate(value, format = "YYYY-MM-DD HH:mm:ss") {
      console.log(this)//undefined 过滤器没有this指向的
      return moment(value).format(format);
    }
  },
  data() {
    return {
      date: new Date()
    };
  }

Reference articles and books


星野
410 声望1.2k 粉丝