平时在项目中很少用到Vue的继承特性,通常是封装单文件组件,然后在其他组件中引入。继承是面向对象程序设计的一个核心概念之一,通过它可以实现子类对父类的数据和功能的复用,同时还可以选择性地覆写父类的属性和方法。那么,前端组件开发如何利用这个特性呢?
最近刚好碰到一个应用场景,某个业务模块的各个页面在结构上几乎完全一致,区别只在于里面的数据。首先想到的实现方式是封装成一个组件,然后在各个页面中引入,通过props
传递不同的数据来区分不同行为。但这种方式需要在组件里做各种判断,每多一个页面类型就需要增加一个条件分支,这样就违背了面向对象设计里的“开发-封闭”原则,不利于以后的扩展。
该业务模块是要展示多个统计指标的趋势,页面包含一个走势图和报表,以及一些条件筛选控件。每个指标的页面几乎都是一样的。使用继承,刚好可以解决这个问题。定义一个基类页面,包含页面模板和公共属性和方法,各数据指标页面作为子类继承该基类,这样就拥有了同样的页面模板和事件绑定,只需要在子类覆写事件处理方法就能实现不同统计指标展示不同数据。
Vue 支持组件的继承,在单文件组件里指定extends
即可:
<!-- 某统计指标报表 -->
<script>
import ReportBase from './ReportBase.vue';
export default {
extends: ReportBase,
data() {
return {
title: '数据指标标题',
tableTitle: '数据指标表格标题',
chartTitle: '数据指标趋势图标题',
columns: [
{
label: '日期',
prop: 'date',
},
{
label: '总数(台)',
prop: 'total',
},
{
label: '故障数量(台)',
prop: 'failure',
},
{
label: '占比(%)',
prop: 'ratio',
},
],
};
},
methods: {
// 覆写基类的事件处理方法,根据该统计指标请求不同的业务接口
onFactoryChange() {},
onChartTimeChange() {},
onTableDateChange() {},
},
};
</script>
<style lang="scss" scoped>
</style>
基类组件代码大致如下:
<!-- ReportBase.vue -->
<template>
<div class="report-page router-page widget">
<div class="widget-header">
<div class="widget-header-title">{{title}}</div>
</div>
<div class="widget-body">
<div class="filter flexbox">
<span class="filter-name">{{ $t('pool.farm') }}</span>
<el-radio-group class="plain" size="mini" v-model="factoryId" @change="onFactoryChange">
<el-radio-button
v-for="(item, index) in factoryList"
:label="item.id"
:key="index"
>{{ item.name }}</el-radio-button>
</el-radio-group>
</div>
<section class="chart-area section">
<div class="flexbox align-center">
<div class="chart-title">{{chartTitle}}</div>
<el-radio-group size="mini" v-model="timespan" @change="onChartTimeChange">
<el-radio-button label="week">周</el-radio-button>
<el-radio-button label="month">月</el-radio-button>
<el-radio-button label="year">年</el-radio-button>
</el-radio-group>
</div>
</section>
<section class="grid-area section">
<div class="flexbox space-bt">
<div class="grid-title">{{tableTitle}}</div>
<el-date-picker
v-model="month"
type="month"
placeholder="选择月"
@change="onTableDateChange"
></el-date-picker>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column v-bind="item" v-for="(item, index) in columns" :key="index"></el-table-column>
</el-table>
</section>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return {
title: '',
factoryId: 0,
month: '',
tableTitle: '',
columns: [],
tableData: [],
timespan: 'week',
};
},
watch: {
factoryList(value) {
if (value.length > 0) {
this.factoryId = value[0].id;
this.onFactoryChange(this.factoryId);
}
},
},
computed: {
...mapGetters(['factoryList']),
},
methods: {
onFactoryChange() {},
onChartTimeChange() {},
onTableDateChange() {},
},
created() {
if (this.factoryList.length > 0) {
this.factoryId = this.factoryList[0].id;
this.onFactoryChange(this.factoryId);
}
},
};
</script>
<style lang="scss">
这样,后续如果有更多的统计指标页面,只需要继承和覆写方法就行,而基类几乎不用改动。当然,Vue组件的这种继承方式也不是完美的,比如 HTML
模板要么完全继承,要么完全重写,不能按需继承某个部分。如果子类在结构上跟基类有所差异,还是需要在基类中做条件判断。如果模板差异太大,可以重新定义子类自己的template
,至少还可以重用一部分业务逻辑代码。像本文提到的应用场景,就非常适合用继承来实现。
各位看官如果有更好的思路,欢迎在评论中一起探讨!