如何处理导致不需要的组件重新安装的反应 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()?的主要内容,如果未能解决你的问题,请参考以下文章

Vue 组件 - 如何处理非反应性数据?

平板电脑开不了机怎么办?如何处理

通量/反应:如何处理商店中过滤的 api 数据

SQL2016卸载后重新安装,安装时显示数据库找不到启动引擎句柄,如何处理?

如何处理 React / Flux 组件中的状态转换

由于内存导致重新分配失败时如何处理?