Vue小技巧

154 阅读10分钟
给 props 属性设置多个类型这个技巧在开发组件的时候用的较多,为了更大的容错性考虑,同时代码也更加人性化:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
export default {
props: {
width: {
type: [String, Number],
default: '100px'
}
// 或者这样
// width: [String, Number]
}
}

比如一个 <my-button> 上暴露了一个 width 属性,我们既可以传 100px,也可以传 100 :
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<!-- my-button.vue -->
<template>
<button :style="computedWidth">{{ computedWidth }}</button>
</template>
<script>
export default {
props: {
width: [String, Number]
},
computed: {
computedWidth () {
let o = {}
if (typeof this.width === 'string') o.width = this.width
if (typeof this.width === 'number') o.width = this.width + 'px'
return o
}
}
}
</script>
使用:
[HTML]
纯文本查看
复制代码
1
2
3
4
5
6
<!-- 在其他组件中使用 -->
<template>
<my-button width="100px"></my-button>
<!-- or -->
<my-button :width="100"></my-button>
</template>
阻止 <el-form> 默认提交行为有时候我们在用饿了么组件 <el-form> 在文本框中键入 enter 快捷键的时候会默认触发页面刷新。我们可以加入如下代码解决其默认行为:
[HTML]
纯文本查看
复制代码
1
2
3
<el-form @submit.native.prevent>
...
</el-form>
data 初始化因为 props 要比 data 先完成初始化,所以我们可以利用这一点给 data 初始化一些数据进去,看代码:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
export default {
data () {
return {
buttonSize: this.size
}
},
props: {
size: String
}
}
除了以上,子组件的 data 函数也可以有参数,且该参数是当前实例对象。所有我们可以利用这一点做一些自己的判断。如,改写上面的代码:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
export default {
data (vm) {
return {
buttonSize: vm.size
}
},
props: {
size: String
}
}
template我们在做 v-if 判断的时候,可以把判断条件放在 template 组件上,最终的渲染结果将不包含 <template> 元素。
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
<template>
<div class="box">
<template v-if="isVal">
<h2>...</h2>
</template>
<template v-else>
<h2>...</h2>
</template>
</div>
</template>
v-for 也同样适用。
Lifecycle hook生命周期钩子可以是一个数组类型,且数组中的函数会依次执行。
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
export default {
...
created: [
function one () {
console.log(1)
},
function two () {
console.log(2)
}
]
...
}
没什么用,知道就行了。事实上生命周期钩子还可以作用于 DOM 元素上,利用这一点,我们可以用父组件中的方法来初始化子组件的生命周期钩子:
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Child.vue -->
<template>
<h3>I'm child!</h3>
</template>
<!-- Parent.vue -->
<template>
<child @hook:created="handleChildCreated"></child>
</template>
<script>
import Child from './child.vue'
export default {
components: [ Child ],
methods: {
handleChildCreated () {
console.log('handle child created...')
}
}
}
</script>
其他钩子雷同,不再赘述。
v-for在用 v-for 遍历数组的时候,我们一般都会错误的这样去做,举个栗子:
v-for 和 v-if 放在同一个元素上使用:
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
<template>
<ul class="items">
<!-- 只有激活的用户才可以显示 -->
<li
v-for="(user, index) in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>
</ul>
</template>
由于 v-for 和 v-if 放在同一个元素上使用会带来一些性能上的影响,官方给出的建议是在计算属性上过滤之后再进行遍历。所以平时开发不推荐一起使用,知道有这回事即可,不至于面试时不知道。 关于为什么不推荐放在一起使用,参见 避免-v-if-和-v-for-用在一起
混合如果好多组件都共用到一些像 props、data、methods 等,可以单独抽出来放到 mixins 混合器中。比如,在用户管理列表中使用。
分页混合器:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// paging-mixin.vue
export default {
props: {
pageSize: 1,
pageLength: 10,
currentPage: 1
total: 20
},
methods: {
/**
* 上一页
*/
prevPage (page) {
...
},
/**
* 下一页
*/
nextPage (page) {
...
}
/**
* 跳转到当前页
*/
currentPage (page) {
...
}
}
}

Users.vue:
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="user-model">
<my-table :data="users"></my-table>
<my-paging
:page-length="pageLength"
:page-size="pageSize"
:current-page="currentPage"
:total="total">
</my-paging>
</div>
</template>
<script>
import PagingMixin from '../mixins/paging-mixin.vue'
export default {
mixins: [PagingMixin],
data () {
return {
users: [],
pageLength: 10,
pageSize: 1,
currentPage: 1,
total: 20
}
}
}
</script>

不用每个页面都写一遍 props 和 methods 了。
render 函数下面是一段简单的 template 模板代码:
[HTML]
纯文本查看
复制代码
1
2
3
4
5
6
<template>
<div class="box">
<h2>title</h2>
this is content
</div>
</template>

我们用渲染函数来重写上面的代码:
[JavaScript]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
export default {
render (h) {
let _c = h
return _c('div',
{ class: 'box'},
[_c('h2', {}, 'title'), 'this is content'])
}
}

事实上,Vue 会把模板(template)编译成渲染函数(render)。上面的 template 模板会被编译成如下渲染函数:
[JavaScript]
纯文本查看
复制代码
1
2
3
4
5
let render = function () {
return _c('div',
{staticClass:"box"},
[_c('h2', [_v("title")]), _v("this is content")])
}

是不是很像? 正如官方说的,渲染函数比 template 更接近编译器。如果用一个流程图来解释的话,大概是这个样子:
template ↓预编译工具(vue-loader + vue-template-compile) ↓ render ↓resolve vnode复制代码具体参见 Vue声明周期图示
渲染函数用处:
  • 开发组件库,Element 源码用的都是 render
  • 封装一些高阶组件。组件里面嵌套组件就是高阶组件,前提是要满足组件三要素:props、event、slot
  • 用于处理一些复杂的逻辑判断。如果我们一个组件里面有很多 v-if 判断的话,用模板就显得不合适了,这个时候可以用渲染函数来轻松处理
errorCaptured捕获一个来自子孙组件的错误时被调用。有时候当我们想收集错误日志,却不想把错误暴露到浏览器控制台的时候,这很有用。下面是个例子:
Child.vue
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
<template>
<!-- 省略一些无关代码 -->
</template>
<script>
export default {
mounted () {
// 故意把 console 写错
consol.log('这里会报错!')
}
}
</script>

Parent.vue
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<child></child>
</template>
<script>
import Child from './Child.vue'
export default {
components: [ Child ],
/**
* 收到三个参数:
* 错误对象、发生错误的组件实例
* 以及一个包含错误来源信息的字符串。
* 此钩子可以返回 false 以阻止该错误继续向上传播。
*/
errorCaptured (err, vm, info) {
console.log(err)
// -> ReferenceError: consle is not defined ...
console.log(vm)
// -> {_uid: 1, _isVue: true, $options: {…}, _renderProxy: o, _self: o,…}
console.log(info)
// -> `mounted hook`
// 告诉我们这个错误是在 vm 组件中的 mounted 钩子中发生的
// 阻止该错误继续向上传播
return false
}
}
</script>

关于 errorCaptured 更多说明,请移步官网->
v-once通过 v-once 创建低开销的静态组件。渲染普通的 HTML 元素在 Vue 中是非常快速的,但有的时候你可能有一个组件,这个组件包含了大量静态内容。在这种情况下,你可以在根元素上添加 v-once 特性以确保这些内容只计算一次然后缓存起来,就像这样:
[HTML]
纯文本查看
复制代码
1
2
3
4
5
6
<template>
<div class="box" v-once>
<h2> 用户协议 </h2>
... a lot of static content ...
</div>
</template>
只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。关于 v-once 更多介绍,请移步官网->
slot-scope作用域插槽。vue@2.5.0 版本以前叫 scope,之后的版本用 slot-scope 将其代替。除了 scope 只可以用于 <template> 元素,其它和 slot-scope 都相同。
用过 Element 组件的同学都知道,当我们在使用<el-table> 的时候会看到如下代码:
Element@1.4.x 的版本:
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
<el-table-column label="操作">
<template scope="scope">
<el-button
size="small"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>

但在 2.0 之后的版本替换成了 slot-scope。
Element@2.0.11
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
复制代码说白了,slot-scope 相当于函数的回调,我把结果给你,你想怎么处理就怎么处理,一切随你:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
function getUserById (url, data, callback) {
$.ajax({
url,
data,
success: function (result) {
callback(result)
}
})
}
// 使用
getUserById('/users', { id: 1 }, function (response) {
// 拿到数据并开始处理自己的页面逻辑
})
下面我们来简单模拟下 <el-table> 组件内部是怎么使用 slot-scope 的,看代码:
模拟的 <el-table> 组件:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
// 定义模板
let template = `
<ul class="table">
<li v-for="(item, index) in data" :key="index">
<!-- 我希望数据由调用者自己处理 -->
<!-- 'row' 相当于变量名,随便定义,比如 aaa,bbb 啥的 -->
<slot :row="item">
<!-- 当使用者什么都没写的时候,默认值才会显示-->
{{ item.name }}
</slot>
</li>
</ul>
`
Vue.component('el-table', {
template,
props: {
data: Array,
default: []
}
})
在你需要的地方使用 <el-table> 组件:
HTML:
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<div id="app">
<el-table :data="userData">
<!-- 使用的时候可以用 template -->
<!-- `scope` 也是个变量名,随便命名不是固定的,比如 foo, bar -->
<template slot-scope="scope">
<!-- 其中 `scope.row` 中的 row 就是我们上边定义的变量啦-->
<!-- `scope.row`返回的是 `item` 对象 -->
<template v-if="scope.row.isActived">
<span class="red">{{ scope.row.name }}</span>
</template>
<template v-else>
{{ scope.row.name }}
</template>
</template>
</el-table>
</div>
JavaScript:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
new Vue({
el: '#app',
data: {
userData: [
{id: 1, name: '张三', isActived: false},
{id: 2, name: '李四', isActived: false},
{id: 1, name: '王五', isActived: true},
{id: 1, name: '赵六', isActived: false},
]
}
})
CSS:
[CSS]
纯文本查看
复制代码
1
2
3
.red {
color: red
}

JavaScript:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// `<el-table>` 组件
Vue.component('el-table', {
name: 'ElTable',
render: function (h) {
return h('div', {
class: 'el-table'
}, this.$slots.default)
},
props: {
data: Array
}
})
// `<el-table-column>`
Vue.component('el-table-column', {
name: 'ElTableColumn',
render: function (h) {
// 定义一个存放 li 元素的数组
let lis = [],
// 获取父组件中的 data 数组
data = this.$parent.data
// 遍历数组,也就是上面的 `v-for`,生成 `<li>` 标签
// `this.$scopedSlots.default` 获取的就是上面 slot-scope 作用于插槽的部分,
// 并把 `{ row: item }` 传给上面的 `scope` 变量
data.forEach((item, index) => {
let liEl = h('li', {
key: item.id
}, [ this.$scopedSlots.default({ row: item }) ])
// 把生成的 li 标签存到数组
lis.push(liEl)
})
return h('ul', {
class: 'el-table-column'
}, lis)
}
})

在你的页面这样来使用:
HTMl:
[HTML]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
<div id="app">
<el-table :data="list">
<el-table-column>
<template slot-scope="scope">
<span class="red" v-if="scope.row.actived">{{ scope.row.name }}</span>
<span v-else>{{ scope.row.name }}</span>
</template>
</el-table-column>
</el-table>
</div>

JavaScript:
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
new Vue({
el: '#app',
data: {
list: [
{ id: 1, name: '张三', actived: false },
{ id: 1, name: '李四', actived: false },
{ id: 1, name: '王五', actived: true },
{ id: 1, name: '赵六', actived: false },
]
}
})

更多技术资讯可关注:gzitcast