Gatsby GraphQL讀取JSON


Gatsby GraphQL讀取JSON

文章目錄

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

有了對GraphQL初步的認識後,我們可以嘗試來加入其他資料來源讓GraphQL讀取。
利用react-bootstrap打造React風格頁面裡面我們製作了social media相關的元件,但是不夠好。主要的問題有兩個:

  1. 擴充性不好。如果還要增加其他聯絡方式,我們只能複製貼上。裡面的資訊也是死的。
  2. 沒辦法在其他地方使用。我想要在「關於我」的頁面裡面也放入social media讓畫面更加完整。
    接下來我會把social media做成一個Component並串接GraphQL。

把social media做成一個Component

在components內新增一個叫做socialmedia.js的檔案,接著把之前layout.js裡面的程式碼搬過來。
src/components/layout.js

import SocialMedia from "./socialmedia";
...
        <FooterWrapper>
          <Container className="pt-6">
            <Row>
              <Col md={8} className="d-flex flex-column justify-content-center">
                <h3>Herbert Lin</h3>
                <EmailLink href="mailto:herbert.lin.7@gmail.com">herbert.lin.7@gmail.com</EmailLink>
              </Col>
              <Col md={4} className="d-flex align-items-center justify-content-end">
                <SocialMedia />
              </Col>
            </Row>
          </Container>
        </FooterWrapper>
...

src/components/socialmedia.js

import React from "react";
import styled from "styled-components";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
import { fab } from '@fortawesome/free-brands-svg-icons';

const SocialMedia = styled.div`
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: flex-end;
  align-content: stretch;
  text-align: right;
`;

const SocialMediaUl = styled.ul`
  list-style: none;
  display: inline-block;
  margin: 0;
  padding: 0;
`;

const SocialMediaLi = styled.li`
  display: inline-block;
  margin: 0 18px 0 18px;
  padding: 0;
`;

const SocialMediaLink = styled.a`
  text-decoration: none;
  background-color: transparent;
  color: #f1f1f1;
  &:hover, &:visited, &:active {
    color: #f1f1f1;
  }
`;

class socialmedia extends React.Component {
  render() {
    library.add(fab);

    return (
      <SocialMedia className="social-media">
        <SocialMediaUl>
          <SocialMediaLi>
            <SocialMediaLink href="https://github.com/XcrossD">
              <FontAwesomeIcon icon={['fab', 'github']} />
            </SocialMediaLink>
          </SocialMediaLi>
          <SocialMediaLi>
            <SocialMediaLink href="https://www.facebook.com/herbert.lin">
              <FontAwesomeIcon icon={['fab', 'facebook']} />
            </SocialMediaLink>
          </SocialMediaLi>
          <SocialMediaLi>
            <SocialMediaLink href="https://www.instagram.com/gummypearin/">
              <FontAwesomeIcon icon={['fab', 'instagram']} />
            </SocialMediaLink>
          </SocialMediaLi>
          <SocialMediaLi>
            <SocialMediaLink href="https://www.linkedin.com/in/herbert-lin-28240446/">
              <FontAwesomeIcon icon={['fab', 'linkedin']} />
            </SocialMediaLink>
          </SocialMediaLi>
        </SocialMediaUl>
      </SocialMedia>
    );
  }
}

export default socialmedia;

規劃資料格式

了解GraphQL的運作後,可以先規劃我們要的資料格式。需要的資料應該是一個javascript物件,裡面有各個社群媒體的ID,例如:

{
  "github": "XcrossD",
  "facebook": "herbert.lin",
  "instagram": "gummypearin",
  "linkedin": "herbert-lin-28240446"
}

另外也需要一個儲存網址前段的地方,這樣我們更改ID就可以了。

{
  "github": "https://github.com/",
  "facebook": "https://www.facebook.com/",
  "instagram": "https://www.instagram.com/",
  "linkedin": "https://www.linkedin.com/in/"
}

有這樣的規劃之後我們先反過來看看之前沒有刪掉的src/components/bio.js

/**
 * Bio component that queries for data
 * with Gatsby's StaticQuery component
 *
 * See: https://www.gatsbyjs.org/docs/static-query/
 */

import React from "react"
import { StaticQuery, graphql } from "gatsby"
import Image from "gatsby-image"
import styled from "styled-components"

import { rhythm } from "../utils/typography"

function Bio() {
  return (
    <StaticQuery
      query={bioQuery}
      render={data => {
        const { author, social } = data.site.siteMetadata
        return (
          <Container>
            <Image
              fixed={data.avatar.childImageSharp.fixed}
              alt={author}
              style={{
                marginRight: rhythm(1 / 2),
                marginBottom: 0,
                minWidth: 50,
                borderRadius: `100%`,
              }}
              imgStyle={{
                borderRadius: `50%`,
              }}
            />
            <p>
              Written by <strong>{author}</strong> who lives and works in San
              Francisco building useful things.
              {` `}
              <a href={`https://twitter.com/${social.twitter}`}>
                You should follow him on Twitter
              </a>
            </p>
          </Container>
        )
      }}
    />
  )
}

const bioQuery = graphql`
  query BioQuery {
    avatar: file(absolutePath: { regex: "/profile-pic.jpg/" }) {
      childImageSharp {
        fixed(width: 50, height: 50) {
          ...GatsbyImageSharpFixed
        }
      }
    }
    site {
      siteMetadata {
        author
        social {
          twitter
        }
      }
    }
  }
`

const Container = styled.div`
  display: flex;
`

export default Bio
  1. 這裡串接GraphQL的方式是StaticQuery,這是Gatsby在v2中加入的功能。關於page queryStaticQuery的差別可以參考官方文檔以及這個討論串。因為我們不需要在query內傳入變數,所以socialmedia內會使用StaticQuery
  2. 有發現要了一個資料data.site.siteMetadata.social裡面有twitter嗎?我們也可以在同樣的位置放其他社群媒體的ID。

把社群媒體的ID加入GraphQL
gatsby-config.js

siteMetadata: {
  // edit below
  title: `Gatsby Starter Personal Blog`,
  author: `Kyle Matthews`,
  description: `A starter personal blog with styled components, dark mode, and Netlify CMS.`,
  siteUrl: `https://gatsby-starter-blog-demo.netlify.com/`,
  social: {
    twitter: ``, // 可留可不留,如果是空的GraphQL不會回傳
    github: `XcrossD`,
    facebook: `herbert.lin`,
    instagram: `gummypearin`,
    linkedin: `herbert-lin-28240446`
  },
},

這樣就解決了一部分的資料了,我們還需要獲得網址前段的資料。首先我們先在src/data建立socialurl.json,把上面的JSON放到裡面。
接著還是一樣在gatsby-config.js裡面,我們需要讓資料夾的位置被讀取,並安裝一個可以讓Gatsby讀取JSON的外掛。

npm install --save gatsby-transformer-json
...
  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`,
    // 新增新的外掛
    `gatsby-transformer-json`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `data`,
        path: `${__dirname}/content/data`
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/blog`,
        name: `blog`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/assets`,
        name: `assets`,
      },
    },

更改好後重新執行gatsby develop更新。

這時候我們在GraphQL互動工具()會發現多了allDataJsondataJson欄位,這是我們新增的gatsby-transforer-json外掛所產生的。
執行以下query後,就可以得到我們在JSON裡面放的資料了。

query MyQuery {
  dataJson {
    facebook
    github
    instagram
    linkedin
  }
}

// 回傳資料
{
  "data": {
    "dataJson": {
      "facebook": "https://www.facebook.com/",
      "github": "https://github.com/",
      "instagram": "https://www.instagram.com/",
      "linkedin": "https://www.linkedin.com/in/"
    }
  }
}

串接GraphQL

參考src/components/bio.js檔案裡面的StaticQuery部份內容,我們可以對socialmedia.js進行修改。
src/components/socialmedia.js

import { StaticQuery, graphql } from "gatsby";

class socialmedia extends React.Component {
  render() {
    library.add(fab);

    return (
      <StaticQuery 
        query={socialMediaQuery}
        render={data => {

        }}
      />
    );
  }
}

const socialMediaQuery = graphql`
  query SocialMediaQuery {
    site {
      siteMetadata {
        social {
          facebook
          github
          instagram
          linkedin
        }
      }
    }
    socialUrl: dataJson {
      facebook
      github
      instagram
      linkedin
    }
  }
`

有了資料之後,為了動態產生圖示我在這裡使用Array.map(),根據不同的社群媒體置入相對應的資訊。

      <StaticQuery 
        query={socialMediaQuery}
        render={data => {
          const { social } = data.site.siteMetadata;
          const { socialUrl } = data;
          return (
            <SocialMedia className="social-media">
              <SocialMediaUl>
                {Object.keys(social).map(site => (
                  <SocialMediaLi key={`social-media-${site}`}>
                    <SocialMediaLink href={`${socialUrl[site]}${social[site]}`}>
                      <FontAwesomeIcon icon={['fab', site]} size="lg" />
                    </SocialMediaLink>
                  </SocialMediaLi>
                ))}
              </SocialMediaUl>
            </SocialMedia>
          );
        }}
      />

如此一來,social media就變成一個Component,而且有擴充性了。

製作介紹頁

簡單的製作一下一個更詳細的介紹頁。新增一個檔案about.jssrc/pages
src/pages/about.js

import React from "react";
import styled from "styled-components";
import Button from 'react-bootstrap/Button';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';

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

const ActionTitle = styled.h4`
  margin-top: 18px;
  padding-bottom: 9px;
`;

class AboutPage extends React.Component {
  render() {
    return (
      <Layout>
        <SEO
          title="About"
          keywords={[`blog`, `Herbert Lin`, `javascript`, `react`, `gatsby`]}
        />
        <Row>
          <Col className="profile">
            <h1>I'm Herbert Lin, a programmer who likes to dabble in different stuff.</h1>
            <ActionTitle>Email me</ActionTitle>
            <a href="mailto:herbert.lin.7@gmail.com">herbert.lin.7@gmail.com</a>
            <ActionTitle>Follow me</ActionTitle>
            <SocialMedia />
          </Col>
          <Col>
            <p>Previously a front end developer at <a href="https://celinius.com.tw">Celinius</a>, I'm now looking to transition to a field related to data, as I believe that data is the furture.</p>
            <p>I'm also working at non-profit organization, <a href="https://www.xchange.com.tw">XChange</a>, whose purpose is to expand the influence of Taiwanese talents around the globe.</p>
            <p>Outside of being a programmer, I'm an avid language learner, an anime lover, and a gamer.</p>
            <a target="_blank" rel="noopener noreferrer" href="https://www.dropbox.com/s/likvy8592uf3orm/herbert_lin_resume.pdf?dl=0">
              <Button variant="secondary">Resume</Button>
            </a>
          </Col>
        </Row>
      </Layout>
    )
  }
}

export default AboutPage;

除了介紹篇幅短了點,其他沒什麼大問題。但是我們發現因為SocialMedia之前是做在黑色背景的,所以圖示是白色的。在這裡我們可以考慮使用styled components的方式調整,但是既然做成一個Component了我們乾脆讓他可以透過屬性辨別現在的背景顏色。
src/components/socialmedia.js

const SocialMediaLinkWhite = styled.a`
  text-decoration: none;
  background-color: transparent;
  color: #f1f1f1;
  &:hover, &:visited, &:active {
    color: #f1f1f1;
  }
`;

const SocialMediaLink = styled.a`
  text-decoration: none;
  background-color: transparent;
  color: #171717;
  &:hover, &:visited, &:active {
    color: #171717;
  }
`;

class socialmedia extends React.Component {
  render() {
    const { className } = this.props;
    const darkBackground = this.props.darkBackground | false;
    library.add(fab);

    return (
      <StaticQuery 
        query={socialMediaQuery}
        render={data => {
          const { social } = data.site.siteMetadata;
          const { socialUrl } = data;
          return (
            <SocialMedia className={`social-media ${className}`}>
              <SocialMediaUl>
                {Object.keys(social).map(site => (
                  <SocialMediaLi key={`social-media-${site}`}>
                    {darkBackground ? (
                      <SocialMediaLinkWhite href={`${socialUrl[site]}${social[site]}`}>
                        <FontAwesomeIcon icon={['fab', site]} size="lg" />
                      </SocialMediaLinkWhite>
                    ) : (
                      <SocialMediaLink href={`${socialUrl[site]}${social[site]}`}>
                        <FontAwesomeIcon icon={['fab', site]} size="lg" />
                      </SocialMediaLink>
                    )}
                  </SocialMediaLi>
                ))}
              </SocialMediaUl>
            </SocialMedia>
          );
        }}
      />
    );
  }
}

除了darkBackground屬性外,為了在外頭加上bootstrap class也把className屬性傳入。
因為在這裡使用了javascript OR所以反而要去需要黑色背景的地方傳入屬性。
src/components/layout.js

...
                <SocialMedia darkBackground={true} />
...

最後再調整一下SocialMedia與email之間的距離。
src/pages/about.js

...
            <SocialMedia className="mt-3" />
...

實際效果如下:

本篇文章的程式碼的實作可以在github repo中 4-reading-json-in-graphql 找到

參考資源

#React #gatsby #graphql #JSON #refactor
現在很多網站都不需要複雜的結構,使用靜態網站呈現就可以了。可以產生靜態網站的框架有很多,這次介紹使用React系列的Gatsby來快速製作一個個人頁面。






Related Posts

[ 前端工具 ] - gulp, Babel, SCSS, uglyfy

[ 前端工具 ] - gulp, Babel, SCSS, uglyfy

跨領域自學程式設計常見問題解析 FAQ

跨領域自學程式設計常見問題解析 FAQ

[TensorFlow Certification Day2] 課程規劃與安排時間

[TensorFlow Certification Day2] 課程規劃與安排時間



Comments