如何在10分钟之内完成一个业务页面 - Vue的封装艺术

如何在10分钟之内完成一个业务页面 - Vue的封装艺术

真实干货,走心分享!

如何做一个优秀的时间管理者?
lzx告诉我们,每天少睡几个小时,你就有更多的时间去做运动。

对于程序员来说,每天少点时间写业务,就有更多时间去做(hua)优(shui)化(shui)。

我们今天讲的是Vue中如何更灵活的封装业务组件,使效率翻倍。因为Ctrl + cCtrl + v都不足以满足我们了,搬过来还要改一大堆东西。那怎么办嘛,还有比CV工程师更快的职业吗?

答案是有的!我们称其为配置工程师,简单无脑!

Vue组件封装技巧

这个方法的核心思想就是封装。但是封装方式不同,使用起来也是天差地别。如何实现一个灵活易用的封装,使我们本篇文章的主要讨论目标。

$attrs

最简单的封住是什么?就是往原组件外再套一层,并且保留全组件的所有功能。

真实案例

假设我们有一个组件x-button,很遗憾这个组件居然不支持加载状态!那咋办,为了用户体验,我们得给它加上加载状态才行。所以我们在它的基础上封装一个y-button

<template> <x-button> <i v-if="loading" class="font-loading"></i> <slot></slot> </x-button> </template>
export default { name: 'YButton', props: { loading: { type: Boolean, default: false, } } };

这样一来,我们的y-button就支持loading了!但是等等,如果我们要给原来的x-button传递属性咋办?简单!

<template> <x-button :size="size"> <i v-if="loading" class="font-loading"></i> <slot></slot> </x-button> </template>
export default { props: { loading: { type: Boolean, default: false, }, size: { type: String, default: undefined, } } };

对就是这么简单,原组件的属性,有一个加一个!
好累啊!!!我们不能这么做,这太不优雅了。好在Vue提供了$attrs可以解决这个问题。

那么$attrs是干哈的啊?

$attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)

简单点来说也就是我不要的,全丢给你。那我们来改写一下。

<template> <x-button v-bind="$attrs"> <i v-if="loading" class="font-loading"></i> <slot></slot> </x-button> </template>
export default { props: { loading: { type: Boolean, default: false, } } };

这样写,当我们传递属性给y-button时,除了loading以外,其他全部属性都会透传给x-button。这就是$attrs的作用。

配置+模板

这是本篇关键的一步,也是效率提升最多的一步。
想一想,当你对着原型图一步一步写出页面配置,页面就搭好了是什么感觉。

真实案例

日常我们用的最多,也是最常见的组件:表格+分页!讲真的,表格真的是无处不在!

优秀的表格组件有很多,但它们写起来确很复杂,而且充斥着大量重复代码。是时候将它封装一下了!我们以el-table为基础组件。

ElTable原始操作

首先,el-table是这样用的:

<template> <el-table :data="tableData"> <el-table-column prop="date" label="日期" width="180"> </el-table-column> <el-table-column prop="name" label="姓名" width="180"> </el-table-column> <el-table-column prop="address" label="地址"> </el-table-column> </el-table> </template>
export default { data() { return { tableData: [ { date: "2016-05-02", name: "王小虎", address: "上海市普陀区金沙江路 1518 弄" } ] }; } };

列的配置化

现在我们开始改造这个组件,封装一个f-table!首先我们要将这些el-table-column都干掉。

<template> <div> <el-table> <el-table-column v-for="(col, i) in cols" :key="i" v-bind="col"> </el-table-column> </el-table> </div> </template>
export default { props: { cols: { type: Array, default: () => [] } } };

这样我们在使用的时候就是这样的:

<template> <div> <f-table cols=""></f-table> </div> </template>
export default { data() { return { cols: [ { prop: "date", label: "日期", width: "180", formatter: dateFormatter } ] }; } };

数据获取方式

我们通过最原始的调用知道el-table是通过

<el-table :data="tableData">

data属性来传递数据的。我们只要直接透传数据数组就好了,但是我们不这样做,因为这里我们可以做更多。我们不直接传入数据,而是传入获取数据的方法

<template> <div> <el-table :data="tableData"> <!-- ... --> </el-table> </div> </template>
export default { props: { // ... fetch: { type: Function, default: () => Promise.resolve({ rows: [] }) } }, data() { return { loading: false, tableData: [] }; }, created() { this.fetchData() }, methods: { async fetchData() { this.loading = true; try { const { rows } = await this.fetch(); this.tableData = rows; } catch (error) { console.error(error); } finally { this.loading = false; } } }! };

你可以看到我们在获取数据的时候可以设置表格的加载状态,处理错误。这里我们还可以将分页集成进去,不过这个后面再讲。这可以省去我们很多工作!

我们再来看看组件调用代码:

<template> <div> <f-table :cols="cols" :fetch="fetchUsers"></f-table> </div> </template>
export default { data() { return { cols: [ { prop: "date", label: "日期", width: "180" } ] }; }, methods: { fetchUsers() { return { rows: [{ name: "xxx", date: "xx" }] }; } } };

我们只需要关心怎么获取数据,要展示什么数据。没了!

分页

OK!下面我们顺带解决分页问题,我们使用的是el-pagination

ElPagination原始操作

<template> <div> <el-pagination :current-page.sync="currentPage" :total="total"> </el-pagination> </div> </template>
export default { data() { return { currentPage: 1, total: 0 }; } };

整合到FTable

<template> <div> <el-table :data="tableData"> <!-- --> </el-table> <el-pagination @current-change="fetchData" :current-page.sync="currentPage" :total="total"> </el-pagination> </div> </template>
export default { // ... data() { return { // ... currentPage: 1, total: 0 }; }, methods: { async fetchData() { // ... const { rows, total } = await this.fetch(this.currentPage); this.tableData = rows; this.total = total; } } };

我们看一下我们加了哪些东西:

  • fetch的时候多返回了total表明总数据,以便于分页
  • fetch的时候向函数中传入了当前页码
  • 在页码改变的时候重新获取数据

而我们的组件调用代码,仅仅只增加了几行:

<f-table :cols="cols" :fetch="fetchUsers"></f-table>
methods: { async fetchUsers(currentPage) { const query = { page: currentPage }; const { rows, total } = await api.getUsers(query); return { rows, total }; } }

这样,一个带有自动分页的表格组件就封住好了,使用起来十分简单。

当然这并不能满足你的所有需求我知道。比如你想要使用表格最原始的el-table-column,给表格列加个按钮,加个输入框什么的。

具名slot

要定制内容的时候,使用slot是最合适的。但是如何加在我们的f-table里面呢?

还记得我们使用cols来配置el-table-column。我们只需要表明某个col是某个slot就好了。

<template> <div> <el-table :data="tableData"> <template v-for="(col, i) in cols"> <slot v-if="col.slot" :name="col.slot" /> <el-table-column v-else :key="i" v-bind="col"> </el-table-column> </template> </el-table> </div> </template>

使用方法:

<template> <div> <f-table :cols="cols" :fetch="fetchUsers"> <template slot="action"> <el-table-column>使用原生ElTableColumn的用法</el-table-column> </template> </f-table> </div> </template>
export default { data() { return { cols: [ { slot: 'operation' }, { prop: "date", label: "日期", width: "180" } ] }; } }

小结

一旦习惯这种方法,再配上各种formatter,写页面真的无脑,就差改成拖拽了(有没有嗅到什么)!

写作不易,恳求大家动动手指点个关注!

上一篇 vue3的数据劫持是如何做的
下一篇 新手也能看懂的虚拟滚动实现方法