簡體   English   中英

Vue - 在 Pinia 中存儲一棵樹

[英]Vue - Storing a tree in Pinia

我要問的問題是關於 Pinia 的,但實際上可以推廣到任何基礎商店。

我有一個 Vue + Pinia 應用程序,我希望能夠在其中存儲一棵樹。 該樹由Node類型的對象組成。 我需要一次只存儲一棵樹,我不關心根(我們可以想象它在那里,但我關心的是根的孩子,他們的孩子,等等)。

我想支持以下操作:

  • 創建一個新的頂級節點
  • 創建一個節點,該節點是另一個節點的子節點
  • 修改或刪除節點,無論它是頂級節點還是子節點或子節點的子節點
  • 我希望能夠圍繞一個節點或整個子樹移動,改變它的父節點,甚至只是它的 position 相對於它的兄弟節點
  • 不會立即從后端獲取整棵樹; 它會被懶惰地獲取。 您可以想象當一個節點在 UI 中打開時,它的直接子節點被獲取等等。

這是我想做的事情:

  • 在我的商店中保留一個包含頂級節點的Node數組。 我們稱它為topLevelNodes
  • 保留一個 object nodeIdToChildren ,它將節點的 id 映射到作為其子節點的Node數組

我最初會獲取頂級節點,填充數組topLevelNodes

對於每個需要知道其子節點的節點,獲取它們並將它們作為鍵放入父 ID 下的nodeIdToChildren中。

這種方法的一個優點是很容易添加、刪除和移動節點:只需觸摸映射中的相關條目。 最大的缺點是只找到一個節點的效率要低得多,不管它的 position。假設我想編輯 id 為xyz的節點,不知道它是誰的孩子。

我可以創建一個吸氣劑,將映射 object 中的所有值與頂級節點數組中的值一起展平,但我不確定其效率。

有沒有更好的方法來做到這一點?

最有效的方法是將所有節點存儲在一個數組中,而在一個節點的子/父數組中,您只放置其他節點的 ID。

  1. 您應該選擇是將孩子還是父母存儲到一個項目中,而不是同時存儲兩者。 原則是:你不想在兩個地方存儲相同的信息,因為如果不同步,你就會有微妙/奇怪的錯誤。 例如,如果你存儲 children 的 ids 數組:你可以有一個計算的parents ,但它返回所有項目的 ids,這些項目當前在他們的 children 數組中包含當前項目的 id。 同樣,如果您存儲父母,則孩子將被計算。
  2. 這種平面數組結構允許構建兩種類型的 UI 列表:自上而下和自下而上(您可以在兩個單獨的選項卡中向用戶提供這兩種列表)。
  3. 一個很好的樹導航系統是面包屑(很像計算機上的文件夾)。 面包屑邏輯相對簡單:當您導航到某個項目時,您將其 id 傳遞給面包屑。 如果它已經在面包屑中,則為向上導航,您將面包屑拼接到該項目。 如果不是,則為向下導航,您在面包屑末尾添加項目的 id。
  4. 它還允許您輕松地在項目的所有祖先(無論多少級)或項目的所有后代(無論多少級)中搜索某些內容。 注意:如果您允許循環關系 (A > B > A),則必須在搜索中考慮到這一點。

注意事項

  • 如果您想延遲加載,則必須在后端移動搜索和過濾。 如果節點總數以千計,則不應延遲加載。 您只需請求所有節點一次,然后您可以在不發出另一個請求的情況下導航和搜索它們。 它不會影響性能。 影響性能的是渲染的節點多於您可以在視口上顯示的節點,但這是一個完全不同的主題。 此處要說明的另一點:如果節點具有重依賴性(例如:圖像),則僅延遲加載這些依賴項(例如:在實際顯示項目時加載那些,發出單獨的請求(例如: getItemDetails ))
  • 以上允許擁有兩種類型的系統:允許循環的系統(A > B > A)或不允許的系統。 每個都有自己的限制類型(第一個在遞歸計算父母/孩子時需要限制); 第二個限制從子選擇器中排除祖先並從父選擇器中排除后代(假設您構建 UI 以更改子/父母)。
  • 一個很大的優勢是兩個節點之間的子/父關系只存儲在一個地方。 (例如:如果你想將一個孩子從一個父母移動到另一個父母,你必須:從當前父母的children ID 數組中刪除它的 ID,將它的 ID 添加到新父母的children中。孩子本身不受影響,除了值它的parents計算的變化。如果你正在存儲parents , - 並且計算了children - 你需要改變孩子的parents數組。當然,當你執行這個改變時,每個新舊父母的children計算都會改變) .
  • 另一個優點是它允許最大程度的靈活性(任何節點都可以同時是任何其他數量的節點的子節點或父節點——這並不意味着它們必須:你總是可以將父節點的數量限制為一個,並且你得到“經典”文件夾結構,其中任何節點只能有一個直接父節點)。

您可以在此處查看工作演示 對不起 styles,我只是從不同的沙盒中調整了一些東西。 但是你得到了<TreeView /><TableView />的基本實現。 這個有 1k 個節點,但最多 5k 個節點應該沒有問題。 除此之外,您需要注意渲染的內容和時間。

這不是Vue/Pinia特有的,所以我只是寫了大概的思路,你可以選擇實現的方式。

您將把您的樹存放在一個平面 map 上。

const tree = new Map()

每個項目都是一個node ,鍵是nodeId 每個節點將包含以下屬性:

{
  id: "the node id",
  parentId: "id of the parent, null if it is the root node",
  childIds: "array of the id of its child",
  content: "content of the node, whatever you want"
}

讓我們通過您想要的每個運營商 go:

  • 創建一個新的頂級節點:
// difficulty: easy
const rootNode = {
  id: "nodeId"
  parentId: null,
  childIds: [...],
  content: "..."
}

tree.set(nodeId, rootNode)
  • 創建一個節點,該節點是另一個節點的子節點
// difficulty: easy
// add the child first
const childNode = {
  id: "nodeId"
  parentId: "parentId",
  childIds: [...],
  content: "..."
}
tree.set(nodeId, childNode)

// add the child id to the parent node
const parentNode = tree.get(parentId)
parentNode.childIds.push(childNode.id)

// set the parent back to your tree
tree.set(parentNode.id, parentNode)
  • 修改或刪除節點,無論它是頂級節點還是子節點或子節點的子節點
// modify a node. 
// difficulty: easy
const node = tree.get(nodeId)
// ... make the modification
// set it back to the tree
tree.set(nodeId, node)

// delete a node. 
// difficulty: medium
// retrieve the node first
const node = tree.get(nodeId)

// delete it
tree.delete(nodeId)

// delete all of its children
// you need a recursive delete function here to go through all of the node child and child of child and so on
tree.childIds.forEach(recursiveDelete)
// don't forget to delete the nodeId from its parent node. It's easy
...
  • 圍繞一個節點或整個子樹移動,改變它的父節點,甚至只是它的 position 相對於它的兄弟節點
// moving around the tree is quite easy, you just need to follow the `parentId` and `childIds`

// changing a node's parent (same level)
// difficulty: easy
// you just need to change the parentId of the node. And modify the childIds of its old and new parent

// changing a node level, moving its children accordingly
// difficulty: easy
// same as changing a node parent above. Its children will move accordingly

// changing a node level to be a child of one of its children
// difficulty: hard

// get the node
const node = tree.get(nodeId)

// go through its children and update the parentId of each to the node.parentId (moving its children to be the direct child of its parent)
node.childIds.forEach((childId)=> updateParentId(childId, node.parentId))

// set the node parentId to the new one
node.parentId = newParentId

// set new childIds for the node if you want
node.childIds = [...]

// don't forget to set it back on the tree
tree.set(node.id, node)
  • 懶惰地獲取樹:
// There is no problem at all. You just need to load from the root

優點和缺點

優點:

  • 易於實施
  • 僅通過其 id 即可輕松獲取、更新內容和刪除節點
  • 輕松將節點及其子節點移動到樹中您想要的任何位置

缺點

  • 難以確定節點的級別(您需要遍歷其所有父節點)
  • 難以維護數據的約束。 假設您找不到節點的父節點
  • 很難准確判斷一個節點是否是另一個節點的子節點(子節點的子節點...)(您需要遍歷其所有父節點)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM