在 Gatsby 中查询 child 个节点字段

Querying child node fields in Gatsby

我有以下 GraphQL 模式,它定义了 3 种类型:CondaPackage 有很多 CondaVersion,有很多 CondaExecutable。我希望能够查询一个 CondaVersion 并询问“你拥有多少 CondaExecutable,我的分析成功了”。目前我已经写了一个 succeededExeCountallExeCount,它们通过加载所有 children 并手动计算成功的 children 的数量来解析这个字段。

exports.createSchemaCustomization = ({ actions: { createTypes }, schema }) => {
  createTypes([
    schema.buildObjectType({
      name: "CondaPackage",
      fields: {
        succeededExeCount: {
          type: "Int!",
          resolve(source, args, context){
              // TODO
          }
        },
        allExeCount: {
          type: "Int!",
          resolve(source, args, context){
              // TODO
          }
        }
      },
      interfaces: ["Node"]
    }),
    schema.buildObjectType({
      name: "CondaVersion",
      fields: {
        succeededExeCount: {
          type: "Float!",
          resolve(source, args, context){
            const children = context.nodeModel.getNodesByIds({
              ids: source.children,
              type: "CondaExecutable"
            })
            return children.reduce((acc, curr) => acc + curr.fields.succeeded, 0)
          }
        },
        allExeCount: {
          type: "Int!",
          resolve(source, args, context){
            return source.children.length;
          }
        }
      },
      interfaces: ["Node"]
    }),
    schema.buildObjectType({
      name: "CondaExecutable",
      fields: {
        succeeded: {
          type: "Boolean!",
          resolve(source, args, context, info) {
            return source.fields.succeeded || false;
          }
        },
      },
      interfaces: ["Node"]
    })
  ])
}

我的第一个问题是,这似乎非常低效。对于每个 CondaVersion,我 运行 为其 children 单独查询,这是一个经典的 N+1 查询问题。有没有办法告诉 Gatsby/GraphQL 像我使用 SQL 那样简单地“加入”两个表来避免这种情况?

我的第二个问题是,我现在需要从顶级类型中计算成功 children 的数量:CondaPackage。我想问一下“你的childCondaVersion有多少CondaExecutable我的分析成功了”。同样,在 SQL 中这很容易,因为我只需要 JOIN 3 种类型。但是,我目前唯一可以做到这一点的方法是对每个 child 使用 getNodesByIds,然后对每个 child 的 child 使用 n*m*o 运行时间,太可怕了。我想 运行 一个 GraphQL 查询作为字段解析的一部分,这样我就可以从每个 child 中获取 succeededExeCount。但是,Gatsby 的 runQuery 似乎 return 节点没有包含派生字段,并且它不会让我 select 附加字段到 return。如何在 Gatsby 中访问节点 child 的 child 上的字段?

编辑

这是 Gatsby 维护者关于解决方法的回复:

Gatsby has an internal mechanism to filter/sort by fields with custom resolvers. We call it materialization. [...] The problem is that this is not a public API. This is a sort of implementation detail that may change someday and that's why it is not documented.

参见full thread here

原答案

这里有一点 'secret'(在撰写本文时未在文档中的任何地方提及):

当您使用 runQuery 时,Gatsby 将尝试解析派生字段...但前提是该字段被传递给查询的选项(过滤、排序、分组、不同)。

例如,在 CondaVersion 中,而不是访问子节点并查找 fields.succeeded,您可以这样做:

const succeededNodes = await context.nodeModel.runQuery({
  type: "CondaExecutable",
  query: { filter: { succeeded: { eq: true } } }
})

CondaPackage 也是如此。您可以尝试这样做

const versionNodes = await context.nodeModel.runQuery({
  type: "CondaVersion",
  query: {}
})

return versionNodes.reduce((acc, nodes) => acc + node.succeededExeCount, 0) // Error

您可能会发现 succeededExeCountundefined

诀窍是这样做:

const versionNodes = await context.nodeModel.runQuery({
  type: "CondaVersion",
- query: {}
+ query: { filter: { succeededExeCount: { gte: 0 } } }
})

这是违反直觉的,因为你会认为 Gatsby 只会解析类型上的所有可解析字段。相反,它只解析 'used' 的字段。因此,为了解决这个问题,我们添加了一个什么都不做的过滤器。

但这还不是全部,node.succeededExeCount仍然是undefined

解析后的数据(succeededExeCount)并没有直接存储在节点本身,而是在node.__gatsby_resolvedsource中。我们必须在那里访问它。

const versionNodes = await context.nodeModel.runQuery({
  type: "CondaVersion",
  query: { filter: { succeededExeCount: { gte: 0 } } }
})

return versionNodes.reduce((acc, node) => acc + node.__gatsby_resolved.succeededExeCount, 0)

试一试,如果可行请告诉我。

PS:我注意到您可能使用 createNodeField(在 CondaExecnode.fields.succeeded 中?)createTypes 也可以在 [=28 中访问=],因此您可以直接添加此 succeeded 字段。