跳至主要内容

02 如何在 wordpress 使用 GraphQL

當我們打算在 WordPress 中利用 GraphQL API 進行前後端資料串接時,WP GraphQL 插件是個不錯的選擇,它可以為我們生成 API 端點以及方便地查詢和管理後端資料。

安裝 WP GraphQL 與基本配置

1. 安裝 WP GraphQL 插件:

  • 直接在 WordPress 的插件目錄中搜尋「WP GraphQL」,安裝後啟動即可。

2. 設定 GraphQL 端點:

  • 安裝好 WP GraphQL 之後,它會自動為你的網站創建一個 GraphQL 端點。
  • 在「GraphQL」>「Settings」>「GraphQL Endpoint」中,你會看到 API 端點的網址。這個地址就是前端通過如 Apollo Client 等 GraphQL 套件來查詢和獲取後端資料的接口。
提示

直接用瀏覽器點開 API 端點網址查看,如果遇到「not found」錯誤,則需要將端點地址改為 index.php?graphql(預設通常為 graphql)。

設置正確後,瀏覽器打開該網址應該顯示出 GraphQL 查詢界面,表示 API 端點設定成功。此時,前端網頁將使用 https://<your-site>/index.php?graphql 進行資料串接。成功的畫面如下圖所示

3. 查詢和變更操作:

GraphQL 主要有兩種操作:查詢(Query)用於獲取數據,如文章和用戶信息;變更(Mutation)則用於更新或修改數據,例如發布文章或更新資料。

4. 前端請求開發:

前端開發中可以利用多種 GraphQL 客戶端,例如 Apollo Client,進行查詢和變更操作。

5. 安全性與權限設置:

出於安全考慮,WP GraphQL 提供了細致的過濾和權限檢查機制,允許根據不同操作設定合適的權限。

6. 測試你的 GraphQL 查詢:

WP GraphQL 隨附的 GraphiQL 工具是一個實用的瀏覽器界面,可以在此編寫和測試你的 GraphQL 查詢並即時查看結果。

WP graphQL 基本查詢介紹-以頁面查詢為例

  1. 假設我現在要 query 到「頁面」底下的 "Privacy Policy" 頁面,

  2. 點開『快速編輯』,查看『代稱』

  3. 開啟 GraphiQL IDE,輸入以下 query

    INPUT

    {
    pages(where:{name:"privacy-policy"}){#這裡填寫你要查詢的頁面代稱
    nodes{
    title
    }
    }
    }

    OUTPUT

    {
    "data": {
    "pages": {
    "nodes": [
    {
    "title": "Privacy Policy"
    }
    ]
    }
    },
    "extensions": {
    "debug": [
    {
    "type": "DEBUG_LOGS_INACTIVE",
    "message": "GraphQL Debug logging is not active. To see debug logs, GRAPHQL_DEBUG must be enabled."
    }
    ]
    }
    }
  4. 前端頁面串接後台資料,以 Next.js + Apollo Client 為例

    • Apollo Client 基本配置

      const defaultOptions: DefaultOptions = {
      watchQuery: {
      fetchPolicy: "no-cache",
      errorPolicy: "ignore",
      },
      query: {
      fetchPolicy: "no-cache",
      errorPolicy: "all",
      },
      };

      const uri = "https://你的 wp graphQL api 端點 URL";

      const client = new ApolloClient({
      uri: uri,
      cache: new InMemoryCache(),
      defaultOptions: defaultOptions,
      });
    • Next.js-以 privacy 頁面 為例

      export const getStaticProps: GetStaticProps = async () => {
      const { data } = await client.query<Privacy>({
      query: gql(`{
      pages(where:{name:"privacy-policy"}){
      nodes{
      content
      }
      }
      }`),
      });

      return {
      props: { data },
      };
      };

      function privacy(props: { data: IPrivacy }) {
      console.log(props);
      return <div>privacy</div>;
      }

      export default privacy;
      如果需要 ts 型別參考,可以點開這邊
        export interface IPrivacy {
      pages: Pages;
      }

      export interface Pages {
      nodes: Node[];
      }

      export interface Node {
      content: string;
      }
  5. 成功串接到後台資料

Bonus:如何將 HTML 字串轉換到頁面上-dangerouslySetInnerHTML

我們成功拿到了 wordpress 後台頁面的 content 資料,但是我們不可能直接將 content 貼在瀏覽器上,因為那是 html 字串。這時,我們要做的是使用 dangerouslySetInnerHTML 來轉換 html 字串,同時借助 dompurify 套件的解析,以便防止 XSS 攻擊

// Privacy 組件將從 props 接收資料並渲染。
function Privacy(props: { data: IPrivacy }) {
// 使用 useState 來存儲清潔後的 HTML 字串。
const [cleanText, setCleanText] = useState<string>();

// 使用 useEffect 進行組件的副作用操作。
useEffect(() => {
// 動態導入 dompurify 套件。
import('dompurify').then((DOMPurifyModule) => {
// 獲取 DOMPurify 物件的默認導出。
const DOMPurify = DOMPurifyModule.default;

// 使用 DOMPurify 清潔 HTML 字串並儲存到狀態中。
setCleanText(DOMPurify.sanitize(props.data.pages.nodes[0].content));
});
}, [props.data.pages.nodes]); // 添加依賴項目以避免不必要的重新渲染。

// 渲染組件。
return (
<>
<h1>下方是經過處理後的乾淨 inner HTML</h1>
{/* 使用 dangerouslySetInnerHTML 属性來渲染清潔後的 HTML。 */}
{cleanText && <div dangerouslySetInnerHTML={{ __html: cleanText }} />}
</>
);
}

export default Privacy;
提示

由於我們使用 Next.js 伺服器框架作為範例,加上 dompurify 是一個 client side only 的套件,因此我們需要注意幾點事項:

  1. 避免 dompurify 在伺服器階段運作
  2. 只允許 dompurify 在瀏覽器階段發揮作用

為了解決上述問題,我們使用 useEffect 非同步加載 dompurify 套件來解析 HTML 字串

  useEffect(() => {
import("dompurify").then((DOMPurifyModule) => {
const DOMPurify = DOMPurifyModule.default(window);
console.log("pure");
setCleanText(DOMPurify.sanitize(props.data.pages.nodes[0].content));
});
}, [props.data.pages.nodes]);
  • 使用 dangerouslySetInnerHTML + dompurify

  • 使用 dangerouslySetInnerHTML + dompurify

本篇文章的全部程式碼
import {
ApolloClient,
DefaultOptions,
InMemoryCache,
gql,
} from "@apollo/client";
import { GetStaticProps } from "next";
import React, { useEffect, useState } from "react";

export interface IPrivacy {
pages: Pages;
}

export interface Pages {
nodes: Node[];
}

export interface Node {
content: string;
}

const defaultOptions: DefaultOptions = {
watchQuery: {
fetchPolicy: "no-cache",
errorPolicy: "ignore",
},
query: {
fetchPolicy: "no-cache",
errorPolicy: "all",
},
};

const uri = "https://你的 wp graphQL api 端點 URL";

const client = new ApolloClient({
uri: uri,
cache: new InMemoryCache(),
defaultOptions: defaultOptions,
});

export const getStaticProps: GetStaticProps = async () => {
const { data } = await client.query<Privacy>({
query: gql(`{
pages(where:{title:"Privacy Policy"}){
nodes{
content
}
}
}`),
});

return {
props: { data },
};
};

// Privacy 組件將從 props 接收資料並渲染。
function Privacy(props: { data: IPrivacy }) {
// 使用 useState 來存儲清潔後的 HTML 字串。
const [cleanText, setCleanText] = useState<string>();

// 使用 useEffect 進行組件的副作用操作。
useEffect(() => {
// 動態導入 dompurify 套件。
import('dompurify').then((DOMPurifyModule) => {
// 獲取 DOMPurify 物件的默認導出。
const DOMPurify = DOMPurifyModule.default;

// 使用 DOMPurify 清潔 HTML 字串並儲存到狀態中。
setCleanText(DOMPurify.sanitize(props.data.pages.nodes[0].content));
});
}, [props.data.pages.nodes]); // 添加依賴項目以避免不必要的重新渲染。

// 渲染組件。
return (
<>
<h1>下方是經過處理後的乾淨 inner HTML</h1>
{/* 使用 dangerouslySetInnerHTML 属性來渲染清潔後的 HTML。 */}
{cleanText && <div dangerouslySetInnerHTML={{ __html: cleanText }} />}
</>
);
}

export default Privacy;