更新答案
原创
您应该在 getStaticProps 或 getServerSideProps 中传递该数据
两者都在服务器上执行,都被任何给定页面中的默认导出消耗。
export async function getStaticProps(
ctx: GetStaticPropsContext
): Promise<
GetStaticPropsResult<{
updateCache: typeof updateCache;
}>
> {
// initialize apollo? I think you need to initialize apollo here.
const updateCache = (cache, mutationResult) => {
const newTask = mutationResult.data.createJob;
const data = cache.readQuery({
query: LIST_JOBS
});
console.log(data); // this is null
await cache.writeQuery({
query: LIST_JOBS,
variables: { input: newTask.type },
data: { tasks: [...data.jobs, newTask] }
});
};
return {
props: {
updateCache
},
revalidate: 60
};
}
export default function postJob<T extends typeof getStaticProps>({ updateCache
}: InferGetStaticPropsType<T>) {
const [
createJob,
{ loading: mutationLoading, error: mutationError }
] = useMutation(CREATE_JOB, { update: updateCache });
const onFinish = (values: any) => {
const newJob = { ...values, status: 'POSTED' };
newJob.status = 'POSTED';
console.log('Success ONFINISH:', newJob);
createJob({ variables: { input: newJob } }).then(_data =>
console.log(_data)
);
};
}
使用 nextjs 实用程序类型(从 Next 导入)进行强类型推断。
另外,我认为您需要在开始调用缓存等之前在服务器上初始化 apollo。
这是我目前正在构建的回购中的一个页面示例
export async function getStaticProps(
ctx: GetStaticPropsContext
): Promise<
GetStaticPropsResult<{
other: LandingDataQuery['other'];
popular: LandingDataQuery['popular'];
places: LandingDataQuery['Places'];
merchandise: LandingDataQuery['merchandise'];
businessHours: LandingDataQuery['businessHours'];
Header: DynamicNavQuery['Header'];
Footer: DynamicNavQuery['Footer'];
}>
> {
console.log('getStaticProps Index: ', ctx.params?.index ?? {});
const apolloClient = initializeApollo(
{ headers: ctx.params } ?? {}
);
await apolloClient.query<
DynamicNavQuery,
DynamicNavQueryVariables
>({
query: DynamicNavDocument,
variables: {
idHead: 'Header',
idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
idFoot: 'Footer'
}
});
await apolloClient.query<
LandingDataQuery,
LandingDataQueryVariables
>({
query: LandingDataDocument,
variables: {
other: WordPress.Services.Other,
popular: WordPress.Services.Popular,
path: Google.PlacesPath,
googleMapsKey: Google.MapsKey
}
});
return addApolloState(apolloClient, {
props: {},
revalidate: 600
});
}
export default function Index<T extends typeof getStaticProps>({
other,
popular,
Header,
Footer,
merchandise,
places,
businessHours
}: InferGetStaticPropsType<T>) {
const reviews_per_page = 10;
const [reviews_page, setCnt] = useState<number>(1);
const page = useRef<number>(reviews_page);
const data = useSWR<BooksyReviewFetchResponse>(
`/api/booksy-fetch?reviews_page=${reviews_page}&reviews_per_page=${reviews_per_page}`
);
const reviewCount =
data.data?.reviews_count ?? reviews_per_page;
// total pages
const totalPages =
(reviewCount / reviews_per_page) % reviews_per_page === 0
? reviewCount / reviews_per_page
: Math.ceil(reviewCount / reviews_per_page);
// correcting for array indeces starting at 0, not 1
const currentRangeCorrection =
reviews_per_page * page.current - (reviews_per_page - 1);
// current page range end item
const currentRangeEnd =
currentRangeCorrection + reviews_per_page - 1 <= reviewCount
? currentRangeCorrection + reviews_per_page - 1
: currentRangeCorrection +
reviews_per_page -
(reviewCount % reviews_per_page);
// current page range start item
const currentRangeStart =
page.current === 1
? page.current
: reviews_per_page * page.current - (reviews_per_page - 1);
const pages = [];
for (let i = 0; i <= reviews_page; i++) {
pages.push(
data.data?.reviews !== undefined ? (
<BooksyReviews
pageIndex={i}
key={i}
reviews={data.data.reviews}
>
<nav aria-label='Pagination'>
<div className='hidden sm:block'>
<p className='text-sm text-gray-50'>
Showing{' '}
<span className='font-medium'>{`${currentRangeStart}`}</span>{' '}
to{' '}
<span className='font-medium'>{`${currentRangeEnd}`}</span>{' '}
of <span className='font-medium'>{reviewCount}</span>{' '}
reviews (page:{' '}
<span className='font-medium'>{page.current}</span> of{' '}
<span className='font-medium'>{totalPages}</span>)
</p>
</div>
<div className='flex-1 inline-flex justify-between sm:justify-center my-auto'>
<button
disabled={reviews_page - 1 === 0 ? true : false}
onClick={() => setCnt(reviews_page - 1)}
className={cn(
'm-3 relative inline-flex items-center px-4 py-2 border border-olive-300 text-sm font-medium rounded-md text-olive-300 bg-redditBG hover:bg-redditSearch',
{
' cursor-not-allowed bg-redditSearch':
reviews_page - 1 === 0,
' cursor-pointer': reviews_page - 1 !== 0
}
)}
>
Previous
</button>
<button
disabled={reviews_page === totalPages ? true : false}
onClick={() => setCnt(reviews_page + 1)}
className={cn(
'm-3 relative inline-flex items-center px-4 py-2 border border-olive-300 text-sm font-medium rounded-md text-olive-300 bg-redditBG hover:bg-redditSearch',
{
' cursor-not-allowed bg-redditSearch':
reviews_page === totalPages,
' cursor-pointer': reviews_page < totalPages
}
)}
>
Next
</button>
</div>
</nav>
</BooksyReviews>
) : (
<div className='loading w-64 h-32 min-w-full mx-auto min-h-full'>
<LoadingSpinner />
</div>
)
);
}
useEffect(() => {
if (page.current) {
setCnt((page.current = reviews_page));
}
}, [page.current, reviews_page, setCnt, data]);
return (
<>
<AppLayout
title={'The Fade Room Inc.'}
Header={Header}
Footer={Footer}
>
<LandingCoalesced
other={other}
popular={popular}
places={places}
businessHours={businessHours}
merchandise={merchandise}
>
{data.data?.reviews ? (
<>
<>{pages[page.current]}</>
<p className='hidden'>
{
pages[
page.current < totalPages
? page.current + 1
: page.current
]
}
</p>
</>
) : (
<div className='fit mb-48 md:mb-0'>
<Fallback />
</div>
)}
</LandingCoalesced>
</AppLayout>
</>
);
}
在默认导出中有一些全局 SWR 到 api 路由的东西,但是使用 apollo + graphql 的初始 getStaticProps 保持不变。您还可以尝试在 api 路由中执行您的变异服务器端,并在给定的作业创建事件上使用带有变量的 post fetch。
更新
要更新缓存,它必须在本地调用。您真正在客户端中的唯一一次是在 JSX 返回组件或 .tsx 文件中。因此,只要您想访问它(无论是通过 useApollo(客户端点击)还是 initializeApollo(服务器点击)),您都可以将它实例化为本地函数。 AddApolloState 可以用作getStaticProps、getServerSideProps 的返回的包装器,甚至可以在无服务器节点环境(api 路由)中使用。如果您通过调用 Promise<GetServerSideProps<P>> 或 Promise<GetStaticPropsResult<P>> 或 NextApiResult<P> 显式定义返回类型(请参阅我共享的原始代码),它隐式注入带有返回类型的全局缓存,然后可以推断在客户端的默认导出中使用InferGetStaticPropsType 等。
这是我的apollo.ts 文件的内容
import { useMemo } from 'react';
import {
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
ApolloLink,
HttpLink
} from '@apollo/client';
import fetch from 'isomorphic-unfetch';
import { IncomingHttpHeaders } from 'http';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import { AppInitialProps, AppProps } from 'next/app';
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
let apolloClient:
| ApolloClient<NormalizedCacheObject>
| undefined;
const wpRefresh = process.env.WORDPRESS_AUTH_REFRESH_TOKEN ?? '';
const oneEndpoint =
process.env.NEXT_PUBLIC_ONEGRAPH_API_URL ?? '';
function createApolloClient(
headers: IncomingHttpHeaders | null = null
// ctx?: NextPageContext
): ApolloClient<NormalizedCacheObject> {
// isomorphic unfetch -- pass cookies along with each GraphQL request
const enhancedFetch = async (
url: RequestInfo,
init: RequestInit
): Promise<
Response extends null | undefined ? never : Response
> => {
return await fetch(url, {
...init,
headers: {
...init.headers,
'Access-Control-Allow-Origin': '*',
Cookie: headers?.cookie ?? ''
}
}).then(response => response);
};
const httpLink = new HttpLink({
uri: `${oneEndpoint}`,
// fetchOptions: {
// mode: 'cors'
// },
credentials: 'include',
fetch: enhancedFetch
});
const authLink: ApolloLink = setContext(
(_, { ...headers }: Headers) => {
let token: any;
// const token = localStorage.getItem('token' ?? '') ?? '';
if (!token) {
return {};
}
return {
headers: {
...headers,
'Accept-Encoding': 'gzip',
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json; charset=utf-8',
auth: `Bearer ${wpRefresh}`,
'x-jwt-auth': token ? `Bearer ${token}` : ''
}
// ...(typeof window !== undefined && { fetch })
};
}
);
const errorLink: ApolloLink = onError(
({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError)
console.log(
`[Network error]: ${networkError}. Backend is unreachable. Is it running?`
);
}
);
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: authLink.concat(httpLink) ?? errorLink,
connectToDevTools: true,
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// typeof: definition
// merge?: boolean | FieldMergeFunction<TExisting, TIncoming> | undefined;
// mergeObjects: FieldFunctionOptions<Record<string, any>, Record<string, any>>
wordpress: {
merge(existing, incoming, { mergeObjects }) {
// Invoking nested merge functions
return mergeObjects(existing, incoming);
}
},
google: {
merge(existing, incoming, { mergeObjects }) {
// Invoking nested merge functions
return mergeObjects(existing, incoming);
}
}
}
}
}
})
});
}
type InitialState = NormalizedCacheObject | any;
type IInitializeApollo = {
headers?: null | IncomingHttpHeaders;
initialState?: InitialState | null;
};
export function initializeApollo(
{ headers, initialState }: IInitializeApollo = {
headers: null,
initialState: null
}
) {
const _apolloClient =
apolloClient && headers
? createApolloClient(headers)
: createApolloClient();
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 = { ...existingCache, ...initialState };
// deep merge approach doesn't seem to play well with invoked nested merge functions in MemoryCache
// const data = merge(initialState, existingCache, {
// arrayMerge: (destinationArray, sourceArray) => [
// ...sourceArray,
// ...destinationArray.filter(d =>
// sourceArray.every(s => !isEqual(d, s))
// )
// ]
// });
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
// always create a new Apollo Client
// server
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function addApolloState(
client: ApolloClient<NormalizedCacheObject> | any,
pageProps: (AppInitialProps | AppProps)['pageProps'] | any
) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] =
client.cache.extract();
}
return pageProps;
}
export function useApollo(
pageProps: (AppInitialProps | AppProps)['pageProps'] | any
) {
const state = pageProps[APOLLO_STATE_PROP_NAME];
const store = useMemo(
() => initializeApollo({ initialState: state }),
[state]
);
return store;
}
以及_app.tsx中默认导出的内容
import '@/styles/index.css';
import '@/styles/chrome-bug.css';
import 'keen-slider/keen-slider.min.css';
import { AppProps, NextWebVitalsMetric } from 'next/app';
import { useRouter } from 'next/router';
import { ApolloProvider } from '@apollo/client';
import { useEffect, FC } from 'react';
import { useApollo } from '@/lib/apollo';
import * as gtag from '@/lib/analytics';
import { MediaContextProvider } from '@/lib/artsy-fresnel';
import { Head } from '@/components/Head';
import { GTagPageview } from '@/types/analytics';
import { ManagedGlobalContext } from '@/components/Context';
import { SWRConfig } from 'swr';
import { Provider as NextAuthProvider } from 'next-auth/client';
import fetch from 'isomorphic-unfetch';
import { fetcher } from '@/lib/swr-fetcher';
import { Configuration, Fetcher } from 'swr/dist/types';
const Noop: FC = ({ children }) => <>{children}</>;
export default function NextApp({
Component,
pageProps
}: AppProps) {
const apolloClient = useApollo(pageProps);
const LayoutNoop = (Component as any).LayoutNoop || Noop;
const router = useRouter();
useEffect(() => {
document.body.classList?.remove('loading');
}, []);
useEffect(() => {
const handleRouteChange = (url: GTagPageview) => {
gtag.pageview(url);
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return (
<>
<SWRConfig
value={{
errorRetryCount: 5,
refreshInterval: 43200 * 10,
onLoadingSlow: (
key: string,
config: Readonly<
Required<Configuration<any, any, Fetcher<typeof fetcher>>>
>
) => [key, { ...config }]
}}
>
<ApolloProvider client={apolloClient}>
<NextAuthProvider session={pageProps.session}>
<ManagedGlobalContext>
<MediaContextProvider>
<Head />
<LayoutNoop pageProps={pageProps}>
<Component {...pageProps} />
</LayoutNoop>
</MediaContextProvider>
</ManagedGlobalContext>
</NextAuthProvider>
</ApolloProvider>
</SWRConfig>
</>
);
}
所以在app中调用useApollo(pageProps),进行Module Augmentation注入
intellisense 表示它有以下类型
为此,您可以在根 @/types/* 目录或类似目录中使用扩充。 @/types/augmented/next.d.ts的内容
import type { NextComponentType, NextPageContext } from 'next';
import type { Session } from 'next-auth';
import type { Router } from 'next/router';
import { DynamicNavQuery } from '@/graphql/generated/graphql';
import { APOLLO_STATE_PROP_NAME } from '@/lib/apollo';
declare module 'next/app' {
type AppProps<P = Record<string, unknown>> = {
Component: NextComponentType<NextPageContext, any, P>;
router: Router;
__N_SSG?: boolean;
__N_SSP?: boolean;
pageProps: P & {
session?: Session;
APOLLO_STATE_PROP_NAME: typeof APOLLO_STATE_PROP_NAME;
};
};
}