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

混入(mixin)

vue 官方介绍

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

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

  1. <template>
  2. <div>
  3. <el-table :v-loading='isLoading' :data="tableData" style="width: 100%" ref="table">
  4. <el-table-column type="selection" width="55"></el-table-column>
  5. <el-table-column prop="name" label="规则名称"></el-table-column>
  6. <el-table-column prop="description" label="描述"></el-table-column>
  7. <el-table-column prop="count" label="服务调用次数(万)"></el-table-column>
  8. <el-table-column prop="state" label="状态">
  9. <template slot-scope="{row}">
  10. <span :class="['run-state',row.state]"></span>
  11. {{ row.state=='close'?'关闭':'运行中' }}
  12. </template>
  13. </el-table-column>
  14. <el-table-column prop="time" label="上次调度时间"></el-table-column>
  15. <el-table-column label="操作">
  16. <template>
  17. <el-button type="text">编辑</el-button>
  18. <el-divider direction="vertical"></el-divider>
  19. <el-button type="text">订阅警报</el-button>
  20. </template>
  21. </el-table-column>
  22. </el-table>
  23. <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>
  24. </div>
  25. </template>
  26. <script>
  27. export default {
  28. data () {
  29. return {
  30. tableData:[],
  31. isLoading: false,
  32. total: 0,
  33. page: {
  34. pageSize: 20,
  35. pageIndex: 1
  36. }
  37. };
  38. },
  39. watch: {
  40. 'page':{
  41. deep: true,
  42. immediate: true,
  43. handler(){
  44. this.getList()
  45. }
  46. }
  47. },
  48. methods: {
  49. getList(pageIndex = this.page.pageIndex,pageSize = this.page.pageSize){
  50. //获取列表数据
  51. this.isLoading = true;
  52. setTimeout(() => {
  53. this.tableData = MockData();
  54. this.total=300;
  55. this.isLoading = false;
  56. }, 2000);
  57. }
  58. }
  59. };
  60. </script>

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

  1. // mixins.js
  2. <script>
  3. export default {
  4. data () {
  5. return {
  6. isLoading: false,
  7. total: 0,
  8. page: {
  9. pageSize: 20,
  10. pageIndex: 1
  11. }
  12. };
  13. },
  14. watch: {
  15. 'page':{
  16. deep: true,
  17. immediate: true,
  18. handler(){
  19. this.getList()
  20. }
  21. }
  22. }
  23. };
  24. </script>
  1. <template>
  2. <div>
  3. <el-table :v-loading='isLoading' :data="tableData" style="width: 100%" ref="table">
  4. <el-table-column type="selection" width="55"></el-table-column>
  5. <el-table-column prop="name" label="规则名称"></el-table-column>
  6. <el-table-column prop="description" label="描述"></el-table-column>
  7. <el-table-column prop="count" label="服务调用次数(万)"></el-table-column>
  8. <el-table-column prop="state" label="状态">
  9. <template slot-scope="{row}">
  10. <span :class="['run-state',row.state]"></span>
  11. {{ row.state=='close'?'关闭':'运行中' }}
  12. </template>
  13. </el-table-column>
  14. <el-table-column prop="time" label="上次调度时间"></el-table-column>
  15. <el-table-column label="操作">
  16. <template>
  17. <el-button type="text">编辑</el-button>
  18. <el-divider direction="vertical"></el-divider>
  19. <el-button type="text">订阅警报</el-button>
  20. </template>
  21. </el-table-column>
  22. </el-table>
  23. <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>
  24. </div>
  25. </template>
  26. <script>
  27. import PageMixins from "./mixins.js";
  28. export default {
  29. mixins:[PageMixins],
  30. data () {
  31. return {
  32. tableData:[]
  33. };
  34. },
  35. methods: {
  36. getList(pageIndex = this.page.pageIndex,pageSize = this.page.pageSize){
  37. //获取列表数据
  38. this.isLoading = true;
  39. setTimeout(() => {
  40. this.tableData = MockData();
  41. this.total=300;
  42. this.isLoading = false;
  43. }, 2000);
  44. }
  45. }
  46. };
  47. </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

  1. <template>
  2. <div>
  3. <el-table ref="table" v-bind="$attrs" v-on="$listeners">
  4. <slot></slot>
  5. </el-table>
  6. <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>
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. name: 'm-table',
  12. data() {
  13. return {
  14. page: {
  15. pageSize: 20,
  16. pageIndex: 2
  17. }
  18. }
  19. },
  20. props: {
  21. total: {
  22. type: Number,
  23. default: 0
  24. }
  25. },
  26. watch: {
  27. page: {
  28. deep: true,
  29. handler: function () {
  30. this.$emit("page-chagne")
  31. }
  32. }
  33. },
  34. methods: {
  35. // 将el-table 方法暴露出去
  36. ...(() => {
  37. let methodsJson = {};
  38. ['reloadData', 'clearSelection', 'toggleRowSelection', 'toggleAllSelection', 'setCurrentRow', 'clearSort', 'clearFilter', 'doLayout', 'sort']
  39. .forEach(key => {
  40. methodsJson = {
  41. ...methodsJson, [key](...res) {
  42. this.$refs['table'][key].apply(this, res);
  43. }
  44. };
  45. });
  46. return methodsJson;
  47. })()
  48. }
  49. }
  50. </script>

使用 m-table

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

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

render 函数

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

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

  1. <script>
  2. import mSetting from './setting'
  3. export default {
  4. components: { mSetting },
  5. name: 'm-table',
  6. data() {
  7. return {
  8. showTable: true,
  9. setShow: false,
  10. config: [],
  11. copySlots: [], // 展示solt数据
  12. page: {
  13. pageSize: 20,
  14. pageIndex: 1
  15. }
  16. }
  17. },
  18. props: {
  19. total: {
  20. type: Number,
  21. default: 0
  22. }
  23. },
  24. watch: {
  25. page: {
  26. deep: true,
  27. handler: function () {
  28. this.$emit("page-chagne")
  29. }
  30. }
  31. },
  32. created() {
  33. this.initConfig()
  34. },
  35. render() {
  36. return (
  37. <div>
  38. <el-table ref="table" {...{ attrs: this.$attrs }} {...{ on: this.$listeners }}>
  39. {
  40. this.copySlots
  41. }
  42. </el-table>
  43. {this.showTable ? (<el-pagination layout="total, sizes, prev, pager, next, jumper"
  44. {...{
  45. on: {
  46. 'size-change': (e) => this.page.pageSize = e,
  47. 'current-change': (e) => this.page.pageIndex = e
  48. }
  49. }}
  50. {...{
  51. attrs: {
  52. 'current-page': this.page.pageIndex,
  53. 'page-sizes': [20, 30, 40, 50],
  54. 'total': this.total
  55. }
  56. }}
  57. > </el-pagination>) : null}
  58. <m-setting {...{
  59. on: {
  60. 'update:show': e => this.setShow = e,
  61. 'change': this.initConfig
  62. }
  63. }} show={this.setShow} config={this.config}></m-setting>
  64. </div >
  65. )
  66. },
  67. methods: {
  68. initConfig(config = []) {
  69. if (config.length === 0) {
  70. config = this.$slots.default
  71. .filter(item => item.componentOptions && item.componentOptions.tag === "el-table-column")
  72. .map(item => {
  73. if (item.componentOptions.propsData.prop === 'index') {
  74. if (!item.data.scopedSlots) {
  75. item.data.scopedSlots = {};
  76. }
  77. item.data.scopedSlots.header = () => (
  78. <i class="el-icon-s-tools" onClick={() => this.setShow = true} />
  79. );
  80. }
  81. return { ...item.componentOptions.propsData };
  82. })
  83. this.sourceConfig = JSON.parse(JSON.stringify(config))
  84. this.copySlots = this.$slots.default;
  85. this.sourceSlots = this.$slots.default;
  86. } else {
  87. let arr = []
  88. this.sourceSlots.forEach(item => {
  89. let temp = config.find(subItem =>
  90. (subItem.prop && subItem.prop === item.componentOptions.propsData.prop) ||
  91. (subItem.type && subItem.type === item.componentOptions.propsData.type)
  92. );
  93. if (temp && temp.isShow) {
  94. Object.assign(item.componentOptions.propsData, temp);
  95. arr.push(item)
  96. }
  97. })
  98. this.copySlots = arr;
  99. this.showTable = false
  100. this.$nextTick(() => {
  101. this.showTable = true
  102. })
  103. }
  104. this.config = config;
  105. },
  106. ...(() => {
  107. let methodsJson = {};
  108. ['reloadData', 'clearSelection', 'toggleRowSelection', 'toggleAllSelection', 'setCurrentRow', 'clearSort', 'clearFilter', 'doLayout', 'sort']
  109. .forEach(key => {
  110. methodsJson = {
  111. ...methodsJson, [key](...res) {
  112. this.$refs['table'][key].apply(this, res);
  113. }
  114. };
  115. });
  116. return methodsJson;
  117. })()
  118. }
  119. }
  120. </script>
  1. <template>
  2. <el-dialog title="表格设置" :visible.sync="shows" width="600px" size="small" :before-close="handleClose">
  3. <el-table :data="list" size='mini'>
  4. <el-table-column prop="showLabel" label="名称"></el-table-column>
  5. <el-table-column prop="label" label="显示名称">
  6. <template slot-scope="{row}">
  7. <el-input size="mini" v-model="row.label"></el-input>
  8. </template>
  9. </el-table-column>
  10. <el-table-column prop="isShow" label="是否显示" width="100" align="center">
  11. <template slot-scope="{row}">
  12. <el-switch v-model="row.isShow"></el-switch>
  13. </template>
  14. </el-table-column>
  15. </el-table>
  16. <span slot="footer" class="dialog-footer">
  17. <el-button @click="shows = false" size="small">取 消</el-button>
  18. <el-button type="primary" @click="handleClose()" size="small">确 定</el-button>
  19. </span>
  20. </el-dialog>
  21. </template>
  22. <script>
  23. export default {
  24. name: 'm-setting',
  25. data() {
  26. return {
  27. list: []
  28. };
  29. },
  30. computed: {
  31. shows: {
  32. get() {
  33. return this.show;
  34. },
  35. set() {
  36. this.$emit("update:show")
  37. }
  38. }
  39. },
  40. props: {
  41. show: {
  42. type: Boolean,
  43. required: true,
  44. default: false
  45. },
  46. config: {
  47. type: Array,
  48. default() {
  49. return []
  50. }
  51. }
  52. },
  53. created() {
  54. this.init()
  55. },
  56. methods: {
  57. init() {
  58. this.list = this.config.map(item => {
  59. return {
  60. ...item,
  61. showLabel: item.showLabel || item.label, // 名称
  62. isShow: item.isShow || true // 是否显示
  63. }
  64. })
  65. },
  66. handleClose() {
  67. this.$emit('change', this.list);
  68. this.shows = false
  69. }
  70. }
  71. };
  72. </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. modal 遮罩层,滚动穿透 bug

    modal 遮罩层,滚动 穿透bug float 弹层 taro 小程序弹框 滚动击穿 问题 https://segmentfault.com/q/1010000011134345 solution ...

  2. SVG (viewBox) & DOM (viewport)

    SVG (viewBox) & DOM (viewport) circle "use strict"; /** * * @author xgqfrms * @license ...

  3. js showOpenFilePicker showSaveFilePicker showDirectoryPicker API

    选择文件,获取文件句柄 btn.addEventListener("click", async (e) => { try { const hFiles = await win ...

  4. 01_MySQL从下载—>安装—>到快速上手

    一.MySQL下载 二.MySQL安装 三.MySQL几条简单命令快速上手(增删改查) 一.MySQL下载与安装 下载地址:https://dev.mysql.com/downloads/mysql/ ...

  5. 【Python】set 与 list ——如何对列表进行去重?

    在Python中,形如 {1,2,3,4,5} 这样的数据类型叫做"集合",外形酷似列表list [1,2,3,4,5] 但是集合与列表有很多区别,具体表现在以下几方面: List ...

  6. 微信小程序:点击预览大图功能

    点击预览大图功能 1. 给轮播图swiper-item绑定点击事件 2. 预览功能的本质是调用了小程序的api:previewImage 微信公众号----文档----开发----API----媒体- ...

  7. Python3.x 基础练习题100例(41-50)

    练习41: 题目: 模仿静态变量的用法. 程序: def varfunc(): var = 0 print('var = %d' % var) var += 1 if __name__ == '__m ...

  8. Python3.x 基础练习题100例(11-20)

    练习11: 题目: 古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 分析: 兔子的规律为数列1,1,2, ...

  9. WIFI6 基本知识(一)

    什么是WI-FI6(802.11ax) Wi-Fi 6 是下一代 802.11ax 标准的简称.随着 Wi-Fi 标准的演进,WFA 为了便于 Wi-Fi 用户和设备厂商轻松了解其设备连接或支持的 W ...

  10. 9.Vue之webpack打包基础---模块化思维

    主要内容: 1. 什么是模块化思维? 2.  ES6包的封装思想 一.什么是模块化思维呢? 现实工作中, 一个项目可能会有多个人同时开发. 然后, 将所有人开发的内容, 合并到一个文件中. 比如: 1 ...