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

混入(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. yapi & mock JSON

    yapi & mock JSON json, body https://hellosean1025.github.io/yapi/documents/mock.html response bo ...

  2. C++使用libcurl进行http通讯

    借着curl 7.75.0版本更新, 最近又下载下来玩了玩, 在此做个简单记录 1.环境搭建 首先是libcurl动态库, 自己下载源码编译的话如果要使用https协议还要下载OpenSSL和libs ...

  3. java自学第5期——Object、Date、Calender、System、StringBuilder、基本类型包装类

    一.Object类 作用:对象操作 位置:java.lang.Object 方法: public String toString() :返回对象的字符串表示形式. public boolean equ ...

  4. TKMybatis

    TKMybatis与Mybatis-plus都是mybatis的扩展,有相同的地方,也有不同的地方. 1.导入坐标 <!--mybatis依赖--> <dependency> ...

  5. docker仓库之分布式harbor (一)

    1.harbor介绍 harbor是一个用于存储和分发docker镜像的企业级Registry服务器,由VMware开源.其通过添加一些企业必须的功能特性,例如安全,标识和管理,扩展了开源docker ...

  6. go的循环

    目录 go的循环 一.语法 二.语法简写 1.省略第一部分 2.省略第二部分 3.省略第三部分 4.全省略:死循环 5.终极写法,简洁变形 go的循环 Go中只有for循环,没有while循环.因为w ...

  7. 看完我的笔记不懂也会懂----ECMAscript 567

    目录 ECMAscript 567 严格模式 字符串扩展 数值的扩展 Object对象方法扩展 数组的扩展 数组方法的扩展 bind.call.apply用法详解 let const 变量的解构赋值 ...

  8. MySQL注入流程

    目录 确认注入点 信息收集 数据获取 提权 写个MySQL注入流程的大纲,类似一份全局地图,能指导下一步工作.MySQL注入流程分为四步: 确认注入点 信息收集 数据获取 提权 确认注入点 参考:ht ...

  9. Linux添加普通权限账号并授予root权限

    命令创建账号和密码 adduser Mysticbinary #添加一个Mysticbinary用户 passwd Mysticbinary # 输入密码 授予可以切换root的权限 修改/etc/s ...

  10. Hi3559AV100板载开发系列-pthread_create()下V4L2接口MJPEG像素格式的VIDIOC_DQBUF error问题解决-采用阻塞方式下select监听

     最近一直加班加点进行基于Hi3559AV100平台的BOXER-8410AI板载开发,在开发的过程中,遇到了相当多的问题,其一是板载的开发资料没有且功能不完整,厂家不提供太多售后技术支持,厂家对部分 ...