将仅查看属性添加到树数据对象

时间:2019-01-22 08:03:52

标签: javascript vue.js vuejs2

我正在构建一个树组件,可以在其中移动节点。组件和数据结构与Tree View example in the Vue.js documentation中使用的组件和数据结构相似。

数据结构如下(删除了不相关的属性):

[
  {"id": 1, "children": []},
  {"id": 2, "children": []},
  {"id": 3, "children": [
    {"id": 4, "children": [
      {"id": 5, "children": []},
      {"id": 6, "children": []}
    ]}
  ]}
]

节点表示可以在视图中折叠或展开的“文件夹”。 FolderNode组件如下所示:

export default {
  name: 'FolderNode',
  props: {
    node: { type: Object, required: true },
  },
  data: () => ({
    expanded: true,
  }),

  methods: {
    toggleExpand() {
      this.expanded = !this.expanded;
    },
  },
};

模板为:

<template>
  <li>
    <span class="node-icon">
      <span @click="toggleExpand">[{{ expanded ? '-' : '+' }}]</span>
    </span>
    <span class="node-label">{{ node.id }}</span>
    <ol
      v-if="node.children && node.children.length"
      v-show="expanded"
    >
      <FolderNode
        v-for="child in node.children"
        :key="child.id"
        :node="child"
      />
    </ol>
  </li>
</template>

这部分工作正常。我添加了拖放功能以在树中四处移动节点(为简化起见,未在上面显示)。当节点被删除并插入到其他位置时,Vue.js会自动实例化新的FolderNode组件以反映更改。将创建这些新的FolderNode实例(将节点移至其他父节点时),其默认expanded状态为true。我希望:key属性可以在不同的父级上工作,但是它只为同一父级expanded的子级重用组件(并保持其FolderNode状态)。

那么,移动文件夹时,如何保持文件夹的expanded状态(和其他显示状态)?

该树对象将由应用程序的其他部分使用,因此我无法将expanded属性直接添加到其节点,这将是最简单的解决方案。此外,expanded严格来说是“显示状态”,与树数据无关。

我想到了2种可能并不吸引人的解决方案:

  1. 创建一个并行树结构,以镜像保存显示状态的数据树。将其传递给文件夹组件,并让他们查询以了解其状态。
  2. 使用映射将数据树的节点映射到状态对象。问题是maps are not reactive in Vue.js,所以我不得不求助于丑陋的骇客才能使其正常工作。

还有其他想法吗?

2 个答案:

答案 0 :(得分:1)

构建树组件时,我们遇到了类似的问题。我们有完全相同的想法。

最初,我们创建了一个并行数据结构,借助于Ramda,我们将合并内部数据模型外部树结构。但是事实证明这不是很优雅。而且,树协调很难实现。

但是,如果您对此方法有另一种看法,那么将expanded作为 tree Node对象的一部分是有意义的。有很多用例:

  • 有时您可能希望将第一个节点显示为展开状态
  • 在搜索操作期间,所有具有特定名称的节点必须扩展其在大多数代码编辑器中的显示方式。
  • 用作文件浏览器时,默认情况下必须扩展包含特定文件的节点。

在许多其他情况下,它被证明是有用的。我们正在使用TypeScript,我们只是将该属性声明为可选属性,例如:

export interface TreeNode<T extends any> {
    label: string;
    expanded: boolean;
    children: Array<TreeNode<T>>;

    isDisabled?: boolean;

    // Holds the state if tree/node selected or not
    select?: boolean;

    // Unique key which identifies each node
    _id?: string;

    // Any other data that needs to be stored
    context?: T;
}

就API而言,不必担心这些附加键,因为它们是可选的。因此,以扩展状态支持 drag-n-drop 变得很简单

答案 1 :(得分:0)

另一种可能性是不使用地图就使用选项2。实际上,该地图尽管在语义上是正确的,但实际上根本不需要!

“根”组件创建一个“树视图状态”对象,该对象保存每个节点的expanded状态。像这样:

const treeViewState = {};
// Visit each node and create its initial view state object to make it reactive
walkNodes(tree, (node, level) => {
  treeViewState[node.id] = {
    expanded: level < 1, // Simple logic to only expand the first level by default
  };
});

然后,“根”组件通过Vue的注入机制provide treeViewState为其子组件。

每个FolderNode然后通过其节点的唯一ID访问其显示状态。这用于索引treeViewState对象。