我们通常会说一个人的英文够不够地道,够不够 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 会进一步更新
作者:KrisLeeSH
链接:https://www.jianshu.com/p/e9ad533d536d