我们通常会说一个人的英文够不够地道,够不够 native,同样在使用 vue 的时候希望大家也能够更地道的书写 vue 代码。
这可能需要我们抛弃一些思想,比如我认为 jQuery 中页面/组件的状态变更是事件驱动的,而 Vue/React 中则更多的是数据驱动,即 data/prop 的变化引起页面展示的变化。
所以,请不要在 Vue 中带入其他框架的思想,一个典型的特例就是 滥用 watch 监听数据变化来生成新的数据。
computed 衍生数据
vue 官网的介绍中 computed 数据直译为“计算属性”,但从功能应用上个人觉得叫做“衍生数据”更为贴切。
computed
中定义的数据大体有两种用法:
-
衍生,即通过
props
、data
与其他数据(如import
导入的外部数据)的组合、运算生成新的数据对象 -
代理,即通过定义的
set
、get
方法实现通过衍生取数据,修改该数据时,映射/更新到他的衍生
衍生示例
{
data () {
return {
menuList: [], // 账户下的菜单列表,通过请求接口获得
}
},
computed: {
hasPagePermission () { // 是否具备当前页面的权限
const currentPath = this.$route.path
return this.menuList.includes(currentPath)
}
}
}
例子中 hasPagePermission
数据即为衍生数据,路由变化时 vue 自动根据当前路由和用户的菜单列表 menuList
计算出用户是否有该路由的权限。
代理示例
{
data () {
return {
firstName: '',
familyName: ''
}
},
computed: {
fullName: {
get () {
return `${this.firstName} ${this.familyName}`
},
set (val) {
const [ firstName, familyName ] = val.split(' ')
this.firstName = firstName
this.familyName = familyName
}
}
}
}
例子来源于官网,这个不需要过多解释了。
watch 数据监听
注册数据变化的 callback
,参数依次时新数据、旧数据。
在对对象型数据监听时注意设置 deep
属性为 true
达到对象深层属性/值变化时能够触发回调。
大多数你觉得需要用到 watch
的场景其实更适合用 computed
,比如 代理示例 中,多数人会选择监听 $route
来更新 data
定义的 hasPagePermission
,当然功能是可以实现的,但是不是那个味儿。
建议在 数据驱动组件状态变化 时使用 watch
。如 checkbox-group
,当勾选了 Thanos 时,需要禁用掉 Doctor Strange、Scarlet Witch 等选项时,可以使用 watch
监听 checkbox-group
绑定的 v-model
,在其 handler
中更新选项属性。这个思想可以理解为数据驱动型,当然如果你在 checkbox-group
的 change
事件回调中修改选项属性也是可以的(事件驱动型),但个人更偏向于在 vue 中尽量使用数据驱动型的处理模式。
v-model 实现自定义 input 组件
v-model
其实是块语法糖:
- 通过
props.value
接收父组件的数据 - 通过
this.$emit('input', DATA)
抛出数据给父组件
基于以上,我们可以实现自定义的 input 组件
自定义 checkbox-group 组件
{
template: `
<div class="checkbox-group">
<label v-for="ele in choices" @click="handleCheck"><input type="checkbox" :checked="value.includes(ele)" :value="ele">{{ele}}</label>
</div>
`,
props: {
value: Array,
choices: Array
},
methods: {
handleCheck (e) {
const currentOptionVal = e.target.value
let result = []
if (e.target.checked) {
result = [ ...this.value, currentOptionVal ]
} else {
result = this.value.filter(ele => ele !== currentOptionVal)
}
this.$emit('input', result)
}
}
}
demo 及 源码 点击 这里
mixin 混入
混入,通过它可以抽离/封装公共逻辑,在需要时混入组件中就可以直接用其中的方法、数据了。
可以理解为它是一个没有 template
的抽象组件。
混入 mixin 后,组件中定义的数据/方法会覆盖掉 mixin 中定义的同名数据/方法。
其逻辑有点像面向对象语言中的继承。
合理使用 mixin 可以充分发扬程序员懒的美德。
使用场景:多组件逻辑重复。
比如,报表业务开发中多个页面都是筛选表单 + 展示表格 + 分页,附加上loading、导出等 feature,那么这部分功能就可以通过 mixin 进行封装抽离。
示例
const TableDataMixin = {
/*
* 表格数据展示/导出逻辑 mixin
*
* 使用时需要在 data/computed 中配置以下数据
* 1. 数据请求 api/参数:loadDataApi / loadDataParam
* 2. 数据导出 api/参数:exportDataApi / exportDataParam
*/
data () {
return {
tableData: [], // 表格数据
tableLoading: false, // loading 状态
pagination: { // 分页
currentPage: 1,
pageSize: 10,
total: 0
},
tableExporting: false // exporting 状态
}
},
computed: {
shouldDisableExport () { // 是否需要禁用 export
return this.tableLoading || this.tableExporting
}
},
methods: {
loadTableData () {
this.tableLoading = true
api.get(this.loadDataApi, this.loadDataParam)
.then(({ data: { status, message, data: { data, total } } }) => { // 解析 api 返回数据,项目中应该是统一格式的,如 { status, message, data }
this.tableData = data
this.tablePagination.total = total
this.tableLoading = false
})
.catch(err => {
console.error('load table data failed:', err)
this.tableLoading = false
})
},
exportTableData () {
this.tableExporting = true
api.get(this.exportDataApi, this.exportDataParam)
.then(({ data: { status, message, data: fileURI } }) => { // 解析 api 返回数据,项目中应该是统一格式的,如 { status, message, data }
window.location.href = fileURI
this.tableExporting = false
})
.catch(err => {
console.error('export table data failed:', err)
this.tableExporting = false
})
},
handlePaginationChange ({ currentPage, pageSize }) {
this.tbalePagination.currentPage = currentPage
this.tbalePagination.pageSize = pageSize
}
}
}
有了以上 mixin 后就不需要在每个页面中写一遍获取/导出数据的逻辑了,只需混入后配置相关数据即可:
const TablePage = Vue.component('my-page', {
mixins: [ TableDataMixin ],
data () {
return {
formData: {},
loadDataApi: 'YOUR LOAD DATA API',
exportDataApi: 'YOUR EXPORT DATA API'
}
},
computed: {
loadDataParam () {
const { currentPage, pageSize } = this.tablePagination
return Object.assign({ currentPage, pageSize }, this.formData, { /* some other params */ })
},
exportDataParam () {
return Object.assign({}, this.formData, { /* some other params */ })
}
}
})
而对于页面中导出按钮点击回调、分页变化回调、筛选表单提交后进行的数据检索等都可以直接使用 TableDataMixin
中的 exportTableData
| handlePaginationChange
| loadTableData
函数,而表格数据读取、分页数据读写等也可以直接绑定 tableData
| tablePagination
等。
directive 自定义指令
指令也是代码封装/复用的大杀器。
详细介绍可以自行参考 vue 官方文档 自定义指令。
使用场景 不限于一些需要底层 dom 操作的情况,如任何 ui 框架中的 v-loading 等,或者官方文档中提到的 v-focus。
这里以一个页标题为例,在 SPA 开发中,每个页面一般都具备一个单独的 document.title
,在每个页面的挂载/更新钩子中设置 document.title
显然太繁琐,那么我们可以通过自定义指令解决:
自定义页标题指令
const Title = {
inserted: function (el, binding, vnode, oldVnode) {
const { value: title = 'DEFALT TITLE' } = binding
document.title = title
},
update: function (el, binding, vnode, oldVnode) {
const { value: title = 'DEFALT TITLE' } = binding
document.title = title
}
}
const Page = Vue.component('my-page', {
directives: [ Title ],
template: `
<div class="my-page" v-title="My Page">
<!-- page content -->
</div>
`
})
以上来源于个人开发中的一些反思总结,如有不同意见可以在评论中回复。
后续开发中如果发现其他的 little tricks 会进一步更新。
网友评论