Tips for using Vue

In the process of using vue, you will encounter various scenarios. When you use it normally, you will feel nothing, but maybe you can optimize it to develop more efficiently and beautifully. 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, and we want the chart to be resized when the browser is 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 sub-chart component is not on the same page as the parent container component, the state of the sub-component will be managed by the parent component. For the convenience of maintenance, we naturally hope that the events and states of the sub-component will be maintained by ourselves. When you don't need to go to the parent component to modify it one by one

1.2 Optimization

The throttling 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: {
  /**
   * 图表DOM
   */
  chartWrapperDom() {
    const dom = document.getElementById('consume-analy-chart-wrapper')
    return dom && Echarts.init(dom)
  },
  /**
   * 图表resize节流,这里使用了lodash,也可以自己使用setTimout实现节流
   */
  chartResize() {
    return _.throttle(() => this.chartWrapperDom && this.chartWrapperDom.resize(), 400)
  }
},
mounted() {
  window.addEventListener('resize', this.chartResize)
},
destroyed() {
  window.removeEventListener('resize', this.chartResize)
}

2. Global filter registration

2.1 General situation

The official way to register a filter:

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

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

// 注册
Vue.filter('my-filter', function (value) {
  // 返回处理后的值
})
// getter,返回已注册的过滤器
var myFilter = Vue.filter('my-filter')

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

2.2 Optimization

We can extract it to a separate file, and then use Object.keys to register it at 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 we 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 require 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 the artifact require.context()webpack to create our own module context using the method to achieve automatic dynamic require components. This method takes 3 parameters: the folder directory to search, whether its subdirectories should also be searched, and a regular expression to match files. We add a file called componentRegister.js to the components folder, and in this file, we use webpack to dynamically package all the required basic components.

/src/components/componentRegister.js

import Vue from 'vue'

/**
 * 首字母大写
 * @param str 字符串
 * @example heheHaha
 * @return {string} HeheHaha
 */
function capitalizeFirstLetter(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

/**
 * 对符合'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$/)

// 找到组件文件夹下以.vue命名的文件,如果文件名为index,那么取组件中的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)
})

This folder structure:

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

The component name is judged here. If it is index, the name attribute in the component is processed as the registered component name, so the last registered component is: multi-condition, basic-tableFinally, we import 'components/componentRegister.js' in main.js, Then we can use these basic components anytime, anywhere without manual introduction~

4. Component reuse of different routes

4.1 Scene Restoration

When vue-router jumps from /post-page/a to /post-page/b in a scene. Then we surprisingly found that the data was not updated 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 wrote in the created function is not executed at all. 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': {        // 使用watch来监控是否是同一个路由
    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

How to achieve this effect? ​​The answer is to router-viewadd a different key, 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()timestamp after it to ensure uniqueness

5. Component event attribute penetration

5.1 General situation

// 父组件
<BaseInput :value="value"
           label="密码"
           placeholder="请填写密码"
           @input="handleInput"
           @focus="handleFocus">
</BaseInput>

// 子组件
<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 component instance of vue $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. In this way, our child components need to declare a lot of props each time, here we know that v-bind can pass objects , and we vm.$propscan get the values ​​of all parent component props inv-bind="$props"

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

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

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

$attrsContains property bindings (except class and style) that are not recognized (and obtained) as props in the parent scope. When a component doesn't declare any props, all parent-scoped bindings are included here, and can be v-bind="$attrs"passed to inner components via .

3. Notice that the child component @focus="$emit('focus', $event)"actually does nothing, just pass the event back to the parent component, which is similar to the above, and 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 be passed v-on="$listeners"into internal components - useful when creating higher-level components.

It should be noted that since our input is not the root node of the BaseInput component, by default, the unrecognized attribute binding of propsthe will "fall back" and be applied to the child component as a normal HTML attribute. on the root element. So we need to set inheritAttrs: false, these default behaviors will be removed, and the above optimization can succeed.

6. Routes are lazy loaded according to the development status

6.1 General

Generally, when we load components in routes:

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

export default new Router({
  routes: [{ path: '/login', name: '登陆', 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, it will become inappropriate to use lazy-loading in the development environment, and it will become very slow to trigger hot updates every time you change the code. Therefore, it is recommended to use the routing lazy loading function only in the production environment.

6.2 Optimization

According to Vue's asynchronous components and Webpack's code splitting function , lazy loading of components can be easily implemented, 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 file that sets the route router/index.js:

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

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

In this way, the components are not lazy loaded in the development environment, and lazy loaded in the production environment.

7 vue-loader tips

vue-loader is a webpack loader that handles *.vue files. It itself provides a rich set of APIs, some of which are useful but less well known. For example, to be introduced next preserveWhitespaceandtransformToRequire

7.1 preserveWhitespaceReduce file size with

Sometimes we don't want to have spaces between elements when writing templates, we may write 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, in order to remove the spaces between elements. In fact, we can completely achieve this requirement by configuring vue-loader.

{
  vue: {
    preserveWhitespace: false
  }
}

Its role is to prevent blank content from being generated between elements, and is _v(" ")represented . If there are many templates in the project, they will still take up some file size. For example, after Element configures this property, the file size is reduced by nearly 30Kb without compression.

7.2 transformToRequireUse No need to write pictures as variables anymore

In the past, when writing Vue, I often wrote such code: pass the image require 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, transformToRequireafter , you can configure it directly, so that vue-loader will automatically require the corresponding attributes and pass them to the component

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

Under 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

There are also many useful APIs in vue-loader, such as the recently added custom block . If you are interested, you can go to the documentation to find it.

8. render function

In some scenarios you may need the full programming power of the render function to solve less-than-easy-to-solve problems, especially scenarios where tags and component types are dynamically selected.

8.1 Dynamic tags

1. General

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

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

Where level is a variable in data, you can see that there is a lot of repetitive code here. If the logic is complicated, it will be more complicated with some bindings and judgments. Here, you can use the render function to judge the tags to be generated.

2. Optimization

Using the render method to generate corresponding labels based on 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, the render function has many uses. For example, if you want to use dynamic components, you can also use the render function in :isaddition

<template>
  <div>
    <button @click='level = 0'>嘻嘻</button>
    <button @click='level = 1'>哈哈</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


Most of the posts on the Internet are of different depths and even some inconsistencies. The following articles are the summary of the learning process. If you find any mistakes, please leave a message to point out~

refer to:

Vue2 global filter (vue-cli)

Vue.js Best Practices

webpack documentation - require.context

Use webpack's require.context to achieve routing "decentralized" management

vue-element-admin documentation

Practical Tips for Vue.js


Author: SHERlocked93
Link: https://juejin.im/post/5ae02f39518825672f198ac2
Source: Nuggets The
copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325867837&siteId=291194637