上集已經詳細講解如何寫Query/Mutation Statement及如何是Apollo GraphQL提供的Playground做測試。下一步就是請解如何是Client Side Application(以React Application為例)中使用Query/Mutation Statement。我會分開兩個版本示範,實作GraphQL Client (基本版)及GraphQL Client (應用版 - Apollo Client)。
大家請是VS Code下create左React App先。由於本節目的只是示範Client及Server之間用GraphQL溝通的基本原理,所以當中的Code會被簡化及無定義Data Type。以下Code只用作示範,敬請留意!
![]() | 左邊圖片是GraphQL React Client(基本版)的Project Files的樹狀結構。 由於只是示範性質,一切從簡,所以我只會加/更新以下files。 更新App.tsx - 顯示由GraphQL Server取得的資料 加入query.ts - 儲存query statement,例如,CommoditiesByCategory * 加入handler.ts - 負責傳送query/mutation statement給GraphQL Server並根據statement要求取得相關資料。 |
---|
首先,請準備要給GraphQL Server的query/mutation statement,所以請新增一個檔案query.ts是src folder下面。並export一個常數(const)。呢個常數是儲存左query/mutation statement備用。
//query.ts - 儲存所有query statements。今次用CommoditiesByCategory field做例子。
export const query = `
query($categoryName:CATEGORY) {
CommoditiesByCategory(category: $categoryName) {
name
category
unit
stockQuantity
}
}
`;
重點嚟la ! 重點嚟la ! 重點嚟la ! 重要的事講3次。 如何同GraphQL Server溝通呢?就是handler.ts處理溝通部分。
export type Query = {
variables: any
query: string
}
export const loadDataHandler = async (query:Query) => {
const result = await fetch(process.env.REACT_APP_BACKEND_SERVER_URL as string, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(query)
});
const {data} = await result.json();
return data;
}
是Tec ‧ 士多開發日記系列:第一篇:GraphQL簡介曾經提過GraphQL溝通方法都是以REST為基礎。GraphQL是用POST方法加入固定endpoint名,例如,POST /graphql。/graphql就是endpoint。最後,query/mutation statement及variables是會被放入Request Body當中。
要同GraphQL Server溝通,Client必須根據以下要求砌一個JSON物件,並放入Request Body。
{
variables: {
//query statement當中需要的參數(Arguments)
}
query:`query{....}` //query statement
}
//OR
{
variables: {
//mutation statement當中需要的參數(Arguments)
}
query:`mutation{....}` //mutation statement
}
例子如下:
{
variables:{
{categoryName:"DRINK"}
}
query: `query($categoryName:CATEGORY) {
CommoditiesByCategory(category: $categoryName) {
name
category
unit
stockQuantity
}
}`
}
{
variables:{
commodityInput:{
name:"Item#4",
unit:"CAN",
category:"DRINK",
stockQuantity:1
}
}
query: `
mutation($commodityInput:CommodityInput!){
addCommodity(newCommodity:$commodityInput){
...CommodityInfo
}
}
fragment CommodityInfo on Commodity{
name
category
unit
stockQuantity
}`
}
為方便示範及簡化Coding,用Query Type代表它。免得大家要是VS Code中scrolling,跳嚟跳去太亂跟唔到。 GraphQL Server會用以下格式返回的資料:
{
"data": {
//query/mutation statement要求的物件
}
}
所以, 是handler.ts最後直接抽出data的結果出嚟, 再俾返UI Component結果顯示。
//handler.ts
const {data} = await result.json();
是App.tsx當中問經handler.ts問GraphQL Server取得資料,並顯示結果。相信大家有一程度programming經驗,不作解釋。
//App.tsx
import './App.css';
import {useEffect,useState} from 'react';
import {query} from './query';
import {loadDataHandler} from './handler';
function App() {
const [data,setData] = useState<any>([]);
useEffect(()=>{
const variables = {categoryName:"DRINK"}; //俾Variable
const graphQLQuery = {variables,query}; //俾query statement
(async()=>{
//結果是{CommoditiesByCategory{....}
const {CommoditiesByCategory} = await loadDataHandler(graphQLQuery);
setData(CommoditiesByCategory);
})();
},[])
return (
<div>
{data.map((commodity:any)=>(
<>
<div>Commodity Name: {commodity.name}</div>
<div>Category: {commodity.category}</div>
<div>State in Store: {commodity.stockQuantity} {commodity.unit}</div>
</>
))}
</div>
);
}
export default App;
GraphQL Client(基本版) Source Code
使用Apollo Client的好處:
首先,大家請是VS Code下create左React App先。
![]() | 左邊圖片是GraphQL React Client(應用版 - Apollo Client)的Project Files的樹狀結構。 更新App.tsx - 載入Commodity List Component 加入query.ts - 儲存query statement,例如,CommoditiesByCategory 加入mutation.ts - 儲存mutation statement,例如,addCommodity 加入CommodityList.ts - 負責傳送query/mutation statement給GraphQL Server並根據statement要求取得相關資料並載入UI。 |
---|
下一步,就是安裝Apollo Client Library及GraphQL Library:
yarn add @apollo/client graphql
步驟 3 : 打開App.tsx, 由 @apollo/client 載入需要的工具:
import {ApolloClient,InMemoryCache,ApolloProvider} from '@apollo/client';
ApolloClient, 負責產生Apollo Client 物件。
InMemoryCache,由GraphQL Server取得的資料是Client存放的位置(快取,Cache)。InMemoryCache,存放是記憶體(memory)。
ApolloProvider,是
步驟 3 :建立Apollo Client 物件
const client = new ApolloClient({
uri:process.env.REACT_APP_BACKEND_SERVER_URL,//你的GraphQL Server URL
cache: new InMemoryCache()
});
步驟 4 : 傳送Apollo Client 物件給ApolloProvider。並放入Component中。
<ApolloProvider client={client}>
<div></div>
</ApolloProvider>
完整Source Code:
import React from 'react';
import {ApolloClient,InMemoryCache,ApolloProvider} from '@apollo/client';
const client = new ApolloClient({
uri:process.env.REACT_APP_BACKEND_SERVER_URL,
cache: new InMemoryCache(),
connectToDevTools:true
});
function App() {
return (
<ApolloProvider client={client}>
<div>To be implemented</div>
</ApolloProvider>
);
}
export default App;
步驟 5 : 建立query statement,並以export module形式交俾其他module使用。
//query.ts,儲存所有query statements。
import {gql} from '@apollo/client';
export const COMMODITIES_BY_CATEGORY_QUERY=gql`
query($categoryName:CATEGORY) {
CommoditiesByCategory(category: $categoryName) {
name
category
unit
stockQuantity
}
}`;
步驟 6 : 建立mutation statement,並以export module形式交俾其他module使用。
//mutation.ts,儲存所有mutation statements。
import {gql} from '@apollo/client';
export const ADD_COMMODITY = gql`
mutation($commodityInput:CommodityInput!){
addCommodity(newCommodity:$commodityInput){
...CommodityInfo
}
}
fragment CommodityInfo on Commodity{
name
category
unit
stockQuantity
}`;
步驟 7 : 建立一個Component, CommodityList.tsx,負責問GraphQL Server提取資料及顯示資料。
import React from 'react';
export function CommodityList(props:{categoryName:string}){
return <div></div>
}
步驟 8 : 由GraphQL Server取得售賣貨品清單,例如,飲品清單。
import React from 'react';
import {COMMODITIES_BY_CATEGORY_QUERY} from './query';
import {useQuery} from '@apollo/client';
export function CommodityList(props:{categoryName:string}){
const {data,loading,error} = useQuery(COMMODITIES_BY_CATEGORY_QUERY,{variables:props});
if(loading) return <p>Loading...</p>;
if(error) return <p>Error!</p>;
return <div></div>
}
useQuery,是一個React Hook,專門負責處理GraphQL query statement。當建立useQuery object的時候,useQuery object即時向GraphQL Server取得query statement要求的資料。有另一個React Hook不會即時向GraphQL Server取得query statement要求的資料。為免混亂, 我會開另一個side track section解釋。
const {data,loading,error} = useQuery(COMMODITIES_BY_CATEGORY_QUERY,{variables:props});
data,返回query statement要求的資料。
loading,是顯示目前是否仍然是載入狀態。
error,如果載入過程有錯誤,返回一個錯誤資訊。
useQuery(<query statement>,<variables>) //useQuery需要兩個參數
步驟 9 :加入新增貨品,並通知GraphQL Server。
import React from 'react';
import {COMMODITIES_BY_CATEGORY_QUERY} from './query';
import {useQuery} from '@apollo/client';
//新增部分 - 示範Only
import {ADD_COMMODITY} from './mutation';
import {useMutation} from '@apollo/client';
export function CommodityList(props:{categoryName:string}){
const {data,loading,error} = useQuery(COMMODITIES_BY_CATEGORY_QUERY,{variables:props});
if(loading) return <p>Loading...</p>;
if(error) return <p>Error!</p>;
//新增部分
const [newCommunityMutation] = useMutation(ADD_COMMODITY,{
refetchQueries:[{query:COMMODITIES_BY_CATEGORY_QUERY,variables:props}],
});
return <div></div>
}
先載入mutation statement。
import {ADD_COMMODITY} from './mutation';
建立一個Mutation Object負責執行該mutation statement。⚠️注意: 此Mutation Object不會即時執行。
const [newCommunityMutation] = useMutation(
ADD_COMMODITY,//mutation statement
{
/*
如果成功執行mutation,透過refetchQueries更新指定query的cache。是某D情況下唔駛用refetchQueries。
為免又話要跳嚟跳去太亂跟唔到,我會開另一個side track section解釋。
*/
refetchQueries:[
/* const {data,loading,error} =
useQuery(COMMODITIES_BY_CATEGORY_QUERY,{variables:props}); <-- 記唔記得佢。
*/
{query:COMMODITIES_BY_CATEGORY_QUERY,variables:props}
],
}
);
步驟 10 :建立Button,並執行Mutation Object。
import React from 'react';
import {COMMODITIES_BY_CATEGORY_QUERY} from './query';
import {ADD_COMMODITY} from './mutation';
import {useQuery,useMutation} from '@apollo/client';
export function CommodityList(props:{categoryName:string}){
const {data,loading,error} = useQuery(COMMODITIES_BY_CATEGORY_QUERY,{variables:props});
const [newCommunityMutation] = useMutation(ADD_COMMODITY,{
refetchQueries:[{query:COMMODITIES_BY_CATEGORY_QUERY,variables:props}],
});
if(loading) return <p>Loading...</p>;
if(error) return <p>Error!</p>;
return (<>
<button onClick={()=>{
newCommunityMutation({
variables: {
commodityInput: {
name: "Coffee",
unit: "CAN",
category: "DRINK",
stockQuantity: 1
}
}
})
}}>Add</button>
</>)
}
當執行Mutation Object時提供需要的變數。
//newCommunityMutation({ variables:{...} })
newCommunityMutation({
variables: {
commodityInput: {
name: "Coffee",
unit: "CAN",
category: "DRINK",
stockQuantity: 1
}
}
});
完整Source Code(連顯示售賣貨品)。
import React from 'react';
import {COMMODITIES_BY_CATEGORY_QUERY} from './query';
import {ADD_COMMODITY} from './mutation';
import {useQuery,useMutation} from '@apollo/client';
export function CommodityList(props:{categoryName:string}){
const {data,loading,error} = useQuery(COMMODITIES_BY_CATEGORY_QUERY,{variables:props});
const [newCommunityMutation] = useMutation(ADD_COMMODITY,{
refetchQueries:[{query:COMMODITIES_BY_CATEGORY_QUERY,variables:props}],
});
if(loading) return <p>Loading...</p>;
if(error) return <p>Error!</p>;
return (<>
{data.CommoditiesByCategory.map((commodity:any,index:number)=>(<div key={index}>
<span>Name : {commodity.name}</span>{' '},
<span>Category : {commodity.category}</span>
<div>Store State : {commodity.stockQuantity} {commodity.unit}</div>
</div>))}
<button onClick={()=>{
newCommunityMutation({
variables: {
commodityInput: {
name: "Coffee",
unit: "CAN",
category: "DRINK",
stockQuantity: 1
}
}
})
}}>Add</button>
</>)
}
Apollo Client提供另一個React Hook,useLazyQuery達成目的。
import React from 'react';
import {COMMODITIES_BY_CATEGORY_QUERY} from './query';
import {ADD_COMMODITY} from './mutation';
import {useQuery,useMutation,useLazyQuery} from '@apollo/client';
export function CommodityList(props:{categoryName:string}){
const [query,{called,loading,data}] = useLazyQuery(COMMODITIES_BY_CATEGORY_QUERY,{variables:props}); //<-- "useQuery" is changed to "useLazyQuery"
const [newCommunityMutation] = useMutation(ADD_COMMODITY,{
refetchQueries:[{query:COMMODITIES_BY_CATEGORY_QUERY,variables:props}],
});
if (loading) return <p>Loading...</p>;
if (called && loading) return <p>Loading ...</p>
if (!called){
return <button onClick={()=>query()}>Load Data</button>;
}
return (<>
{data.CommoditiesByCategory.map((commodity:any,index:number)=>(<div key={index}>
<span>Name : {commodity.name}</span>{' '},
<span>Category : {commodity.category}</span>
<div>Store State : {commodity.stockQuantity} {commodity.unit}</div>
</div>))}
<button onClick={()=>{
newCommunityMutation({
variables: {
commodityInput: {
name: "Coffee",
unit: "CAN",
category: "DRINK",
stockQuantity: 1
}
}
})
}}>Add</button>
</>)
}
const [query,{called,loading,data}] = useLazyQuery(
COMMODITIES_BY_CATEGORY_QUERY,
{variables:props}
);
query, 即將被執行的query function。
called,顯示目前query function執行左未。執行後會從新 render component。
loading,是顯示目前是否仍然是載入狀態。
data,返回query statement要求的資料。
首先是query.ts中的CommoditiesByCategory加入id field。
export const COMMODITIES_BY_CATEGORY_QUERY=gql`
query($categoryName:CATEGORY) {
CommoditiesByCategory(category: $categoryName) {
id
name
category
unit
stockQuantity
}
}
`;
步驟 2 :是mutation.ts加入以下mutation statement。
export const UPDATE_COMMODITY = gql`
mutation($id:ID!,$updateCommodity:CommodityInput!){
updateCommodity(id:$id,updateCommodity:$updateCommodity){
id
name
category
unit
stockQuantity
}
}
`;
步驟 3 :是CommodityList.tsx中建立UPDATE_COMMODITY useMutation Object。
const [updateCommunityMutation] = useMutation(UPDATE_COMMODITY);
步驟 4 :建立Button執行UPDATE_COMMODITY useMutation Object。
//....
return (<>
{data.CommoditiesByCategory.map((commodity:any,index:number)=>(<div key={index}>
....
<button onClick={
()=>{
updateCommunityMutation({ //更新對應Community做以下資料
variables:{
id:commodity.id,
updateCommodity:{
name: "Drink",
unit: "CAN",
category: "DRINK",
stockQuantity: 1
}
}
})
}
}>Update</button>
</div>))}
//....
</>)
大家打開React Application按Update,react會自動render component(就算無加refetchQueries@useMutation)。因為CommoditiesByCategory@useQuery當中的Community Object(必須包括id)被cache低。當UPDATE_COMMODITY@useMutation被執行並返回更新左的Community Object(必須包括id), Apollo Client 發現呢個對應Community Object有更新,因為Apollo Client check到cache中的id 同 更新左的Community Object的id是一樣。
Apollo Client會自動更新cache並render component。
GraphQL Client (應用版 - Apollo Client) Source Code
大家應該對GraphQL Server同Client設計有基本概念。我會暫時離開一下GraphQ呢個topic, 俾大家沉澱下先。下一編blog講D輕鬆的topic。敬請留意。
https://www.apollographql.com/docs/react/data/mutations/ JavaScript Everywhere: Building Cross-Platform Applications with Graphql, React, React Native, and Electron