如何处理导致不需要的组件重新安装的反应 History.Push()?
Posted
技术标签:
【中文标题】如何处理导致不需要的组件重新安装的反应 History.Push()?【英文标题】:How to handle react History.Push() causing undesired component remounts? 【发布时间】:2020-11-05 13:06:06 【问题描述】:问题
在组件内部调用history.push()
似乎会导致整个react 组件卸载和重新安装;导致毫无意义的远程服务调用。
具体来说,我有一个在组件入口时触发的远程服务调用。 我不希望组件重新挂载,也不希望服务调用重新运行(它很慢)。
似乎history.push(location.pathname + '?' + encodeURI(urlSearchParams.toString()));
将导致卸载无论如何。我使用不正确吗?是否有更好的方法来跟踪用户过滤器更改的历史记录,而不必担心无关的服务调用?
意图
我正在使用history.push()
来更新浏览器历史记录,以更改查询参数。查询参数控制表数据的过滤,例如?sort=asc&isCompleted=true等
当用户更改他们的过滤设置时,我打算简单地过滤存储在状态中的现有表数据,而不是远程重新加载数据并迫使用户坐下来等待。我还希望用户能够与包含适当过滤器的其他用户共享 URL。
我的尝试
尝试完全删除 history.push(),只使用状态。这可行,但意味着不可能有一个可共享的 URL,其中过滤器作为查询参数附加。 尝试修改 useEffect() 和 useRef() 但因不断重新安装而感到沮丧。组件代码
import React, useEffect, useState from 'react';
import useLocation, useHistory from 'react-router-dom';
function useQuery()
return new URLSearchParams(useLocation().search);
export const WidgetTable = () =>
let urlSearchParams = useQuery();
let history = useHistory();
let location = useLocation();
const [originalTableData, setOriginalTableData] = useState<TableData| undefined>(undefined);
const [filteredTableData, setFilteredTableData] = useState<TableData| undefined>(undefined);
// go get the table data from the remote service
const fetchTableData = async () =>
<- go remotely fetch table data and then set originalTableData ->
// triggered when a user sets a filter on the table (updates the data displayed in the table)
const filterTableData = () =>
<- filter the existing table data in state and then set the filterdTableData ->
// also triggered when a user sets a filter on the table (updates the URL in the browser)
const setFilter = (filterToSet: ReleasePlanFilterType, value: string) =>
switch (filterToSet)
case ReleasePlanFilterType.Target:
if (urlSearchParams.get(filterToSet))
urlSearchParams.set(filterToSet, value);
else
urlSearchParams.append(filterToSet, value);
break;
<snip>
// We've set the filter in the query params, but persisting this to the history causes a reload :(
history.push(location.pathname + '?' + encodeURI(urlSearchParams.toString()));
useEffect(() =>
fetchTableData();
, []);
return (<snip> a fancy table and filtering controls <snip>);
【问题讨论】:
【参考方案1】:据我所知,在阅读了其他几个类似的堆栈溢出问题之后,似乎没有办法不重新安装(重新渲染)组件。历史更改会自动导致 react 路由器 dom 如何处理幕后事务的 prop 更改。我发现这些问题中的大多数都使用了类组件,答案是使用 shouldComponentUpdate 来查看以前的路径是否等于新路径。如果它们是平等的,那么它们就会返回,本质上是这样它们就不会重新渲染。
shouldComponentUpdate: function(nextProps, nextState)
if(this.props.route.path == nextProps.route.path) return false;
return true;
来源:Prevent react-router history.push from reloading current route
所以现在的问题是将其转换为使用钩子。我需要运行一些测试,但我相信这应该适用于您的用例。
useEffect(() =>
fetchTableData();
, [location.pathname]);
通过将location.pathname
添加为依赖项,此效果应该仅在路径名实际更改时调用。
【讨论】:
使用 location.pathname 似乎没有按预期工作。上面或下面的组件是否可能由于类似原因导致卸载?例如,如果父级也有位置道具,它不会也触发卸载吗? @Ray 发生了什么事,它似乎没有按预期工作?我已经通过代码沙箱尝试了一个简单的例子,它似乎正在工作。您的示例似乎也可以正常工作。可能有其他组件会导致卸载/重新安装。你能提供一个带有包装组件的代码框吗? 有效地获得相同的行为,更改过滤器并调用 history.push() 触发 useEffect,即使按照描述设置 location.pathname。该组件有好几层深 - 它可能是其中一个父组件的问题。有没有办法诊断其中一位父母是否有嫌疑?一些父组件也使用了历史。我可以尝试在沙盒中复制它,但我需要一些时间来解开任何“特定于工作”的代码。 经过一番摸索,我发现一个父组件正在使用component='WidgetTable'。将其切换为渲染,以及您建议的更改,现在会产生正确的行为! 太棒了!真高兴你做到了。不久前我打算把我的 Codesandbox 发给你,但是开会了。【参考方案2】:经过数小时的调试,我设法解决了这个问题:
<Route exact path="/">
<Home />
</Route>
代替:
<Route exact path="/" component=Home />
这适用于react-router
v5。
【讨论】:
我确认这也是我所看到的。知道为什么会这样吗? 我认为这是库中的一个错误。 应该是公认的答案!这节省了我的时间以上是关于如何处理导致不需要的组件重新安装的反应 History.Push()?的主要内容,如果未能解决你的问题,请参考以下文章