写在前面
随着区块链技术的流行,也促进了 subgraph 工具的兴起。那么如何在前端接入 graphql 节点就成了关键,其接入方式既存在与 restful 接口相类似的方式,也有其独特接入风格。本文将介绍如何接入 graphql 以及如何应对多个 graphql 节点的情况。
如有不当之处,还望批评指正。
什么是 graphql
官网:https://graphql.org/
GraphQL 服务是通过定义类型和这些类型的字段来创建的,然后为每种类型的每个字段提供函数。例如,告诉您登录用户是谁(我)以及该用户名的 GraphQL 服务可能如下所示:
type Query {me: User
}type User {id: IDname: 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