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

混入(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. js 获取是否为闰年,以及各月的天数 & isLeapYear

    js 获取是否为闰年,以及各月的天数 calendar utils isLeapYear const isLeapYear = (year) => { return (year % 4 === ...

  2. macOS finder show hidden files

    macOS finder show hidden files 显示 MacOS 上的隐藏文件和文件夹 https://zh.wikihow.com/显示Mac-OS-X上的隐藏文件和文件夹 $ def ...

  3. 苏黎世财经对话区块链专家,NGK如何利用时间价值实现自身的垂直扩张?

    近日,苏黎世财经日报联合法兰西金融等多家知名媒体,专访了NGK. 苏黎世财经日报专栏记者玛科尔德表示,随着NGK DeFi的明星代币BGV登上去中心化金融的舞台,它千倍的收益率让生态投资者趋之若鹜. ...

  4. DeFi 热潮下,NGK将成为下一个财富密码

    区块链正在脱虚向实,处于大规模落地,赋能实体产业的前夜,而在这个关键的关口,一个万亿市场的蓝海正在缓缓生成,成为区块链落地的急先锋,这个先锋便是DeFi. DeFi,即Decentralized Fi ...

  5. WPF 关于ComboBox在前台绑定XML数据的一些方法,使用XML数据提供器 XmlDataProvider

    关于使用 数据提供器:XmlDataProvider 的一些问题,以及在WPF中是如何使用的一些介绍,还有踩到的一些坑,希望其他和我碰到一样问题的,可以更快的解决. 首先,要求是 在WPF 的前台代码 ...

  6. JAVA基础(三)—— 输入输出处理

    JAVA基础(三)-- 输入输出处理 1 输入解析 //Scanner获取输入 import java.util.Scanner; Scanner s = new Scanner(System.in) ...

  7. 由endl对printf和cout的思考

    [前言]二者的区别就不介绍了.二者使用方法: printf("%s",a); cout<<a<<endl; endl的作用是什么? 一.endl作用 众所周 ...

  8. OpenGL中的坐标系统详细概括:包括Z缓冲

    一: 首先就是关于几个坐标系统的概括: 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标. 下一步是将局部坐标变换为世界空间坐标,世界空间坐标是处于一个更大的空间范围的.这些坐标相对于世界的全局 ...

  9. 2020年12月-第01阶段-前端基础-认识HTML

    1. HTML 初识 HTML 指的是超文本标记语言 (Hyper Text Markup Language)是用来描述网页的一种语言. HTML 不是一种编程语言,而是一种标记语言 (markup ...

  10. 关于主机不能访问虚拟机的web服务解决

    centos7默认并没有开启80端口,我们只有开启就行 [root@localhost sysconfig]# firewall-cmd --permanent --add-port=3032/tcp ...