【问题标题】:Change detection in ReactReact 中的变更检测
【发布时间】:2021-02-05 13:54:12
【问题描述】:

我想在某些数据更改时执行副作用,例如

const useSearchResults = () => {
  const location = useLocation();
  const [data, setData] = useState();
  useEffect(() => {
    Api.search(location.query.q).then(data => setData(data));
  }, [location.query.q]);

  // Compute some derived data from `data`.
  const searchResults =
    data &&
    data.map(item => ({
      id: item.id,
      name: `${item.firstName} ${item.lastName}`
    }));

  return searchResults;
};

const Component = () => {
  const searchResults = useSearchResults();
  useEffect(() => {
    alert('Search results have changed'); // Side-effect
  }, [searchResults]);
  return <pre>{JSON.stringify(searchResults)}</pre>;
};

如果某些原因导致 Component 重新渲染,即使 searchResults 没有更改,alert 也会触发,因为我们映射了 useSearchResults 中的底层稳定数据,在每次渲染时创建一个新实例。

我最初的方法是使用useMemo

  // Stabilise `searchResults`'s identity with useMemo.
  const searchResults = useMemo(
    () =>
      data &&
      data.map(item => ({
        id: item.id,
        name: `${item.firstName} ${item.lastName}`
      })),
    [data]
  );

但是useMemo has no semantic guarantee 所以它(理论上)只对性能优化有好处。

React 是否为这个(常见的?)问题提供了直接的解决方案?

【问题讨论】:

  • 过去我使用 reselect 从解决了这个问题的 redux 存储中获取数据,但是考虑到它一次只存储 1 个值,所以如果你有多个实例,这有点复杂您需要创建一个选择器的多个实例,这很繁琐。
  • 做一些研究,我在这里找到了一些详细的讨论github.com/facebook/react/issues/15278,这使我找到了github.com/alexreardon/use-memo-one,这似乎可以解决问题,但是鉴于该软件包不是很受欢迎,我很困惑为什么这是一个更广泛记录/解决的问题????

标签: javascript reactjs react-hooks


【解决方案1】:

如果副作用不重新运行非常重要,您可以将比较基于深度相等性检查。我们希望看到搜索结果实际上是不同的结果,而不仅仅是具有相同结果的新数组。

您可以使用浮动的usePrevious 钩子的各种实现,但基本上它只是一个ref。将以前版本的搜索结果保存到ref。当您的效果运行时,查看当前结果是否与之前的结果不同。如果是,请执行您的副作用并将以前的更新为当前。

const Component = () => {
  const searchResults = useSearchResults();

  // create ref for previous
  const comparisonRef = useRef('');

  useEffect(() => {
    // stringify to compare
    if ( JSON.stringify(searchResults) !== JSON.stringify(comparisonRef.current) ) {
       // run side effect only if true
       alert('Search results have changed');
       // update the ref for the next check
       comparisonRef.current = searchResults;
    }
  }, [searchResults, comparisonRef]);

  return <pre>{JSON.stringify(searchResults)}</pre>;
};

【讨论】:

  • 是的,重要的是副作用不会重新运行,因为它是一个分析事件。我想避免深度相等检查,因为 API 响应可能非常大,所以它不会对性能有好处(当然它现在不会经常重新渲染,但谁知道呢,有人可能会添加 useTime 或类似的东西可能需要重构此更改检测)。到目前为止,我认为github.com/alexreardon/use-memo-one 是最明智的解决方案。
猜你喜欢
  • 2018-05-03
  • 2015-11-24
  • 2017-05-25
  • 1970-01-01
  • 2020-06-11
  • 1970-01-01
  • 2020-12-15
  • 2021-10-06
  • 2022-11-11
相关资源
最近更新 更多