首页 > 解决方案 > 如何处理导致不需要的组件重新安装的反应 History.Push()?

问题描述

问题

在组件内部调用history.push()似乎会导致整个反应组件卸载和重新安装;导致毫无意义的远程服务调用。

具体来说,我有一个在组件条目上触发的远程服务调用。我不希望组件重新安装,也不希望服务调用重新运行(它很慢)。

似乎无论如何history.push(location.pathname + '?' + encodeURI(urlSearchParams.toString()));都会导致卸载。我使用不正确吗?是否有更好的方法来跟踪用户过滤器更改的历史,而不必担心无关的服务调用?

意图

我正在利用history.push()对查询参数的更改来更新浏览器历史记录。查询参数控制表数据的过滤,例如?sort=asc&isCompleted=true等。

当用户更改他们的过滤设置时,我打算简单地过滤存储在 state 中的现有表数据,而不是远程重新加载数据并强迫用户坐下来等待。我还希望用户能够与包含适当过滤器的其他用户共享 URL。

我试过的

组件代码

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>);

}

标签: javascriptreactjstypescriptreact-router

解决方案


From what I can tell, after reading through several other similar stack overflow questions, there doesn't seem to be a way to not remount (rerender) the component. The history changes automatically causes a prop change from how react router dom handles things under the hood. Most of these questions that I find used class components and the answer was to use shouldComponentUpdate to see if the previous path was equal to the new path. If they were equal then they would return, essentially making it so they wouldn't rerender.

shouldComponentUpdate: function(nextProps, nextState) {
  if(this.props.route.path == nextProps.route.path) return false;
  return true;
}

Source: Prevent react-router history.push from reloading current route

So now the issue would be converting this to work with hooks. I'll need to run some tests, but I believe this should work for your use case.

useEffect(() => {
    fetchTableData();
}, [location.pathname]);

通过添加location.pathname为依赖项,仅当路径名实际更改时才应调用此效果。


推荐阅读