这是我在这里发表的第一篇讨论文章。我通过
Odyssey学会了
Apollo
+ GraphQL
。目前,我正在使用 Next.js 构建自己的项目,该项目需要从 2 个 GraphQL 端点获取数据。
我的问题:如何使用 ApolloClient
从
多个 GraphQL 端点获取数据?
下面是我的第一个端点的代码:
import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
const client = new ApolloClient({
ssrMode: true,
link: createHttpLink({
uri: "https://api.hashnode.com/",
credentials: "same-origin",
headers: {
Authorization: process.env.HASHNODE_AUTH,
},
}),
cache: new InMemoryCache(),
});
export default client;
你想要完成的事情有点反对阿波罗的“一张图”方法。 查看网关和联合 - https://www.apollographql.com/docs/federation/
话虽如此,一些 hacky 解决方案是可能的,但您将需要维护更复杂的结构并在每个查询中指定端点,这会破坏内置机制并可能导致优化问题。
//Declare your endpoints
const endpoint1 = new HttpLink({
uri: 'https://api.hashnode.com/graphql',
...
})
const endpoint2 = new HttpLink({
uri: 'endpoint2/graphql',
...
})
//pass them to apollo-client config
const client = new ApolloClient({
link: ApolloLink.split(
operation => operation.getContext().clientName === 'endpoint2',
endpoint2, //if above
endpoint1
)
...
})
//pass client name in query/mutation
useQuery(QUERY, {variables, context: {clientName: 'endpoint2'}})
这个包似乎可以满足您的要求:https://github.com/habx/apollo-multi-endpoint-link
另外,请查看此处的讨论:https://github.com/apollographql/apollo-client/issues/84
今天也遇到同样的问题。我想让它充满活力,所以这就是我的结果:
export type DynamicLinkClientName = "aApp" | "bApp" | "graphqlApp";
type Link = RestLink | HttpLink;
type DynamicLink = { link: Link; name: DynamicLinkClientName };
const LINK_MAP: DynamicLink[] = [
{ link: aRestLink, name: "aApp" },
{ link: bAppRestLink, name: "bApp" },
{ link: graphqlAppLink, name: "graphqlApp" },
];
const isClientFromContext = (client: string) => (op: Operation) =>
op.getContext().client === client;
const DynamicApolloLink = LINK_MAP.reduce<ApolloLink | undefined>(
(prevLink, nextLink) => {
// When no name is specified, fallback to defaultLink.
if (!prevLink) {
return ApolloLink.split(
isClientFromContext(nextLink.name),
nextLink.link,
defaultLink
);
}
return ApolloLink.split(
isClientFromContext(nextLink.name),
nextLink.link,
prevLink
);
},
undefined
) as ApolloLink;
非常喜欢 Pete 的解决方案,它允许超过 2 个端点。
决定编写我自己的版本以进行更好的类型检查。
这是我对他的解决方案的看法:
打字稿:
const defaultClient: keyof typeof clients = "heroku";
const clients = {
"heroku": new HttpLink({ uri: "https://endpointURLForHeroku" }),
"lists": new HttpLink({uri: "https://endpointURLForLists" })
}
const isRequestedClient = (clientName: string) => (op: Operation) =>
op.getContext().clientName === clientName;
const ClientResolverLink = Object.entries(clients)
.map(([clientName, Link]) => ([clientName, ApolloLink.from([Link])] as const))
.reduce(([_, PreviousLink], [clientName, NextLink]) => {
const ChainedLink = ApolloLink.split(
isRequestedClient(clientName),
NextLink,
PreviousLink
)
return [clientName, ChainedLink];
}, ["_default", clients[defaultClient]])[1]
declare module "@apollo/client" {
interface DefaultContext {
clientName: keyof typeof clients
}
}
JS:
const defaultClient = "heroku";
const clients = {
"heroku": new HttpLink({ uri: "https://endpointURLForHeroku" }),
"lists": new HttpLink({uri: "https://endpointURLForLists" })
}
const isRequestedClient = (clientName) => (op) =>
op.getContext().clientName === clientName;
const ClientResolverLink = Object.entries(clients)
.reduce(([_, PreviousLink], [clientName, NextLink]) => {
const ChainedLink = ApolloLink.split(
isRequestedClient(clientName),
NextLink,
PreviousLink
)
return [clientName, ChainedLink];
}, ["_default", clients[defaultClient]])[1]
我将此视为一个 Apollo Links 任务,它是普通 Apollo 客户端的一部分。
具体是一个定向组成链接链
我的原型可能有点太冗长了,所以你需要担心的是:
ApolloLink.split
将查询定向到不同的端点。Apollo Link
中重定向。
请注意,您不需要
libs/apollo/index.ts
(无论您想在哪里定义 apollo 客户端)
import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from '@apollo/client'
// Declare your endpoints
const animeEndpoint = new HttpLink({
uri: 'https://graphql.anilist.co',
})
const countriesEndpoint = new HttpLink({
uri: 'https://countries.trevorblades.com/',
})
// Not necessary. Just helps with type safety.
export enum Endpoint {
anime = 'anime',
country = 'country',
}
//pass them to apollo-client config
const client = new ApolloClient({
// Version here is just a custom property that we can use to determine which endpoint to use
// Truthy = animeEndpoint (second) parameter, falsy = countriesEndpoint (third) parameter
link: ApolloLink.split((operation) => operation.getContext().version === Endpoint.anime, animeEndpoint, countriesEndpoint),
cache: new InMemoryCache(),
})
export default client
app/page.tsx
(因为我为此使用 NextJS)
'use client'
import { useQuery } from '@apollo/client'
import GetAnimeQuery from '@/libs/gql/GetAnime' // Just a graphql query
import { Endpoint } from '@/libs/apollo'
import GetCountriesQuery from '@/libs/gql/GetCountries' // Just a graphql query
export default function Home() {
// Anime data
const { loading: loadingAnime, error: errorAnime, data: dataAnime } = useQuery(GetAnimeQuery, { context: { version: Endpoint.anime } })
// Countries data
const {
loading: loadingCountries,
error: errorCountries,
data: dataCountries,
} = useQuery(GetCountriesQuery, { context: { version: Endpoint.country } })
console.log('Countries', dataCountries)
console.log('Anime', dataAnime)
return (
<main>
{loadingAnime && <p>Loading Anime...</p>}
{errorAnime && <p>Error Anime :{errorAnime.message}</p>}
{loadingCountries && <p>Loading Countries...</p>}
{errorCountries && <p>Error Countries:{errorCountries.message}</p>}
</main>
)
}
libs/gql/GetAnime.ts
import { gql } from '@apollo/client'
const GetAnimeQuery = gql`
query Get {
Page(page: 1, perPage: 5) {
pageInfo {
total
currentPage
lastPage
hasNextPage
perPage
}
media {
id
title {
romaji
}
}
}
}
`
export default GetAnimeQuery
libs/gql/GetCountries.ts
import { gql } from '@apollo/client'
const GetCountriesQuery = gql`
query GetAllCountries {
countries {
code
currency
name
}
}
`
export default GetCountriesQuery