在项目中,一般我们经常会基于一套现有组件库进行快速开发,但是现实中往往需要对组件库进行定制化改造二次封装

混入(mixin)

vue 官方介绍

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

简单来说就是将组件的对象的属性,方法,钩子函数等等进行提取封装,以便达到可以多出复用。来看一个简单例子

<template>
<div>
<el-table :v-loading='isLoading' :data="tableData" style="width: 100%" ref="table">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="name" label="规则名称"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column prop="count" label="服务调用次数(万)"></el-table-column>
<el-table-column prop="state" label="状态">
<template slot-scope="{row}">
<span :class="['run-state',row.state]"></span>
{{ row.state=='close'?'关闭':'运行中' }}
</template>
</el-table-column>
<el-table-column prop="time" label="上次调度时间"></el-table-column>
<el-table-column label="操作">
<template>
<el-button type="text">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text">订阅警报</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page.sync="page.pageIndex" :page-sizes="[20, 30, 40, 50]" @size-change="(e)=>page.pageSize=e" :total="total" layout="total, sizes, prev, pager, next, jumper"> </el-pagination>
</div>
</template>
<script>
export default {
data () {
return {
tableData:[],
isLoading: false,
total: 0,
page: {
pageSize: 20,
pageIndex: 1
}
};
},
watch: {
'page':{
deep: true,
immediate: true,
handler(){
this.getList()
}
}
},
methods: {
getList(pageIndex = this.page.pageIndex,pageSize = this.page.pageSize){
//获取列表数据
this.isLoading = true;
setTimeout(() => {
this.tableData = MockData();
this.total=300;
this.isLoading = false;
}, 2000);
}
}
};
</script>

上面是个常见报表分页使用场景,假如有很多个表报,那就需要写很多次分页的逻辑,正常开发中当然不可能这么处理的, 这种情况就可以使用mixins来提取分页的逻辑

// mixins.js
<script>
export default {
data () {
return {
isLoading: false,
total: 0,
page: {
pageSize: 20,
pageIndex: 1
}
};
},
watch: {
'page':{
deep: true,
immediate: true,
handler(){
this.getList()
}
}
}
};
</script>
<template>
<div>
<el-table :v-loading='isLoading' :data="tableData" style="width: 100%" ref="table">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="name" label="规则名称"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column prop="count" label="服务调用次数(万)"></el-table-column>
<el-table-column prop="state" label="状态">
<template slot-scope="{row}">
<span :class="['run-state',row.state]"></span>
{{ row.state=='close'?'关闭':'运行中' }}
</template>
</el-table-column>
<el-table-column prop="time" label="上次调度时间"></el-table-column>
<el-table-column label="操作">
<template>
<el-button type="text">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text">订阅警报</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page.sync="page.pageIndex" :page-sizes="[20, 30, 40, 50]" @size-change="(e)=>page.pageSize=e" :total="page.total" layout="total, sizes, prev, pager, next, jumper"> </el-pagination>
</div>
</template>
<script>
import PageMixins from "./mixins.js";
export default {
mixins:[PageMixins],
data () {
return {
tableData:[]
};
},
methods: {
getList(pageIndex = this.page.pageIndex,pageSize = this.page.pageSize){
//获取列表数据
this.isLoading = true;
setTimeout(() => {
this.tableData = MockData();
this.total=300;
this.isLoading = false;
}, 2000);
}
}
};
</script>

这样就将分页的逻辑分离出来了,也可以被其他组件混入使用,大大的减少了代码量,当然mixin过度滥用也是存在缺点的

  • 命名冲突

    使用mixins是将两个组件对象合并的,当两个组件属性名重复时候,vue默认会将本地组件的属性覆盖mixin的,虽然vue提供了合并策略配置,但是同时存在多个mixin存在命名冲突时候就会变得处理起来非常麻烦了
  • 隐含的依赖关系

    很明显上面的组件是依赖于mixin的,这种情况会存在潜在问题。如果我们以后想重构一个组件,改变了mixin需要的变量的名称,就会影响现有的组件的使用了,而且当项目中使用了很多这个mixin的时候,就只能去手动搜索修改了。因为不知道哪些组件使用了这些mixin

组件封装

上面表格还有中处理方法,就是将el-table和el-pagination封装成一个组件去是使用,也能提高复用性

template封装

使用template创建组件,来对el-table进行二次封装满足上面需求,增加一个total参数

提供是一个分页改变事件,再把m-table的\(attrs和\)listeners 绑定到el-table上,然后把el-table 方法暴露出去,这样就可像使用 el-table 一样使用 m-table

<template>
<div>
<el-table ref="table" v-bind="$attrs" v-on="$listeners">
<slot></slot>
</el-table>
<el-pagination :current-page.sync="page.pageIndex" :page-sizes="[20, 30, 40, 50]" @size-change="(e)=>page.pageSize=e" :total="total" layout="total, sizes, prev, pager, next, jumper"> </el-pagination>
</div>
</template> <script>
export default {
name: 'm-table',
data() {
return {
page: {
pageSize: 20,
pageIndex: 2
}
}
},
props: {
total: {
type: Number,
default: 0
}
},
watch: {
page: {
deep: true,
handler: function () {
this.$emit("page-chagne")
}
}
},
methods: {
// 将el-table 方法暴露出去
...(() => {
let methodsJson = {};
['reloadData', 'clearSelection', 'toggleRowSelection', 'toggleAllSelection', 'setCurrentRow', 'clearSort', 'clearFilter', 'doLayout', 'sort']
.forEach(key => {
methodsJson = {
...methodsJson, [key](...res) {
this.$refs['table'][key].apply(this, res);
}
};
});
return methodsJson;
})()
}
}
</script>

使用 m-table

 <m-table @page-chagne="GetTableDataList()" :total="page.total" :data="tableData">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="name" label="规则名称"></el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column prop="count" label="服务调用次数(万)"></el-table-column>
<el-table-column prop="state" label="状态">
<template slot-scope="{row}">
<span :class="['run-state',row.state]"></span>
{{ row.state=='close'?'关闭':'运行中' }}
</template>
</el-table-column>
<el-table-column prop="time" label="上次调度时间"></el-table-column>
<el-table-column label="操作">
<template>
<el-button type="text">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text">订阅警报</el-button>
</template>
</el-table-column>
</m-table>

一般情况下这样使用 template 封装就满足了需求,但是总有些时候这样封装是满足不了需求的。比如现在m-table现在需要动态支持修改配置显示列,并且不希望修改m-table的基本使用方式, 这个时候就需要使用 render 了。

render 函数

Vue 的模板实际上都会被编译成了渲染函数,render 函数有一个 createElement 参数,用来创建一个VNode。

要满足上面的需求,首先是的获得el-table的插槽(slot)中的内容,根据插槽的内容生成每列信息,根据配置的信息动态创建插槽的内容就可以实现了。简单示例代码入下

<script>
import mSetting from './setting'
export default {
components: { mSetting },
name: 'm-table',
data() {
return {
showTable: true,
setShow: false,
config: [],
copySlots: [], // 展示solt数据
page: {
pageSize: 20,
pageIndex: 1
}
}
},
props: {
total: {
type: Number,
default: 0
}
},
watch: {
page: {
deep: true,
handler: function () {
this.$emit("page-chagne")
}
}
},
created() {
this.initConfig()
},
render() {
return (
<div>
<el-table ref="table" {...{ attrs: this.$attrs }} {...{ on: this.$listeners }}>
{
this.copySlots
}
</el-table>
{this.showTable ? (<el-pagination layout="total, sizes, prev, pager, next, jumper"
{...{
on: {
'size-change': (e) => this.page.pageSize = e,
'current-change': (e) => this.page.pageIndex = e
}
}}
{...{
attrs: {
'current-page': this.page.pageIndex,
'page-sizes': [20, 30, 40, 50],
'total': this.total
}
}}
> </el-pagination>) : null}
<m-setting {...{
on: {
'update:show': e => this.setShow = e,
'change': this.initConfig
}
}} show={this.setShow} config={this.config}></m-setting>
</div >
)
},
methods: {
initConfig(config = []) {
if (config.length === 0) {
config = this.$slots.default
.filter(item => item.componentOptions && item.componentOptions.tag === "el-table-column")
.map(item => {
if (item.componentOptions.propsData.prop === 'index') {
if (!item.data.scopedSlots) {
item.data.scopedSlots = {};
}
item.data.scopedSlots.header = () => (
<i class="el-icon-s-tools" onClick={() => this.setShow = true} />
);
}
return { ...item.componentOptions.propsData };
})
this.sourceConfig = JSON.parse(JSON.stringify(config))
this.copySlots = this.$slots.default;
this.sourceSlots = this.$slots.default;
} else {
let arr = []
this.sourceSlots.forEach(item => {
let temp = config.find(subItem =>
(subItem.prop && subItem.prop === item.componentOptions.propsData.prop) ||
(subItem.type && subItem.type === item.componentOptions.propsData.type)
);
if (temp && temp.isShow) {
Object.assign(item.componentOptions.propsData, temp);
arr.push(item)
}
})
this.copySlots = arr;
this.showTable = false
this.$nextTick(() => {
this.showTable = true
})
}
this.config = config;
},
...(() => {
let methodsJson = {};
['reloadData', 'clearSelection', 'toggleRowSelection', 'toggleAllSelection', 'setCurrentRow', 'clearSort', 'clearFilter', 'doLayout', 'sort']
.forEach(key => {
methodsJson = {
...methodsJson, [key](...res) {
this.$refs['table'][key].apply(this, res);
}
};
});
return methodsJson;
})()
}
}
</script>
<template>
<el-dialog title="表格设置" :visible.sync="shows" width="600px" size="small" :before-close="handleClose">
<el-table :data="list" size='mini'>
<el-table-column prop="showLabel" label="名称"></el-table-column>
<el-table-column prop="label" label="显示名称">
<template slot-scope="{row}">
<el-input size="mini" v-model="row.label"></el-input>
</template>
</el-table-column>
<el-table-column prop="isShow" label="是否显示" width="100" align="center">
<template slot-scope="{row}">
<el-switch v-model="row.isShow"></el-switch>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="shows = false" size="small">取 消</el-button>
<el-button type="primary" @click="handleClose()" size="small">确 定</el-button>
</span>
</el-dialog>
</template> <script>
export default {
name: 'm-setting',
data() {
return {
list: []
};
},
computed: {
shows: {
get() {
return this.show;
},
set() {
this.$emit("update:show")
}
}
},
props: {
show: {
type: Boolean,
required: true,
default: false
},
config: {
type: Array,
default() {
return []
}
}
},
created() {
this.init()
},
methods: {
init() {
this.list = this.config.map(item => {
return {
...item,
showLabel: item.showLabel || item.label, // 名称
isShow: item.isShow || true // 是否显示
}
})
},
handleClose() {
this.$emit('change', this.list);
this.shows = false
}
}
};
</script>

这样就简单实现了可以动态显示列,而且不需要去修改原组件的使用方式了

hoc 高阶组件

vue高阶组件可以参考这篇

Vue 中的 mixin,component,render,hoc的更多相关文章

  1. 从源码看 Vue 中的 Mixin

    最近在做项目的时候碰到了一个奇怪的问题,通过 Vue.mixin 方法注入到 Vue 实例的一个方法不起作用了,后来经过仔细排查发现这个实例自己实现了一个同名方法,导致了 Vue.mixin 注入方法 ...

  2. 前端框架中 “类mixin” 模式的思考

    "类 mixin" 指的是 Vue 中的 mixin,Regular 中的 implement 使用 Mixin 的目的 首先我们需要知道为什么会有 mixin 的存在? 为了扩展 ...

  3. 理解Vue中的Render渲染函数

    理解Vue中的Render渲染函数 VUE一般使用template来创建HTML,然后在有的时候,我们需要使用javascript来创建html,这时候我们需要使用render函数.比如如下我想要实现 ...

  4. [Vue warn]: You may have an infinite update loop in a component render function

    [Vue warn]: You may have an infinite update loop in a component render function 这个问题很奇怪,之前从来没有遇到过.如果 ...

  5. vue中extend/component/mixins/extends的区别

    vue中extend/component/mixins/extends的区别 教你写一个vue toast弹窗组件 Vue.extend构造器的延伸

  6. 在vue中使用 layui框架中的form.render()无效解决办法

    下面简单介绍在vue中使用 layui框架中的form.render()无效解决办法. 原文地址:小时刻个人技术博客 > http://small.aiweimeng.top/index.php ...

  7. vue中mixin的理解与用法

    vue中提供了一种混合机制--mixins,用来更高效的实现组件内容的复用.最开始我一度认为这个和组件好像没啥区别..后来发现错了.下面我们来看看mixins和普通情况下引入组件有什么区别? 组件在引 ...

  8. 【Vue高级知识】细谈Vue 中三要素(响应式+模板+render函数)

    [Vue高级知识]细谈Vue 中三要素(响应式+模板+render函数):https://blog.csdn.net/m0_37981569/article/details/93304809

  9. 在vue中结合render函数渲染指定的组件到容器中

    1.demo 项目结构: index.html <!DOCTYPE html> <html> <head> <title>标题</title> ...

随机推荐

  1. Axios & POST & application/x-www-form-urlencoded

    Axios & POST & application/x-www-form-urlencoded application/x-www-form-urlencoded https://g ...

  2. Flutter: SliverAppBar 应用程序栏与滚动视图集成,以便它可以根据滚动偏移量在高度上变化

    API class _MyHomeState extends State<MyHome> with SingleTickerProviderStateMixin { @override W ...

  3. 【C#】反射的用法及效率对比

    反射实例化类 public class Person { public string Name { get; set; } public Person(string name) { this.Name ...

  4. PAA养老房产:以情怀打造精细化服务

    养老服务工作需要从业者具备尊老.爱老.敬老的职业道德,这种职业道德的培养非一朝一夕可锻造,而是需要长年累月.一点一滴的渗透和养成.PAUL ADAMS ARCHITECT(以下简称PAA)(公司编号: ...

  5. Linux 内核和 Windows 内核有什么区别?

    Windows 和 Linux 可以说是我们比较常见的两款操作系统的. Windows 基本占领了电脑时代的市场,商业上取得了很大成就,但是它并不开源,所以要想接触源码得加入 Windows 的开发团 ...

  6. 12_MySQL如何对查询结果进行排序

    本节所涉及的sql语句: -- 排序关键字 SELECT empno,ename,hiredate FROM t_emp ORDER BY hiredate DESC; -- 排序字段相同的情况 SE ...

  7. 一次"内存泄漏"引发的血案

    本文转载自一次"内存泄漏"引发的血案 导语 2017年末,手Q春节红包项目期间,为保障活动期间服务正常稳定,我对性能不佳的Ark Server进行了改造和重写.重编发布一段时间后, ...

  8. 执行Python程序时模块报错

    1. 在执行python程序时遇到 'ModuleNotFoundError: No module named 'xxxxx'' : 例如: 图片中以导入第三方的 'requests' 模块为例,此报 ...

  9. Tawk.to一键给自己的网站增加在线客服功能

    Tawk.to一键给自己的网站增加在线客服功能 很多外贸网站只有contact页面,留下邮箱.电话等联系方式,而在国际贸易当中能够及时在线交流沟通,能给客户留下更好的印象.接下来,就让我们一起来了解一 ...

  10. 设置mysql的字符集永远为UTF-8

    1.在虚拟机/usr路径下创建一个文件命名为:mysql.cnf cd /usr touch mysql.cnf 2.在该文件中使用vim命令插入配置文本 vim mysql.cnf 按i键进入编辑模 ...