Что должен делать Vue, когда сталкивается с динамически сгенерированными компонентами перетаскивания?

внешний интерфейс Vue.js
Что должен делать Vue, когда сталкивается с динамически сгенерированными компонентами перетаскивания?

спрос на продукт

После встречи со спросом на продукт я столкнулся со спросом.Во-первых, страница разделена на две колонки, а компоненты данных отображаются слева, что поддерживает сортировку перетаскиванием.Нажмите кнопку, чтобы очистить компоненты. Правая сторона поддерживает перетаскивание миниатюры компонента влево для создания нового компонента.

идеи

Для динамически генерируемых компонентов каждый раз должен генерироваться новый компонент, тогда можно поставитьКомпонент помещается в функцию return. Вызов функции в JSX возвращает совершенно новый компонент каждый раз, когда вызывается функция.С React это очень просто, но для Vue невозможно вернуть компонент напрямую. Хотя этоreturnМетод написания не подходит для Vue, но мы не можем отрицать, что идея очень правильная, поэтому следует рассмотреть другой метод написания. Что касается динамически генерируемых компонентов, мы должны управлять генерацией компонентов с данными. Для сортировки компонентов перетаскивания можно напрямую использовать библиотеку перетаскивания! !

проблема, с которой мы сталкиваемся

  1. Выбор библиотеки перетаскивания
  2. Как генерировать компоненты
  3. Динамическая генерация компонентов на основе данных

Перетащите выбор библиотеки

Библиотека перетаскивания Здесь я выбираю библиотеку перетаскивания, которая существует в проекте.Vue.Draggable Нажмите здесь для просмотраСтарт 14,9К — это неплохо. Если вы не используете эту библиотеку перетаскивания в своем проекте Vue, вы можете самостоятельно обратиться к идеям дизайна из этой статьи.

Как генерировать компоненты

Здесь я использую Vue.extend() Если вы не знаете, как его использовать, проверьте его в официальной документации, а затем изучите эту статью.Vue.extend. Затем мы создаем файл js для написания кода для создания компонента.

Компоненты сборки

/* generateComponents.js 文件名 */

import Vue from "vue";

// 想要动态生成的组件,先引入这个文件。
import components1 from "./components/TestCom1.vue";
import components2 from "./components/TestCom2.vue";

// 将组件的名称和组件做一个对应Map
const comMap = {
  components1,
  components2,
};

// 接收生成组件需要的组件名称,和想要传递给组件的
// props, 和 事件
const ReturnNewCom = function ({ props, on }) {
  const {
    comItem: { name },
  } = props;
  const newComponent = Vue.extend({
    render(createElement) {
      // 使用传进来的组件name来决定渲染哪一个组件。
      return createElement(comMap[name], {
        props,
        on,
      });
    },
  });
  return new newComponent();
};

export default ReturnNewCom;

компоненты

Здесь мы пишем два компонента для демонстрации этого демо, component1.vue, component2.vue.

/*components1.vue*/
<template>
  <div class="widget-wrapper">
    <header class="header">{{ comDetail.name }}--{{ comDetail.id }}</header>
    <h1>查询条件:{{ queryObj }}</h1>
    <button @click="handleDelete">清除</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      comDetail: this.comItem,
      _queryObj: this.queryObj,
    };
  },
  props: {
    comItem: {
      type: Object,
      default() {
        return {
          id: 0,
          name: "",
        };
      },
    },
    queryObj: {
      // 可以接收父组件传递的晒选条件,必须是Object
      type: Object,
      default() {
        // 定义默认的查询条件。
        return {
          num: 0,
        };
      },
    },
  },
  watch: {
    comItem(val) {
      this.comDetail = val;
      return val;
    },
    queryObj(val) {
      this._queryObj = val;
      return val;
    },
  },
  created() {
    console.log("data -> this.comItem", this.comItem);
  },
  methods: {
    handleDelete() {
      // 删除组件方法
      this.$el.remove();
      // 调用父组件的函数。修改父组件中的 leftComList 数组的数据。
      this.$emit("handleDelete", this.comDetail);
    },
  },
};
</script>
<style scoped>
.widget-wrapper {
  background: #ff7b7b;
  border-radius: 12px;
  overflow: hidden;
  width: 200px;
}
.header {
  height: 50px;
  padding: 0 15px;
}
</style>

На самом деле код в файле component2.vue аналогичен коду в файле component1.vue, разница только в том, что цвет фона другой.

Генерация динамических компонентов на основе данных

Затем вы должны использовать библиотеку перетаскивания Vue.Draggable для перетаскивания и модификации данных. Мы можем писать прямо в файле App.vue.

/* App.vue */
<template>
  <div class="dragCom">
    <h1>{{ leftComList }}</h1>
    <button @click="queryObj.num++">改变查询条件</button>
    <div class="body">
      <div class="left">
        <draggable class="left" :list="leftComList" :group="'people'">
          <div
            ref="comBody"
            v-for="({ name, id }, index) in leftComList"
            :key="id"
            class="comCard"
          >
            <!-- 循环 leftComList 数组,利用数据来渲染组件, 
            将动态生成的数组添加到这个DOM元素当中。 -->
            {{
              handleAddCom({
                props: { comItem: { name, id }, queryObj },
                index,
              })
            }}
          </div>
        </draggable>
      </div>
      <div class="right">
        <draggable
          class="dragArea"
          :list="rightComList"
          :group="{ name: 'people', pull: 'clone', put: false }"
          :clone="handleCloneDog"
        >
          <div class="card" v-for="element in rightComList" :key="element.id">
            {{ element.name }}
          </div>
          <!-- 右侧的 卡片 数据, rightComList 数组对象中的name就对应了generateComponents.js
          中的ComMap中的属性 -->
        </draggable>
      </div>
    </div>
  </div>
</template>

<script>
import draggable from "vuedraggable";
import CreateCom from "./generateComponents";
export default {
  components: {
    draggable,
  },
  data() {
    return {
      rightComList: [
        {
          id: Math.random(),
          name: "components1",
        },
        {
          id: Math.random(),
          name: "components2",
        },
      ],
      leftComList: [], // 存储驱动动态生成组件的数据。
      comMap: new Map(), // 主要的作用就是用来记录 
      // 组件有没有渲染到 class="comCard" 这个DOM中,
      // 如果渲染了就不能再往进添加子元素了。
      queryObj: {
        // 主要的作用就是向子组件传递查询条件
        num: 0,
      },
    };
  },
  beforeDestroy() {
    // 清除 记录 的数据
    this.comMap.clear();
  },
  methods: {
    handleAddCom({ index, on = {}, props = { comItem: { name: "", id: 0 } } }) {
      const {
        comItem: { id },
      } = props;
      this.$nextTick(() => {
        // 获取该节点的子节点的长度
        const childNodesLength = this.$refs.comBody[index].childNodes.length;
        // 获取comBody 这个DOM 数组的长度
        const comLine = this.$refs.comBody.length;
        if (!this.comMap.get(id)) {
          // 如果没有渲染过组件

          // 1. 调用 CreateCom 方法 创建组件。 并传递 props 和 事件
          const com = CreateCom({
            props,
            on: {
              handleDelete: this.handleDeleteCom,
              ...on,
            },
          });
          // 2. 生成组件
          com.$mount();
          if (childNodesLength === 2) {
            // 如果要添加到两个组件中间。那么就将新生成的组件DOM位置进行修改放到中间。
            // 将最后的组件DOM添加到正确的位置
            this.$refs.comBody.splice(
              index,
              0,
              this.$refs.comBody[comLine - 1]
            );
          }
          // 3. 将生成的组件添加到改DOM中。
          this.$refs.comBody[index].appendChild(com.$el);
          // 4. 记录该组件实现了渲染。
          this.comMap.set(id, true);
        } else {
          // 该位置的组件已经渲染,不需要再次渲染直接返回
          return;
        }
      });
    },
    handleDeleteCom({ id }) {
      // 传递给子组件删除的方法,根据组件的id来删除数据
      const index = this.leftComList.findIndex((item) => item.id === id);
      if (~index) {
        // 如果存在这个id的组件,就删除
        this.leftComList.splice(index, 1);
      }
    },
    handleCloneDog(item) {
      // 给 leftComList 数组添加数据
      return {
        ...item,
        id: Math.random(),
      };
    },
  },
};
</script>

<style>
.dragCom {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.body {
  width: 100%;
  height: 800px;
  display: flex;
  justify-content: space-between;
}
.left {
  flex: 1;
  height: 800px;
  border: 1px solid pink;
}
.right {
  width: 20%;
  height: 800px;
}
.card {
  height: 50px;
  background-color: #40cec7;
  margin: 12px 0;
  font-size: 12px;
  line-height: 50px;
  cursor: pointer;
}
.comCard {
  margin: 12px;
  display: inline-block;
}
</style>


Это обеспечивает динамическую визуализацию компонентов и сортировку методом перетаскивания.

Эффект

屏幕录制2021-05-04 17.gif

исходный код

Студенты, которые хотят попробовать, могут сами загрузить код этой статьи.Исходный кодgithub

Замечательные статьи в прошлом

Руководство по оптимизации пакетов Webpack (теория)