Kwe*_*Dev 19 react-redux redux-toolkit rtk-query
我正在尝试在我们当前的应用程序中实现无限滚动功能;
我们首先获取页面的前 5 个“帖子”。滚动到页面底部后,我们会获取接下来的 5 篇帖子。
这很好用,但是使用相同的查询意味着现有数据(前 5 个帖子)已被新数据替换。
是否可以将现有数据与新数据合并?
我可以将它们合并到位,例如使用类似的内容;const posts = [newPosts, oldPosts]但如果现有数据被修改,我们就会失去 RTK 查询提供的数据失效。
对于这种情况,推荐的方法是什么?
Daa*_*ijn 31
在 RTK 1.9 中,现在可以使用该merge选项将新获取的数据与当前位于缓存内的数据合并。确保将该选项与serializeQueryArgs或一起使用forceRefetch以保留数据的缓存条目。
createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
listItems: build.query<string[], number>({
query: (pageNumber) => `/listItems?page=${pageNumber}`,
// Only have one cache entry because the arg always maps to one string
serializeQueryArgs: ({ endpointName }) => {
return endpointName
},
// Always merge incoming data to the cache entry
merge: (currentCache, newItems) => {
currentCache.push(...newItems)
},
// Refetch when the page arg changes
forceRefetch({ currentArg, previousArg }) {
return currentArg !== previousArg
},
}),
}),
})
Run Code Online (Sandbox Code Playgroud)
使用这个你可以轻松实现无限滚动。更改pageNumber查询的参数,将自动获取新数据并将其与缓存中已有的数据连接起来。
为了说明这一点,我在CodeSandbox上创建了一个工作示例。
这是一种利用 rtk-query 的缓存优势实现无限加载的解决方法
在我的示例中,后端响应类型是
{ items: anyDTO[], count: number /* totalCount */ }
为了使其在使标签无效时正常工作,我必须使用钩子获取第一页并在 useEffect 中处理其余部分。
import { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import * as R from 'ramda';
import { ApiEndpointQuery } from '@reduxjs/toolkit/dist/query/core/module';
import { QueryHooks } from '@reduxjs/toolkit/dist/query/react/buildHooks';
interface UseLazeyInfiniteDataProps<T, N> {
api: T;
/** any rtk-query api: passing the whole enpoint so we have access to api utils to invalidate provided tags */
apiEndpointName: N;
/** apiEndpoint name to retrieve correct apiEndpoint query which will have 'initiate' and 'useQuery' */
apiArgs: { [key: string]: any; params: object };
/** apiArgs are the query arguments it should have a params objec */
limit?: number;
/** limit or page-size per request (defaults 20) */
invalidatesTags?: any[];
}
/**
* This hook is for having infinite loading experience with caching posibility of rtk-query
* it's storing the data comming from rtk-q to local useState throgh a useEffect hook
* in orther to make it work when invalidating tags it makes the first page request through rtk-query hook
* and whenever it changes it will refetch the rest data
*/
const useLazyInfiniteData = <
T extends { endpoints: any; util: any },
N extends keyof T['endpoints'],
>({
api,
apiEndpointName,
apiArgs,
limit = 20,
invalidatesTags,
}: UseLazeyInfiniteDataProps<T, N>) => {
const dispatch = useDispatch<any>();
const [pageNumber, setPageNumber] = useState(1); // first load only page 1
const [maxPage, setMaxPage] = useState(0); // we don't know how many pages could exists yet
const [accData, setAccData] = useState<any[]>([]);
const [isFetchingMore, setIsFetchingMore] = useState(false);
const apiEndpoint: ApiEndpointQuery<any, any> & QueryHooks<any> =
api.endpoints[apiEndpointName];
// we need this extra hook to automate refetching when invalidating tag
// this will make the useEffect rerender if the first page data changes
const {
currentData: firstPageData,
isLoading,
isFetching,
refetch: refetch_,
} = apiEndpoint.useQuery({
...apiArgs,
params: R.mergeRight(apiArgs.params, { offset: 0, limit }),
});
const refetch = useCallback(() => {
if (invalidatesTags) {
dispatch(api.util.invalidateTags());
}
refetch_();
}, [api.util, dispatch, invalidatesTags, refetch_]);
/** when params change like changing filters in the params then we reset the loading pages to 1 */
useEffect(
function resetPageLoadDataForSinglePage() {
setPageNumber(1);
},
[apiArgs.params],
);
useEffect(
function loadMoreDataOnPageNumberIncrease() {
if (firstPageData)
setMaxPage(Math.ceil((firstPageData as any).count / limit));
if (pageNumber === 1) {
setAccData((firstPageData as any)?.items ?? []);
}
if (pageNumber > 1) {
setIsFetchingMore(true);
const promises = R.range(1, pageNumber).map((page) =>
dispatch(
apiEndpoint.initiate({
...apiArgs,
params: R.mergeRight(apiArgs.params, {
offset: page * limit,
limit,
}),
}),
).unwrap(),
);
Promise.all(promises)
.then((data: any[]) => {
const items = R.chain(R.propOr([], 'items'), [
firstPageData,
...data,
]);
setAccData(items);
})
.catch(console.error)
.finally(() => {
setIsFetchingMore(false);
});
}
},
[apiEndpoint, apiArgs, dispatch, firstPageData, limit, pageNumber],
);
/** increasing pageNumber will make the useEffect run */
const loadMore = useCallback(() => {
setPageNumber(R.inc);
}, []);
return {
data: accData,
loadMore,
hasMore: pageNumber < maxPage,
isLoading,
isFetching,
isFetchingMore,
refetch,
};
};
export default useLazyInfiniteData;
Run Code Online (Sandbox Code Playgroud)
用法:假设你有rtk查询API:
const extendedApi = emptySplitApi.injectEndpoints({
endpoints: (build) => ({
example: build.query({
query: ({x, params: { offset, limit }}) => 'test'
})
}),
})
Run Code Online (Sandbox Code Playgroud)
您可以像这样使用它:
useLazyInfiniteData({
api: extendedApi,
apiEndpointName: 'example',
apiArgs: { x }, // better to be a memorized value
})
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
16208 次 |
| 最近记录: |