preface

I’ve been writing vUE for a long time and have encountered challenges in various scenarios. This article documents some good practices I’ve found in using VUE that I hope will help you.

Tips for using vue.js

Override the Element-UI style

Override only the Element-UI style within a component, not the whole world.

The namespace

Since the element-UI style was introduced globally, you can’t add scoped if you want to cover only the element style on a page. If you want to cover only the element style on that page, you can add a class to its parent and use the namespace to solve the problem.

.article-page {
  /* Your namespace */
  .el-tag {
    /* element- UI element */
    margin-right: 0px;
 } } Copy the code

Style through

Vue projects are large and complex, and it is possible to load many components with the same name on a single page. Unless your project has strict namespace management, I recommend using depth selector to override styles.

 <el-dialog>
       <el-table style="margin-left:30px"></el-table>
   </el-dialog>
   <style scoped>
 .el-dialog >>> .el-table{  margin-left:20px! important;  /* margin-left:20px; Inline styles */ will be overridden  }  </style> Copy the code

Note: If you use something preprocessed, such assassYou can go through/deep/To take the place of>>>Achieve the desired effect.


Variable sharing between Sass and JS

Usage scenario: Dynamic skin, the theme selected by the user needs to be passed to the CSS, and the CSS needs to pass the default theme color to js during initialization.

Js passes the variable to sass

This can be done using vue inline tags, CSS var(), modifyVars with less, and so on.

<div :style="{'background-color':color}" ></div>
Copy the code

Sass gives the variable to JS

How to obtain sass variables in CSS -modules:export

// var.scss
$theme: blue;

:export {
  theme: $theme;
} Copy the code
// test.js
import variables from '@/styles/var.scss'
console.log(variables.theme) // blue
Copy the code

Debugging the Template code

During vue.js development, we can use DevTools to debug the code, but sometimes we encounter JavaScript variable errors when rendering the Template template.

This can be done using console.log, and it is recommended to use the Vue prototype mount method as there is not only one component to debug.

// main.js
Vue.prototype.$log = window.console.log;

// .vue
<div>{{$log(message)}}</div>
Copy the code

, of course, we can also use | | operators can debug already so, also won’t affect rendering.

<div>{{ $log(message) || message }}</div>
Copy the code

Attrs and Listeners encapsulate components twice

When we write business, we inevitably need to re-encapsulate some third-party components. When we re-encapsulate others’ components, there may be many attributes on others’ components, and we do not want to rewrite them again.

<template>
  <div>
    <el-button v-bind="$attrs">determine</el-button>
  <div>
</template>
 // Used by the parent component<my-button type='primary' size='mini'/> Copy the code

Here are some examples of attrs and listeners who may be unfamiliar with them.

/ / the parent component<home title="Here's the title." @change="change" width="80" height="80" imgUrl="imgUrl"/>
Copy the code
/ / child component
mounted() {
  console.log(this.$attrs) / / {title: "this is the title", width: "80", height: "80", imgUrl: "imgUrl"}
  console.log(this.$listeners) // Get the change event
},
Copy the code

The sync syntactic sugar

This method is useful when you need to change the value of the parent component in the child component. It is implemented in the same way as the V-Model.


Optimization of hot update speed

Hot update is something we deal with every day. The speed of hot update directly affects our development efficiency.

First, route lazy loading is very, very unsuitable for development environments and can severely heat up update speeds.

Differentiate the development environment

The earliest solution is to separate the development environment from the production environment and create two files under the routing folder respectively:

Development environment:

module.exports = file= > require("@iews/" + file + ".vue").default;
Copy the code

Generation environment:

module.exports = file= > (a)= > import("@iews/" + file + ".vue");
Copy the code

This allows components to be lazily loaded in development and lazily loaded in production.

dynamic-import-node

Babel’s dynamic-import-Node is a new solution that does the same thing, but does not require developers to build two routes.

"env": {
    "development": {
      "plugins": ["dynamic-import-node"]
    }
 }
Copy the code

Finally, in addition to lazy route loading, the following problems should also be checked:

  1. Not fair usesouce map
  2. Development environment do not compress code, extractcss babel polyfill

Use Object.freeze to improve performance

Let’s say we need to render a very large array object, such as a list of users, a list of objects, a list of articles, etc.

export default {
  data: (a)= > ({
    users: {}
  }),
  async created() {
 const users = await axios.get("/api/users");  this.users = users;  } }; Copy the code

Vue adds all the properties of the data object to vue’s responsive system. When the values of these properties change, the view will respond. If the object is large, it will consume a lot of browser parsing time.

So we can provide front-end performance by reducing reactive conversion of data.

export default {
  data: (a)= > ({
    users: {}
  }),
  async created() {
 const users = await axios.get("/api/users");  this.users = Object.freeze(users);  } }; Copy the code

Always use :key in V-for

Key can maximize the use of nodes and reduce performance consumption. As for the reason, you need to understand the process of diff and have an understanding of the Virtual DOM.

<! -- Bad practice -->
<div v-for='product in products'>  </div>

<! -- Good practice -->
<div v-for='product in products' :key='product.id'>
Copy the code

Note: At work, many people use index as the key and seem to have few problems. It is true that index, as a key, has almost no problems in performance, but it has two main shortcomings.

  • Index is the key, which is the same thing as no key

  • Index is a key that can only be used for list rendering output that does not depend on child component state or temporary DOM state (e.g. form input value).

Do not use both the V-if and v-for directives on the same element

Never use v-if and V-for on the same element at the same time. V-for has a higher priority than V-if, so if nested, v-for will perform V-if every time, causing unnecessary calculation and affecting performance, especially if it requires a small part of rendering.

<ul>
 <li 
  v-for='product in products' 
  :key='product.id' 
 v-if='product.price < 50'  >  {{ product.name }}  <> </ul> Copy the code

To avoid the above problems, a filtering method can be used as follows:

<ul>
  <li v-for='products in productsUnderPrice(50)' :key='product._id' >
    {{ product.name }}
  <>
</ul>
 <script>  export default {  data () {  return {  products: []  }  },  methods: {  productsUnderPrice (price) {  return this.products.filter(product= > product.price < price)  }  }  } </script>  Copy the code

Simplify VUX with require.context

Require. context is a function webpack uses to manage dependencies and can be used to implement unrestricted multi-level module imports.

First look at my store directory structure:


Each file is a module, so in store/index.js you can write:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
const modulesFiles = require.context('./modules'.true, /\.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) = > {  // set './app.js' => 'app'  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/.'$1')  const value = modulesFiles(modulePath)  modules[moduleName] = value.default  return modules }, {})  const store = new Vuex.Store({  modules })  export default store Copy the code

Use Render to render components

Template syntax is fine most of the time, but sometimes it’s not, so that’s where the render function comes in.

// Template syntax
<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>  // render render function <script>  export default {  name: hehe ,  render(h) {  const tag = [ div , p , strong , h1 , h2 , textarea ][this.level- 1]  return h(tag, this.$slots.default)  },  props: {  level: { type: Number.required: true }  }  } </script> Copy the code

Much simpler and clearer! In short, the code is much simpler.

Note: If the component has template syntax, the render function will not work, and if the render function is used, some of the built-in vue directives will not work, including V-if, V-for, and V-model, and will need to be implemented by ourselves.

Render suitable for complex logic, template suitable for simple logic;

In the vue principle, the Template is compiled to generate the AST, which generates the Render function, and finally generates the virtual DOM, so the performance of render is higher.

It is important to understand some of the basic concepts in VUE, and finally the last diagram, which gives a macro view of the overall VUE process.


JSX syntactic sugar

The complex render function is a pain to write, but a Babel plugin is available and JSX syntax can be used directly in vue’s render function.

Note: If you are using vue-CLI 3.x created projects, you can use JSX directly without any configuration.

// Template syntax
  <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>  //jsx const App = {  render() {  const tag = `hThe ${this.level}`  return <tag>{this.$slots.default}</tag>  } } Copy the code

Functional component

If the component we need is simple, doesn’t manage any state, doesn’t have lifecycle methods, and just accepts some prop functions, we can use functional components in such scenarios.

render

<script>
export default{
  functional: true.// Add the attribute functional: true to indicate that the component is functional
  // Props is optional
  props: {
 // ...  },  // To make up for missing instances  // Provide the second argument as context  render: function (createElement, context) {  // ...  }, } </script> Copy the code

functional

<template functional>
    <div v-for="(item,index) in props.arr">{{item}}</div>
</template>
Copy the code

The context to describe

Functional components are stateless, have no this context, and have no properties such as data, so all data is retrieved by the second context argument to the Render function.

  • Props: Objects that provide all prop
  • Data: The entire data object passed to the component as the second parameter to createElement
  • Children: array of VNode children
  • Parent: reference to the parent component
  • Slots: a function that returns an object containing all slots
  • ScopedSlots: (2.6.0+) An object that exposes the incoming scope slots. Normal slots are also exposed as functions.
  • An object containing all event listeners registered by the parent component for the current component. This is an alias for data.on.
  • Injections: (2.3.0+) If the Inject option is used, then the object contains attributes that should be injected.

Dynamically switched module

If we intend to refer to different components based on state, such as TAB pages, then dynamic component loading is involved.

<component :is="currentTabComponent"></component>
Copy the code

However, each time the component is reloaded, it can consume a lot of performance, so

comes into play

<keep-alive>
  <component :is="currentTabComponent"></component>
</keep-alive>
Copy the code

Vue. Js common API

vue.directive

In VUE, in addition to v-model, V-IF, V-show and other instructions, we can also define some instructions to achieve the desired function.

Usage scenario: For unavoidable manipulation of DOM elements, put it in a custom directive.

Global directives

A directive is defined that all components can use.

Argument 1 is the name of the instruction, and argument 2 can be either a function or an object.

/ / function
Vue.directive("save-color".function(el,binding,vnode){
  el.style["color"]= binding.value;  // Change the element color
  console.log(el)   // The dom element of the directive
  console.log(binding)  // Store the binding information
 console.log(vnode) // virtual node, the node of the instruction binding element })  / / object Vue.directive("save-color", { bind() {  Emitted when a directive is bound to an HTML element.** is called only once  el.style["color"]= binding.value;  },  } )  // Use custom directives <template> <div v-save-color="color">{{message}}</div> </template> <script>  export default{  data(){  return{  color: red  }  }  } </script> Copy the code

Component instruction

Defined within a component and used only within that component.

/ / a component
data(){
    return {
       // ... 
    }
}, directives: {  save-color: {  // The definition of a directive  bind:function(el,binding){  el.style["color"]= binding.value;  }  } }, methods: {/ /... } Copy the code

Custom instruction life cycle

bind()   Emitted when a directive is bound to an HTML element. ** is called only once
inserted()  Triggered when the HTML element bound to the directive is inserted into the parent element (in this case, 'div#app')
updated // called when the component's 'VNode' is updated
componentUpdated()  // call after the component's VNode and its child VNodes are all updated.
unbind()  // Only called once, when the instruction is unbound from the element.
Copy the code

vue.mixin

In the component, there are some common methods or duplicate JS logic, such as verification of mobile phone verification code, parsing time, etc., we can separate them out and use mixins to implement mixing.

Mixin: Objects and methods that extend a component, which can be interpreted as a new component that reuses the same code in different components.

Note: Not only methods can be mixed in, but life cycle, data, computed are quite flexible.

Local mixed with

When a component uses a mixin, all information about the mixin object is accessible to the component as if it were accessing its own object.

// mixin.js file
export default {
   data () {
      msg: 'Hello World'
   },
 created: function () {  console.log('Printing from the Mixin')  },  methods: {  displayMessage: function () {  console.log('Now printing from a mixin function')  }  } }  // -----------------------------------------  // main.js file import mixin from './mixin.js' new Vue({  mixins: [mixin],  created: function () {  console.log(this.$data)  this.displayMessage()  } }) // => "Printing from the Mixin" // => {msg: 'Hello World'} // => "Now printing from a mixin function"  Copy the code

With global

Global means that mixin objects can be used in all components.

Vue.mixin({
   // Here is the content to be mixed in
})
Copy the code

Note: Be careful with the global blending method. After all, whenever global mixins are used, all instances share the mixins, whether you add mixins or not.

Mixins characteristics

  • Methods and parameters are not shared among components
  • When the mixin data object conflicts with the component data, the component data takes precedence
  • If there are life-cycle hooks in a mixin, both the mixin and the mixin hooks are executed, and the mixin hooks are executed before the instance (or component) itself. Because the hook function of the same name will be mixed into an array, both will be called.
  • There can be more than one mixins, for example, mixins: [mix1, mix2, mix3], if these mixins are defined, otherwise an error will be reported.

vue.filter

It can filter data and return the required results, which is very convenient and fast.

Global filter

Most filters are intended to be used globally and are not written to components whenever they are used. It is better to use global filters.

srcb/filters.js

function formatMoney(value, fractionDigits = 2) {
  if(! value) {    return "";
  }
  return (value / 100).toFixed(fractionDigits).toString();
} export default function(instance) {  instance.filter("formatMoney", formatMoney); } Copy the code

src/main.js

import Vue from 'vue'
import App from './App'
import router from './router'
import initFilters from "@b/filters"

Vue.config.productionTip = false initFilters(Vue)  new Vue({  el: '#app'. router,  components: { App },  template: '<App/>' }) Copy the code

Template file call

<template>
  <div class="demo">
    <h1>{{ msg }}</h1>
    <h2>{{ price | formatMoney }}</h2>
    <h2>{{ (price/100).toFixed(2) }}</h2>
 </div> </template>  <script> export default {  name: 'Demo'. data () {  return {  msg: 'Welcome to Your Vue.js App'. price: 1000  }  } } </script>  <style scoped></style> Copy the code

Local filter

Local filters are stored in the Vue component as a function in the Filters property.

export default {
  data() {
    return {
    }
  },
 filters: {  // Uppercase filter  capitalize: function (value) {  if(! value)return ' '  value = value.toString()  return value.charAt(0).toUpperCase() + value.slice(1)  }  } }; Copy the code

How to use the filter

  1. Interpolation with double curly braces
{{ phone | privatePhone }}
Copy the code
  1. V-bind expression (2.1.0+ starting support).
<div v-bind:data=" phone | privatephone "></div>
Copy the code
  1. Use multiple filters
{{ phone | privatePhone | privatePhon2 }}
Copy the code

The need for pipe between filter operator | is separated, the execution order from left to right.

vue.extend

We’ll use vue.extend to encapsulate global plug-ins like Toast, Diolog, etc.

While components are normally registered with Component and used in templates, vue. Extend is written programmatically and requires manual component mounting and destruction.

toast.js

import Vue from 'vue'

const Toast = Vue.extend({
  template: '<div>{{ text }}</div>'.  data: function () {
 return {  text: 'extend test'  }  } })  let instance const toast = function(options) {  options = options || {}  instance = new Toast({  data: options  })  instance.vm = instance.$mount()  document.body.appendChild(instance.vm.$el)  return instance.vm }  export default toast; Copy the code

Introduce the Toast group valence in main.js and mount it on the vue prototype

import Vue from 'vue'
import toast from './components/toast'
Vue.prototype.$toast = toast
Copy the code

Called in the project

this.$toast({ text: 'Hello Word' })
Copy the code

vue.nextTick

Definition: A deferred callback is performed after the next DOM update loop ends. Use this method immediately after modifying the data to get the updated DOM.

This function is automatically executed after the data is updated and rendered in the DOM.

testClick:function(){
      let that=this;
      that.testMsg="Modified value";
      that.$nextTick(function(){
        console.log(that.$refs.aa.innerText);  // Output: modified value
 });  } Copy the code

other

watch

Regular use

created(){
  this.getList()
},
watch: {
  inpVal(){
 this.getList()  } }  Copy the code

Listening to the routing

watch:{
    $route(to,from) {      console.log(from.path);  / / come from
      console.log(to.path);    / /
    }
}  Copy the code

Executed immediately

watch:{
    $route: {      handler(val,oldval){
        console.log(val);     // New routing information
        console.log(oldval);  // Old routing information
 },  // Deep observation monitor  deep: true  }  } Copy the code

route

Routing and the cords

The default package
// Route definition
{
  path:  /describe/:id ,
  name:  Describe ,
  component: Describe
} // Pass the page parameter this.$router.push({  path: `/describe/${id}`.}) // page fetch this.$route.params.id Copy the code
params
// Route definition
{
  path:  /describe ,
  name:  Describe ,
 omponent: Describe } // Page parameter transferthis.$router.push({  name: Describe ,  params: {  id: id  } }) // Page accessthis.$route.params.id Copy the code
query
// Route definition
{
  path:  /describe ,
  name:  Describe ,
 component: Describe } // Page parameter transferthis.$router.push({  path: /describe ,  query: {  id: id  `} ) // page fetchthis.$route.query.id Copy the code
contrast

The params parameter will not be concatenated after the route, the page refresh parameter will lose the default schema and the Query parameter will be concatenated after the route, exposing information

Routing hop

this.$router.push(): Jumps to a different URL, but this method adds a record back to the history stack, and clicking back returns you to the previous pagethis$router.replace(): no recordthis.$router. Go (n):n can be positive or negative. A positive number returns the previous page, similarwindow.history.go(n)
Copy the code

v-slot

The default slot

/ / the parent component
<todo-list> 
    <template v-slot:default>
Any content <p>I'm the default slot</p>  </template> </todo-list>  / / child component <slot>I'm the default</slot> Copy the code

A named slot

In short, it’s a slot with a name.

/ / the parent component
<todo-list> 
    <template v-slot:todo>
Any content <p>I'm the default slot</p>  </template> </todo-list>  / / child component <slot name="todo">I'm the default</slot> Copy the code

Scope slot

The data in the child component can be retrieved by the parent page, so the data can only be passed from the parent page to the child component.

/ / the parent component
<todo-list>
 <template v-slot:todo="slotProps" >
   {{slotProps.user.firstName}}
 </template> 
</todo-list> //slotProps can be named at will //slotProps is a set of attribute data on the subcomponent tag slot. All v-bind:user="user"  / / child component <slot name="todo" :user="user" :test="test">  {{ user.lastName }}  </slot> data() {  return {  user: { lastName:"Zhang". firstName:"yue"  },  test: [1.2.3.4]  }  }, // {{user.lastName}} is the default data v-slot:todo when the parent page does not (="slotProps") Copy the code

At the end

If this article has helped you, please like it and follow it, search for Manta Fish in the Ocean and join our technology group to learn and discuss and explore the frontier together.