这15个Vue开发技巧你都知道多少呢?

1、样式穿透

在开发中修改第三方组件样式是很常见,但由于 scoped 属性的样式隔离,可能需要去除 scoped 或是另起一个 style 。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在css预处理器中使用才生效。
我们可以使用 >>> 或 /deep/ 解决这一问题:



2、使用回调函数定义watch属性配置

通常定义数据观察,会使用选项的方式在 watch 中配置:

export default {
    data() {
        return {
            count: 1
        }
    },
    watch: {
        count(newVal) {
            console.log('count 新值:' + newVal)
        }
    }
}

除此之外,数据观察还有另一种函数式定义的方式:

export default {
    data() {
        return {
            count: 1
        }
    },
    created() {
        this.$watch('count', function () {
            console.log('count 新值:' + newVal)
        })
    }
}

它和前者的作用一样,但这种方式使定义数据观察更灵活,而且 $watch 会返回一个取消观察函数,用来停止触发回调:

let unwatchFn = this.$watch('count', function () {
    console.log('count 新值:' + newVal)
})
this.count = 2 // log: count 新值:2
unwatchFn()
this.count = 3 // 什么都没有发生...
//$watch 第三个参数接收一个配置选项:


this.$watch('count', function () {
    console.log('count 新值:' + newVal)
}, {
    immediate: true // 立即执行watch
})

参考文档

3、watch监听多个变量

watch本身无法监听多个变量。但我们可以将需要监听的多个变量通过计算属性返回对象,再监听这个对象来实现“监听多个变量”

export default {
    data() {
        return {
            width: 100,
            height: 100
        }
    },
    compouted: {
        msgObj() {
            const { width, height} = this
            return {
                width,
                height
            }
        }
    },
    watch: {
        msgObj: {
            handler(newVal, oldVal) {
                if (newVal.width!= oldVal.width) {
                    console.log('width is change')
                }
                if (newVal.height!= oldVal.height) {
                    console.log('height is change')
                }
            },
            deep: true
        }
    }
}

4、监听组件生命周期#

通常我们监听组件生命周期会使用 $emit ,父组件接收事件来进行通知

子组件

export default {
    mounted() {
        this.$emit('listenMounted')
    }
}

父组件


其实还有一种简洁的方法,使用 @hook 即可监听组件生命周期,组件内无需做任何改变。同样的, created 、 updated 等也可以使用此方法。



5、优雅更新props

更新 prop 在业务中是很常见的需求,但在子组件中不允许直接修改 prop,因为这种做法不符合单向数据流的原则,在开发模式下还会报出警告。因此大多数人会通过 $emit 触发自定义事件,在父组件中接收该事件的传值来更新 prop。

child.vue:
export defalut {
    props: {
        title: String
    },
    methods: {
        changeTitle(){
            this.$emit('change-title', 'hello')
        }
    }
}

parent.vue:


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

这种做法没有问题,我也常用这种手段来更新 prop。但如果你只是想单纯的更新 prop,没有其他的操作。那么 sync 修饰符能够让这一切都变得特别简单。

parent.vue:

 < child : title.sync = "title" >
        child.vue:


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

只需要在绑定属性上添加.sync,在子组件内部就可以触发 update: 属性名 来更新 prop。可以看到这种手段确实简洁且优雅,这让父组件的代码中减少一个“没必要的函数”。

参考文档

6、自动化引入模块

在开发中大型项目时,会将一个大功能拆分成一个个小功能,除了能便于模块的复用,也让模块条理清晰,后期项目更好维护。

像 api 文件一般按功能划分模块,在组合时可以使用 require.context 一次引入文件夹所有的模块文件,而不需要逐个模块文件去引入。每当新增模块文件时,就只需要关注逻辑的编写和模块暴露,require.context 会帮助我们自动引入。

需要注意 require.context 并不是天生的,而是由 webpack 提供。在构建时,webpack 在代码中解析它。

let importAll = require.context('./modules', false, /\.js$/)

class Api extends Request{
       constructor(){
       super()
       //importAll.keys()为模块路径数组
       importAll.keys().map(path =>{
       //兼容处理:.default获取ES6规范暴露的内容; 后者获取commonJS规范暴露的内容
       let api = importAll(path).default || importAll(path)
           Object.keys(api).forEach(key => this[key] = api[key])
       })
   }
}

export default new Api()

require.context 参数:

文件夹路径
是否递归查找子文件夹下的模块
模块匹配规则,一般匹配文件后缀名
只要是需要批量引入的场景,都可以使用这种方法。包括一些公用的全局组件,只需往文件夹内新增组件即可使用,不需要再去注册。如果还没用上的小伙伴,一定要了解下,简单实用又能提高效率。

参考文档

7、provide / inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

简单来说,一个组件将自己的属性通过 provide 暴露出去,其下面的子孙组件 inject 即可接收到暴露的属性。

App.vue:

export default {
    provide() {
        return {
            app: this
        }
    }
}

child.vue:

export default {
    inject: ['app'],
    created() {
        console.log(this.app) // App.vue实例
    }
}
//在 2.5.0 + 版本可以通过设置默认值使其变成可选项:
export default {
    inject: {
        app: {
            default: () => ({})
        }
    },
    created() {
        console.log(this.app)
    }
}
//如果你想为 inject 的属性变更名称,可以使用 from 来表示其来源:
export default {
    inject: {
        myApp: {
            // from的值和provide的属性名保持一致
            from: 'app',
            default: () => ({})
        }
    },
    created() {
        console.log(this.myApp)
    }
}

需要注意的是 provide 和 inject 主要在开发高阶插件 / 组件库时使用。并不推荐用于普通应用程序代码中。但是某些时候,或许它能帮助到我们。

参考文档

8、小型状态管理器

大型项目中的数据状态会比较复杂,一般都会使用 vuex 来管理。但在一些小型项目或状态简单的项目中,为了管理几个状态而引入一个库,显得有些笨重。

在 2.6.0 + 版本中,新增的 Vue.observable 可以帮助我们解决这个尴尬的问题,它能让一个对象变成响应式数据:

// store.js
import Vue from 'vue'

export const state = Vue.observable({
    count: 0
})

使用:

 
{{ count }} import { state } from '../store.js' export default { computed: { count() { return state.count } }, methods: { setCount() { state.count++ } } }

//当然你也可以自定义 mutation 来复用更改状态的方法:

import Vue from 'vue'

export const state = Vue.observable({
    count: 0
})

export const mutations = {
    SET_COUNT(payload) {
        if (payload > 0) {
            state.count = payload
        }
    }
}

使用:

import { state, mutations } from '../store.js'

export default {
    computed: {
        count() {
            return state.count
        }
    },
    methods: {
        setCount() {
            mutations.SET_COUNT(100)
        }
    }
}

参考文档

9、巧用template

相信 v -if 在开发中是用得最多的指令,那么你一定遇到过这样的场景,多个元素需要切换,而且切换条件都一样,一般都会使用一个元素包裹起来,在这个元素上做切换。

 

Title

Paragraph 1

Paragraph 2

如果像上面的 div 只是为了切换条件而存在,还导致元素层级嵌套多一层,那么它没有“存在的意义”。
我们都知道在声明页面模板时,所有元素需要放在 < template > 元素内。除此之外,它还能在模板内使用,