In the process of using VUE, you will encounter various scenarios, which may not seem to matter when you use it in general, but may be optimized to make the development more efficient and elegant. Here are some tips that I use on a daily basis and will be updated from time to time

Interested students can add the end of the wechat group, discuss together ~

1. Multiple chart resize events are decentralized

1.1 General Information

Sometimes we have a scenario where a component has several charts in it, and we want the charts to be resized when the browser resize them, so we write in the parent container component:

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

In this way, if the chart component is not on the same page as the parent container component, the state of the child component is managed by the parent component. For easy maintenance, we naturally want the events and state of the child component to be maintained by itself, so that when adding or deleting components, we do not need to go to the parent component to modify

1.2 optimization

Lodash’s throttling throttle function is used here, or you can implement it yourself. This article also has a throttling implementation. Take Echarts as an example. Within each chart component:

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

1.3 Re-optimization

Thanks to @Jserwang for reminding me that since multiple chart instances use the same set of initialization logic, you can use extends to allow for reuse, so I thought of the Mixins provided by Vue, so I did a little optimization here. To make each chart component of the same type a little more elegant, create a new mixin.js file:

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

export default {
  computed: {
    /* Graph DOM */
    $_chartMixin_chartWrapperDom() {
      const dom = document.getElementById(this.thisDomId)
      return dom && Echarts.init(dom)
    },
    
    /** Chart resize throttling, which uses lodash, or you can use setTimout to achieve throttling yourself */
    $_chartMixin_chartResize() {
      return _.throttle((a)= > 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)
  }
}
Copy the code

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

This allows each diagram component to mixin the resize event logic defined in mixin.js, which is automatically initialized and automatically destroyed when destroyed

For example, if there are more than one chart on one page, the above implementation will not be able to solve the problem, and this needs to be refacfacted. Please refer to the chartinitMixin-Github implementation for details

2. Register global filters

2.1 General Information

How to officially register filters:

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

However, for our projects, most of the filters are to be used globally. It is better not to write them in components every time they are used. Official registration of the global way:

/ / register
Vue.filter('my-filter'.function (value) {
  // Return the processed value
})
// Getter, which returns the registered filter
var myFilter = Vue.filter('my-filter')
Copy the code

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

2.2 optimization

We can extract it into a separate file and register it in main.js using object.keys

/src/common/filters.js

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

export { dateServer }
Copy the code

/src/main.js

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

The global filters we defined are then happily used 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>
Copy the code

3. Register global components

3.1 General Situation

Scenarios where components are required:

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

We wrote a bunch of basic UI components, and then every time we needed to use those components, we had to import them first, and then declare the components. It was tedious, but we could use a unified registry

3.2 optimization

We need to use the webPack artifact to create our own module context using the require.context() method to implement an automatic dynamic require component. This method takes three parameters: the directory of the folder to be searched, whether its subdirectories should also be searched, and a regular expression that matches the file. We add a file called ComponentRegister.js to the Components folder, where we use WebPack to dynamically package all the basic components we need.

/src/components/componentRegister.js

import Vue from 'vue'

/** * Capitalization * @param STR string * @example heheHaha * @return {string} heheHaha */
function capitalizeFirstLetter(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)}/ * * * to conform to the 'xx/xx. Vue' component format the component from the component name * @ 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.vue file 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)
})
Copy the code

Here’s the folder structure:

Components │ ├─BasicTable │ ├─MultiCondition │ index.vueCopy the code

If the component name is index, the name attribute in the component is processed as the registered component name, so the last registered component is: Multi – condition, the basic – table. Finally, we in the main import in js’ components/componentRegister. Js’, and then we can use these basic components, anytime and anywhere without having to manually ~ is introduced

4. Reuse components of different routes

4.1 Scenario Restoration

Example In a scenario, the Vue-router changes from /post-page/a to /post-page/b. And then, to our surprise, the data wasn’t updated when the page jumped, right? ! The reason is that the Vue-Router “intelligently” realizes that this is the same component, and then it decides to reuse that component, so the method you wrote in the created function never executes. The usual solution is to listen for changes in $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){ }
}
Copy the code

4.2 optimization

To achieve this, you can add a different key to the router-view, so that even if it is a common component, it will be recreated whenever the URL changes.

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

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

If the component is placed in

, it is possible to put the method to retrieve new data in the Activated hook instead of the task to retrieve data in the created or mounted hook.

5. Component event attribute penetration

5.1 General Information

/ / the parent component<BaseInput :value="value"
           label="Password"
           placeholder="Please fill in your password"
           @input="handleInput"
           @focus="handleFocus">
</BaseInput>/ / child component<template>
  <label>
    {{ label }}
    <input :value=" value"
           :placeholder="placeholder"
           @focus="$emit('focus', $event)"
           @input="$emit('input', $event.target.value)">
  </label>
</template>
Copy the code

5.2 optimization

$props and $attrs in vue’s component instance are very handy, especially when passing values between parent and child components. For every props passed from a parent component to a child component, we must explicitly declare them in the child component’s props. V -bind=” props” props =” props” props =” props” props =” props” props = props

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

A DOM native property like a placeholder can use $attrs to go directly from a parent to a child without having to declare it. Here’s how:

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

$attrs contains feature bindings (other than class and style) that are not identified (and retrieved) in the parent scope as a prop. When a component does not declare any prop, all parent scope bindings are included and internal components can be passed through v-bind=”$attrs”.

$emit(‘focus’, $event) = ’emit(‘focus’, $event)’; $emit(‘focus’, $event) = ’emit(‘focus’, $event)’;

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

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

$Listeners include v-ON event listeners in the parent scope (without.native decorators). It can pass in internal components via V-ON =”$Listeners “– useful when creating higher-level components.

Note that since input is not the root node of the component BaseInput, by default the parent scope’s feature bindings that are not known as props will be “rolled back” and applied to the root element of the child component as normal HTML features. So we need to set inheritAttrs: false to remove all of these default behaviors in order for the optimization to succeed.

6. Routes are lazily loaded based on the development status

6.1 General Situation

When we load components in a route:

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

export default new Router({
  routes: [{ path: '/login'.name: 'login'.component: Login}]
})
Copy the code

When you want to load lazy-loading, you need to change the component of routes one by one to () => import(‘@/views/login.vue’).

When you have more and more pages in your project, lazy loading becomes inappropriate in your development environment, and it becomes very slow to trigger hot updates every time you change your code. Therefore, it is recommended to use lazy route loading only in the build environment.

6.2 optimization

Using Vue’s asynchronous components and Webpack’s code splitting capabilities, lazy loading of components can be easily achieved, such as:

const Foo = (a)= > import('./Foo.vue')
Copy the code

To distinguish between the development and production environments, you can create two files in the routing folder:

_import_production.js

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

_import_development.js (this is written at least v13.0.0 for the Vue-loader version)

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

In the router/index.js file that sets the route:

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

export default new Router({
  routes: [{ path: '/login'.name: 'login'.component: _import('login/index')}]})Copy the code

This makes the component non-lazily loaded in the development environment and lazily 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 useful but less well known. Examples include preserveWhitespace and transformToRequire

7.1 withpreserveWhitespaceReduce file size

Sometimes when we write a template we don’t want Spaces between elements. We might write something like this:

<ul>
  <li>1111</li><li>2222</li><li>333</li>
</ul>
Copy the code

There are other options, such as setting the font size to font-size: 0 and then setting a separate font size for the content you want, in order to remove the space between elements. You can do this by configuring vue-Loader.

{
  vue: {
    preserveWhitespace: false}}Copy the code

This prevents whitespace from being generated between elements and is represented by _v(” “) after the Vue template is compiled. If you have a lot of templates in your project, they will still take up some file space. For example, when Element is configured with this attribute, the uncompressed file size is reduced by nearly 30Kb.

7.2 the use oftransformToRequireI don’t have to write the picture as a variable anymore

In the past, when writing Vue, I often wrote code that required the image to a variable before passing 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>
Copy the code

In fact, you can configure the transformToRequire directly by configuring the Ue – Loader so that the corresponding properties are automatically required to the component

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

So we can simplify our code a lot

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

Under the Vue-CLI Webpack template, the default configuration is:

transformToRequire: {
  video: ['src'.'poster'].source: 'src'.img: 'src'.image: 'xlink:href'
}
Copy the code

A similar configuration can be done by analogy

Vue-loader also has a number of useful apis, such as custom blocks, which have been added recently. If you are interested, check out the documentation.

8. Render function

In some scenarios you may need the full programming capabilities of the Render function to solve problems that are not easy to solve, especially for scenarios that dynamically select generated tags and component types.

8.1 Dynamic Labels

1. General

For example, generate a tag scenario 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>
Copy the code

As you can see, there is a lot of repetitive code here. If the logic is a little more complex, with some bindings and judgments, it becomes more complicated. Here we can use the render function to judge the tags to be generated.

2. Optimize

This can be avoided by using the Render method to generate the corresponding tags based on the parameters.

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

For an example, see CodePen

8.2 Dynamic Components

Of course, there are many uses for the render function. For example, to use dynamic components, you can use the render function in addition to :is

<template>
  <div>
    <button @click='level = 0'>Hee hee</button>
    <button @click='level = 1'>Ha ha</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>
Copy the code

For an example, see CodePen


@20180702 Added @Jserwang alerts multi-icon event decentralization optimization method


Most of the online posts are different in depth, and even some inconsistent, the following article is a summary of the learning process, if you find mistakes, welcome to leave a message to point out ~

Reference:

  1. Vue2 Global Filter (vue-cli)
  2. Vue.js best practices
  3. Webpack documentation – require.context
  4. Use Webpack’s require.context to “decentralize” routing
  5. Vue – element – admin document
  6. Practical tips for vue.js
  7. Optimize the page open speed, want to understand ~

PS: Welcome everyone to pay attention to my public number [front afternoon tea], refueling together ~

In addition, you can join the wechat group of “front end afternoon tea Communication Group”. Long press to identify the qr code below to add my friend, note to add group, I pull you into the group ~