如何在数组依赖中正确使用 useEffect 挂钩。我从 redux 商店传递了状态,但我的组件仍然无限渲染

Posted

技术标签:

【中文标题】如何在数组依赖中正确使用 useEffect 挂钩。我从 redux 商店传递了状态,但我的组件仍然无限渲染【英文标题】:How to use useEffect hook properly with array dependency. I passed state from redux store and still my component renders infinitely 【发布时间】:2019-09-08 13:59:33 【问题描述】:

我正在使用 useEffect 钩子并使用函数 getStoreUsers 获取用户数据列表,该函数在响应上调度操作并存储 shopUsers(这是一个数组)在 redux 商店里面。

在数组依赖中,我写的是[shopUsers]。我不知道为什么它会导致无限渲染。

这是我如何使用 useEffect 钩子:

useEffect(() => 
    const  getStoreUsers, shopUsers  = props;
    setLoading(true);
    getStoreUsers().then(() => 
      setLoading(false);
    ).catch(() => 
      setLoading(false);
    );
  , [shopUsers]);

我只想在 shopUsers 数组中的数据发生变化时重新渲染组件。

如果我在数组依赖项中编写 shopUsers.length。它停止重新渲染。

但是,假设我有一个页面,当用户单击 userList 并在下一页更新用户数据时会打开该页面。更新后,我希望用户返回到以前未卸载的相同组件。因此,在这种情况下,数组长度保持不变,但数组索引中的数据会更新。所以 shopUsers.length 在这种情况下不起作用。

【问题讨论】:

【参考方案1】:

你可以做一个自定义的钩子来做你想做的事:

在本例中,我们替换数组中的最后一个元素,并在控制台中查看输出。

import React,  useState, useEffect, useRef  from "react";
import ReactDOM from "react-dom";
import  isEqual  from "lodash";

const usePrevious = value => 
  const ref = useRef();
  useEffect(() => 
    ref.current = value;
  );
  return ref.current;
;

const App = () => 
  const [arr, setArr] = useState([2, 4, 5]);
  const prevArr = usePrevious(arr);

  useEffect(() => 
    if (!isEqual(arr, prevArr)) 
      console.log(`array changed from $prevArr to $arr`);
     
  , [prevArr]);

  const change = () => 
    const temp = [...arr];
    temp.pop();
    temp.push(6);
    setArr(temp);
  ;

  return (
      <button onClick=change>change last array element</button>
  )
;

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

现场示例here。

【讨论】:

谢谢,看来您的解决方案对我有用。你能在这里先解释几件事吗,我们作为依赖传递数组的参数,react 是否像 isEqual 一样执行浅比较?如果不是,那么您不认为它应该其次,这个 useRef 是仅通过 redux hooks 引入的,还是以前也存在? isEqual 使用深度比较。 useRef 是 React 提供的钩子,是的。它的用法类似于React.createRef() 是的,抱歉我说的是深度比较:) 目前 React 只进行浅层比较,但未来可能会改变!【参考方案2】:

你的效果是基于“shopUsers”道具触发的,它本身会触发一个更新“shopUsers”道具的redux动作,这就是它不断触发的原因。

我认为您要优化的是组件本身的渲染,因为您已经在使用 redux,我假设您的 props/state 是不可变的,因此您可以使用 React.memo 重新渲染您的组件仅当其中一个道具发生变化时。

你还应该在你的钩子之外定义你的 state/props 变量,因为它们像这样在整个函数的范围内使用。

在您的情况下,如果您将一个空数组作为第二个参数传递给 memo,那么它只会在 ComponentDidMount 上触发,如果您传递 null/undefined 或不传递任何内容,它将在 ComponentDidMount + ComponentDidUpdate 上触发,如果您想要优化它,即使道具发生变化/组件更新,除非特定变量发生变化,否则钩子不会触发,那么您可以添加一些变量作为第二个参数

React.memo(function(props)
  const [isLoading, setLoading] = useState(false);
  const  getStoreUsers, shopUsers  = props;
  useEffect(() => 
    setLoading(true);
    getStoreUsers().then(() => 
      setLoading(false);
    ).catch((err) => 
      setLoading(false);
    );
  , []);
...
)

【讨论】:

是的,redux 操作会在组件首次挂载时更新 shopUsers。因此,让我们考虑 shopUsers 最初设置为 null 以及组件何时呈现。 useEffect 被执行。它访问 API 并将 shopUsers 设置为包含对象数组的用户数据。所以现在 shopUsers 更改它重新渲染,并尝试再次点击 api,并再次获取 shopUsers,但数据现在与以前相同。所以,从下一次开始,它不应该触发重新渲染。我有一个疑问。 useEffect 是否对数组和对象进行浅比较? 我不明白你想要做什么,这让我感觉很奇怪.. 你为什么要更新一个变量,它本身用于触发一个更新自身的调用?我不理解用例,但最常见的是你想要做的是有一个父组件,它在 componentDidMount 上进行 API 调用,或者使用空数组作为第二个参数的 useEffect(fn, []) 被触发一次,并且将结果作为道具传递给子组件以呈现它们。然后每当你想再次调用 API 时,它应该在父组件的事件处理程序中,它将更新道具。 你可以监听一些事件或者传递一个回调道具,当用户导航回来触发 API 调用再次获取数据时触发,这比每次组件更新时调用 API 更干净或一个道具被改变了。 你是对的,回调可以是一个解决方案。但是,我大多避免使用回调。它使登录流程变得不可预测。在一种情况下,我在应用程序中有第三个屏幕,一旦更新导航回同一页面。所以我需要将回调从一个屏幕传递到另一个屏幕,然后再传递到第三个屏幕。使用反应导航将数据作为参数传递需要做很多工作。我太懒了 你是对的。我很奇怪。现在我得到了这个。

以上是关于如何在数组依赖中正确使用 useEffect 挂钩。我从 redux 商店传递了状态,但我的组件仍然无限渲染的主要内容,如果未能解决你的问题,请参考以下文章

useEffect 依赖数组的问题

如何从 Reactjs 中的 useEffect 挂钩访问道具

如何正确使用 useEffect 与 useContext 作为依赖

如何正确地将事件侦听器添加到 React useEffect 挂钩?

在 useEffect 挂钩中取消所有异步/等待任务以防止反应中的内存泄漏的正确方法是啥?

如何将 setState 值插入 useEffect 挂钩?