【问题标题】:How to model recursive data structures in GraphQL如何在 GraphQL 中对递归数据结构进行建模
【发布时间】:2017-11-28 12:55:15
【问题描述】:

我有一个树形数据结构,我想通过 GraphQL API 返回它。

该结构不是特别大(小到足以在一次调用中返回它不会成为问题)。

结构的最大深度没有设置。

我已经将结构建模为:

type Tag{
    id: String!
    children: [Tag]
}

当人们想要将标签获得任意深度时就会出现问题。

要让所有孩子(例如)达到 3 级,可以编写如下查询:

{ tags { id children { id children { id } } } }

有没有办法编写查询以将所有标签返回到任意深度?

如果不是,在 GraphQL API 中为上述结构建模的推荐方法是什么。

【问题讨论】:

  • 这个想法是不需要一次全部嵌套,而只在需要时。参见例如stackoverflow.com/questions/32497759/…github.com/facebook/relay/issues/246。他们都创建了实体 Human(分别是 Babushka),它在需要时加载孩子。有一些停靠点。 (因为它没有完全回答“如何让标签达到任意深度”,我只添加这个作为评论)
  • 谢谢您的回答。当然,您可以分步加载分层数据结构,这对于任何足够大的结构都是正常的方法。然而,在我的情况下(总共少于 100 个节点),这将是多余的,并且会不必要地使我的 UI 复杂化,因为节点扩展操作的回调。
  • 其他可能性是使服务器端控制器github.com/thecodingmachine/graphql-controllers... 或展平树以仅具有 parentId 属性。两者都很好,很干净,但两者都应该工作......你觉得呢?
  • 我真的很喜欢将整个结果展平并在客户端重建它的想法。我认为这将是最干净的方法,并且我可能会实施,再次感谢。

标签: graphql


【解决方案1】:

如果您愿意放弃 GraphQL 提供的类型安全和子字段查询以及通过 ID 缓存和引用对象的能力,那么另一个选择是将数据编码为 JSON。 gaphql-type-json 包提供了解析器来简化此操作。这些也包含在graphql-scalars 的许可下,其中包含许多其他方便的标量。

我正在为定义动态表单控件的分层数据执行此操作。在这种情况下,没有任何 ID 会丢失,因此很容易获胜。

【讨论】:

    【解决方案2】:

    前段时间我想出了另一个解决方案,与@WuDo 建议的方法相同。

    这个想法是使用 ID 在数据级别上扁平化树以引用它们(每个孩子都有它的父母)并标记树的根,然后在客户端再次递归地构建树。​​

    这样您就不必担心像@samcorcos 的回答那样限制查询的深度。

    架构:

    type Query {
        tags: [Tag]
    }
    
    type Tag {
        id: ID!
        children: [ID]
        root: Boolean
    }
    

    回复:

    { 
        "tags": [
            {"id": "1", "children": ["2"], "root": true}, 
            {"id": "2", "children": [], "root": false}
        ] 
    }
    

    客户端树构建:

    import find from 'lodash/find';
    import isArray from 'lodash/isArray';
    
    const rootTags = [...tags.map(obj => {...obj)}.filter(tag => tag.root === true)];
    const mapChildren = childId => {
        const tag = find(tags, tag => tag.id === childId) || null;
    
        if (isArray(tag.children) && tag.children.length > 0) {
            tag.children = tag.children.map(mapChildren).filter(tag => tag !== null);
        }
    }
    const tagTree = rootTags.map(tag => {
        tag.children = tag.children.map(mapChildren).filter(tag => tag !== null);
        return tag;
    });
    

    【讨论】:

    • 顺便说一句,你可以用parent: ID代替root: Boolean。这传达了相同的信息(parent = null 表示 root = true),但允许客户端按他们认为合适的方式按父级和/或子级导航结构。
    • @Tobia 是的,我想你最好同时拥有。拥有两者的唯一原因是订购。如果您没有 children 数组,另一种方法是确保展平的树始终保持正确的顺序,以便正确重建它。当然,操作这样的结构比较棘手。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    • 2018-08-02
    相关资源
    最近更新 更多