gpt4 book ai didi

graphql - 我可以将 useSWR 与 apollo-client 一起使用吗?

转载 作者:行者123 更新时间:2023-12-02 02:18:14 25 4
gpt4 key购买 nike

我对 next.js graphQL 世界还很陌生。

我刚找到 useSWR,我想知道我是否可以将它与 Apollo-client 一起使用,不使用 graphql 请求。

最佳答案

是的,你可以,我也可以。我有两个 headless CMS 共存。一种是通过 OneGraph 与 google 捆绑在一起的 headless wordpress;另一个是 Headless Booksy,一个没有可公开访问的端点的闭源 CMS——在用户驱动的事件期间剖析网络选项卡以确定任何所需的 header /参数,并最终反向工程他们的身份验证流程以使用异步分区在我的 repo 中自动化它。

就是说,是的,我同时使用 apollo Client 和 SWR。这是 _app.tsx 配置

  • _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, fetcherGallery } from '@/lib/swr-fetcher';
import { Configuration, Fetcher } from 'swr/dist/types';

type T = typeof fetcher | typeof fetcherGallery;
interface Combined extends Fetcher<T> {}

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, Combined>>
>
) => [key, { ...config }]
}}
>
<ApolloProvider client={apolloClient}>
<NextAuthProvider session={pageProps.session}>
<ManagedGlobalContext>
<MediaContextProvider>
<Head />
<LayoutNoop pageProps={pageProps}>
<Component {...pageProps} />
</LayoutNoop>
</MediaContextProvider>
</ManagedGlobalContext>
</NextAuthProvider>
</ApolloProvider>
</SWRConfig>
</>
);
}

然后,我有这个 api 路由处理评论 + 通过 index.tsx 中的 SWR 对这些评论进行分页

  • pages/api/booksy-fetch.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { BooksyReviewFetchResponse } from '@/types/booksy';
import fetch from 'isomorphic-unfetch';
import { getAccessToken } from '@/lib/booksy';

const API_KEY = process.env.NEXT_PUBLIC_BOOKSY_BIZ_API_KEY ?? '';
const FINGERPRINT =
process.env.NEXT_PUBLIC_BOOKSY_BIZ_X_FINGERPRINT ?? '';

export default async function (
req: NextApiRequest,
res: NextApiResponse<BooksyReviewFetchResponse>
) {

const {
query: { reviews_page, reviews_per_page }
} = req;

const { access_token } = await getAccessToken();
const rev_page_number = reviews_page ? reviews_page : 1;
const reviews_pp = reviews_per_page ? reviews_per_page : 10;

const response = await fetch(
`https://us.booksy.com/api/us/2/business_api/me/businesses/481001/reviews/?reviews_page=${rev_page_number}&reviews_per_page=${reviews_pp}`,
{
headers: {
'X-Api-key': API_KEY,
'X-Access-Token': `${access_token}`,
'X-fingerprint': FINGERPRINT,
Authorization: `s-G1-cvdAC4PrQ ${access_token}`,
'Cache-Control':
's-maxage=86400, stale-while-revalidate=43200',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.152 Safari/537.36',
Connection: 'keep-alive',
Accept: '*/*',
'Accept-Encoding': 'gzip, deflate, br'
},
method: 'GET',
keepalive: true
}
);

const booksyReviews: BooksyReviewFetchResponse =
await response.json();
res.setHeader(
'Cache-Control',
'public, s-maxage=86400, stale-while-revalidate=43200'
);

return res.status(200).json(booksyReviews);
}

和以下 api 路由处理自定义选取框的图像数据也在 index.tsx 中

  • pages/api/booksy-images.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { Gallery } from '@/types/index';
import { getLatestBooksyPhotos } from '@/lib/booksy';

export default async function (
_req: NextApiRequest,
res: NextApiResponse<Gallery>
) {
const response: Response = await getLatestBooksyPhotos();
const booksyImages: Gallery = await response.json();
res.setHeader(
'Cache-Control',
'public, s-maxage=86400, stale-while-revalidate=43200'
);

return res.status(200).json(booksyImages);
}

现在,检查 index.tsx。为了清楚起见,我将代码分为服务器端和客户端

  • index.tsx(getStaticProps -- 服务器)

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'];
initDataGallery: Partial<
Configuration<Gallery, any, Fetcher<Gallery>>
>;
initialData: Partial<
Configuration<
BooksyReviewFetchResponse,
any,
Fetcher<BooksyReviewFetchResponse>
>
>;
}>
> {
console.log(ctx.params ?? '');
const apolloClient = initializeApollo();
const { data: DynamicSlugs } = await apolloClient.query<
DynamicNavQuery,
DynamicNavQueryVariables
>({
query: DynamicNavDocument,
variables: {
idHead: 'Header',
idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
idFoot: 'Footer'
}
});
const { data: LandingData } = await apolloClient.query<
LandingDataQuery,
LandingDataQueryVariables
>({
query: LandingDataDocument,
variables: {
other: WordPress.Services.Other,
popular: WordPress.Services.Popular,
path: Google.PlacesPath,
googleMapsKey: Google.MapsKey
}
});
const { other, popular, Places, businessHours, merchandise } =
LandingData;
const { Header, Footer } = DynamicSlugs;
const dataGallery = await getLatestBooksyPhotos();
const initDataGallery: Gallery = await dataGallery.json();

const dataInit = await getLatestBooksyReviews({
reviewsPerPage: 10,
pageIndex: 1
});
const initialData: BooksyReviewFetchResponse =
await dataInit.json();
return addApolloState
? addApolloState(apolloClient, {
props: {
Header,
Footer,
other,
popular,
Places,
businessHours,
merchandise
},
revalidate: 600
})
: {
props: {
initialData,
initDataGallery
},
revalidate: 600
};
}

注意返回的 props 是如何根据它是 SWR 还是 Apollo Client 数据来处理的?接下来是非常棒的

  • 注意在 getStaticProps 中调用的函数
    const dataGallery = await getLatestBooksyPhotos();
const initDataGallery: Gallery = await dataGallery.json();

const dataInit = await getLatestBooksyReviews({
reviewsPerPage: 10,
pageIndex: 1
});
const initialData: BooksyReviewFetchResponse =
await dataInit.json();

-- 它们来自 lib 目录。它们旨在在页面文件的服务器上使用,以将初始数据注入(inject) SWR。从本质上讲,它实现了与 api 路由文件相同的方法,但由于它们只能在客户端上使用,因此这是一个必要的解决方法。

现在为客户

  • index.tsx(默认导出 -- 客户端)

export default function Index<T extends typeof getStaticProps>({
other,
popular,
Header,
Footer,
merchandise,
Places,
businessHours,
initialData,
initDataGallery
}: InferGetStaticPropsType<T>) {
const GalleryImageLoader = ({
src,
width,
quality
}: ImageLoaderProps) => {
return `${src}?w=${width}&q=${quality || 75}`;
};
const reviews_per_page = 10;
const [reviews_page, set_reviews_page] = 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}`,
fetcher,
initialData
);
const { data: galleryData } = useSWR<Gallery>(
'/api/booksy-images',
fetcherGallery,
initDataGallery
);

// total items
const reviewCount = 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?.reviews ? (
<BooksyReviews pageIndex={i} key={i} reviews={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={page.current - 1 === 0 ? true : false}
onClick={() => set_reviews_page(page.current - 1)}
className={cn('landing-page-pagination-btn', {
' cursor-not-allowed bg-redditSearch':
reviews_page - 1 === 0,
' cursor-pointer': reviews_page - 1 !== 0
})}
>
Previous
</button>

<button
disabled={page.current === totalPages ? true : false}
onClick={() => set_reviews_page(page.current + 1)}
className={cn('landing-page-pagination-btn', {
' cursor-not-allowed bg-redditSearch':
reviews_page === totalPages,
' cursor-pointer': reviews_page < totalPages
})}
>
Next
</button>
</div>
</nav>
</BooksyReviews>
) : (
<ReviewsSkeleton />
)
);
}

useEffect(() => {
(async function Update() {
return (await page.current) === reviews_page
? true
: set_reviews_page((page.current = reviews_page));
})();
}, [page.current, reviews_page]);
return (
<>
<AppLayout
title={'The Fade Room Inc.'}
Header={Header}
Footer={Footer}
>
{galleryData?.images ? (
<Grid>
{galleryData.images
.slice(6, 9)
.map((img, i) => {
<GalleryCard
key={img.image_id}
media={galleryData}
imgProps={{
loader: GalleryImageLoader,
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540
}}
/>;
})
.reverse()}
</Grid>
) : (
<LoadingSpinner />
)}
{galleryData?.images ? (
<Marquee variant='secondary'>
{galleryData.images
.slice(3, 6)
.map((img, j) => (
<GalleryCard
key={img.image_id}
media={galleryData}
variant='slim'
imgProps={{
loader: GalleryImageLoader,
width: j === 0 ? 320 : 320,
height: j === 0 ? 320 : 320
}}
/>
))
.reverse()}
</Marquee>
) : (
<LoadingSpinner />
)}
<LandingCoalesced
other={other}
popular={popular}
places={Places}
businessHours={businessHours}
merchandise={merchandise}
>
{data?.reviews ? (
<>
<>{pages[page.current]}</>
<span className='hidden'>
{
pages[
page.current < totalPages
? page.current + 1
: page.current - 1
]
}
</span>
</>
) : (
<ReviewsSkeleton />
)}
</LandingCoalesced>
</AppLayout>
</>
);
}

着陆页上有两个 useSWR 钩子(Hook):

    const { data } = useSWR<BooksyReviewFetchResponse>(
() =>
`/api/booksy-fetch?reviews_page=${reviews_page}&reviews_per_page=${reviews_per_page}`,
fetcher,
initialData
);
const { data: galleryData } = useSWR<Gallery>(
'/api/booksy-images',
fetcherGallery,
initDataGallery
);

initialDatainitDataGallery在各自的 fetcher 之后列出的值是从服务器传输到客户端并通过 InferGetStaticPropsType<T> 推断的初始数据。 .这为客户端数据获取时的首次加载数据问题提供了解决方案。

当使用 SWR 时,您可以进行一个额外的配置来加快客户端上的数据获取,即指定应在 _document.tsx 中预加载哪些 api 路由。以及它们对应的 fetcher 的名称

  • _document.tsx

<link
rel='preload'
href={`/api/booksy-fetch?reviews_page=1&reviews_per_page=10`}
as='fetcher'
crossOrigin='anonymous'
/>
<link
rel='preload'
href='/api/booksy-images'
as='fetcherGallery'
crossOrigin='anonymous'
/>

我在串联使用这两个方面遇到了 0 个问题,我已经使用了大约一个月,并且实际上将 SWR 纳入了混合 增强 DX/UX 和下一个分析反射(reflect)了(它将 FCP 时间缩短 50% 以上至 0.4 秒,将 LCP 时间缩短至 0.8 秒左右)。

关于graphql - 我可以将 useSWR 与 apollo-client 一起使用吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66851630/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com