- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我对 next.js graphQL 世界还很陌生。
我刚找到 useSWR,我想知道我是否可以将它与 Apollo-client 一起使用,不使用 graphql 请求。
最佳答案
是的,你可以,我也可以。我有两个 headless CMS 共存。一种是通过 OneGraph 与 google 捆绑在一起的 headless wordpress;另一个是 Headless Booksy,一个没有可公开访问的端点的闭源 CMS——在用户驱动的事件期间剖析网络选项卡以确定任何所需的 header /参数,并最终反向工程他们的身份验证流程以使用异步分区在我的 repo 中自动化它。
就是说,是的,我同时使用 apollo Client 和 SWR。这是 _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 对这些评论进行分页
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 中
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。为了清楚起见,我将代码分为服务器端和客户端
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 数据来处理的?接下来是非常棒的
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 路由文件相同的方法,但由于它们只能在客户端上使用,因此这是一个必要的解决方法。
现在为客户
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
);
initialData
和 initDataGallery
在各自的 fetcher 之后列出的值是从服务器传输到客户端并通过 InferGetStaticPropsType<T>
推断的初始数据。 .这为客户端数据获取时的首次加载数据问题提供了解决方案。
当使用 SWR 时,您可以进行一个额外的配置来加快客户端上的数据获取,即指定应在 _document.tsx
中预加载哪些 api 路由。以及它们对应的 fetcher 的名称
<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/
有没有全局loading react-apollo 客户端在任何地方都可以使用标志吗?我有一个“页面包装器”组件,我想在所有子组件都收到它们的数据后应用 ui 效果。 我已经使用 redux 设置了
我试图通过 React 了解 Apollo。如何将状态添加到我本地的 Apollo 商店? 我正在使用 meteor 。它在客户端提供了一个功能来测试用户是否登录并返回他们的 id 如果他们是 Met
我有一个关于 cacheRedirects 的问题在 Apollo 中,我有项目概览和详细 View 。我希望将 detailview 缓存重定向到概览中的一个项目(如文档中的 the book ex
我们可以使用查询和变异向服务器发出一些请求。在这些查询中,我们可以传递一些参数,并且在这两种情况下我们都会从服务器获得一些结果。唯一的一个强制性区别是,我们可以从 props 中调用突变,例如“thi
我正在使用 @apollo/client 实现,但我没有看到任何完整的 @apollo/client 示例与 react . 如果我搜索,我会得到示例 apollo-client和 apollo bo
我正在使用 apollo-client、apollo-link 和 react-apollo,我想完全禁用缓存,但不知道该怎么做。 我看了apollo-cache-inmemory的来源,它有一个 c
我正在构建基于 JWT 的身份验证系统. JWT已过期。当JWT过期,我 catch JWT使用 apollo-link-error 的过期错误.我想调用 apolloClient.resetStor
Apollo 文档 discusses the use of cacheRedirects 告诉 Apollo 如何从其他查询访问缓存中的数据。 它给出了一个例子: In some cases, a
react-apollo的Query组件可以使用Recompose吗? ? https://www.apollographql.com/docs/react/essentials/queries.ht
我正在尝试从“ apollo-server”更新:“^2.9.4”和 “阿波罗服务器 express ” :“^2.9.4”到 2.12.0 版本 在 typescript 中,在应用程序的构建过程中
http://dev.apollodata.com/react/mutations.html 我正在尝试使用 optimisticResponse,但我很困惑...无法让它在我的本地运行。 我的问题是
我正在制作一个社交网站。当任何用户在站点上更新或创建新内容时,我需要查看站点的任何其他用户来查看更改更新。 我有一些需要低延迟的评论,因此建议为此订阅。 我也有事件,但这些不需要这么低的延迟。每 10
我在一个简单的 React 应用程序中使用最新版本的 Apollo Client,我试图从用于显示返回的记录集大小的响应中提取 header 值。 我很欣赏这不是提供结果集大小的最优雅的方式,但这就是
我想在突变后使用乐观 UI 更新:https://www.apollographql.com/docs/react/basics/mutations.html 我对“乐观响应”和“更新”之间的关系感到
在 Apollo 服务器中,当客户端用户订阅订阅(使用 WebSocket)时,我们可以使用订阅解析器检测到这一点。 但是有没有办法检测取消订阅? 我看到 WebSocket 发送了一个 {"id":
我创建这个问题是为了防止有人对如何在 Apollo 中添加 union/多态类型感到好奇。希望这会让他们更容易。 在此示例中,我希望响应为 Worksheet 或 ApiError // typede
我的项目目录结构是这样的: - schema.graphql - package.json - packages -- types --- package.json --- src ---- grap
我想知道当订阅接收到新数据时是否有一种优雅的方式来触发react-apollo中查询的重新获取(数据在这里并不重要,将与前一个相同)。我只是在这里使用订阅作为通知触发器,告诉 Query 重新获取。
我使用 Apollo、React 和 Graphcool。我有一个查询来获取登录的用户 ID: const LoginServerQuery = gql` query LoginServerQ
出于分析目的,我想跟踪所有 graphql 操作的客户端(包括 @client 操作)。我无法在 API 中找到合适的选项,想知道这在 apollo 客户端级别是否可行,或者我是否需要引入一些代理来拦
我是一名优秀的程序员,十分优秀!