在用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 } }
