vue 递归组件的使用及组件间的数据传递
背景项目需求原型图如下可以添加/删除条件和条件组添加的条件组和父组件有相同的功能由此可以判断,这是一个递归组件的调用规划数据结构递归类型的数据结构,那么子和父的数据结构是一样的,那我们不难想到父里边带children数组,所以我们大概是这样的数据结构:let parent: {id: 1,...children: [{id: 1,...children: [
·
背景
项目需求
-
原型图如下
-
可以添加/删除条件和条件组
-
添加的条件组和父组件有相同的功能
由此可以判断,这是一个递归组件的调用
规划数据结构
- 递归类型的数据结构,那么
子和父的数据结构是一样的
,那我们不难想到父里边带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']); },
- 建立一个空vue组件作为事件总线
组件实现
- 父组件做数据的修改
<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使用记得注销事件监听
- 将数据独立出来修改(即在父组件修改,自组件不做数据的修改)
- 注意递归组件的递归判断,不做判断将溢出栈,结果也是很糟糕的。
更多推荐
已为社区贡献3条内容
所有评论(0)