Day9 在 Next.js 安装 apollo-graphql,串接 WordPress GraphQL API(上)

上一篇文章我们成功在 WordPress 安装 WPGraphQL plugin,启动了 GraphQL API,现在我们要来在 Next.js 部落格前端串接它,抓取文章列表资料,并呈现在首页。

参照 Next.js 官方范例

这部分实作我们会参照 Next.js 官方这 2 套 sample code:

  1. cms-wordpress
  2. with-apollo

第一个 cms-wordpress 是 demo 如何在 Next.js 串接 WordPress GraphQL API,里面使用 fetch function 呼叫 GraphQL API 抓取文章资料,用来显示首页的文章列表、及文章细节页的文章内容。我们可以参考这范例里用到哪些 GraphQL 栏位,我们也将用差不多的栏位来实作各页面。

而因为第一个范例是使用单纯 fetch 函式来呼叫 GraphQL API,功能上稍嫌不足,後续若要加入更多判断或功能,像是 pagination 分页功能(一页显示 10 篇文章之类)的话,需要自己实作重造轮子,所以这部分我们会参考第二个 with-apollo 范例,改安装 apollo-client 来执行 GraphQL API call,里面就帮我们处理了很多常见功能,像是 loading state、error state、client-side cache、fetch more function 等等,让我们能用更多元的方式使用 GraphQL API。

实作:安装 apollo client

这部分我们参照 with-apollo 范例来安装,主要安装 @apollo/clientgraphql 这两个套件当作 GraphQL client,并且额外安装 deepmergelodash 来处理资料

yarn add @apollo/client graphql deepmerge lodash

接着新建 /lib/apolloClient.js 档案,它提供了 useApollo function,让我们等等能在整个 Next.js 专案套上 ApolloClient:

// Mainly follow this example
// https://github.com/vercel/next.js/tree/canary/examples/with-apollo
import { useMemo } from 'react'
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
import { concatPagination } from '@apollo/client/utilities'
import merge from 'deepmerge'
import isEqual from 'lodash/isEqual'

import { NEXT_PUBLIC_GRAPHQL_ENDPOINT } from '../constants/envValues'

export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'

let apolloClient

function createApolloClient() {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: new HttpLink({
      uri: NEXT_PUBLIC_GRAPHQL_ENDPOINT, // Server URL (must be absolute)
      credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
    }),
    cache: new InMemoryCache({
      typePolicies: {
        Query: {
          fields: {
            posts: concatPagination(),
          },
        },
      },
    }),
  })
}

export function initializeApollo(initialState = null) {
  const _apolloClient = apolloClient ?? createApolloClient()

  // If your page has Next.js data fetching methods that use Apollo Client, the initial state
  // gets hydrated here
  if (initialState) {
    // Get existing cache, loaded during client side data fetching
    const existingCache = _apolloClient.extract()

    // Merge the existing cache into data passed from getStaticProps/getServerSideProps
    const data = merge(initialState, existingCache, {
      // combine arrays using object equality (like in sets)
      arrayMerge: (destinationArray, sourceArray) => [
        ...sourceArray,
        ...destinationArray.filter((d) => sourceArray.every((s) => !isEqual(d, s))),
      ],
    })

    // Restore the cache with the merged data
    _apolloClient.cache.restore(data)
  }
  // For SSG and SSR always create a new Apollo Client
  if (typeof window === 'undefined') return _apolloClient
  // Create the Apollo Client once in the client
  if (!apolloClient) apolloClient = _apolloClient

  return _apolloClient
}

export function addApolloState(client, pageProps) {
  if (pageProps?.props) {
    pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
  }

  return pageProps
}

export function useApollo(pageProps) {
  const state = pageProps[APOLLO_STATE_PROP_NAME]
  const store = useMemo(() => initializeApollo(state), [state])
  return store
}

接着修改 /pages/_app.js,用刚刚的 useApollo 以及 @apollo/client package 提供的 ApolloProvider 包住整个 Next.js app,这样之後才能在各个 page 或 component 内呼叫 GraphQL API:

import { ApolloProvider } from '@apollo/client'

import { useApollo } from '../lib/apolloClient'

import '../styles/globals.css'

export default function App({ Component, pageProps }) {
  const apolloClient = useApollo(pageProps)

  return (
    <ApolloProvider client={apolloClient}>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}

在先前的 apolloClient.js 里,建立 client 时需指定 GraphQL API endpoint,常规做法是会把路径抽成环境变数,在这个专案我把它命名成 NEXT_PUBLIC_GRAPHQL_ENDPOINT。

注意它有 NEXT_PUBLIC_ 这个前缀,这是因为我们希望这个环境变数在浏览器端也能看到,因为在後面几篇文章我们要实作分页功能时,抓取第二页的文章列表,这动作是做在 client-side 的,因此浏览器也要知道我们 API_ENDPOINT 为何。在 Next.js 就是透过前缀决定浏览器是否看得到,可参阅相关文件,类似 Create-react-app 的 REACT_APP_ 前缀

我习惯将环境变数集中到一支 JavaScript 档案,集中 export 出去,这样方便我们一眼看出专案有哪些环境变数,在这里我们建立 /constants/envValues.js

export const NEXT_PUBLIC_GRAPHQL_ENDPOINT = process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT

再来在 Next.js local 开发过程,环境变数设定通常是用建立环境变数档方式设定的,启动 Next.js dev server 时会自动来读取特定名称的档案,通常叫做 .env.local,所以让我们建立 /.env.local,後面 endpoint 网址替换成你自己的网址,通常用 WPGraphQL 启用的话都会是在你的 WordPress domain 里的 /graphql 路径:

NEXT_PUBLIC_GRAPHQL_ENDPOINT=https://xxxxxx.mybluehost.me/graphql

下一篇:抓取文章列表资料

到此我们成功在 Next.js 安装 ApolloClient 了,不过还没完,下一篇文章我们会继续使用刚安装的 ApolloClient 从 WordPress 抓取文章资料,在首页显示文章列表!

关於这篇文章的改动可以参考这支 commit


<<:  DAY23 搞样式--CSS Gird小进阶(间隔/fr)

>>:  Day9:今天来讲一下Microsoft Defender for Endpoint的装置调查

javascript函式与动态变更网页内容(DAY17)

在许多网页中我们都可以看到动态变更网页内容的身影,像是我们按下一个按钮,它就会做出相对应的事情呈现在...

DAY9 样式属性权重 - Nav Bar实作之遇到的小卡顿

错误版 正确版 比对两个,发现哪里有bug了吗? 对就是,鼠标移开後,样式应该变回原本的,但它没有。...

Golang 测试

Golang 测试 转换一下心情,来尝试看看单元测试好了 在golang上要跑测试的话,可以考虑先试...

JavaScript Day 25. DOM Node 常用方法

文章写到第 25 篇,从这样的过程也稳固了自己不少观念,今天依然搅进脑汁将查找的资料尽量用自己的话写...

[Day10] - Tab页签切换效果 - Web Component 的样式设定

在 Web Component 中有些特别的 css styling 可以设定 , ex : 如果我...