如何在数组依赖中正确使用 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 商店传递了状态,但我的组件仍然无限渲染的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Reactjs 中的 useEffect 挂钩访问道具
如何正确使用 useEffect 与 useContext 作为依赖
如何正确地将事件侦听器添加到 React useEffect 挂钩?