现在,Vue.js 已成为前端开发的热门框架。有很多工程师利用 Vue.js 的便利性和强大功能。但是,我们完成的某些解决方案可能未遵循最佳做法。今天本文就盘点一下那些鲜为人知的 Vue 技术。

路由器参数解耦

相信这是大多数人处理组件中路由参数的方式:

1
2
3
4
5
6
7
export default {
methods: {
getRouteParamsId() {
return this.$route.query.id
}
}
}

在组件内部使用 $route 会对某个 URL 产生强耦合,这限制了组件的灵活性。

正确的解决方案是向路由器添加 props

1
2
3
4
5
6
7
8
// 路由配置
const router = new VueRouter({
routes: [{
path: '/:id',
component: Component,
props: true
}]
})

这样,组件可以直接从 props 获取 id

1
2
3
4
5
6
7
8
export default {
props: ['id'],
methods: {
getParamsId() {
return this.id
}
}
}

此外,你还可以传入函数以返回自定义 props

1
2
3
4
5
6
7
8
// 路由配置
const router = new VueRouter({
routes: [{
path: '/:id',
component: Component,
props: router => ({ id: router.query.id })
}]
})

这里参数涉及两种, 分别是路由的 paramsquery,需要注意区分

  • params 表示 router 对象下的 params 属性, 可以由 props 的回调函数内置参数访问, 参数名由 path 属性 /: 输入, 这里是 id, 也可以写成别的,在路由展示上是用 / 拼接在路由名后面, 例如: http://localhost:8080/test/9010903

  • query 则是表示 router 对象下的 query 属性, 也可以由 props 的回调函数内置参数访问, 参数可以在页面中操作 router 对象输入, 在路由展示上是用 ? 拼接在路由末端, 例如: http://localhost:8080/test/9010903?name=ogliu

如果两种参数一起使用,可如下配置:

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
path: 'test/:id',
component: () => import('../views/test.vue'),
name: 'test',
props: router => ({
id: router.params.id,
name: router.query.name
})
})

对应的页面就可以像这样获取参数

1
2
3
4
5
6
7
8
// 当前位置 test.vue
export default {
props: ['id', 'name'],
mounted() {
console.log(this.id) // 假设当前路由为 http://localhost:8080/test/9010903?name=ogliu, 则会输出 9010903
console.log(this.name) // 假设当前路由为 http://localhost:8080/test/9010903?name=ogliu, 则会输出 'ogliu'
}
}

监听多个变量

watcher 不能监听多个变量,但我们可以将目标组合在一起作为一个新的 computed,并监视这个新的 “变量”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export default {
data() {
return {
msg1: 'apple',
msg2: 'banana'
}
},
compouted: {
msgObj() {
const { msg1, msg2 } = this
return {
msg1,
msg2
}
}
},
watch: {
msgObj: {
handler(newVal, oldVal) {
if (newVal.msg1 != oldVal.msg1) {
console.log('msg1 is change')
}
if (newVal.msg2 != oldVal.msg2) {
console.log('msg2 is change')
}
},
deep: true
}
}
}

函数式组件

函数组件 是无状态的(没有响应式数据),没有生命周期或方法,因此无法实例化, 创建一个函数组件非常容易,你需要做的就是在 SFC 中添加一个 functional: true 属性,或者在模板中添加 functional 。由于它像函数一样轻巧,没有实例引用,所以渲染性能提高了不少, 函数组件依赖于上下文,并随着其中给定的数据而突变。

下列示例与 vue 官方文档示例不同,它是基于 vue-cli 构建的项目写法,更贴近日常使用,也更适合新手学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<test-components text="test"></test-components>
</template>

<script>
export default {
components: {
testComponents: {
functional: true,
props: {
text: {
type: String,
required: true
}
},
render: function (createElement, context) {
return createElement('h1', context.props.text)
}
}
}
}
</script>

组件生命周期 Hook

通常,你可以像这样监听子组件的生命周期(例如 mounted

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Child -->
<script>
export default {
mounted () {
this.$emit('onMounted')
}
}
</script>

<!-- Parent -->
<template>
<Child @onMounted="handleOnMounted" />
</template>

还有另一种简单的解决方案,你可以改用 @hook:mountVue 内部系统中使用。

1
2
3
4
<!-- Parent -->
<template>
<Child @hook:mounted="handleOnMounted" />
</template>

样式穿透

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

我们可以使用 >>>/deep/ 解决这一问题:

1
2
3
4
5
6
7
8
9
10
<style scoped>
外层 >>> .el-checkbox {
display: block;
font-size: 26px;

.el-checkbox__label {
font-size: 16px;
}
}
</style>
1
2
3
4
5
6
7
8
9
10
<style scoped>
/deep/ .el-checkbox {
display: block;
font-size: 26px;

.el-checkbox__label {
font-size: 16px;
}
}
</style>

事件参数 $event

$event 是事件对象的特殊变量,在一些场景能给我们实现复杂功能提供更多可用的参数

原生事件

在原生事件中表现和默认的事件对象相同

1
2
3
4
5
<template>
<div>
<input type="text" @input="inputHandler('hello', $event)" />
</div>
</template>
1
2
3
4
5
6
7
export default {
methods: {
inputHandler(msg, e) {
console.log(e.target.value)
}
}
}

自定义事件

在自定义事件中表现为捕获从子组件抛出的值

my-item.vue:

1
2
3
4
5
6
7
export default {
methods: {
customEvent() {
this.$emit('custom-event', 'some value')
}
}
}

App.vue:

1
2
3
4
5
6
<template>
<div>
<my-item v-for="(item, index) in list" @custom-event="customEvent(index, $event)">
</my-list>
</div>
</template>
1
2
3
4
5
6
7
export default {
methods: {
customEvent(index, e) {
console.log(e) // 'some value'
}
}
}

触发监听执行多个方法

你可以传入回调数组,它们会被逐一调用,形式包括字符串、函数、对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export default {
data: {
name: 'Joe'
},
watch: {
name: [
'sayName1',
function(newVal, oldVal) {
this.sayName2()
},
{
handler: 'sayName3',
immaediate: true
}
]
},
methods: {
sayName1() {
console.log('sayName1==>', this.name)
},
sayName2() {
console.log('sayName2==>', this.name)
},
sayName3() {
console.log('sayName3==>', this.name)
}
}
}

Watch 中的引号

在 vuejs 项目中, 免不了使用 watch, 而在我们项目代码中经常看到 watch 监听的属性部分,有些有引号,有些没引号,甚至有些不用引号不行,官方文档没有详细说明这个引号的作用,所以很多人弄不清楚它又什么作用。

对象具体属性的 watch 可以直接用引号把属性括起来,就可以实现对象中特定属性的监听事件

1
2
3
4
5
6
7
watch: {
'queryData.name': {
handler: function() {
//do something
}
}
}

很多时候,我们监听一个属性,不会使用到改变前后的值,只是为了执行一些方法,这时可以使用字符串代替

1
2
3
4
5
6
7
8
9
10
11
data:{
name:'Joe'
},
watch:{
name:'sayName'
},
methods:{
sayName(){
console.log(this.name)
}
}