下面是我对该组件的第一次尝试,该组件应该在过滤器道具更改时重新查询数据。此外,无论何时检测到这样的更改,它都应该包装回第一页(第一次出现useEffect
)。第三,用户应该能够手动转到下一页(nextPage
回调)。
function Fetcher(filters) {
const [page, setPage] = React.useState(0);
const nextPage = React.useCallback(() => {
setPage((p) => p + 1);
}, []);
React.useEffect(() => {
setPage(0);
}, [filters]);
React.useEffect(() => {
externalRequest(filters, page);
}, [filters, page]);
return <button onClick={nextPage}></button>;
}
不幸的是,这自然不会正常工作,因为在第一效果钩子中重置页面将被异步执行并延迟拾取:如果用户坐在第1页并且过滤器更改,externalRequest
将以以下方式触发两次:
externalRequest(1, newFilters)
externalRequest(0, newFilters)
我认为是一个解决方案是将page
存储为可变引用,如下所示:
function Fetcher(filters) {
const page = React.useRef(0);
const nextPage = React.useCallback(() => {
page.current = page.current + 1;
}, []);
React.useEffect(() => {
page.current = 0;
}, [filters]);
React.useEffect(() => {
externalRequest(filters, page);
}, [filters, page.current]);
return <button onClick={nextPage}></button>;
}
或将其提升并作为prop从外部传递(这不是理想的,因为我想避免将此依赖项泄漏到外部)。前一种解决方案(使用useRef
)的问题是,当nextPage
被调用时,组件不会重新渲染,并且需要强制重新循环。后者的问题是,它把管理page
依赖的"负担"放在了外部。它解决了这个问题。我的问题是,是否存在一种方法来构建Fetcher,使我们得到两全其美,即页面状态内部的组件和它的状态的剩余部分,而不是一个引用?如果这个例子看起来有点做作,我很抱歉,但我觉得这种模式经常发生,我真的很感激你对这件事的一些投入!
您可以通过从第二个效果依赖项中删除filters
来解决此问题
为什么?因为你要确保页面会触发重新渲染效果,所以,通过在两个效果中设置过滤器,这意味着触发两次,一次用于过滤器,然后页面也更新,所以第二次渲染将被触发。
你可以在这里查看演示。
function Fetcher(props) {
const [page, setPage] = useState(0);
const nextPage = useCallback(() => {
setPage((p) => p + 1);
}, []);
useEffect(() => {
setPage(0);
}, [props.filters]);
useEffect(() => {
console.log(props.filters, page);
}, [page]);
return <button onClick={nextPage}>Next Page</button>;
}
这里有一个重要的提示:每个依赖项在更新时都会触发重新渲染,所以我们把它放在依赖项数组中,但是你也需要确保的渲染树。同样,你有另一个解决方案,通过保存旧的过滤器并检查新值是否等于旧值。
同样,你可以使用重构组件和构建函数,并根据条件调用你需要的…
我不建议在这种情况下使用useRef…这不是一个正常国家的解决方案,我认为第一个解决方案是公平的。
UPDATE 1:(为了注意过滤器的变化):
function Fetcher(props) {
const [page, setPage] = useState(0);
const [oldFilters, setOldFilters] = useState(props.filters);
const nextPage = useCallback(() => {
setPage((p) => p + 1);
}, []);
useEffect(() => {
setPage(0);
if(JSON.stringify(props.filters) !== JSON.stringify(oldFilters)){
setOldFilters(props.filters);
}
}, [props.filters]);
useEffect(() => {
console.log(props.filters, page);
}, [page, oldFilters]);
return <button onClick={nextPage}>Next Page</button>;
}
此外,setPage(() => () => 0);
是一个很好的选择(像下面的Pasato回答),通过每次更新引用来触发重新渲染来处理这个问题。
谢谢Anees,这太简单了。您的解决方案没有解决一个边缘警告,即过滤器更改时仍然在第0页上—在这种情况下,依赖于页面的效果不会触发。我最终选择了以下内容:
function Fetcher(props) {
const [page, setPage] = useState(() => () => 0);
const nextPage = useCallback(() => {
setPage((p) => () => p() + 1);
}, []);
// do not run on the mount, but only when props change
// as otherwise the initial request is fired twice
useUpdate(() => {
setPage(() => () => 0);
}, [props.filters]);
useEffect(() => {
const actualPage = page();
}, [page]);
return <button onClick={nextPage}>Next Page</button>;
}
状态的惰性在这种情况下是无关的-我只是"包装"它。使两个page
实例在引用上不同,即使它们返回相同的数字。此外,页面重置器不会在挂载时运行,因此我们不会查询两次。