了解Gatsby中GraphQL如何運作


文章目錄

  1. Gatsby初試啼聲
  2. 利用react-bootstrap打造React風格頁面
  3. 了解Gatsby中GraphQL如何運作
  4. Gatsby GraphQL讀取JSON
  5. 在Gatsby GraphQL中組合出完美資料
  6. Gatsby程序化產生頁面
  7. 上線個人頁面到Netlify+啟動Netlify CMS

在上一篇文章中,我們修改了Layout以及首頁,完成了第一個個人頁面。因為上次動到了typographyjs影響到部落格的樣式,我們可以在修改部落格頁面的同時,趁這次機會了解GraphQL是如何運作的。

什麼是GraphQL?

GraphQL簡單的來說就是比REST API更好的API標準(至少擁護者是這麼說的)。GraphQL是Facebook內部為了解決行動裝置資料傳輸的問題而開發的API,但是後續開源了。雖然開源後一開始跟React比較緊密的連結在一起,但是經過一段時間的發展也支援其他語言以及框架了。在官網上說GraphQL的優勢還有開發速度快、可以精確的得到你想得到的資料,開發速度快不知道是否為真,但是可以精確得到想要的資料在使用Gatsby過後這點確實也得到了驗證。

部落格的資料怎麼從GraphQL來?

src/pages/blog.js

import React from "react"
import { Link, graphql } from "gatsby"

import Bio from "../components/bio"
import Layout from "../components/layout"
import SEO from "../components/seo"
import Button from "../components/button"

class Blog extends React.Component {
  render() {
    const { data } = this.props
    const siteTitle = data.site.siteMetadata.title
    const posts = data.allMdx.edges

    return (
      <Layout location={this.props.location} title={siteTitle}>
        <SEO title="All posts" />
        <Bio />
        <div style={{ margin: "20px 0 40px" }}>
          {posts.map(({ node }) => {
            const title = node.frontmatter.title || node.fields.slug
            return (
              <div key={node.fields.slug}>
                <h3
                  style={{
                    marginBottom: `10px`,
                  }}
                >
                  <Link
                    style={{ boxShadow: `none` }}
                    to={`blog${node.fields.slug}`}
                  >
                    {title}
                  </Link>
                </h3>
                <small>{node.frontmatter.date}</small>
                <p
                  dangerouslySetInnerHTML={{
                    __html: node.frontmatter.description || node.excerpt,
                  }}
                />
              </div>
            )
          })}
        </div>
        <Link to="/">
          <Button marginTop="85px">Go Home</Button>
        </Link>
      </Layout>
    )
  }
}

export default Blog

export const pageQuery = graphql`
  query {
    site {
      siteMetadata {
        title
      }
    }
    allMdx(sort: { fields: [frontmatter___date], order: DESC }) {
      edges {
        node {
          excerpt
          fields {
            slug
          }
          frontmatter {
            date(formatString: "MMMM DD, YYYY")
            title
            description
          }
        }
      }
    }
  }
`

在部落格頁面中我們可以看到最後有個變數pageQuery,在定義好資料的格式後,跟graphql要資料,之後就會直接傳到Blogdata屬性之中。我們就可以從data提取資料出來使用。
如果還是覺得很陌生可以使用console.log觀察datadata中的格式就是我們在pageQuery中定義的樣子。

class Blog extends React.Component {
  render() {
    const { data } = this.props
    console.log('data', data)
  }
}

那麼資料的欄位又是怎麼定義的呢?Gatsby的模板中都有提供資料互動工具,可以由以下地址進入:

http://localhost:8000/__graphql

在最左邊的欄位雖然有很多項目可以選,但是先不要緊張,我們先看allMdx這一項就好了。透過以下的順序選我們可以發現他會自動把資料的格式製作出來,所以我們可以簡單做出另外一套格式如下:

allMdx > edges > node > fields > slug


按下執行按鈕後我們可以預覽資料的結果。這些其實就是部落格文章的網址ID,例如/blog/example。如果選擇其他欄位我們也可以得到其他資料。

想要更深入了解GraphQL可以聽這個免費課程

解釋資料來源

在資料互動工具中,我們看到左邊欄位有很多項目,這些項目其實都是Gatsby的外掛產生的。
gatsby-config.js

  plugins: [
    `gatsby-plugin-netlify-cms`,
    `gatsby-plugin-styled-components`,
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    `gatsby-plugin-offline`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-feed-mdx`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/blog`,
        name: `blog`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/assets`,
        name: `assets`,
      },
    },
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        extensions: [".mdx", ".md"],
        gatsbyRemarkPlugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 590,
            },
          },
          {
            resolve: `gatsby-remark-responsive-iframe`,
            options: {
              wrapperStyle: `margin-bottom: 1.0725rem`,
            },
          },
          {
            resolve: `gatsby-remark-vscode`,
          },
          {
            resolve: `gatsby-remark-copy-linked-files`,
          },
          {
            resolve: `gatsby-remark-smartypants`,
          },
        ],
      },
    },
    {
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        // edit below
        // trackingId: `ADD YOUR TRACKING ID HERE`,
      },
    },
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Gatsby Starter Blog`,
        short_name: `GatsbyJS`,
        start_url: `/`,
        background_color: `#ffffff`,
        theme_color: `#663399`,
        display: `minimal-ui`,
        // edit below
        icon: `content/assets/gatsby-icon.png`,
      },
    },
  ],

雖然有很多外掛的運作原理我沒有去查,但是就跟部落格相關的首先是gatsby-source-filesystem,這樣才能夠讀取${__dirname}/content/blog資料夾裡面的檔案,然後再透過gatsby-plugin-mdx把markdown檔裡面的內容轉換為html。後面的文章我會講到如何把JSON檔案裡面的資料加到GraphQL中,不過如果有興趣的朋友,也可以自己先玩玩看。

修改部落格

了解會得到的GraphQL資料格式後,剩下的就是用react-bootstrap改寫,把資料放到正確的位置。
src/pages/blog.js

import React from "react";
import { Link, graphql } from "gatsby";
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';

import Layout from "../components/layout";
import SEO from "../components/seo";

class Blog extends React.Component {
  render() {
    const { data } = this.props;
    const posts = data.allMdx.edges;

    return (
      <Layout>
        <SEO title="All posts" />
        <Row className="mb-4">
          <Col>
            <h3>Blog</h3>
          </Col>
        </Row>
        {posts.map(({ node }) => {
           const title = node.frontmatter.title || node.fields.slug
           return (
             <Row key={node.fields.slug}>
               <Col>
                <h3>
                  <Link to={`blog${node.fields.slug}`}>
                    {title}
                  </Link>
                </h3>
                <small>{node.frontmatter.date}</small>
                <p
                  dangerouslySetInnerHTML={{
                    __html: node.frontmatter.description || node.excerpt,
                  }}
                />
               </Col>
             </Row>
           )
        })}
      </Layout>
    )
  }
}

export default Blog

export const pageQuery = graphql`
  query {
    allMdx(sort: { fields: [frontmatter___date], order: DESC }) {
      edges {
        node {
          excerpt
          fields {
            slug
          }
          frontmatter {
            date(formatString: "MMMM DD, YYYY")
            title
            description
          }
        }
      }
    }
  }
`

除了改寫之外,我也把siteMetadata的部份刪除了,因為在這裡Layout中的title不需要變化。Bio我也刪除了,因為有做footer的關係重新再放一次顯得多餘。不過Bio檔案裡的內容在後面的部份還有參考的價值,所以我先把檔案保留著。
src/templates/blog-post.js

import React from "react";
import { Link, graphql } from "gatsby";
import { MDXRenderer } from "gatsby-plugin-mdx";
import styled from "styled-components";
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';

import Layout from "../components/layout";
import SEO from "../components/seo";

const DividingLine = styled.hr`
  margin-bottom: 20px;
`;

const PrevNextUl = styled.ul`
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  list-style: none;
`;

class BlogPostTemplate extends React.Component {
  render() {
    const post = this.props.data.mdx
    const { previous, next } = this.props.pageContext

    return (
      <Layout title={post.frontmatter.title}>
        <SEO
          title={post.frontmatter.title}
          description={post.frontmatter.description || post.excerpt}
        />
        <Row>
          <Col>
            <h1>{post.frontmatter.title}</h1>
            <p>{post.frontmatter.date}</p>
            <MDXRenderer>{post.body}</MDXRenderer>
          </Col>
        </Row>
        <Row>
          <Col>
            <DividingLine />
            <PrevNextUl>
              <li>
                {previous && (
                  <Link to={`blog${previous.fields.slug}`} rel="prev">
                    ← {previous.frontmatter.title}
                  </Link>
                )}
              </li>
              <li>
                {next && (
                  <Link to={`blog${next.fields.slug}`} rel="next">
                    {next.frontmatter.title} →
                  </Link>
                )}
              </li>
            </PrevNextUl>
          </Col>
        </Row>
      </Layout>
    )
  }
}

export default BlogPostTemplate

export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    mdx(fields: { slug: { eq: $slug } }) {
      id
      excerpt(pruneLength: 160)
      body
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
        description
      }
    }
  }
`

實際效果如下:

部落格主頁
部落格文章

本篇文章的程式碼的實作可以在github repo3-understanding-graphql找到

參考資源

#React #gatsby #graphql







你可能感興趣的文章

【Day 4】Docker 架構與底層實現

【Day 4】Docker 架構與底層實現

關於 React 小書:將 JSX 傳入函式,再把回傳的 JSX 放到頁面

關於 React 小書:將 JSX 傳入函式,再把回傳的 JSX 放到頁面

筆記、版本控制前言

筆記、版本控制前言






留言討論