Nextjs 使用 graphql,并且接入多个节点

11 篇文章 0 订阅
5 篇文章 0 订阅

写在前面

随着区块链技术的流行,也促进了 subgraph 工具的兴起。那么如何在前端接入 graphql 节点就成了关键,其接入方式既存在与 restful 接口相类似的方式,也有其独特接入风格。本文将介绍如何接入 graphql 以及如何应对多个 graphql 节点的情况。

如有不当之处,还望批评指正。

什么是 graphql

官网:https://graphql.org/

GraphQL 服务是通过定义类型和这些类型的字段来创建的,然后为每种类型的每个字段提供函数。例如,告诉您登录用户是谁(我)以及该用户名的 GraphQL 服务可能如下所示:

type Query {
  me: User
}
 
type User {
  id: ID
  name: String
}

GraphQL 服务运行后(通常在 Web 服务的 URL 上),它可以接收 GraphQL 查询以进行验证和执行。服务首先检查查询以确保它仅引用定义的类型和字段,然后运行提供的函数以生成结果。
例如,查询:

{
  me {
    name
  }
}

可以产生以下 JSON 结果:

{
  "me": {
    "name": "My Name"
  }
}

接入 graphql

Fetch

由于 HTTP 的普遍性,它是使用 GraphQL 时最常见的客户端-服务器协议选择。我们可以通过 HTTP 来请求 graphql 接口。

  const data = await fetch(
    "https://your-api-domain/graphql",
    {
      method: "POST",
      body: JSON.stringify({
        query: '{ me { name } }',
      }),
      headers: {
        "Content-Type": "application/json",
      },
    }
  ).then((res) => res.json());

如示例代码所示,可以直接通过请求 graphql 的地址,其 body 则是所请求字段的 schema。

apollo-client

除了使用原生的 fetch 方法外,还可以通过市面上的工具库请求,我采用的则是 apollo/client
安装依赖:

npm install @apollo/client@rc @apollo/experimental-nextjs-app-support

创建一个文件,用于随时随地获取注册完的 ApollpClient

// lib/client.js
import { HttpLink, InMemoryCache, ApolloClient } from "@apollo/client";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc";

export const { getClient } = registerApolloClient(() => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({
      uri: "https://your-api-domain/graphql",
    }),
  });
});

registerApolloClient 里面判断是否存在 ApolloClient,不存在则创建一个新实例。而我们只需要通过 getClient 就能够获取 ApolloClient。

Server Side

在服务端渲染的组件中,是不允许使用 use- 的 hooks 的,因此可以这么使用 ApolloClient:

// app/page.tsx
import { getClient } from "@/lib/client";

import { gql } from "@apollo/client";

export const revalidate = 5;
const query = gql`query Now {
    now(id: "1")
}`;

export default async function Page() {
  const client = getClient();
  const { data } = await client.query({ query });

  return <main>{data.now}</main>;
}
Client Side

那么在 Client 端,则可以使用以下步骤在 client 端渲染的组件中使用:

// lib/apollo-provider.js
"use client";

import { ApolloLink, HttpLink } from "@apollo/client";
import {
  ApolloNextAppProvider,
  NextSSRInMemoryCache,
  NextSSRApolloClient,
  SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr";

function makeClient() {
  const httpLink = new HttpLink({
      uri: "https://your-api-domain/graphql",
  });

  return new NextSSRApolloClient({
    cache: new NextSSRInMemoryCache(),
    link:
      typeof window === "undefined"
        ? ApolloLink.from([
            new SSRMultipartLink({
              stripDefer: true,
            }),
            httpLink,
          ])
        : httpLink,
  });
}

export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}

在 app/layout 中使用 ApolloWrapper,便可以将 Apollo 的相关数据注入到 context 中:

// app/layout.js
import { ApolloWrapper } from "/@lib/apollo-wrapper";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode,
}) {
  return (
    <html lang="en">
      <body>
        <ApolloWrapper>{children}</ApolloWrapper>
      </body>
    </html>
  );
}

最后在任何需要请求的时候,使用 useSuspenseQuery 获取数据即可:

"use client";

import { useSuspenseQuery } from "@apollo/experimental-nextjs-app-support/ssr";

import { gql } from "@apollo/client";

const query = gql`query Now {
    now(id: "1")
}`;

export default function Page() {
  const { data } = useSuspenseQuery(query);

  return <main>{data.now}</main>;
}

接入多个 graphql 节点

上述讲述了通过 new HttpLink 来生成请求后端的链接,那么我们如何处理多个 api 节点时的情况呢?

多个 Link

首先仍是采用 HttpLink 生成不同 api 节点的链接:


const firstLink = new HttpLink({
  uri: 'https://your-first-api-doamin',
});
const secondLink = new HttpLink({
  uri: 'https://your-second-api-doamin',
});
const defaultLink = new HttpLink({ uri: 'https://your-default-api-doamin' });

拼接 Link

让我们创建一个特殊的 function 来完成链接管理的所有工作:

type LinkConditionPair = {
  condition: (operation: Operation) => boolean;
  link: HttpLink;
};

function getApolloLink(pairs: LinkConditionPair[]): ApolloLink {
  if (pairs.length === 1) {
    return pairs[0].link;
  } else {
    const [firstPair, ...restPairs] = pairs;
    return ApolloLink.split(
      firstPair.condition,
      firstPair.link,
      getApolloLink(restPairs)
    );
  }
}

初始化 Client

然后我们初始化 Client,将多个 api 节点传递给 NextSSRApolloClient

const client = new NextSSRApolloClient({
  cache: new NextSSRInMemoryCache(),
  link: getApolloLink([
    {
      condition: (operation: Operation) =>
        operation.getContext().apiName === "first",
      link: firstLink,
    },
    {
      condition: (operation: Operation) =>
        operation.getContext().apiName === "second",
      link: secondLink,
    },
    {
      condition: () => true,
      link: defaultLink,
    },
  ]),
});

完整代码

'use client';

import { Operation } from '@apollo/client';
import { ApolloLink, HttpLink } from '@apollo/client';
import {
  ApolloNextAppProvider,
  NextSSRApolloClient,
  NextSSRInMemoryCache,
  SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support/ssr';

const firstLink = new HttpLink({
  uri: 'https://your-first-api-doamin',
});
const secondLink = new HttpLink({
  uri: 'https://your-second-api-doamin',
});
const defaultLink = new HttpLink({ uri: 'https://your-default-api-doamin' });
type LinkConditionPair = {
  condition: (operation: Operation) => boolean;
  link: HttpLink;
};

function getApolloLink(pairs: LinkConditionPair[]): ApolloLink {
  if (pairs.length === 1) {
    return pairs[0].link;
  } else {
    const [firstPair, ...restPairs] = pairs;
    return ApolloLink.split(
      firstPair.condition,
      firstPair.link,
      getApolloLink(restPairs),
    );
  }
}

function makeClient() {
  const httpLink = getApolloLink([
    {
      condition: (operation: Operation) => 
      	operation.getContext().apiName === 'first',
      link: firstLink,
    },
    {
      condition: (operation: Operation) =>
        operation.getContext().apiName === 'second',
      link: secondLink,
    },
    {
      condition: () => true,
      link: defaultLink,
    },
  ]);

  return new NextSSRApolloClient({
    cache: new NextSSRInMemoryCache(),
    link:
      typeof window === 'undefined'
        ? ApolloLink.from([
            new SSRMultipartLink({
              stripDefer: true,
            }),
            httpLink,
          ])
        : httpLink,
  });
}

export const ApolloWrapper = ({
  children,
}: {
  children: React.PropsWithChildren;
}) => {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
};

而我们调用请求的时候,则只需要传递 context 就行:

const { data } = useSuspenseQuery(..., { context: { apiName: 'first' } });

总结

当然,对于请求多个后端节点,我们可以简单粗暴地通过 fetch 来请求不同的后端接口实现功能,也可以声明多个 ApolloClient 实例来区分不同的后端节点。方法有很多,并没有完全的最佳解决方案。

上面是我尝试使用 apollo/client 来请求 graphql 的过程,以及通过配置 link 来请求多个后端实例的尝试。在此记录下,如有问题,还请指正。

参考:
Graphql 官网
React/Next.js: Working with multiple GraphQL endpoints and automatic type generation via Apollo Client
How to use Apollo Client with Next.js 13

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GJWeigege

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值