Gatsby.js 的 GraphQL 查询回调

Posted

技术标签:

【中文标题】Gatsby.js 的 GraphQL 查询回调【英文标题】:GraphQL query callbacks for Gatsby.js 【发布时间】:2019-02-20 23:26:42 【问题描述】:

在 Contentful CMS 中,我有两种不同的内容类型:BigCaseStudyBigCaseStudySection。为了让这些内容出现在我的 Gatsby 2.x 网站中,我的想法是:

    执行查询 1,它获取我要显示的所有 BigCaseStudy 字段,并且还包含内容的 ID 字段作为元数据。 从查询 1 中获取 ID,匹配查询 2 中的内容引用字段(其中包含 ID) 查询2,返回所有匹配的BigCaseStudySection字段

最终目标是显示原始BigCaseStudy 和所有BigCaseStudySection(通常编号在3-5 个之间)。您可以查看我的查询以查看字段,有一堆。

我认为 GraphQL 变量和查询的某种组合会让我到达那里(可能是突变)?目前尚不清楚,我还没有看到任何复杂的示例,例如查询一组内容然后使用响应进行另一个调用,例如链式承诺或 JS 中的异步/等待。

关于正确构造的任何想法?

bigcasestudy.js 带有 GraphQL 查询的组件:

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

import  graphql  from 'gatsby'
import Img from 'gatsby-image'

import Layout from '../Layout/layout'

/** 
 * Hero Section
 */ 
const HeroContainer = styled.header`
  align-items: center;
  background-image: url($ props => props.bgImgSrc );
  background-position: center center;
  background-size: cover;
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: calc(100vh - 128px);
`
const HeroTitle = styled.h1`
  color: #fff;
  font-size: 70px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-bottom: 15px;
`
const HeroSubtitle = styled.h2`
  color: #fff;
  font-size: 24px;
  font-weight: 300;
  letter-spacing: 5px;
  text-transform: uppercase;
`
/** 
 * Intro Section
 */ 
const IntroBG = styled.section`
  background-color: $ props => props.theme.lightGray ;
  padding: 50px 0;
`
const IntroContainer = styled.div`
  padding: 25px;
  margin: 0 auto;
  max-width: $ props => props.theme.sm ;

  @media (min-width: $ props => props.theme.sm ) 
    padding: 50px 0;
  
`
const IntroTitle = styled.h2`
  font-size: 50px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-bottom: 45px;
  text-align: center;
`
const IntroText = styled.p`
  font-size: 22px;
  line-spacing: 4;
  text-align: center;
`

const IntroButton = styled.a`
  background-color: #fff;
  color: $ props => props.theme.darkGray ;
  border: 1px solid $ props => props.theme.mediumGray ;
  border-radius: 25px;
  display: block;
  font-size: 16px;
  letter-spacing: 5px;
  margin: 30px auto;
  padding: 15px 45px;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  width: 300px;
`

// BigCaseStudy Component
class BigCaseStudy extends React.Component 
  render() 
    // Setup destructured references to each Contentful object passed in through the GraphQL call
    const  caseStudyTitle  = this.props.data.contentfulBigCaseStudy
    const  caseStudySubtitle  = this.props.data.contentfulBigCaseStudy
    const  caseStudyIntroTitle  = this.props.data.contentfulBigCaseStudy
    const  caseStudyIntro  = this.props.data.contentfulBigCaseStudy.caseStudyIntro
    const  caseStudyLink  = this.props.data.contentfulBigCaseStudy

    console.log(this)

    return (
      <Layout>
        <HeroContainer 
          bgImgSrc= this.props.data.contentfulBigCaseStudy.caseStudyHero.fixed.src >
          <HeroTitle> caseStudyTitle </HeroTitle>
          <HeroSubtitle> caseStudySubtitle </HeroSubtitle>
        </HeroContainer>
        <IntroBG>
          <IntroContainer>
            <IntroTitle> caseStudyIntroTitle </IntroTitle>
            <IntroText> caseStudyIntro </IntroText>
          </IntroContainer>
          <IntroButton href= caseStudyLink  target="_blank" rel="noopener noreferrer">
            Visit the site >
          </IntroButton>
        </IntroBG>
      </Layout>
    )
  


// Confirm data coming out of contentful call is an object
BigCaseStudy.propTypes = 
  data: PropTypes.object.isRequired


// Export component
export default BigCaseStudy

// Do call for the page data
// This needs to mirror how you've set up the dynamic createPage function data in gatsby-node.js
export const BigCaseStudyQuery = graphql`
  query BigCaseStudyQuery 
    contentfulBigCaseStudy 
      id
      caseStudyTitle
      caseStudySubtitle
      caseStudyIntroTitle
      caseStudyIntro 
        caseStudyIntro
      
      caseStudyLink
      caseStudyHero 
        fixed 
          width
          height
          src
          srcSet
                          
      
    ,
    contentfulBigCaseStudySection (id: $postId) 
      title
      order
      images 
        fixed 
          width
          height
          src
          srcSet
        
      
      bigCaseStudyReference 
        id
      
      body 
        body
      
      stats 
        stat1 
          word
          number
        
        stat2 
          word
          number
        
        stat3 
          word
          number
        
        stat4 
          word
          number
         
      
      id
    
  
`

gatsby-node.js文件:

/**
 * Implement Gatsby's Node APIs in this file.
 *
 * ######################################################
 * BIG CASE STUDY BACKEND CODE
 * ######################################################
 * 
 * We are using the .createPages part of the Gatsby Node API: https://next.gatsbyjs.org/docs/node-apis/#createPages 
 * What this does is dynamically create pages (surprise) based on the data you feed into it
 * 
 * Feed the contentful API call into the promise
 * Here I'm calling BigCaseStudy, which is a custom content type set up in contentful
 * This is briefly explained over here: https://www.gatsbyjs.org/packages/gatsby-source-contentful/
 * 
 * Also, note the caseStudyIntro field is long text `markdown`
 * Gatsby returns the long text field as an object
 * Calling it's name inside of the object returns the html
 * Read more here: https://github.com/gatsbyjs/gatsby/issues/3205
 */

// Set Gatsby path up to be used by .createPages
const path = require('path')

// Using Node's module export, Gatsby adds in a createPages factory 
exports.createPages = ( graphql, actions ) => 

  // We setup the createPage function takes the data from the actions object
  const  createPage  = actions

  // Setup a promise to build pages from contentful data model for bigCaseStudies
  return new Promise((resolve, reject) => 

    // Setup destination component for the data
    const bigCaseStudyComponent = path.resolve('src/components/BigCaseStudy/bigcasestudy.js')

    resolve(
      graphql(`
        
          allContentfulBigCaseStudy 
            edges 
              node  
                id
                caseStudySlug
                caseStudyTitle
                caseStudySubtitle
                caseStudyIntroTitle
                caseStudyIntro 
                  caseStudyIntro
                
                caseStudyLink
                caseStudyHero 
                  fixed 
                    width
                    height
                    src
                    srcSet
                                    
                
              
            
          
          allContentfulBigCaseStudySection 
            edges 
              node 
                title
                order
                images 
                  fixed 
                    width
                    height
                    src
                    srcSet
                  
                
                bigCaseStudyReference 
                  id
                
                body 
                  body
                
                stats 
                  stat1 
                    word
                    number
                  
                  stat2 
                    word
                    number
                  
                  stat3 
                    word
                    number
                  
                  stat4 
                    word
                    number
                   
                
                id
              
            
          
        
      `).then((result) => 

        // Now we loop over however many caseStudies Contentful sent back
        result.data.allContentfulBigCaseStudy.edges.forEach((caseStudy) => 
          const caseStudySections = result.data.allContentfulBigCaseStudySection.edges

          let caseStudySectionMatches = caseStudySections.filter( 
            caseStudySection => caseStudySection.bigCaseStudyReference.id === caseStudy.id 
          )

          createPage (
            path: `/work/$caseStudy.node.caseStudySlug`,
            component: bigCaseStudyComponent,
            context: 
              id: caseStudy.node.id,
              slug: caseStudy.node.caseStudySlug,
              title: caseStudy.node.caseStudyTitle,
              subtitle: caseStudy.node.caseStudySubtitle,
              hero: caseStudy.node.caseStudyHero,
              introTitle: caseStudy.node.caseStudyIntroTitle,
              intro: caseStudy.node.caseStudyIntro.caseStudyIntro,
              link: caseStudy.node.caseStudyLink,
              caseStudySection: caseStudySectionMatches.node
            
          )

        )
      )

      // This is the error handling for the calls
      .catch((errors) => 
        console.log(errors)
        reject(errors)
      )

    ) // close resolve handler
  ) // close promise

【问题讨论】:

感谢您的更新。这里有很多信息让我给出更深入的回应。我会在今天晚些时候回复。 我的假设正确吗?用户可以看到BigCaseStudy 的列表,当他们单击一个时,他们会被带到特定的BigCaseStudy 页面,该页面上可以有多个BigCaseStudySection 关闭 - 这一切都应该呈现在一页上。一个BigCaseStudy 有3-5 个BigCaseStudySection 节点,您希望它们全部显示在一页上。这是一个奇怪的结构,但它是为了克服内容模型中的一个巨大的疏忽——你不能在他们的数据模型中做可变的、重复的字段。所以这个两步内容类型是我能想到的最好的。在接下来的几个小时里我也在玩这个,也许我会想办法。 知道了,所以一个页面有多个BigCaseStudys,每个页面可以有多个BigCaseStudySections?包含所有内容的单个索引页面,用户不能点击过去到单个索引页面?如果是这样的话,那么我可以提供比下面更好的建议。 单个页面,具有单个 BigCaseStudy 和多个 BigCaseStudySection 节点。在我的gatsby-node.js 中,我想做的是让forEach 遍历每个BigCaseStudy,然后执行filter 匹配BigCaseStudyIDsbigCaseStudyReference.id 的@987654350 @。那有意义吗?这对我来说是个模糊不清的地方。 【参考方案1】:

简短回答:您不使用 GraphQL 进行回调。您只需执行一个查询,即可一次获得所需的一切。

更长的答案:我必须重构 gatsby-node.js 文件如何获取内容内容,然后对其进行过滤。在 Gatsby 中,您希望在 gatsby-node.js 中设置查询以从您的数据源中获取所有内容,因为它是一个静态站点生成器。它的架构将所有数据带入,然后相应地解析出来。

我最初的问题中的 GraphQL 查询很好。我将 promise 的 .then() 更改为在我的结果中使用 .filter(),将子节点的关系字段与父节点的 id 进行比较。

gatsby-node.js:

// Set Gatsby path up to be used by .createPages
const path = require('path')

// Using Node's module export, Gatsby adds in a createPages factory 
exports.createPages = ( graphql, actions ) => 

  // We setup the createPage function takes the data from the actions object
  const  createPage  = actions

  // Setup a promise to build pages from contentful data model for bigCaseStudies
  return new Promise((resolve, reject) => 

    // Setup destination component for the data
    const bigCaseStudyComponent = path.resolve('src/components/BigCaseStudy/bigcasestudy.js')

    resolve(
      graphql(`
        
          allContentfulBigCaseStudy 
            edges 
              node  
                id
                caseStudySlug
                caseStudyTitle
                caseStudySubtitle
                caseStudyIntroTitle
                caseStudyIntro 
                  caseStudyIntro
                
                caseStudyLink
                caseStudyHero 
                  fixed 
                    width
                    height
                    src
                    srcSet
                                    
                
              
            
          
          allContentfulBigCaseStudySection 
            edges 
              node 
                title
                order
                images 
                  fixed 
                    width
                    height
                    src
                    srcSet
                  
                
                bigCaseStudyReference 
                  id
                
                body 
                  body
                
                stats 
                  stat1 
                    word
                    number
                  
                  stat2 
                    word
                    number
                  
                  stat3 
                    word
                    number
                  
                  stat4 
                    word
                    number
                   
                
                id
              
            
          
        
      `).then((result) => 

        // Now we loop over however many caseStudies Contentful sent back
        result.data.allContentfulBigCaseStudy.edges.forEach((caseStudy) => 
          let matchedCaseStudySections = result.data.allContentfulBigCaseStudySection.edges.filter(
            caseStudySection => 
              caseStudySection.node.bigCaseStudyReference.id === caseStudy.node.id 
          )

          createPage (
            path: `/work/$caseStudy.node.caseStudySlug`,
            component: bigCaseStudyComponent,
            context: 
              id: caseStudy.node.id,
              slug: caseStudy.node.caseStudySlug,
              title: caseStudy.node.caseStudyTitle,
              subtitle: caseStudy.node.caseStudySubtitle,
              hero: caseStudy.node.caseStudyHero,
              introTitle: caseStudy.node.caseStudyIntroTitle,
              intro: caseStudy.node.caseStudyIntro.caseStudyIntro,
              link: caseStudy.node.caseStudyLink,
              caseStudySection: matchedCaseStudySections.node
            
          )

        )
      )

      // This is the error handling for the calls
      .catch((errors) => 
        console.log(errors)
        reject(errors)
      )

    ) // close resolve handler
  ) // close promise

设置完成后,Gatsby 节点 API 的 createPage 部分会将父节点及其所有节点发送到您设置的 component 参数。

在我的组件内部,我现在可以对所有子节点进行 GraphQL 查询。现在返回了我想要的内容,并且符合 GraphQL 发出一个请求而不是像我试图做的多个请求的想法。唯一棘手的部分是您必须在组件的渲染部分中使用.map() 来遍历从 Contentful 发回的所有子节点。

bigcasestudy.js组件:

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

import  graphql  from 'gatsby'
import Img from 'gatsby-image'

import Layout from '../Layout/layout'

/** 
 * Hero Section
 */ 
const HeroContainer = styled.header`
  align-items: center;
  background-image: url($ props => props.bgImgSrc );
  background-position: center center;
  background-size: cover;
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: calc(100vh - 128px);
`
const HeroTitle = styled.h1`
  color: #fff;
  font-size: 70px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-bottom: 15px;
`
const HeroSubtitle = styled.h2`
  color: #fff;
  font-size: 24px;
  font-weight: 300;
  letter-spacing: 5px;
  text-transform: uppercase;
`
/** 
 * Intro Section
 */ 
const IntroBG = styled.section`
  background-color: $ props => props.theme.lightGray ;
  padding: 50px 0;
`
const IntroContainer = styled.div`
  padding: 25px;
  margin: 0 auto;
  max-width: $ props => props.theme.sm ;

  @media (min-width: $ props => props.theme.sm ) 
    padding: 50px 0;
  
`
const IntroTitle = styled.h2`
  font-size: 50px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-bottom: 45px;
  text-align: center;
`
const IntroText = styled.p`
  font-size: 22px;
  line-spacing: 4;
  text-align: center;
`

const IntroButton = styled.a`
  background-color: #fff;
  color: $ props => props.theme.darkGray ;
  border: 1px solid $ props => props.theme.mediumGray ;
  border-radius: 25px;
  display: block;
  font-size: 16px;
  letter-spacing: 5px;
  margin: 30px auto;
  padding: 15px 45px;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  width: 300px;
`



// BigCaseStudy Component
class BigCaseStudy extends React.Component 
  render() 

    // Destructure Case Study Intro stuff
    const  
      caseStudyHero, 
      caseStudyIntro, 
      caseStudyIntroTitle, 
      caseStudyLink, 
      caseStudySubtitle, 
      caseStudyTitle 
     = this.props.data.contentfulBigCaseStudy

    // Setup references to Case Study Sections, destructure BigCaseStudySection object
    const caseStudySections = this.props.data.allContentfulBigCaseStudySection.edges.map( 
      (currentSection) => 
        return currentSection.node
      
    )

    // Case Study Section can be in any order, so we need to sort them out
    const caseStudySectionsSorted = caseStudySections.sort( (firstItem, secondItem) => 
      return firstItem.order > secondItem.order ? 1 : -1
    )

    console.log(caseStudySectionsSorted)

    return (
      <Layout>
        <HeroContainer 
          bgImgSrc= caseStudyHero.fixed.src >
          <HeroTitle> caseStudyTitle </HeroTitle>
          <HeroSubtitle> caseStudySubtitle </HeroSubtitle>
        </HeroContainer>
        <IntroBG>
          <IntroContainer>
            <IntroTitle> caseStudyIntroTitle </IntroTitle>
            <IntroText> caseStudyIntro.caseStudyIntro </IntroText>
          </IntroContainer>
          <IntroButton href= caseStudyLink  target="_blank" rel="noopener noreferrer">
            Visit the site >
          </IntroButton>
        </IntroBG>
        
          caseStudySectionsSorted.map( (caseStudySection, index) => 
            return <IntroTitle key= index > caseStudySection.title </IntroTitle>
          )
        
      </Layout>
    )
  


// Confirm data coming out of contentful call is an object
BigCaseStudy.propTypes = 
  data: PropTypes.object.isRequired


// Export component
export default BigCaseStudy

// Do call for the page data
// This needs to mirror how you've set up the dynamic createPage function data in gatsby-node.js
export const BigCaseStudyQuery = graphql`
  query BigCaseStudyQuery 
    contentfulBigCaseStudy 
      id
      caseStudyTitle
      caseStudySubtitle
      caseStudyIntroTitle
      caseStudyIntro 
        caseStudyIntro
      
      caseStudyLink
      caseStudyHero 
        fixed 
          width
          height
          src
          srcSet
                          
      
    
    allContentfulBigCaseStudySection 
      edges 
        node 
          title
          order
          images 
            fixed 
              width
              height
              src
              srcSet
            
          
          bigCaseStudyReference 
            id
          
          body 
            body
          
          stats 
            stat1 
              word
              number
            
            stat2 
              word
              number
            
            stat3 
              word
              number
            
            stat4 
              word
              number
             
          
          id
        
      
    
  
`

H/t:感谢 @taylor-krusen 重新安排我处理这个问题的方式。

【讨论】:

很好地解决了这个问题!但是......你实际上做了两次工作。您根本不需要从您的gatsby-node.js 传递所有这些数据。您需要的一切都来自您的bigcasestudy.js 文件。您的道具由该页面上的 GraphQL 查询填充 - 无需通过 createPage() 函数中的 context 属性传递任何内容。您可以将排序逻辑从gatsby-node.js 移动到bigcasestudy.js,它会正常工作。查询返回相同的数据,因此您可以直接在组件中对其进行排序。 在此处查看文档:gatsbyjs.org/docs/creating-and-modifying-pages gatsby-node.js 只有在您以编程方式创建页面(例如在博客中)时才真正需要。对于单个静态页面,您的查询和组件应位于单个页面上。 很有趣,所以我应该将排序逻辑移动到bigcasestudy.js 而不是gatsby-node.js。但这又引发了另一个问题——我猜在gatsby-node.js 中你只需要查询你想输入createPage() 的程序化内容,而不是全部?这是有道理的,根据你所说的。 没错!在博客设置中,gatsby-node.js 通常会将 slug 或 ID 传递给正在创建的页面,而不是整个查询的结果。然后,正在创建的页面将使用该上下文属性中的信息在页面构建时针对该特定资源运行 graphql 查询。【参考方案2】:

我也遇到了这个挑战,但找不到一个好的解决方案来完成这个任务(尽管我没有使用 Contentful),但我确实克服了它并认为我可以提供帮助。你需要稍微改变一下思路。

基本上,GraphQL 并不是真的要查询运行另一个查询所需的数据。它更像是一种“询问你需要什么”的工具。 GraphQL 想要完全运行单个查询来满足您的需求。

查询所需的参数实际上来自您的gatsby-node.js 文件。具体来说,createPages()(gatsby 提供的 Node API)的 context 属性。

这足以让你指向正确的方向吗?如果您需要更多帮助,那么我需要知道两件事: 1. 更多关于你想要完成的事情的背景。您希望最终用户获得哪些具体数据? 2. 你的gatsby-node.js 文件是什么样的。

【讨论】:

有趣的反馈。我在尝试 GraphQL 时感觉到类似的东西,比如我的心智模型还没有适应“GraphQL 方式”。我会用这些东西来修改这个问题。

以上是关于Gatsby.js 的 GraphQL 查询回调的主要内容,如果未能解决你的问题,请参考以下文章

Gatsby.js - GraphQL 查询 pdf 文件在 allMarkdownRemark

Gatsby & GraphQL - 从查询文件中呈现下载链接

如何使用 Graphql 从 Strapi 查询 Gatsby 中的多个图像

无法在 allMarkdownRemark graphql 上查询字段

如何在 graphQL 中获取 Gatsby 页面 HTML

如何让 Drupal 8 视图与 GraphQL / Gatsby.js 一起使用