用户
 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,登录网站

小程序社区 首页 教程 实战教程 查看内容

Vue 组件开发中的小技巧

Rolan 2020-10-16 10:10

日常开发中,我们会用到很多第三方组件库,学习组件开发最好的方法就是看这些组件库的源码,并从中学到一些小技巧

日常开发中,我们会用到很多第三方组件库,学习组件开发最好的方法就是看这些组件库的源码,并从中学到一些小技巧

element-ui 大家基本都用过,总结一下组件库中 TreeCollapse 用到的小技巧,下图为简易版实现演示效果

Collapse

先来看下使用到的 API:

  • provide/inject
  • $emit
  • $on
  1. 使用 provide/inject祖先组件实例 作为依赖,注入到子孙组件中
  2. 因为子组件是采用 slot 方式插入,所以要使用 祖先组件实例 发起自定义事件
<mx-collapse v-model="activeNames" @change="handleChange" accordion>
  <mx-collapse-item title="一致性 Consistency" name="1">
    <div>
      与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;
    div>
    <div>
      在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。
    div>
  mx-collapse-item>
  ......
mx-collapse>
复制代码

  • 父组件 实例作为依赖,注入到子组件中, 在子组件中使用父组件实例 发起自定义事件
  • 父组件中使用 $on 监听 item-click 事件,并接收子组件传回来的数据做进一步处理
provide() {
  return {
    collapse: this,
  };
},
created() {
  // 自定义事件监听
  this.$on('item-click', this.handleItemClick);
},
//...
复制代码

  • 在子组件 collapse-item 中使用 父组件实例 触发点击事件,发送当前组件数据
inject: ['collapse'],
methods: {
  // 使用父组件实例触发自定义事件
  handleHeaderClick() {
    this.collapse.$emit('item-click', this);
  },
},
//...
复制代码

  • collapse.vue 完整代码

<script>
export default {
  name: 'MxCollapse',
  componentName: 'MxCollapse',
  props: {
    accordion: Boolean,
    value: {
      type: [Array, String, Number],
      default() {
        return [];
      },
    },
  },
  data() {
    return {
      activeNames: [].concat(this.value),
    };
  },
  // 将当前组件实例作为依赖,用于注入到子组件中,
  // 在子孙后代中可以使用祖先组件实例 发起自定义事件
  provide() {
    return {
      collapse: this,
    };
  },
  watch: {
    value(value) {
      this.activeNames = [].concat(value);
    },
  },
  created() {
    // 自定义事件监听
    this.$on('item-click', this.handleItemClick);
  },
  methods: {
    /**
     * item-click 自定义事件处理
     * 1. 手风琴模式下,展开的元素只有一个
     * 2. 普通模式可以多个展开
     */
    handleItemClick(item) {
      const { name } = item;

      // 手风琴模式
      if (this.accordion) {
        this.setActiveNames(
          (this.activeNames[0] || this.activeNames[0] === 0) &&
          this.activeNames[0] === name
          ? ''
          : name
        );
      }
      // 普通模式
      else {
        const activeNames = this.activeNames.slice(0);
        const index = activeNames.indexOf(name);
        if (index > -1) {
          activeNames.splice(index, 1);
        } else {
          activeNames.push(name);
        }
        this.setActiveNames(activeNames);
      }
    },

    /**
     * 实时修改 activeNames 值
     * 当 activeNames 数据变化时,触发组件自定义 change 事件
     */
    setActiveNames(activeNames) {
      activeNames = [].concat(activeNames);
      this.activeNames = activeNames;
      const value = this.accordion ? activeNames[0] : activeNames;
      this.$emit('change', value);
    },
  },
};
script>
<style lang="less" scoped>
.mx-collapse {
  border-top: 1px solid #ebeef5;
  border-bottom: 1px solid #ebeef5;
}
style>
复制代码

  • collapse-item.vue 完整代码

<script>
export default {
  name: 'MxCollapseItem',
  componentName: 'MxCollapseItem',
  data() {
    return {};
  },
  inject: ['collapse'],
  props: {
    disabled: Boolean,
    title: String,
    name: {
      type: [String, Number],
    },
  },
  computed: {
    isActive() {
      return this.collapse.activeNames.indexOf(this.name) > -1;
    },
  },
  methods: {
    // 使用父组件实例触发自定义事件,并将本组件数据回传
    handleHeaderClick() {
      this.collapse.$emit('item-click', this);
    },
  },
};
script>
<style lang="less" scoped>
.mx-collapse-item {
  font-size: 13px;
  user-select: none;
  &:last-child {
    margin-bottom: -1px;
  }
  .mx-collapse-item__header {
    height: 48px;
    line-height: 48px;
    color: #303133;
    cursor: pointer;
    border-bottom: 1px solid #ebeef5;
    font-weight: bold;
    outline: 0;
  }
  .mx-collapse-item__content {
    padding: 25px 0;
    color: #303133;
    line-height: 1.769230769230769;
  }
}
style>
复制代码

Tree

组件循环引用,递归组件

  1. 使用递归组件,调用组件自身完成树结构渲染
  2. 必须指定组件 name 属性,并将子数据元素作为递归的数据源传入
  • tree.vue 完整代码
<script>
export default {
  // 递归组件必须指定name
  name: 'MxTree',
  componentName: 'MxTree',
  props: {
    treeData: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      open: true,
    };
  },
  computed: {
    // 计算是否可以展开 or 收起
    isFolder() {
      return this.treeData.children && this.treeData.children.length;
    },
  },
  methods: {
    // 展开 or 收起
    toggle() {
      if (this.isFolder) {
        this.open = !this.open;
      }
    },
  },
};
script>

<style lang="less" scoped>
.mx-tree-label {
  text-align: left;
  font-size: 13px;
}
style>

复制代码

Install

安装 Vue.js 插件

  1. 如果插件是一个对象,必须提供 install 方法
  2. 如果插件是一个函数,它会被作为 install 方法
  3. install 方法调用时,会将 Vue 作为参数传入
  4. 该方法需要在调用 new Vue() 之前被调用
  5. install 方法被同一个插件多次调用,插件将只会被安装一次
  • 每个组件都是独立的模块,目录结构如下
mx
├── collapse
│   ├── index.js
│   └── src
│       └── collapse.vue
├── collapse-item
│   ├── index.js
│   └── src
│       └── collapse-item.vue
├── index.js
└── tree
    ├── index.js
    └── src
        └── tree.vue
复制代码

  • 组件文件夹中 index.js 为入口文件,在其中定义 install 方法并将模块暴露
import MxTree from './src/tree';

MxTree.install = function(Vue) {
  Vue.component(MxTree.name);
};

export default MxTree;
复制代码

  • 根目录下的 index.js 为总入口文件,在其中将所有组件集中,定义 install 方法并将模块暴露
import Collapse from './collapse/index';
import CollapseItem from './collapse-item/index';
import Tree from './tree/index';

const components = [Collapse, CollapseItem, Tree];

const install = function(Vue, options = {}) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};

export default {
  install,
};
复制代码

  • 最后一步,像使用 element-ui 一样的方式,来使用自己的组件库
import Vue from 'vue';
import App from './App.vue';
import MxUI from './mx/index';

Vue.config.productionTip = false;
Vue.use(MxUI)

new Vue({
  render: h => h(App),
}).$mount('#app');
复制代码

  • 入口页面完整代码


<script>
export default {
  name: 'App',
  data() {
    return {
      activeNames: ['1'],
      treeData: {
        label: 'JavaScript',
        children: [
          {
            label: '数据类型',
            children: [
              {
                label: 'string',
              },
              {
                label: 'number',
              },
              {
                label: 'boolean',
              },
              {
                label: 'null',
              },
              {
                label: 'undefined',
              },
              {
                label: 'symbol',
              },
              {
                label: 'object',
              },
            ],
          },
          {
            label: '变量声明',
            children: [
              {
                label: 'var',
              },
              {
                label: 'let',
              },
              {
                label: 'const',
              },
            ],
          },
        ],
      },
    };
  },
  methods: {
    handleChange(val) {
      console.log(`activeNames: ${val}`);
    },
  },
};
script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin: 40px;
  .logo {
    width: 40px;
    margin-bottom: 30px;
  }
  .mx-collapse {
    float: right;
    width: 70%;
    height: 600px;
  }
  .mx-tree:first {
    float: left;
    width: 30%;
    height: 600px;
  }
  .fade-enter-active {
    transition: opacity 0.3s;
  }
  .fade-leave-active {
    transition: opacity 0.1s;
  }
  .fade-enter,
  .fade-leave-to {
    opacity: 0;
  }
}
style>
复制代码

总结

  1. provide/inject 依赖注入,祖先与子孙之间的配合
  2. $emit $on 自定义事件,需注意 slot 方式子组件与父组件通讯的方式
  3. 递归组件 必须指定 name 选项
  4. Vue.use 配合 install 将自己的组件添加到 Vue

作者:__mxin
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
鲜花
鲜花
鸡蛋
鸡蛋
分享至 : QQ空间
收藏