Gatsby.js 的 GraphQL 查询回调
Posted
技术标签:
【中文标题】Gatsby.js 的 GraphQL 查询回调【英文标题】:GraphQL query callbacks for Gatsby.js 【发布时间】:2019-02-20 23:26:42 【问题描述】:在 Contentful CMS 中,我有两种不同的内容类型:BigCaseStudy
和 BigCaseStudySection
。为了让这些内容出现在我的 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
节点,您希望它们全部显示在一页上。这是一个奇怪的结构,但它是为了克服内容模型中的一个巨大的疏忽——你不能在他们的数据模型中做可变的、重复的字段。所以这个两步内容类型是我能想到的最好的。在接下来的几个小时里我也在玩这个,也许我会想办法。
知道了,所以一个页面有多个BigCaseStudy
s,每个页面可以有多个BigCaseStudySection
s?包含所有内容的单个索引页面,用户不能点击过去到单个索引页面?如果是这样的话,那么我可以提供比下面更好的建议。
单个页面,具有单个 BigCaseStudy
和多个 BigCaseStudySection
节点。在我的gatsby-node.js
中,我想做的是让forEach
遍历每个BigCaseStudy
,然后执行filter
匹配BigCaseStudy
的IDs
到bigCaseStudyReference.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 上查询字段