文章目錄
- Gatsby初試啼聲
- 利用react-bootstrap打造React風格頁面
- 了解Gatsby中GraphQL如何運作
- Gatsby GraphQL讀取JSON
- 在Gatsby GraphQL中組合出完美資料
- Gatsby程序化產生頁面
- 上線個人頁面到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
要資料,之後就會直接傳到Blog
的data
屬性之中。我們就可以從data
提取資料出來使用。
如果還是覺得很陌生可以使用console.log
觀察data
。data
中的格式就是我們在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 repo中
3-understanding-graphql
找到