如何在多种类型上使用 GraphQL 片段

How to use GraphQL fragment on multiple types

我有一个 Gatsby 项目,它具有非常相似的 GraphQL 查询,用于两种不同类型的内容:常规页面和 wiki 文章。

根据 slug 分页

export const query = graphql`
  query($slug: String!) {
    page: contentfulPage(slug: {eq: $slug}) {
      title
      slug
      body {
        remark: childMarkdownRemark {
          excerpt
          html
          headings {
            value
            depth
          }
        }
      }
      updatedAt(formatString: "D. MMM YYYY")
      authors {
        name
        email
      }
    }
  }
`

slug 的 Wiki 文章

export const query = graphql`
  query($slug: String!) {
    article: contentfulWikiArticle(slug: {eq: $slug}) {
      title
      slug
      body {
        remark: childMarkdownRemark {
          excerpt
          html
          headings {
            value
            depth
          }
        }
      }
      updatedAt(formatString: "D. MMM YYYY")
      authors {
        name
        email
      }
 +    section {
 +      title
 +      slug
 +    }
 +    subsection {
 +      title
 +      slug
 +    }
    }
  }
`

除了 wiki 文章的附加部分和小节外,查询是相同的。为了保持干燥,我如何将页面字段移动到一个单独的片段中,尽管类型不同,但也可以传播到 wiki 文章查询中? GraphQL 能否提供类似的东西:

fragment pageFields on [ContenfulPage, ContenfulWikiArticle] {
  ...
}

Gatsby recent release 允许用户为 graphql 模式设置自己的类型,使这个问题终于成为可能。

It's always been possible with graphql if users have control of the schema, but thanks to the recent Gatsby update, users can finally implement this on their own.

设置

为了设置一个简单的示例,我将在像这样的简单文件夹上使用 gatsby-transformer-json

jsonFolder
  |--one.json { "type": "One", "name": "a", "food": "pizza" }
  `--two.json { "type": "Two", "name": "b", "game": "chess" }

并使用选项声明我的类型名称:

{
  resolve: `gatsby-transformer-json`,
  options: { 
    typeName: ({ object }) => object.type,
  },
},

现在我有两种为我创建的类型。我可以在其中一个上创建一个片段,但不能同时创建两个片段:

export const name = graphql`
  fragment name on One {
    name
  }
`

export const pageQuery = graphql`
  query {
    one {
      ...name
    }
    two {
      ...name <-- ⚠️ throw type error
    }
  }
`

让我们解决这个问题。

设置类型

我将使用一个名为 createTypes 的新 API 来注册一个新接口以及每个 json 的 2 种类型。请注意 JsonNode 包含 OneTwo 的公共字段:

exports.sourceNodes = ({ actions }) => {
  const { createTypes } = actions
  const typeDefs = `
    interface JsonNode {
      name: String
      type: String!
    }

    type One implements Node & JsonNode {
      name: String
      type: String!
      food: String
    }

    type Two implements Node & JsonNode {
      name: String
      type: String!
      game: String
    }
  `
  createTypes(typeDefs)
}

奇迹发生在这一行,其中 One & Two 实现了 JsonNode(自定义接口)和 Node(Gatsby 接口)。

type One implements Node & JsonNode { ... }

现在我可以编写实现 JsonNode 的片段,它适用于两种类型。

// blogPostTemplate.js
import React from "react"
import { graphql } from "gatsby"

export default ({ data }) => <div>{JSON.Stringify(data)}</div>

export const name = graphql`
  fragment name on JsonNode {
    name
    level
  }
`

export const pageQuery = graphql`
  query {
    one {
      ...name <-  works
    }
    two {
      ...name <-  works
    }
  }
`

这需要一些设置,但如果您提前知道您的数据类型并且需要大量重用片段,那么这可能是值得的。