先前我们已经了解了 CSR、SSR 与 SSG 的优劣,SSR 与 SSG 都是 pre-rendering 的策略,在 Next.js 中分别提供 getServerSideProps
与 getStaticProps
两个 API,可以很简单地让页面变成 pre-rending。
但是让所有的页面都是 pre-rendering 会让服务器的负担很大,并非所有的页面都需要 pre-rendering,像是资料变动快速的页面,或是需要动态跟使用者互动拿到资料的页面,采用 CSR 的策略会是更好的选择。
在 Next.js 中并没有内建的 CSR 的解决方案,同个团队开发了一个名为 SWR 的套件,可以用来打 API 获得资料,并且拥有许多很棒的功能,让跟服务器互动拿到资料这件事如鱼得水。
SWR 是由开发 Next.js 的团队成员在 2019/10/29 开源的专案,到 2021 年 9 月时每周的下载量大约 30 万左右,且星星数有 1.8 万颗。
SWR 这个套件的名称是来自於 stale-while-revalidate
,这是一个判别 HTTP cache 失效的策略,被发表於 HTTP RFC 5861。 SWR 会优先从 cache 中取得资料,如果资料的 cache 已经过期,再打 API 取得新的资料 (revalidate),同时也会更新 cache 中的资料。
这里的 revalidate 策略是不是听起来跟 getStaticProps
的 revalidate
有点相近,在前面几个章节中我们有谈到 revalidate
这项参数可以决定一个页面多久需要重新被生成新的 HTML 档案,同样地也是使用 stale-while-revalidate
的策略。
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((r) => r.json());
function Profile() {
const { data, error } = useSWR("/api/user", fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}
SWR 的起手式非常简单,在以上的范例中 useSWR
带有 2 个参数:
key
: key
是一个字串,作为资料 unique idfetcher
: fetcher
被用来传入如何取得资料的函式,例如: 请求 API 经常使用的 fetch 或是 axios,将它们包装成函式传入。范例中 SWR 的回传值有 data
与 error
,它们分别为 fetcher
的回传结果,以及从 fetcher
被丢出的错误。
在资料尚未载入时,data
都是回传 undefined
,因此,我们就可以利用 data
的回传值判断要不要渲染资料,用这种方式实现非同步渲染的效果。
有时候,我们希望 API 的请求是能够由使用者自己掌握的,而不是在元件载入时就自动发出请求。像是常见我们在购物网站看到「查看更多」的按钮,在点选按钮後才载入更多的商品资讯。
要使用 SWR 达成这件事也很简单,有三种不同的写法:
// key 值为 null 时不打 API
const { data } = useSWR(shouldFetch ? "/api/data" : null, fetcher);
// 跟前一种很像,只是变成 callback function
const { data } = useSWR(() => (shouldFetch ? "/api/data" : null), fetcher);
// 或者是让 SWR 帮我们判断,如果 user.id 为 undefined 则呼叫 API 会抛出 error
const { data, error } = useSWR(() => "/api/data?uid=" + user.id, fetcher);
有时候 API 需要带多个参数,例如 /products/[id]
或 /posts/[year]/[month]/[day]
两者都会额外带一些参数,而且可能是动态的数值
❌ 然而,要注意的是在使用 SWR 时,官方建议不要这样做:
useSWR('/product', url => fetcher(url, id))
原因是,SWR 决定要不要 refetch 取决於第一个参数 key
有没有改变,如果像是上述这样使用,尽管 id
变动了,key
仍然是 /product
,因此,SWR 的回传值就会是错误的。
? 正常的使用方式是将 key
变成 array,把 id
放 array 中:
useSWR(['/product', id], fetcher)
如此一来, 当 id
改变时,SWR 就可以成功被通知并发送 API 的请求。
你以为这样就没问题了吗 ?
❌ 因为 SWR 用的是 shallow compare,当参数的型态是 Object 时,比较的是 reference。因此,在每次元件渲染时,reference 都会被重新分配,SWR 会误以为 key
改变了,会再次发送一次请求。
useSWR(["/api/user", { id }], fetcher);
? 要解决传物件的方法有两种,第一种是官方推荐的方式,如果真的需要传入物件到 fetcher
中,则把物件当作是 fetcher
的参数,而不是 key
的参数。
useSWR(["/api/user", id], (url, id) => fetcher(url, { id }));
第二种方法是用 react 的 useMemo
把物件的 reference 记忆起来,如此一来 SWR 就不会因为重新渲染而不断执行 fetcher
const params = useMemo(() => ({ id }), [id]);
useSWR(["/api/user", params], fetcher);
在 Next.js 中很常见 dynamic routes 的页面,在这些页面中想要使用 SWR 则必须搭配 conditional fetching 与 multiple arguments 两种技巧。在前面的章节中有提到 Next.js 中 router.query
在第一次渲染时是空物件 {}
,所以从 router.query
中解构赋值的 id
为 undefined
,此时如果直接打 API 就会发生错误。
而为了根据不同的页面,需要传入不同的 id
至 fetcher
中,所以要用 multiple arguments 的方法传递 id
。
const router = useRouter();
const { id } = router.query;
const { data: product } = useSWR(id ? ["/products", id] : null, fetcher);
在前面几个章节中,我们使用 pre-rendering 建构了产品列表页面与产品详细页面,资料是透过 getStaticProps
或 getServerSiderProps
传入 props 到 component 中,现在我们要尝试另一种做法,在 component 中打 API 拿到页面中需要的资料,并渲染页面。
产品列表页面可以看到所有商品的讯息,以卡片列表是呈现所有的商品;而产品详细页面则是显示单一商品的详细讯息,使用者可以点击列表中的卡片标题进入产品详细页面。
/pages/products/index.tsx
style: https://gist.github.com/leochiu-a/c4b8ac14ed823bcf6b8326717e594910
SWR 会需要放入两个参数,分别为 key
与 fetcher
, key
是用来定义资料的 unique id,而 fetcher
则是用来呼叫 API 取得资料的 function。
因为我们需要的资料是所有的产品,先定义 key
为 /products
代表的是产品列表这个资源,而 fetcher
则是使用原生的 fetch API,打 [https://fakestoreapi.com](https://fakestoreapi.com)
这个服务提供的 API,并回传 json
资料格式。
由於 SWR 是非同步的,第一次渲染时从 data
解构赋值的 products
是 undefined
,如果直接透过 map
迭代资料则是会发生错误,为了避免这个情况,则在渲染列表之前,用条件式渲染的方式先渲染资料正在载入中讯息,并在资料取得後再渲染产品列表。
import useSWR from "swr";
import ProductCard from "../../components/ProductCard";
import { Product } from "../../fake-data";
import { PageTitle, ProductGallery } from "./index.style";
const fetcher = (url: string) =>
fetch(`https://fakestoreapi.com${url}`).then((res) => res.json());
const Home = () => {
const { data: products } = useSWR<Product[]>("/products", fetcher);
if (!products) return <div>loading</div>;
return (
<>
<PageTitle>商品列表</PageTitle>
<ProductGallery>
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</ProductGallery>
</>
);
};
export default Home;
/pages/products/[id].tsx
style: https://gist.github.com/leochiu-a/56106bd3bd24efb7d75082f0fb60b2f3
由於产品详细页面是 dynamic routes, 要注意的有三点:
id
是一个动态的值,所以我们要藉由从 router.query
中取得 id
再决定要打哪支 API。这边有一个要注意的地方是 router.query
第一次渲染时是空物件 {}
,解构赋值取得的 id
是 undefined
,如果打 API 其 url 则会是 /products/undefined
,此时就会发生错误id
有值时才打 APIid
是动态的,所要使用前面提及的 multiple arguments 传入 id
至 fetcher
中剩下的程序码则是与产品列表页面大同小异,所以就不再赘述。
import useSWR from "swr";
import { useRouter } from "next/router";
import Link from "next/link";
import { Product as ProductType } from "../../fake-data";
import ProductCard from "../../components/ProductCard";
import { PageTitle, ProductContainer, BackLink } from "./[id].style";
const fetcher = (url: string, id: string) => {
return fetch(`https://fakestoreapi.com${url}/${id}`).then((res) =>
res.json()
);
};
const Product = () => {
const router = useRouter();
const { id } = router.query;
const { data: product } = useSWR<ProductType>(
id ? ["/products", id] : null,
fetcher
);
if (!product) return <div>loading</div>;
return (
<>
<PageTitle>商品详细页面</PageTitle>
<BackLink>
<Link href="/products">回产品列表</Link>
</BackLink>
<ProductContainer>
<ProductCard product={product} all />
</ProductContainer>
</>
);
};
export default Product;
在产品列表页面与产品详细页面中使用 SWR 的其中一个好处是资料会被 cache ,所以在第一次打 API 取得资料後,再次回到页面时就不用再重新打 API 取得资料,举例来说:
/products
/products
取得资料/products/1
/products/1
取得资料/products
/products/1
取得资料/products/1
时, SWR 也可以直接读取 cache,不用再次打 API 取得资料因为我们使用 TypeScript 撰写 Next.js,如果 useSWR
没有定义范型,则 data
会是 any
的型别,不可控的 any
对於 component 不是件好事。
读者可以从上面的范例中看到,在使用 useSWR
时候会传入一个范型,如此一来 data
的型别就不会是 any
,而是我们传入的型别。
我们再近一步看到 useSWR
的型别定义,实际上 useSWR
可以传入两个范型,分别是 data
与 error
,所以如果有需要使用 error
的资料,则可以传入第二个范型至 useSWR
。
declare function useSWR<Data = any, Error = any>(...)
在这篇文章中,我们了解了 SWR 的基本使用方法,并且知道了在 Next.js 中一个常用的方法,在 dynamic routes 的页面,在这些页面中想要使用 SWR 则必须搭配 conditional fetching 与 multiple arguments 两种技巧。
此外,我们用 SWR 重构了产品列表页面与产品详细页面,除了从 pre-rendering 改成了 CSR 之外,还了解透过 SWR 可以读取 cache 的优点,在切换页面时,可以加速页面载入的速度,提升使用者体验。
>>: 【12】新手容易忽略的 logit 与 loss 之间的搭配
元件间的转换 在元素间的转换可以更加简单,,因为有了这一个动态组件 当按下单选键A会出现Compon...
开发前的新手纠结 商学院出身,非资工背景,团队内也没有熟悉 app 开发的人才 决定做 app 接触...
先到laravel专案找到环境变数档(.env) DB_CONNECTION=mysql DB_HO...
在处理一些短讯息的时候可以用 Message 的功能,这个功能跟 Label 有点类似,不一样的地方...
前言 今天接着把「透明」这个主题再延伸一些。 Scrum 在开发团队人数的考量上,从 2017 版本...