透過 GraphQL 在 Gatsby 中做資料撈取
October 04, 2020
本文會提到
- Gatsby 的資料撈取機制
- 舉例說明
- 一個實際例子
- 從 第三方 API 作為資料來源產生畫面
在之前介紹 Gatsby 的文章中,
我跳過了在 Gatsby 中使用 GraphQL,
在近期修改了 Blog 的樣式,
也補齊了一些之前被我跳過的知識,
這篇文章會記錄我學習 Gatsby 中使用 GraphQL 的一些筆記。
GraphQL 查詢互動介面
GraphQL 被整合在 Gatsby 之中作為 Data layer,
在 Gatsby Project 資料夾下執行 gatsby develop 或 gatsby build 後可以在
http://localhost:8000/___graphql
以互動網頁的方式查詢 GraphQL。
從 Gatsby Hello-World Project 起手
在 terminal 下以下指令新增專案
gatsby new hello-world https://github.com/gatsbyjs/gatsby-starter-hello-world
從左側點選樹狀或是直接輸入底下查詢
query MyQuery {
site {
siteMetadata {
title
description
}
}
}
會拿到以下結果
{
"data": {
"site": {
"siteMetadata": {
"title": null,
"description": null
}
}
},
"extensions": {}
}
這個 Query 是站台的 metadata,
要更改 gatsby-config.js 增加設定,
參考以下程式碼
module.exports = {
siteMetadata: {
title: `some tile`,
description: `some description.`,
},
/* Your site config here */
plugins: [],
}
重新 Query 就可以拿到以下結果
{
"data": {
"site": {
"siteMetadata": {
"title": "some tile",
"description": "some description."
}
}
},
"extensions": {}
}
如果想要在 Gatsby 中使用這組資料,
則必須使用 graphql() 取得。
參考以下程式碼
import React from "react"
import { graphql } from "gatsby"
export default function Home(data) {
return <div>{data.data.site.siteMetadata.title}</div>
}
export const pageQuery = graphql`
query {
site {
siteMetadata {
title
description
}
}
}
`
就可以在畫面上看到剛剛設定的 some title。
其實傳進來的參數是一個物件,
所以我們可以改寫成
export default function Home({data}) {
return <div>{data.site.siteMetadata.title}</div>
}
上面這種 Query 在 Gatsby 被稱為 Page Query。
其中 graphql 是一種 tag function,
這是一種 JavaScript 的函數宣告,
細節原理可以參考 Gatsby 官方網站。
還有一種查詢方式稱為 StaticQuery,
以上面的範例來修改的話會變成
import React from "react"
import { StaticQuery, graphql } from "gatsby"
export default function Home() {
return (
<StaticQuery
query={graphql`
query {
site {
siteMetadata {
title
description
}
}
}
`}
render={data => (
<div>{data.site.siteMetadata.title}</div>
)}
/>
)
}
這部分的文件可以參考 Gatsby 官方網站。
實際應用的例子
如果有使用 gatsby blog starter 的話,
就會知道頁面是放在 content/blog/ 底下,
實際上這是透過 gatsby-source-filesystem 這個模組定義了檔案的位置,
再透過 gatsby-transformer-remark 這個模組將 MD 檔案編譯成 html。
但是在 starter 的設計中,
會無法增加文章以外的頁面。
這部分要改動的話,
就要先了解 Gatsby 新增頁面的原理。
Gatsby 是怎麼增加頁面的
在 gatsby-node.js 中有提供許多建立頁面的 API 讓開發者可以產生出頁面。
createPages 這個 API 會負責建立頁面。
這個 API 會在資料都初始化完成後被呼叫,
而在 Gatsby Blog Starter Project 中,
createPages API 會與 GraphQL data layer 交互產生出頁面。
在 Gatsby Blog Starter Project 中要新增一篇文章,
必須要在 content/blog 資料夾內產生文章的資料夾。
如果想要增加一頁 about 頁的時候,
我們可以新增一個資料夾來放頁面,
比方像是 content/page,
並在 gatsby-config.js 內使用 gatsby-source-filesystem 模組讓 Gatsby 認識 page 資料夾,
才能在編譯的時候讓 gatsby-transformer-remark 編譯到資料夾內的 md 檔。
參考以下設定
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content/page`,
name: `assets`,
},
}
設定完且新增完 about 後,
就可以在新產生的靜態網頁中找到 /about/ 頁,
但這時候 about 頁也會在部落格文章列表中。
調整 新增頁面 GraphQL 以及查詢文章列表 GraphQL
在 Gatsby Blog Starter 的 gatsby-node.js 中,
可以看到 createPage 透過 query allMarkdownRemark 產生出文章列表的資料來產生文章相關檔案,
並在 index.js 內產生文章列表時透過 query allMarkdownRemark 產生文章列表畫面,
所以要做的調整有
- index.js 內的 query 增加 filter,
- 新增 about 頁要用的 templete
- gatsby-node.js 中的 createPage 增加新增 about 頁的相關邏輯
- 預設的 Gatsby Blog Starter 有使用 gatsby-plugin-feed 模組,這是一個產生 RSS 的模組,要調整成指定文章的內容才產生 RSS Feed。
參考以下程式碼
query 增加 filter
`
{
allMarkdownRemark(
sort: {fields: [frontmatter___date], order: DESC},
filter: {fileAbsolutePath: {regex: "/content/blog/"}}
) {
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
}
`
新增 templete 以及 CreatePage 可以參考 Starter 內現有 blog-post.js 產生方式調整。
參考以下程式碼
exports.createPages = async ({ graphql, actions }) => {
await Promise.all([
onCreateBlogPostPage(graphql,actions),
onCreatePagePostPage(graphql,actions)])
}
function onCreatePagePostPage(graphql, actions)
{
const pagePostResultTask = graphql(
`
{
allMarkdownRemark(
sort: {fields: [frontmatter___date], order: DESC},
filter: {fileAbsolutePath: {regex: "/content/page/"}}
) {
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
}
`
).then(function (pagePostResult){
const { createPage } = actions
const pagePost = path.resolve(`./src/templates/page-post.js`)
if (pagePostResult.errors) {
throw pagePostResult.errors
}
// Create page posts pages.
const posts = pagePostResult.data.allMarkdownRemark.edges
posts.forEach((post) => {
createPage({
path: post.node.fields.slug,
component: pagePost,
context: {
slug: post.node.fields.slug,
},
})
})
})
return pagePostResultTask;
}
同場加映-在 Gatsby 中使用 第三方 API 當作來源
從上面的範例,
我們實作了 Gatsby 在 gatsby-node.js 透過 GraphQL 語法取得資料在編譯階段產生畫面,
除了從 Gatsby 專案內取得資料,
我們也可以從外部 API 取得資料,
官方做了一個範例舉例如何在編譯階段使用外部 API 產生畫面。
大致原理是在 createPage 時呼叫 Web API,
如果要使用 GraphQL Server 的資料當成產生畫面來源可以使用 gatsby-source-graphql 這個官方模組。
透過這些設計就可以使用 WordPress 等 CMS 的資料當成資料來源在編譯階段產生畫面。
在執行階段使用 第三方 API 當作來源
從上面資料可以知道 Gatsby 提供以第三方 API 當作來源在編譯階段取得資料後產生畫面。
如果是要一般使用者看到網頁後(執行階段)才呼叫 API 渲染畫面,
可以考慮用 React Component 內建的 componentDidMount() 搭配 setState 來實作。
首先新增一個 time-server.js 並執行
node time-server.js
參考以下程式碼
var http = require('http');
var server = http.createServer(function (req, res) {
res.writeHead(200,{
'Content-Type':'application/json',
"Access-Control-Allow-Origin": "*"
});
res.write('{"time":"'+ new Date() +'"}');
res.end();
});
server.listen(5000);
console.log('Node.js web server at port 5000 is running..')
增加 src/components/timepage.js
import React, { Component } from 'react';
class TimePage extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
loading: true,
items: []
};
}
componentDidMount() {
fetch("http://localhost:5000/")
.then(res => res.json())
.then(
(result) => {
this.setState({
loading: false,
time: result.time
});
},
(error) => {
this.setState({
loading: false,
error
});
}
)
}
render() {
const { error, loading, time } = this.state;
console.log(time,this.state);
if (error) {
return <div>Error: {error.message}</div>;
} else if (loading) {
return <div>Loading...</div>;
} else {
return (
<div>{time}</div>
);
}
}
}
export default TimePage
componentdidmount() 這個方法會在 DOM 被插到 DOM Tree 後被呼叫,
文件可以參考 React 官方網站。
修改 index.js
import React from "react"
import TimePage from "../components/timepage"
export default function Home() {
return <TimePage></TimePage>
}
將 Gatsby 跑起來以後畫面上就能出現從 API 取回的最新時間了。
ref:
- https://www.gatsbyjs.com/docs/graphql-concepts/
- https://www.gatsbyjs.com/docs/page-query/
- https://www.gatsbyjs.com/docs/static-query/
- https://www.gatsbyjs.com/docs/graphql/
- https://www.gatsbyjs.com/docs/node-apis/
- https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-transformer-remark
- https://www.gatsbyjs.com/docs/graphql-reference/
- https://reactjs.org/docs/react-component.html#componentdidmount