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

混入(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. nodejs 调用win32 api

    video 教程文件 win32 api >node -v v12.16.1 >npm install -g node-gyp >npm i @saleae/ffi >node ...

  2. (转)IP协议详解之子网寻址、子网掩码、构造超网

    原文网址:http://www.cnblogs.com/way_testlife/archive/2010/10/05/1844399.html 子网寻址 1. 从两级IP地址到三级IP地址 < ...

  3. 1060 Are They Equal——PAT甲级真题

    1060 Are They Equal If a machine can save only 3 significant digits, the float numbers 12300 and 123 ...

  4. SpringBoot以war包形式部署到外部Tomcat

    SpringBoot 项目打包时能打成 .jar 与 .war包文件,.jar使用 java -jar xx.jar 就可以启动,而 .war 可以部署到tomcat的 webapps 中,随tomc ...

  5. JavaFX桌面应用-版本升级

    好久没有写博客,2021年就以 "JavaFX桌面应用-版本升级" 开篇吧,记录一下JavaFX应用版本升级的开发流程. 桌面应用升级的方案应该很多,这里只是自己想到的方案. 1. ...

  6. 后端程序员之路 46、Redis Sentinel

    Sentinel - Redis 命令参考http://doc.redisfans.com/topic/sentinel.html#sentinel-api Guidelines for Redis ...

  7. Java跨平台原理与Java虚拟机(JVM)

    Java跨平台原理(字节码文件.虚拟机) C/C++语言都直接编译成针对特定平台机器码.如果要跨平台,需要使用相应的编译器重新编译. Java源程序(.java)要先编译成与平台无关的字节码文件(.c ...

  8. C指针与二维数组

    先贴上完整的代码: #include<stdio.h> int main(int argc, char *argv[]){ int a[3] [5]={1,2,3,4,5,6,7,8,9, ...

  9. 安装RPM包或者源码包

    RPM工具 RPM他是以一种数据库记录的方式将我们所需要的套件安装到linux主机的一套管理程序关于RPM各个选项的含义如下-i:表示安装-v:表示可视化-h:表示安装进度在安装RPM包时,常用的附带 ...

  10. 1-认识c指针

    1.指针和内存 c程序在编译后,会以三种形式使用内存 1静态/全局内存 静态声明的变量分配在这里,全局变量也使用这部分内存.这些变量在程序开始运行时分配,直到程序终止时才会消失 2.自动内存 这些变量 ...