Tips in using Vue

Tips in using Vue

In the process of using vue, you will encounter various scenarios. When it is used in general, it feels nothing, but maybe optimization can be more efficient and beautiful for development. Here are some tips I use in my daily development, which will be updated from time to time~

1. Decentralization of multi-chart resize events

1.1 General situation

Sometimes we will encounter such a scenario, there are several charts in a component, when the browser is resized, we hope that the chart will also be resized, so we will write in the parent container component:

mounted() {
  setTimeout(() => window.onresize = () => {
    this.$refs.chart1.chartWrapperDom.resize()
    this.$refs.chart2.chartWrapperDom.resize()
   //... 
  }, 200)
destroyed() {window.onresize = null}

In this way, if the child chart component is not on the same page as the parent container component, the status of the child component will be placed in the parent component for management. For the convenience of maintenance, we naturally hope that the event and status of the child component will be maintained by ourselves. There is no need to go to the parent component to modify one by one

1.2 Optimization

The throttle function of lodash is used here , or you can implement it yourself. This article also has the implementation of throttling for reference. Take Echarts as an example, in each chart component:

computed: {
 /**
   * Chart DOM
   */
  chartWrapperDom() {
    const dom = document.getElementById('consume-analy-chart-wrapper')
    return dom && Echarts.init(dom)
  },
 /**
   * Chart resize throttling, lodash is used here, or you can use setTimout to achieve throttling
   */
  chartResize() {
    return _.throttle(() => this.chartWrapperDom && this.chartWrapperDom.resize(), 400)
  }
},
mounted() {
  window.addEventListener('resize', this.chartResize)
},
destroyed() {
  window.removeEventListener('resize', this.chartResize)
}

1.3 Optimize again

Thanks to @JserWang for the reminder, here because multiple chart instances use the same set of initialization logic, extends can be used to consider reuse, so I thought of the Mixins provided by Vue , so I made some optimizations here to make each of the same The type of chart component is more elegant: Create a new mixin.js file:

import Echarts from'echarts'
import _ from'lodash'

export default {
  computed: {
   /* Chart DOM */
    $_chartMixin_chartWrapperDom() {
      const dom = document.getElementById(this.thisDomId)
      return dom && Echarts.init(dom)
    },
    
   /** Chart resize throttling, lodash is used here, or you can use setTimout to achieve throttling*/
    $_chartMixin_chartResize() {
      return _.throttle(() => this.$_chartMixin_chartWrapperDom.resize(), 400)
    }
  },
  
  methods: {
   /* Chart initialization*/
    $_chartMixin_initChart() {
      this.$_chartMixin_chartWrapperDom.setOption({/* options */}
  },
  
  mounted() {
    this.$_chartMixin_initChart()
    window.addEventListener('resize', this.$_chartMixin_chartResize)
  },
  
  destroyed() {
    window.removeEventListener('resize', this.$_chartMixin_chartResize)
  }
}

Then in each chart component:

<script type='text/javascript'>
import ChartMixin from'./mixin'
export default {
  mixins: [ChartMixin],
  data() {
    return {
      thisDomId:'consume-analy-chart-wrapper'
    }
  }
}
</script>

This may in each graph component prior to incorporation mixin.jsresize event defined logic, and automatically initializes and automatically destroyed when the event is destroyed -

2. Global filter registration

2.1 General situation

The official way to register the filter:

export default {
  data () {return {} },
  filters:{
    orderBy (){
     //doSomething
    },
    uppercase () {
     //doSomething
    }
  }
}

But for our projects, most of the filters are to be used globally, instead of writing them in the components every time they are used, it would be better to extract them globally. The official way to register the global:

//register
Vue.filter('my-filter', function (value) {
 //return the processed value
})
//getter, return the registered filter
var myFilter = Vue.filter('my-filter')

However, it is not beautiful if it is written separately, so it can be extracted into a separate file.

2.2 Optimization

We can extract to a separate file, and then use Object.keys to register in the main.js entry

/src/common/filters.js

let dateServer = value => value.replace(/(\d{4})(\d{2})(\d{2})/g,'$1-$2-$3') 

export {dateServer}

/src/main.js

import * as custom from'./common/filters/custom'
Object.keys(custom).forEach(key => Vue.filter(key, custom[key]))

Then you can happily use these global filters we defined in other .vue files

<template>
  <section class="content">
    <p>{{ time | dateServer }}</p> <!-- 2016-01-01 -->
  </section>
</template>
<script>
  export default {
    data () {
      return {
        time: 20160101
      }
    }
  }
</script>

3. Global component registration

3.1 General situation

Scenarios that need to use components:

<template>
    <BaseInput v-model="searchText" @keydown.enter="search"/>
    <BaseButton @click="search">
        <BaseIcon name="search"/>
    </BaseButton>
</template>
<script>
    import BaseButton from'./baseButton'
    import BaseIcon from'./baseIcon'
    import BaseInput from'./baseInput'
    export default {
      components: {BaseButton, BaseIcon, BaseInput}
    }
</script>

We wrote a bunch of basic UI components, and then every time we need to use these components, we have to import first, and then declare the components, which is very cumbersome. Here we can use the form of unified registration.

3.2 Optimization

We need to use what artifact webpack, use the require.context()method to create your own modules context, in order to achieve the automatic dynamic require assembly. 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. We add a file called componentRegister.js to the components folder, in which all the necessary basic components are dynamically packaged with the help of webpack.

/src/components/componentRegister.js

import Vue from'vue'

/**
 * Capitalize the first letter
 * @param str string
 * @example heheHaha
 * @return {string} HeheHaha
 */
function capitalizeFirstLetter(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

/**
 * Take the component name for the component that conforms to the component format of'xx/xx.vue'
 * @param str fileName
 * @example abc/bcd/def/basicTable.vue
 * @return {string} BasicTable
 */
function validateFileName(str) {
  return/^\S+\.vue$/.test(str) &&
    str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => capitalizeFirstLetter($1))
}

const requireComponent = require.context('./', true,/\.vue$/)

//Find the file named .vue in the component folder, if the file name is index, then take the name in the component as the registered component name
requireComponent.keys().forEach(filePath => {
  const componentConfig = requireComponent(filePath)
  const fileName = validateFileName(filePath)
  const componentName = fileName.toLowerCase() ==='index'
    ? capitalizeFirstLetter(componentConfig.default.name)
    : fileName
  Vue.component(componentName, componentConfig.default || componentConfig)
})

Here is the folder structure:

components
│ componentRegister.js
├─BasicTable
│ BasicTable.vue
├─MultiCondition
│ index.vue

The component name is judged here. If it is an index, the name attribute in the component is processed as the registered component name, so the last registered component is: multi-conditionbasic-table Finally, we import'components/componentRegister.js' in main.js, Then we can use these basic components anytime and anywhere, no need to manually introduce~

4. Reuse of components of different routes

4.1 Scene restoration

When the vue-router jumps from/post-page/a to/post-page/b in a certain scene. Then we were surprised to find that the data was not updated even after the page jumped? ! The reason is that vue-router "intelligently" finds that this is the same component, and then it decides to reuse this component, so the method you write in the created function is not executed at all. The usual solution is to monitor the change of $route to initialize the data, as follows:

data() {
  return {
    loading: false,
    error: null,
    post: null
  }
},
watch: {
  '$route': {//Use watch to monitor whether it is the same route
    handler:'resetData',
    immediate: true
  }
},
methods: {
  resetData() {
    this.loading = false
    this.error = null
    this.post = null
    this.getPost(this.$route.params.id)
  },
  getPost(id){}
}

4.2 Optimization

In order to achieve this effect, router-viewa different key can be added, so that even if it is a public component, as long as the url changes, the component will be recreated.

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

You can also add a + +new Date()time stamp after it to ensure uniqueness

Thanks to the reminder from netizen @rolitter, if the component is placed <keep-alive>in the activated hook, the method of obtaining new data can be placed in the activated hook, instead of the original task of obtaining data in the created and mounted hooks.

5. Component event attribute penetration

5.1 General

//parent component
<BaseInput :value="value"
           label="Password"
           placeholder="Please fill in the password"
           @input="handleInput"
           @focus="handleFocus">
</BaseInput>

//Subassembly
<template>
  <label>
    {{ label }}
    <input :value=" value"
           :placeholder="placeholder"
           @focus="$emit('focus', $event)"
           @input="$emit('input', $event.target.value)">
  </label>
</template>

5.2 Optimization

In the vue component instance $props, $attrsit provides us with great convenience, especially when passing values ​​between parent and child components. 1. Every props passed from the parent component to the child component must be explicitly declared in the props of the child component before it can be used. As a result, we need to affirm sub-assemblies each time a lot of props, where we know that v-bind can pass an object to be in vm.$propsto get the value of all the props in the parent componentv-bind="$props"

<input v-bind="$props" 
       @input="$emit('input', $event.target.value)">

2. DOM native properties like placeholer can be $attrspassed directly from parent to child without declaration. Methods as below:

<input :value="value"
       v-bind="$attrs"
       @input="$emit('input', $event.target.value)">

$attrsContains feature bindings (except class and style) that are not recognized (and acquired) as props in the parent scope. When a component does not declare any prop, where the binding will contain all the parent scope, and can be v-bind="$attrs"passed to internal components.

3. Note that the child component @focus="$emit('focus', $event)"actually does nothing but passes the event back to the parent component, which is similar to the above, there is no need to explicitly declare:

<input :value="value"
       v-bind="$attrs"
       v-on="listeners"/>

computed: {
  listeners() {
    return {
      ...this.$listeners,
      input: event =>
        this.$emit('input', event.target.value)
    }
  }
}

$listenersContains the v-on event listener in the parent scope (without the .native decorator) . It can v-on="$listeners"pass the internal components - very useful when creating a higher level of assembly.

Note that, because we are not the root BaseInput input of this component, and the default is not considered as the case of parent scope propscharacteristic binding will "back" and as an ordinary HTML attributes used in sub-assemblies On the root element. So we need to set inheritAttrs: false, these default behaviors will be removed, the above optimization can be successful.

6. Lazy loading of routing based on development status

6.1 General situation

Generally, when we load components in the route:

import Login from'@/views/login.vue'

export default new Router({
  routes: [{ path:'/login', name:'Login', component: Login}]
})

When you need lazy-loading, you need to change the components of routes one by one () => import('@/views/login.vue'), which is very troublesome.

When you have more and more project pages, using lazy-loading in the development environment will become unsuitable, and every time you change the code to trigger a hot update, it will become very slow. Therefore, it is recommended to use the routing lazy loading function only in the generation environment.

6.2 Optimization

According to Vue's asynchronous components and Webpack's code splitting function , lazy loading of components can be easily realized, such as:

const Foo = () => import('./Foo.vue')

When distinguishing the development environment from the production environment, you can create two new files under the routing folder:

_import_production.js

module.exports = file => () => import('@/views/' + file +'.vue')

_import_development.js(This writing vue-loaderversion is at least v13.0.0 or above)

module.exports = file => require('@/views/' + file +'.vue').default

And in the router/index.jsfile that sets up the route :

const _import = require('./_import_' + process.env.NODE_ENV)

export default new Router({
  routes: [{ path:'/login', name:'Login', component: _import('login/index') }]
})

In this way, the components are non-lazy loaded in the development environment, and lazy loaded in the production environment

7 vue-loader tips

vue-loader is a webpack loader that processes *.vue files. It itself provides a wealth of APIs, some of which are very useful but rarely known. For example, next to introduce preserveWhitespaceandtransformToRequire

7.1 with a preserveWhitespacereduced file size

Sometimes when we write templates, we don’t want to have spaces between elements. We might write it like this:

<ul>
  <li>1111</li><li>2222</li><li>333</li>
</ul>

Of course, there are other ways, such as setting the font font-size: 0, and then setting the font size separately for the required content, the purpose is to remove the spaces between the elements. In fact, we can completely achieve this requirement by configuring vue-loader.

{
  vue: {
    preserveWhitespace: false
  }
}

Its role is to prevent the generation gap between element content, after compiling Vue template _v(" ")representation. If there are many templates in the project, they will still take up some file volume. For example, after Element configures this property, the file size is reduced by nearly 30Kb without compression.

7.2 transformToRequireno longer have to write the picture variables

In the past, when writing Vue, I often wrote such code: pass the image to a variable in advance and then pass it to the component.

<template>
  <div>
    <avatar :default-src="DEFAULT_AVATAR"></avatar>
  </div>
</template>
<script>
  export default {
    created () {
      this.DEFAULT_AVATAR = require('./assets/default-avatar.png')
    }
  }
</script>

In fact, by configuring the transformToRequirerear, it can be configured directly, after such vue-loader will automatically require to pass attributes corresponding components

{
  vue: {
    transformToRequire: {
      avatar: ['default-src']
    }
  }
}

So our code can be simplified a lot

<template>
  <div>
    <avatar default-src="./assets/default-avatar.png"></avatar>
  </div>
</template>

In the webpack template of vue-cli, the default configuration is:

transformToRequire: {
  video: ['src','poster'],
  source:'src',
  img:'src',
  image:'xlink:href'
}

You can make a similar configuration by analogy

vue-loader and many useful API such as the recently added custom blocks , you may be interested to look for documentation.

8. render function

In some scenarios, you may need the full programming capabilities brought by the render function to solve problems that are not easy to solve, especially when you want to dynamically select the scene that generates labels and component types.

8.1 Dynamic tags

1. General

For example, a scenario where tags are generated based on props

<template>
  <div>
    <div v-if="level === 1"> <slot></slot> </div>
    <p v-else-if="level === 2"> <slot></slot> </p>
    <h1 v-else-if="level === 3"> <slot></slot> </h1>
    <h2 v-else-if="level === 4"> <slot></slot> </h2>
    <strong v-else-if="level === 5"> <slot></slot> </stong>
    <textarea v-else-if="level === 6"> <slot></slot> </textarea>
  </div>
</template>

Among them, level is a variable in data. You can see that there are a lot of repeated codes here. If the logic is complicated, adding some binding and judgment is more complicated. Here, you can use the render function to judge the label to be generated.

2. Optimization

Using the render method to generate corresponding labels based on the parameters can avoid the above situation.

<template>
  <div>
    <child :level="level">Hello world!</child>
  </div>
</template>

<script type='text/javascript'>
  import Vue from'vue'
  Vue.component('child', {
    render(h) {
      const tag = ['div','p','strong','h1','h2','textarea'][this.level]
      return h(tag, this.$slots.default)
    },
    props: {
      level: {type: Number, required: true} 
    }
  })   
  export default {
    name:'hehe',
    data() {return {level: 3}}
  }
</script>

Examples can be viewed on CodePen

8.2 Dynamic components

Of course, there are many functions render usage, for example, to use dynamic components, in addition to using :isoutside you can also use the render function

<template>
  <div>
    <button @click='level = 0'>Hee hee</button>
    <button @click='level = 1'>Haha</button>
    <hr>
    <child :level="level"></child>
  </div>
</template>

<script type='text/javascript'>
  import Vue from'vue'
  import Xixi from'./Xixi'
  import Haha from'./Haha'
  Vue.component('child', {
    render(h) {
      const tag = ['xixi','haha'][this.level]
      return h(tag, this.$slots.default)
    },
    props: {level: {type: Number, required: true} },
    components: {Xixi, Haha}
  })
  export default {
    name:'hehe',
    data() {return {level: 0}}
  }
</script>

Examples can be viewed on CodePen

@20180702 Add @JserWang reminded multi-icon event decentralized optimization method

Most of the posts on the Internet are of different depths, and even some are inconsistent. The articles below are summaries of the learning process. If you find errors, please leave a message to point out~

Reference: Vue2 global filter (vue-cli) Vue.js best practice webpack document-require.context Use webpack require.context to realize routing "decentralization" management vue-element-admin document Vue.js practical skills optimization Do you want to know the opening speed of the page~

Reference: https://cloud.tencent.com/developer/article/1356673 Tips for using Vue-Cloud + Community-Tencent Cloud