在用vue实现一个无限层级的树型结构时,遇到了这个问题。
页面结构如图:
其中,父页面的处理逻辑:
步骤一:引用并挂载组件,同时向组件props传递树型JSON列表数据(this.list),当然这时候的 this.list 还只是一个空数组。
步骤二:在 onload 事件中从服务器获取树型JSON数据并回写到 this.list,同时这个 this.list 也会自动通过 props 传递给组件。
当时考虑到对数据的解耦,不想在父页面中对数据进行过多的处理,拿到服务端的JSON后直接交给组件,剩下的都在组件里实现。
所以对于组件来说,就要对 props 中接收到的数据进一步处理,用来满足树型的相关要求。
首先,为了实现对树型节点的展开/闭合操作,每个节点的数据对象都需要增加一个属性(opened),用来记忆节点的状态,同时自动渲染到Dom。
组件的处理逻辑:
步骤一:接收 props 中的 list 数据;
步骤二:在组件的 mounted 事件中遍历 list 中当前层级的节点列表数据对象,增加并初始化属性 item.opened = false,即默认闭合节点。
步骤三:如果存在子节点,则递归引用并挂载组件,同时将子节点列表数传递给组件,实现无限层级的树型渲染。
注意:步骤二中为节点数据对象增加 opened 属性需要使用 this.$set 函数,否则新增加的属性将不支持与视图的自动响应。
按照以上方式完成之后,发现第一层节点的 opened 属性并没有自动响应,通过调试发现组件的 mounted 事件只处理了第一次挂载时接收到的空数组,之后父页面传过来的真实数据压根没经过处理。
想了一下,才反应过来,因为父页面在创建时就已经触发了组件的 mounted 事件;
后来从服务端返回JSON后,修改 props 时不会再次触发组件的 mounted 事件,反而会触发 updated 事件,所以才造成了新增属性无效的情况。
弄明白了问题所在,解决起来也很纠结,因为只有第一层组件的数据传入是异步的,需要从 updated 事件进行处理;
而更下层的组件都是挂载时就会传入的,还需要从 mounted 进行处理。
另一个问题就是,每次点击节点时要修改 opened 属性,而这时也会触发 updated 事件,所以初始化操作还是不能在 updated 中处理。
想明白了问题所在,就尝试通过 this.$refs 来实现,在父页面引用组件时,默认先不通过 props 传入 list 数据,而是在获取服务端JSON之后,使用 this.$refs.comp.treeRoot(list) 向组件传入,而组件本身的 mounted 事件用来处理第二层之后的组件数据初始化。
后来又尝试用 watch 来解决,感觉比 ref 要简洁一些,核心代码如下:
watch: { list: { handler(newVal, oldVal){ if(kit.isEmpty(oldVal) || oldVal.length === 0){ this.opened = this.currentLevel < this.openLevel; let list = JSON.parse(JSON.stringify(newVal)); list.forEach((item, i) => { item[this.attrOpened] = this.opened; }) this.treeList = list; } }, immediate: true } }