2021-04-13

Vue 中的 mixin,component,render,hoc

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

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

混入(mixin)

vue 官方介绍

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

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

<template> <div>  <el-table :v-loading='isLoading' :data="tableData" 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 :></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" 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 :></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 :></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 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" >   <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高阶组件可以参考这篇









原文转载:http://www.shaoqun.com/a/682734.html

跨境电商:https://www.ikjzd.com/

55海淘网:https://www.ikjzd.com/w/1723

敦煌网站:https://www.ikjzd.com/w/189


在项目中,一般我们经常会基于一套现有组件库进行快速开发,但是现实中往往需要对组件库进行定制化改造二次封装在项目中,一般我们经常会基于一套现有组件库进行快速开发,但是现实中往往需要对组件库进行定制化改造二次封装混入(mixin)vue官方介绍混入(mixin)提供了一种非常灵活的方式,来分发Vue组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被"混合
woot:https://www.ikjzd.com/w/604
启明星软件:https://www.ikjzd.com/w/1436
巴士物流:https://www.ikjzd.com/w/2329
印度电商平台的"大日子"即将到来 他们是否能赚个盆满钵满呢?:https://www.ikjzd.com/home/132492
Alexa排名:精准分析各大电商平台流量与市场容量!:https://www.ikjzd.com/home/96028
苦恼 老公喜欢边看A片边爱爱:http://lady.shaoqun.com/m/a/271460.html

No comments:

Post a Comment