文章目錄
- Gatsby初試啼聲
- 利用react-bootstrap打造React風格頁面
- 了解Gatsby中GraphQL如何運作
- Gatsby GraphQL讀取JSON
- 在Gatsby GraphQL中組合出完美資料
- Gatsby程序化產生頁面
- 上線個人頁面到Netlify+啟動Netlify CMS
在上一篇文章中我們安裝了外掛,並且成功的讀取了我們存放在JSON中的資料。雖然GraphQL好用,可以用很好理解的query指定我們要讀取的資料格式,但是有時候回傳的資料還不是我們想要最完美的格式。到底是怎麼一回事呢?
製作成果展示頁面
我們還有一個成果展示頁面還沒有製作出來。如同上一篇文章,我們先新增一個新頁面work.js
。
src/pages/work.js
import React from "react";
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Layout from "../components/layout";
import SEO from "../components/seo";
import Portfolio from "../components/portfolio";
class WorkPage extends React.Component {
render() {
return (
<Layout>
<SEO
title="Work"
keywords={[`blog`, `Herbert Lin`, `javascript`, `react`, `gatsby`]}
/>
<Row>
<Col>
<h2 className="mb-4">Work</h2>
</Col>
</Row>
<Portfolio />
</Layout>
);
}
}
export default WorkPage;
這裡因為預計要跟首頁共用Component
,所以我就直接寫上匯入一個叫做Portfolio
的Component
。在製作Portfolio
的時候會加上顯示多少案例的限制,這樣在首頁就可以只顯示部份案例,不至於讓首頁拉太長。
在src/components
裡面新增portfolio.js
。
src/components/portfolio.js
import React from "react";
class Portfolio extends React.Component {
render() {
return (
<div></div>
);
}
}
export default Portfolio;
先空著,搞定資料格式後等一下再回來補上剩下的內容。
從GraphQL中調取圖片
關於成果展示的部份,我們也用讀取的方式來得到成果資料,這樣以後改JSON就可以更新資料了。先根據在上一篇文章Gatsby GraphQL讀取JSON學到的知識,我們可以新建另一個JSON檔存放關於成果的資料。
src/content/data/work.json
[
{
"title": "XChange",
"description": "The redesigned website for XChange which also serves the purpose of being a media package.",
"image": "xchange.png"
},
{
"title": "Stihl Taiwan",
"description": "The revamped brand website for Stihl Taiwan.",
"image": "stihl.png"
},
{
"title": "LinkMyGoods",
"description": "LinkMyGoods is a CRM system designed to smoothen out the trading process from quotation to shipping.",
"image": "linkmygoods.png"
}
]
以上的資料可以透過下面的query讀取:
query WorkQuery {
allWorkJson {
nodes {
description
image
title
}
}
}
加入了work.json
之後,我們可以發現在互動工具中又新增了兩個項目allWorkJson
與workJson
。這兩者的區別是allWorkJson
會回傳所有的node
,而workJson
只會回傳一個,所以如果我們只想要某個特定的案子,我們可以向workJson
傳入比較參數。
至於圖片,在Gatsby中有提供處理影像的外掛gatsby-plugin-sharp
以及gatsby-transformer-sharp
。配合讀取檔案的外掛gatsby-source-filesystem
我們就可以輕鬆的從GraphQL讀取圖片。
一個從GraphQL要圖片的query範例是:
query {
file(relativePath: { eq: "images/default.jpg" }) {
childImageSharp {
# Specify a fixed image and fragment.
# The default width is 400 pixels
fixed {
...GatsbyImageSharpFixed
}
}
}
}
不過在這裡我想要讀取所有的圖片,因為我的圖片名稱存在JSON裡面我不想要指定,但是相對的我得必須知道圖片名稱是什麼,這樣之後才能夠找到。還有圖片我也不想要固定大小的,我想讓他可以隨著元素大小調整,所以我把query改成下面的形式:
query ImageQuery {
allFile {
nodes {
name
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
再把GraphQL串接到Portfolio
中:
src/components/portfolio.js
import React from "react";
import { StaticQuery, graphql } from "gatsby";
class Portfolio extends React.Component {
render() {
return (
<StaticQuery
query={portfolioQuery}
render={data => {
const { portfolioData, images } = data;
return (
);
}}
/>
);
}
}
const portfolioQuery = graphql`
query WorkQuery {
portfolioData: allWorkJson {
nodes {
description
image
title
}
}
images: allFile {
nodes {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
`;
export default Portfolio;
簡單加上bootstrap風格卡片:
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Card from 'react-bootstrap/Card';
import Img from "gatsby-image";
class Portfolio extends React.Component {
render() {
const limit = this.props.limit | null;
return (
<StaticQuery
query={portfolioQuery}
render={data => {
const { images } = data;
const portfolioData = limit ? data.portfolioData.nodes.slice(0, limit) : data.portfolioData.nodes;
console.log('data', data);
return (
<Row>
{portfolioData.map(work => {
const imageIndex = images.nodes.findIndex(x => x.name === work.image.split('.')[0]);
return (
<Col md={4} className="work" key={work.title}>
<Card>
<Card.Img
variant="top"
as={Img}
fluid={images.nodes[imageIndex].childImageSharp.fluid}
/>
<Card.Body>
<Card.Title>{work.title}</Card.Title>
<Card.Text>{work.description}</Card.Text>
</Card.Body>
</Card>
</Col>
);
})}
</Row>
);
}}
/>
);
}
}
- 這裡使用
gatsby-image
所提供的元件,是對外掛gatsby-plugin-sharp
和響應式固定寬高圖片與全版圖片進行優化過的元件,僅供Gatsby內部圖片使用。 - 因為從GraphQL得到是所有圖片的資料,所以我們要先比對圖片名字,找到我們要的圖片的索引。
- 為了在首頁也可以使用這個元件,加上
limit
屬性。
最後不要忘記在src/content/assets
裡面加入相對應的圖片。
work.js
實際成果如下:
首頁中也加入Portfolio
,並傳入limit
屬性。在首頁我設的數量限制是6個,這個可以自行調整。
src/pages/index.js
import Portfolio from "../components/portfolio";
...
<Hero className="px-0">
<h1>Hi! I'm a person.</h1>
<p>
I am super duper.
<br />
I am looking at a personal page & blog example on github.
</p>
<Link to="/about/">
<Button variant="primary">Learn more about me</Button>
</Link>
</Hero>
<Row>
<Col>
<h2 className="mb-4">Recent work</h2>
</Col>
</Row>
<Portfolio limit={6} />
<Row className="mt-4">
<Col>
<Link to="/work/">
<Button variant="primary">View more</Button>
</Link>
</Col>
</Row>
...
首頁實際成果如下:
GraphQL更上一層樓
既然我們有辦法讀取JSON,也可以讀取圖片,都是從GraphQL要資料,那我們有沒有辦法把這兩個資料組合在一起丟出來,不要再找對應的圖片了?完全可以做到,但是我們要自己資料組合起來,打造出客製化的資料格式。
這個步驟如果沒有興趣的朋友,可以直接跳過。後面的程式碼還是會沿用這個步驟的結果。這裡只是更深入Gatsby GraphQL系統,做資料讀取的優化。
我們可以用Gatsby的sourceNodes
API製作客製化的資料。以下內容轉自這篇教學,裡面有更詳細的說明。
gatsby-node.js
const path = require('path');
// 匯入work.json
const work = require('./content/data/work.json');
// 存放圖片的相對路徑
const IMAGE_PATH = './content/assets/';
...
exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
work.forEach((project, index) => {
// 1. 讀取JSON資料
const {
title,
description,
image,
} = project;
const idTitle = title.split(' ')
.map(elem => elem.toLowerCase())
.join('-');
// 2. 客製化節點的架構
const node = {
title,
description,
image, // 這裡有問題
id: createNodeId(`work-${idTitle}`),
internal: {
type: 'WorkProject',
contentDigest: createContentDigest(project),
},
};
// 3. 建立節點
actions.createNode(node);
});
};
因為id
必須是獨特的,所以我在處理過標題後,把idTitle
插入id
中。到目前為止我們已經製作出客製化的節點了,但是裡面圖片的資料卻還是我們之前舊的格式。那應該如何製作圖片的節點呢?
exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
work.forEach((project, index) => {
const {
title,
description,
image,
} = project;
// 1. 製作圖片的節點必須有名稱、副檔名、以絕對路徑
const { name, ext } = path.parse(image);
const absolutePath = path.resolve(__dirname, IMAGE_PATH, image);
// 2. 製作影像處理外掛`sharp`可以認得的架構
const imageData = {
name,
ext,
absolutePath,
extension: ext.substring(1)
};
// 3. 包括上面的架構在內,製作節點的整體架構
const imageNode = {
...imageData,
id: createNodeId(`work-image-${name}-${index}`),
internal: {
type: 'WorkProjectImage',
contentDigest: createContentDigest(imageData),
},
};
// 4. 建立影像節點。在建立的過程`sharp`會把childImageSharp放進去
actions.createNode(imageNode);
const idTitle = title.split(' ')
.map(elem => elem.toLowerCase())
.join('-');
const node = {
title,
description,
image: imageNode, // 5. 把新建立的圖片節點加到原本的節點中
id: createNodeId(`work-${idTitle}`),
internal: {
type: 'WorkProject',
contentDigest: createContentDigest(project),
},
};
actions.createNode(node);
});
};
之後我們把portfolio.js
改成新query並修改一下相對應欄位就大公告成了。
src/components/portfolio.js
class Portfolio extends React.Component {
render() {
const limit = this.props.limit | null;
return (
<StaticQuery
query={portfolioQuery}
render={data => {
const portfolioData = limit ? data.portfolioData.nodes.slice(0, limit) : data.portfolioData.nodes;
return (
<Row>
{portfolioData.map(work => {
return (
<Col className="work" key={work.title}>
<Card>
<Card.Img
variant="top"
as={Img}
fluid={work.image.childImageSharp.fluid}
/>
<Card.Body>
<Card.Title>{work.title}</Card.Title>
<Card.Text>{work.description}</Card.Text>
</Card.Body>
</Card>
</Col>
);
})}
</Row>
);
}}
/>
);
}
}
const portfolioQuery = graphql`
query WorkQuery {
portfolioData: allWorkProject {
nodes {
description
title
image {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`;
本篇文章的程式碼的實作可以在github repo中 5-creating-custom-data-with-image 找到