背景
项目需求
  • 原型图如下
    在这里插入图片描述

  • 可以添加/删除条件和条件组

  • 添加的条件组和父组件有相同的功能

由此可以判断,这是一个递归组件的调用

规划数据结构
  • 递归类型的数据结构,那么子和父的数据结构是一样的,那我们不难想到父里边带children数组,所以我们大概是这样的数据结构:
      let parent: {
        id: 1,
        ...
        children: [
          {
            id: 1,
            ...
            children: [
              {
                id: 1,
                children: []
              }
              ....
            ]
          }
          ....
        ]
      }
    

这样的数据结构即是我们通过children去递归组件

  • 每个组件内包含添加的N条条件,我们用一个数组(conditionList)去处理。
      // 单个组件的数据结构新增一个conditionList[]
      let parent = {
        id: 1,
        conditionList: [], // 暂不关注条件里面的数据结构
        children: []
      }
    

到这里我们看似完成了这个数据结构。如果只是展示性质的递归组件,到这里我们就ok了。可是我们的需求是组件还可以操作,有数据的交互,添加删除数据。那么我们就该想,递归组件该怎么来通知我修改了谁的数据
走到这里我们可能就要考虑数据该放哪儿?该在哪儿去修改数据最为合适?怎么通知数据,我是谁,我修改了谁?既然递归组件,大家都一样,这该通知谁呢?我又是递归的第几层修改的数据呢?

  • id不能作为层次的标记,因为数组里我们不知道会加多少条数据,id数量不确定也就不合适做层次标记。所以数据的层次我们单独用一个index做标记。
      let parent = {
        id: 1,
        index: 1,
        conditionList: [],
        children: [
          {
            id: 1,
            index: 2,
            conditionList: [],
            children: []
          }
        ]
      }
    

这样我们大概从数据上来说知道了修改了哪一层次的数据。现在看来数据结构就差不多了。剩下的疑问我们放到下面的组件间数据传递。

组件间数据传递
  • 确定一点,我们是递归组件,不知道层级。也就是说这不是简单的父子之间的传值,也不是兄弟组件的传值.
    • 一般的prop传值:适合层次清楚且少的传值,父子组件
    • this.$children/parent:适合父子组件
    • this.$ref: 适合父子组件
    • $sttrs/listeners:适合层次清楚的多组件间传值
    • Vuex: 状态管理,我们这儿只涉及组件,用vuex就大材小用了(但也可以)。适合大型项目
    • event bus: 事件总线。注册/监听事件,传递数据。很符合我的需求。

所以最终我决定使用event bus

  • EventBus:基于注册监听的方式来运行。

    • 建立一个空vue组件作为事件总线
      // bus.js
      import Vue from 'vue';
      export let bus = new Vue();
      
    • 子组件内注册触发事件,使用vue的$emit
      import { bus } from './bus';
      // eventName触发的事件名
      // data: 传递的数据
      bus.$emit('eventName', data);
      
    • 父组件监听事件
      import { bus } from './bus';
      bus.$on('eventName', (res) => {
        // res 即是我们传递的数据
        console.log(res);
      })
      

    到这里我们就成功了一半,event bus有个副作用就是如果不及时注销监听,则会出现多次调用的情况,大大的降低了性能。所以一定不要忘了注销事件监听
    尤大大也就此给出了解决方案
    在这里插入图片描述

    • 注销事件监听$off
        beforeDestroy() {
          // 我这儿有多个事件,所以可以传数组的形式
          bus.$off(['addCondition', 'deleteCondition', 'deleteGroup']);
        },
      
组件实现
  • 父组件做数据的修改
      <template>
        <div class="fliter-condition">
          <fliterItem :group-list="groupList" />
        </div>
      </template>
      <script>
        import fliterItem from './fliter-item.vue';
        import { bus } from './bus.js';
        export default {
          components: {
            fliterItem
          },
          data() {
            return {
              groupList: {
                id: '1',
                index: 1,
                conditionList: [],
                children: []
              }
            };
          },
          created() {
            bus.$on('addCondition', (index, id, type) => {
              // 添加条件或条件组
              this.addCondition(this.groupList, index, id, type);
            });
            bus.$on('deleteCondition', (index, id, conditionId) => {
              // 删除条件
              this.deleteCondition(this.groupList, index, id, conditionId);
            });
            bus.$on('deleteGroup', (index, id) => {
              // 删除条件组
              this.deleteGroup(this.groupList, index, id);
            });
          },
          beforeDestroy() {
            // 注销监听事件
            bus.$off(['addCondition', 'deleteCondition', 'deleteGroup']);
          }
      </script>
    
  • 子组件
    • 递归注意递归终点及传值
      <template>
        <div class="content-condition">
          <q-collapse v-model="activeName" accordion>
            <q-collapse-item name="1">
              <template slot="title">
                <q-select
                  v-model="condition"
                  placeholder="请选择"
                  style="width: 80px;"
                  :disabled="!dataType"
                >
                  <q-option
                    v-for="item in operator"
                    :key="item.id"
                    :label="item.label"
                    :value="item.id"
                  />
                </q-select>
                <q-button
                  type="primary"
                  :disabled="!dataType"
                  class="add-btn"
                  @click.stop="addCondition('single')"
                >
                  <i class="iconfont iconqax-icons-tianjiaxiang-yuanxing" />添加条件
                </q-button>
                <q-button
                  type="primary"
                  :disabled="!dataType"
                  class="add-btn"
                  @click.stop="addCondition('group')"
                >
                  <i class="iconfont iconqax-icons-tianjiaxiang-yuanxing" />添加条件组
                </q-button>
                <q-button
                  v-if="type==='children'"
                  type="danger"
                  class="add-btn"
                  @click.stop="deleteGroup()"
                >
                  <i class="iconfont iconqax-icons-shanchuxiang-yuanxing" />删除条件组
                </q-button>
              </template>
              <div v-for="item in groupList.conditionList" :key="item.id" class="condition-item">
                <q-button type="text" class="icon" @click="deleteCondition(item.id)">
                  <i class="iconfont iconqax-icons-shanchuxiang-yuanxing" />
                </q-button>
                <div class="condition-select">
                  <q-select v-model="item.value">
                    <q-option
                      v-for="e in item.options"
                      :key="e.id"
                      :label="e.label"
                      :value="e.id"
                    />
                  </q-select>
                </div>
                <ConditionItem />
              </div>
              <!-- 注意判断条件,不然会溢出 -->
              <div v-if="groupList.children && groupList.children.length > 0">
                <div v-for="item in groupList.children" :key="item.id">
                  <fliterCondition :group-list="item" type="children" />
                </div>
              </div>
            </q-collapse-item>
          </q-collapse>
        </div>
      </template>
    
      <script>
      import { operator, conditionItem, propItem } from './index';
      import { bus } from './bus.js';
      import ConditionItem from './condition-item';
      export default {
        name: 'FliterCondition',
        components: {
          // 组件内部的内容,在此不做展示
          ConditionItem
        },
        props: {
          // 单个组件的数据结构
          groupList: {
            type: Object,
            default: () => {
              return {
                id: '1',
                index: 1,
                // 单个条件
                conditionList: [],
                children: []
              };
            }
          },
          // 用于判断是不是显示删除条件组
          type: {
            type: String,
            default: ''
          }
        },
        data () {
          return {
            activeName: '1',
            condition: 1,
            operator,
            conditionItem,
            propItem
          };
        },
        methods: {
          addCondition (type) {
            // 注册触发事件并传值
            bus.$emit('addCondition', this.groupList.index, this.groupList.id, type);
          },
          deleteCondition (conditionId) {
            // 注册触发事件并传值
            bus.$emit('deleteCondition', this.groupList.index, this.groupList.id, conditionId);
          },
          deleteGroup () {
            // 注册触发事件并传值
            bus.$emit('deleteGroup', this.groupList.index, this.groupList.id);
          }
        }
      };
      </script>
    
小结
  • 递归组件数据结构要好好整理清楚
  • 选择合适的组件间通信方式。eventbus使用记得注销事件监听
  • 将数据独立出来修改(即在父组件修改,自组件不做数据的修改)
  • 注意递归组件的递归判断,不做判断将溢出栈,结果也是很糟糕的。
Logo

前往低代码交流专区

更多推荐