useSwr 使用分析
第一个参数:
1、第一个参数是key ,下文是写成 getKey() 方法 返回的数据会给到第二个参数的入参。
2、写成 key ,要保证全局是唯一的!重复的key 会被useswr认为是同一个,使用同样的缓存
3、分页接口的情况下,必须写成 getKey() 方法,getKey的依赖项变化时,会导致 接口从第一页重新请求。
4、return null 就是不调接口。
第二个参数:
1、就是接口函数,普通的useswr 直接用接口函数即可
2、下文的代码中是由于分页接口中getkey依赖项参数可能一致,所以写成了这种 return [queryPackagesList, params],保证每个分页的key都不一样。因此 第二个参数接收的时候也是[, v]=> queryPackagesList({ …v, per_page })
第三个参数
是配置项。具体的见本文最后一小节。
const { data = [], size, setSize, isValidating, mutate } = useSWRInfinite(
(pageIndex: number, previousPageData: ListProps<ItemProps>) => {
// 首页,没有 `previousPageData`
if (pageIndex === 0) {
return [queryPackagesList, params];
}
// 加载更多
if (previousPageData && previousPageData.next) {
return [queryPackagesList, { ...params, ...previousPageData.next }];
}
// 最后一页
return null;
},
([, v]) => queryPackagesList({ ...v, per_page }),
useSWRInfiniteDefOption,
);
mutate用途:
1、mutate()从某一个useSWR里return出来,是用作当前key值下强制请求。主要用于刷新、重复点击查询按钮等功能。
2、更新其他接口缓存里的data , mutate(key,data,option)
getKey的依赖项:
1、getKey的依赖项
params
变化的时候,即setParams(newParams)会引起useSWRInfinite
触发重新请求。2、若
hashCode(query) === hashCode(params)
,即params属性没发生变化,则useSWRInfinite
不会重新请求,如此时有需求,需要重新加载,则用mutate()来实现。
示例代码:
const { items: customerList, isLoadingList, has_next, size, setSize, mutate } = useCustomerList( params );
useEffect(() => {
const query = { ...baseParams, ...getParamsFromUrl(location.query), }; setParams(query);
if (hashCode(query) === hashCode(params)) mutate();
}, [location]);
setSize()的用途:
1、setSize()是
useSWRInfinite
return出来的一个方法。通过setSize(size+1)实现加载下一页的功能。2、setSize(1) 会把当前
useSWRInfinite
的size重新置为1,只展示第一页数据,如已经加载过,则用的是缓存数据,此时不调用接口。
使用场景:
在点击查询或重置按钮时,产品这边的需要是,在点击后,应重新查找并展示其相应的第一页内容。
问题描述:
使用mutate+setSize方法来实现,虽页面上给到的结果是正确的,但有可能导致重复调用api的情况
原因分析:
使用useSWRInfinite分页加载数据,(getkey依赖项)相同参数下,若重复点击查询,直接使用mutate(),会导致将当前已加载过的页数,再次全部重新请求并展示。 例如,目前加载了10页数据。参数(getkey依赖项)不变的情况下,调用mutate(),会将前10页的数据再次请求并展示出来。
解决方法:
如果某种场景下,必须实现,重复点击查询按钮,只调用并展示第一页数据。给出3种解决方案。经过讨论分析,使用方法3 。
方法 1
const newParams: UseListProps = {
...baseParams,
...getParamsFromUrl(location.query),//有变动的参数
};
setParams(newParams);
if (hashCode(newParams) === hashCode(params)) {setSize(1); mutate()}
该方法存在的问题:
先把页面变为1 ,再使用mutate()强制重新请求api,此时获取到的第一页的数据是数据库中给出的最新内容,后续点击加载更多,如已经调用过的相关接口(点击查询以前,对相同的参数,进行过加载),则仍使用缓存数据,如未调用过,则重新请求。
方法 2
...同上
if (hashCode(newParams) === hashCode(params)) {mutate();setSize(1); }
该方法存在的问题:
先mutate()强制重新请求数据,此时会重新调用当前已加载过的所有接口。
setSize(1)
只有第一页展示出来
方法3
let newParams: UseListProps = {
...baseParams,
...getParamsFromUrl(location.query),//有变动的参数
};
if (hashCode(newParams) === hashCode(params)) newParams={...newParams, date:+new Date()}
setParams(newParams)
分析:在点击查询的时候给params里加个时间戳,通过更新getkey的依赖项实现此功能。强制促使getKey的依赖项发生变化,从而引起useSWRInfinite
触发重新请求,且不再使用缓存。
点击加载更多,切换分页后,无法正确加载数据问题
问题描述:
使用useSWRInfinite分页加载数据,per_page放到getKey的依赖项params里,会导致改变per_page后,无法正确加载数据问题。
例如,目前加载了第一页,每页5条数据,切换为每页10条,再点击加载更多,会导致仍是per_page=5。若setParams(newParams)则会导致只加载前10条。
原因分析:
useSWRInfinite的第一个参数getKey会有一个依赖项params(即接口入参),一旦setParams(newParams)被调用,就触发数据重新加载。且从第一页开始加载。
export const useCustomerList = (params: UseCustomerListProps) => {
const { data, size, setSize, mutate, isValidating } = useSWRInfinite(
(pageIndex: number, previousPageData: ListProps<CustomerInfo>) => {
// 首页,没有 `previousPageData`
if (pageIndex === 0) {
return [getCustomerInfoList, params];
}
// 加载更多
if (previousPageData && previousPageData.next) {
return [getCustomerInfoList, { ...params, ...previousPageData.next }];
}
// 最后一页
return null;
},
([, v]) => getCustomerInfoList(v),
{ ...useSWRInfiniteDefOption, revalidateOnMount: false },
);
const items = data?.flatMap((value) => value?.items || []) || [];
const has_next = data && data[data?.length - 1]?.has_next;
return { items, has_next, isLoadingList: isValidating, size, setSize, mutate };
};
解决办法:
此种情况下,只能解绑per_page和params(getkey的依赖项),把per_page作为第二单独参数传到自定义hook。使得per_page的变化并不会导致依赖项的变化,这样useSWRInfinite也不会触发重新验证的机制。
export const useCustomerList = (params: UseCustomerListProps, per_page: string) => {
const { data, size, setSize, mutate, isValidating } = useSWRInfinite(
(pageIndex: number, previousPageData: ListProps<CustomerInfo>) => {
// 首页,没有 `previousPageData`
if (pageIndex === 0) {
return [getCustomerInfoList, params];
}
// 加载更多
if (previousPageData && previousPageData.next) {
return [getCustomerInfoList, { ...params, ...previousPageData.next }];
}
// 最后一页
return null;
},
([, v]) => getCustomerInfoList({ ...v, per_page }),
{ ...useSWRInfiniteDefOption, revalidateOnMount: false },
);
const items = data?.flatMap((value) => value?.items || []) || [];
const has_next = data && data[data?.length - 1]?.has_next;
return { items, has_next, isLoadingList: isValidating, size, setSize, mutate };
};
useSWR
通用配置
export const useSWRDefOption = {
// 以下配置(useSWRInfinite正常生效)
revalidateOnMount: true, // 默认true 是否在页面onMount时,自动请求
shouldRetryOnError: true, // 默认true 在出现错误时 SWR 使用指数退避算法重发请求。该算法允许应用从错误中快速恢复,而不会浪费资源频繁地重试。你还可以通过 onErrorRetry 选项覆盖该行为
// 以下配置 (useSWRInfinite 需要配合revalidateFirstPage: true,且重新加载时,只会加载第一页)
refreshInterval: 1000, // 默认不会自动请求 每隔xx毫秒,自动请求接口,获取最新数据。
revalidateOnFocus: false, // 默认true 当你重新聚焦一个页面或在标签页之间切换时,SWR 会自动重新请求数据 。
revalidateOnReconnect: true, // 默认true 网络恢复时,是否自动重新请求 。
// 以下配置(useSWRInfinite永不生效 ,useSWRInfinite会保留页面离开前的数据和请求页数)
revalidateIfStale: true, // 默认true 切换路由,返回后是否使用缓存,若用缓存则不会重新调用接口
};
useSWRInfinite
继承了useSWR
的配置选项,同时,拥有一些特殊属性。
//useSWRInfinite 的几个特殊属性说明:
export const useSWRInfiniteDefOption: SWRInfiniteConfiguration = {
initialSize: 2, // 默认1 。初始加载的页数
revalidateAll: false, // 默认为 false, 每次加载下一页,是否尝试重新请求已加载的所有页面
persistSize: false, // 默认为 false: 当key([xx,params]) 发生变化时,是否将 page_size重置为 1 (或者 initialSize, 如果设置了该参数)
revalidateFirstPage: false, // 默认为 true, 每次加载下一页,是否重新请求第一页。
...useSWRDefOption,
};
revalidateIfStale
在**useSWRInfinite
** 中使用的特殊说明:useSWRInfinite会保留页面离开前的数据和请求页数
场景1:
revalidateIfStale:true
且revalidateOnMount: true
:即此页面是默认发起请求的。那么若此时从其他路由调跳回当路由,且查询参数没变的情况,那么useSWRInfinite
会使用路由离开前的缓存的数据和请求页数,不再重新调用接口。(注:详情参考管理端V3用户页面)
场景2:
revalidateIfStale:true
且revalidateOnMount: false
:即此页面是调用mutate(),手动发起请求的。那么若此时从其他路由调跳回当路由,且查询参数没变的情况,虽然useSWRInfinite
会缓存的离开前的数据和请求页数,但是由于 调用了mutate(),所以相当于强制刷新了,所以会重新调用接口,同时还是会默认加载上次已加载的页数。(注:详情参考管理端V3客户列表页面)