Reprint:Juejin. Cn/post / 685457…

Elegant update props

Updating prop is a common requirement in business, but it is not allowed to modify prop directly in child components because this does not comply with the principle of one-way data flow, and warnings are reported in development mode. So most people will update a prop by sending a custom event via $emit and receiving the value of that event in the parent component.

child.vue:

export defalut {
    props: {
        title: String  
    },
    methods: {
        changeTitle(){
            this.$emit('change-title'.'hello')}}}Copy the codeCopy the code

parent.vue:

<child :title="title" @change-title="changeTitle"></child>
Copy the codeCopy the code
export default {
    data(){
        return {
            title: 'title'}},methods: {
        changeTitle(title){
            this.title = title
        }
    }
}
Copy the codeCopy the code

This is fine, and I often update prop this way too. But if you just want to update a prop, there’s nothing else to do. The Sync modifier makes all of this really easy.

parent.vue:

<child :title.sync="title"></child>
Copy the codeCopy the code

child.vue:

export defalut {
    props: {
        title: String  
    },
    methods: {
        changeTitle(){
            this.$emit('update:title'.'hello')}}}Copy the codeCopy the code

Simply add.sync to the binding property to trigger the UPDATE: property name within the child component to update the prop. As you can see, this approach is really neat and elegant, removing an “unnecessary function” from the parent component’s code.

Reference documentation

provide/inject

This pair of options needs to be used together to allow an ancestor component to inject a dependency into all of its descendants, regardless of how deep the component hierarchy is, and remain in effect for as long as its upstream and downstream relationships are established.

In simple terms, a component exposes its properties through provide, and its descendants inject receive the exposed properties.

App.vue:

export default {
    provide() {
        return {
            app: this}}}Copy the codeCopy the code

child.vue:

export default {
    inject: ['app'],
    created() {
        console.log(this.app) / / App. Examples of vue}}Copy the codeCopy the code

In version 2.5.0+ it can be made optional by setting the default:

export default {
    inject: {
        app: {
            default: (a)= > ({})
        }
    },
    created() {
        console.log(this.app) 
    }
}
Copy the codeCopy the code

If you want to change the name of inject’s attribute, use from to indicate its source:

export default {
    inject: {
        myApp: {
            // The value of from is the same as the attribute name of provide
            from: 'app'.default: (a)= > ({})
        }
    },
    created() {
        console.log(this.myApp) 
    }
}
Copy the codeCopy the code

Note that provide and Inject are mainly used when developing high-level plug-in/component libraries. Not recommended for use in normal application code. But at some point, maybe it can help us.

Reference documentation

Small state manager

Data state in large projects can be complex and is generally managed using VUEX. However, in some small projects or projects with simple states, introducing a library to manage several states can be cumbersome.

In version 2.6.0+, the new Vue.Observable helps solve this awkward problem by turning an object into a responsive data:

// store.js
import Vue from 'vue'
export const state = Vue.observable({ 
  count: 0 
})
Copy the codeCopy the code

Use:

<div @click="setCount">{{ count }}</div>
Copy the codeCopy the code
import {state} from '.. /store.js'
export default {
    computed: {
        count() {
            return state.count
        }
    },
    methods: {
        setCount() {
            state.count++
        }
    }
}
Copy the codeCopy the code

Of course, you can also customize mutation to duplicate the state change method:

import Vue from 'vue'
export const state = Vue.observable({ 
  count: 0 
})
export const mutations = {
  SET_COUNT(payload) {
    if (payload > 0) {
        state.count = payload
    } 
  }
}
Copy the codeCopy the code

Use:

import {state, mutations} from '.. /store.js'
export default {
    computed: {
        count() {
            return state.count
        }
    },
    methods: {
        setCount() {
            mutations.SET_COUNT(100)}}}Copy the codeCopy the code

Reference documentation

Uninstall Watch Observation

Data observation is usually defined and configured in Watch with options:

export default {
    data() {
        return {
            count: 1}},watch: {
        count(newVal) {
            console.log('count new value: '+newVal)
        }
    }
}
Copy the codeCopy the code

In addition, there is another way that data observation can be defined functionally:

export default {
    data() {
        return {
            count: 1      
        }
    },
    created() {
        this.$watch('count'.function(){
            console.log('count new value: '+newVal)
        })
    }
}
Copy the codeCopy the code

It does the same thing, but this way it makes defining the data observation more flexible, and $watch returns a cancel observation function to stop firing the callback:

let unwatchFn = this.$watch('count'.function(){
    console.log('count new value: '+newVal)
})
this.count = 2 // log: count New value: 2
unwatchFn()
this.count = 3 // Nothing happened...
Copy the codeCopy the code

The third parameter of $watch accepts a configuration option:

this.$watch('count'.function(){
    console.log('count new value: '+newVal)
}, {
    immediate: true // Execute watch immediately
})
Copy the codeCopy the code

Reference documentation

Using a template

If v-if is the most commonly used instruction in development, you’ve probably encountered situations where multiple elements need to be switched under the same switching conditions, usually wrapped in a single element and switched on that element.

<div v-if="status==='ok'">
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
</div>
Copy the codeCopy the code

A div like the one above has no “meaning” if it exists only for switching conditions and causes the element hierarchy to be nested one more level.

We all know that when declaring a page template, all elements need to be inside the

<template>
    <div>
        <template v-if="status==='ok'">
          <h1>Title</h1>
          <p>Paragraph 1</p>
          <p>Paragraph 2</p>
        </template>
    </div>
</template>
Copy the codeCopy the code

Similarly, we can use the v-for directive on

<template v-for="item in 10">
    <div v-if="item % 2 == 0" :key="item">{{item}}</div>
</template>
Copy the codeCopy the code

Template uses v-if and template uses V-for

Filter reuse

Filters are used for some common text formatting and are added to the end of expressions, indicated by the “pipe” symbol.

<div>{{ text | capitalize }}</div>
Copy the codeCopy the code
export default {
    data() {
        return {
            text: 'hello'}},filters: {
        capitalize: function (value) {
            if(! value)return ' '
            value = value.toString()
            return value.charAt(0).toUpperCase() + value.slice(1)}}}Copy the codeCopy the code

Imagine a scenario where this function is not only used in a template, but also in a method. Filters cannot be referenced directly through this, so why define the same function in Methods?

Note that option configurations are stored in the instance’s $options, so just get this.$options.filters to get the filters in the instance.

export default {
    methods: {
        getDetail() {
            this.$api.getDetail({
                id: this.id
            }).then(res= > {
                let capitalize = this.$options.filters.capitalize
                this.title = capitalize(res.data.title)
            })
        }
    }
}
Copy the codeCopy the code

Filters will be searched up __proto__, and the global filter will be found in the prototype.

Custom instruction gets instance

In some cases, custom directives are used when low-level operations on ordinary DOM elements are required. Like a permission directive commonly used in a project, it can be down to a module node. If the current bound permission is not in the list, delete the node element.

Vue.directive('role', {
    inserted: function (el, binding, vnode) {
      let role = binding.value
      if(role){
        const applist = sessionStorage.getItem("applist")
        const hasPermission = role.some(item= > applist.includes(item)) 
        // Whether you have permission
        if(! hasPermission){ el.remove()// Delete the module node without permission}}}})Copy the codeCopy the code

The custom instruction hook function receives three parameters, including EL (the real DOM of the binding instruction), binding (instruction related information), and vNode (virtual DOM of the node).

Suppose now that the business has changed, the applist is stored in vuex, but the instruction wants to use properties on the instance, or $store on the prototype. There is no way to get this because the hook function does not provide instance access directly. Vnode is the current virtual DOM, which is bound to the instance context, so accessing vNode. context can easily solve the problem.

Vue.directive('role', {
    inserted: function (el, binding, vnode) {
      let role = binding.value
      if(role){
        // vnode.context is the current instance
        const applist = vnode.context.$store.state.applist
        const hasPermission = role.some(item= > applist.includes(item)) 
        if(! hasPermission){ el.remove() } } } })Copy the codeCopy the code

Elegant plugin registration

Plug-ins are usually used to add global functionality to a Vue. Vue-router and vuex are registered through vue. use. Vue.use is automatically called internally looking for the install method and takes the Vue constructor as the first argument.

Generally, when using component libraries, load on demand is adopted in order to reduce package size. Importing components one by one in an entry file will make Main.js bigger and bigger, and for modular development purposes, it is best to wrap it in a separate configuration file. With vue. use, in the entry file can be used at a glance.

Vant. Config. Js:

import {
  Toast,
  Dialog
} from 'vant'
const components = {
  Toast,
  Button
}
const componentsHandler = {
  install(Vue){
    Object.keys(components).forEach(key= > Vue.use(components[key]))
  }
}
export default componentsHandler
Copy the codeCopy the code

main.js:

import Vue from 'vue'
import vantCompoents from '@/config/vant.config'
Vue.config.productionTip = false
Vue.use(vantCompoents)
new Vue({
  render: h= > h(App)
}).$mount('#app')
Copy the codeCopy the code

Reference documentation

Automatic lead-in module

In the development of medium and large projects, a large function will be divided into small functions, which can not only facilitate module reuse, but also make the module clear and easier to maintain later projects.

As with API files, modules are divided by function. When combining, you can use require.context to import all module files in a folder at once, rather than importing module files one by one. Whenever a new module file is added, it only needs to be concerned with writing the logic and exposing the module. Require.context will help us introduce it automatically.

Note that require.context is not native, but is provided by Webpack. At build time, WebPack parses it in the code.

import Request from '.. /service/request'
let importAll = require.context('./modules'.false, /\.js$/)
class Api extends Request{
    constructor() {super(a)// importall.keys () is the module path array
        importAll.keys().map(path= >{
            // Compatibility processing:.default obtains the content exposed by the ES6 specification; The latter captures the content exposed by the commonJS specification
            let api = importAll(path).default || importAll(path)
            Object.keys(api).forEach(key= > this[key] = api[key])
        })
    }
}
export default new Api()
Copy the codeCopy the code

The require. The context parameter:

  1. Folder path
  2. Whether to recursively look for modules under subfolders
  3. Module matching rules, generally match file name extensions

You can use this method for any scenario that requires batch introduction. Include some common global components, just add components to the folder can be used, no need to register. If you haven’t used a friend yet, be sure to know that simple, practical and efficient.

Reference documentation

Lazy route loading (dynamic chunkName)

As a means of performance optimization, routing lazy loading can give way to component lazy loading. We also typically add a “magic comment” (webpackChunkName) for lazy-loaded routes, which are packaged separately at packaging time.

let router = new Router({
  routes: [{path:'/login'.name:'login'.component: import(/* webpackChunkName: "login" */ `@/views/login.vue`)}, {path:'/index'.name:'index'.component: import(/* webpackChunkName: "index" */ `@/views/index.vue`)}, {path:'/detail'.name:'detail'.component: import(/* webpackChunkName: "detail" */ `@/views/detail.vue`)}]})Copy the codeCopy the code

This is fine, but on closer inspection the structure is similar, and as a good developer, we can use the Map loop to solve this repetitive task.

const routeOptions = [
  {
    path:'/login'.name:'login'}, {path:'/index'.name:'index'}, {path:'/detail'.name:'detail',},]const routes = routeOptions.map(route= > {
  if(! route.component) { route = { ... route,component: (a)= > import(`@/views/${route.name}.vue`)}}return route
})
let router = new Router({
  routes
})
Copy the codeCopy the code

By writing less code, we’re sacrificing magic comments. As you know, you can’t write dynamic comments in code. It’s an awkward question. Isn’t there a way to have it both ways?

Powerful Webpack comes to the rescue. Starting with WebPack 2.6.0, placeholders [index] and [Request] are supported as incrementing numbers or actual parsed file names. We can use magic notes like this:

const routes = routeOptions.map(route= > {
  if(! route.component) { route = { ... route,component: (a)= > import(/* webpackChunkName: "[request]" */ `@/views/${route.name}.vue`)}}return route
})
Copy the codeCopy the code

Reference documents, reference articles

The last

Previous related articles:

10 Vue development Tips to Become a Better engineer