Советы по разработке компонентов Vue

Vue.js
Советы по разработке компонентов Vue

предисловие

В ежедневной разработке мы будем использовать множество сторонних библиотек компонентов.Лучший способ изучить разработку компонентов — просмотреть исходный код этих библиотек компонентов и извлечь из них несколько советов.

element-uiВсе в основном использовали его.Обобщите библиотеку компонентов.Treeа такжеCollapseИспользуемые небольшие навыки, на следующем рисунке показана простая версия для достижения демонстрационного эффекта.

屏幕录制2020-10-12 下午5.36.16.gif

Collapse

Давайте посмотрим на используемый API:

  1. использоватьprovide/injectБудуЭкземпляр компонента-предкаКак зависимость, внедренная в компоненты-потомки
  2. Поскольку подкомпонент используетslotспособ вставки, поэтому используйтеЭкземпляр компонента-предкаПользовательское событие Fire

<!-- collapse -->
<mx-collapse v-model="activeNames" @change="handleChange" accordion>
  <mx-collapse-item title="一致性 Consistency" name="1">
    <div>
      与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;
    </div>
    <div>
      在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。
    </div>
  </mx-collapse-item>
  ......
</mx-collapse>

  • Будуродительский компонентВ качестве примеров зависимости он вводится в субре,Использовать экземпляр родительского компонента в дочернем компонентеПользовательское событие Fire
  • используется в родительском компоненте$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полный код
<template>
  <div class="mx-collapse">
    <slot></slot>
  </div>
</template>
<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полный код
<template>
  <div class="mx-collapse-item">
    <div class="mx-collapse-item__header" @click="handleHeaderClick">
      <slot name="title">{{ title }}</slot>
    </div>
    <transition name="fade" mode="in-out">
      <div class="mx-collapse-item__content" v-show="isActive">
        <slot></slot>
      </div>
    </transition>
  </div>
</template>
<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полный код
<template>
  <div class="mx-tree">
    <div class="mx-tree-label" @click="toggle">
      {{ isFolder && (open ? '-' : '+') }}
      &nbsp;
      {{ treeData.label }}
    </div>
    <ul v-if="isFolder" v-show="open">
      <mx-tree
        v-for="data in treeData.children"
        :treeData="data"
        :key="data.label"
      />
    </ul>
  </div>
</template>

<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');

  • Полный код страницы входа
<template>
  <div id="app">
    <div>
      <img class="logo" alt="Vue logo" src="./assets/logo.png" />
    </div>

    <!-- collapse -->
    <mx-collapse v-model="activeNames" @change="handleChange" accordion>
      <mx-collapse-item title="一致性 Consistency" name="1">
        <div>
          与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;
        </div>
        <div>
          在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。
        </div>
      </mx-collapse-item>
      <mx-collapse-item title="反馈 Feedback" name="2">
        <div>
          控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;
        </div>
        <div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</div>
      </mx-collapse-item>
      <mx-collapse-item title="效率 Efficiency" name="3">
        <div>简化流程:设计简洁直观的操作流程;</div>
        <div>
          清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;
        </div>
        <div>
          帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。
        </div>
      </mx-collapse-item>
      <mx-collapse-item title="可控 Controllability" name="4">
        <div>
          用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;
        </div>
        <div>
          结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。
        </div>
      </mx-collapse-item>
    </mx-collapse>

    <br />

    <!-- tree -->
    <mx-tree :treeData="treeData" />
  </div>
</template>

<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середина