迹忆客 专注技术分享

当前位置:主页 > 学无止境 > WEB前端 > React >

React.useEffect Hook 常见问题及解决方法

作者:迹忆客 最近更新:2022/10/17 浏览次数:

大多数开发人员都非常熟悉 React hooks 的工作方式和常见用例,但是有一个 useEffect 问题很多人可能不太清楚。

用例

让我们从一个简单的场景开始。我们正在构建一个 React 应用程序,希望在一个组件中显示当前用户的用户名。但首先,我们需要从 API 中获取用户名。

因为我们知道我们也需要在应用程序的其他地方使用用户数据,所以我们还想在自定义 React hook 中抽象数据获取逻辑。

我们希望 React 组件看起来像这样:

const Component = () => {
  // useUser custom hook
  
  return <div>{user.name}</div>;
};

看起来很简单!


useUser React hook

第二步是创建我们的 useUser 自定义钩子。

const useUser = (user) => {
  const [userData, setUserData] = useState();
  useEffect(() => {
    if (user) {
      fetch("users.json").then((response) =>
        response.json().then((users) => {
          return setUserData(users.find((item) => item.id === user.id));
        })
      );
    }
  }, []);

  return userData;
};

让我们分解一下。我们正在检查钩子是否正在接收用户对象。之后,我们从名为 users.json 的文件中获取用户列表,并对其进行过滤以找到具有我们需要的 id 的用户。

然后,一旦我们获得了必要的数据,我们就将其保存为钩子的 userData 状态。最后返回 userData。

注意 : 这是一个仅用于说明目的的示例!现实世界中的数据获取要复杂得多。如果你对该主题感兴趣,请查看我关于如何使用 ReactQuery、TypescriptGraphQL 创建出色的数据获取设置的文章。

让我们在 React 组件中插入钩子,看看会发生什么。

const Component = () => {
  const user = useUser({ id: 1 });
  return <div>{user?.name}</div>;
};

好的,一切都按预期进行。但是等等……这是什么?


ESLint exhaustive-deps 规则

我们的钩子中有一个 ESLint 警告:

React Hook useEffect has a missing dependency: 'user'. Either include it or remove the dependency array. 
(react-hooks/exhaustive-deps)

嗯,我们的 useEffect 似乎缺少依赖项。那好吧! 让我们添加它。可能发生的最坏情况是什么? 😂

const useUser = (user) => {
  const [userData, setUserData] = useState();
  useEffect(() => {
    if (user) {
      fetch("users.json").then((response) =>
        response.json().then((users) => {
          return setUserData(users.find((item) => item.id === user.id));
        })
      );
    }
  }, [user]);

  return userData;
};

看起来我们的组件 Component 现在不会停止重新渲染。这里发生了什么?!

让我们解释一下。


无限重新渲染问题

我们的组件重新渲染的原因是因为 useEffect 依赖项在不断变化。但为什么?我们总是将相同的对象传递给钩子!

虽然我们确实传递了一个具有相同键和值的对象,但它并不是完全相同的对象。每次重新渲染组件时,我们实际上都是在创建一个新对象,然后我们将新对象作为参数传递给 useUser 钩子。

在内部,useEffect 比较两个对象,由于它们有不同的引用,它再次获取用户并将新的用户对象设置为状态。状态更新然后触发组件中的重新渲染,不断重复……

所以,我们能做些什么?


如何修复

现在我们了解了问题,可以开始寻找解决方案。

第一个也是最明显的选择是从 useEffect 依赖数组中移除依赖,忽略 ESLint 规则,继续我们的生活。

但这是错误的做法。它可以(并且可能会)导致我们的应用程序中出现错误和意外行为。如果你想了解更多有关 useEffect 如何工作的信息,我强烈推荐 Dan Abramov 的完整指南。

下一个是什么?

在我们的例子中,最简单的解决方案是从组件中取出 { id: 1 } 对象。这将为对象提供稳定的引用并解决我们的问题。

const userObject = { id: 1 };

const Component = () => {
  const user = useUser(userObject);
  return <div>{user?.name}</div>;
};

export default Component;

但这并不总是可能的。想象一下,用户 id 以某种方式依赖于组件 props 或状态。

例如,可能是我们使用 URL 参数来访问它。如果是这种情况,我们可以使用一个方便的 useMemo 钩子来记忆对象并再次确保稳定的引用。

const Component = () => {
  const { userId } = useParams();
  
  const userObject = useMemo(() => {
    return { id: userId };
  }, [userId]); // Don't forget the dependencies here either!

  const user = useUser(userObject);
  return <div>{user?.name}</div>;
};

export default Component;

最后,不是将对象变量传递给我们的 useUser 钩子,而是可以只传递用户 ID 本身,这是一个原始值。这将防止 useEffect 钩子中的引用相等问题。

const useUser = (userId) => {
  const [userData, setUserData] = useState();

  useEffect(() => {
    fetch("users.json").then((response) =>
      response.json().then((users) => {
        return setUserData(users.find((item) => item.id === userId));
      })
    );
  }, [userId]);

  return userData;
};

const Component = () => {
  const user = useUser(1);

  return <div>{user?.name}</div>;
};

问题解决啦!

在此过程中,我们甚至不必违反任何 ESLint 规则……

注意 : 如果我们传递给自定义钩子的参数是一个函数,而不是一个对象,我们将使用非常相似的技术来避免无限重新渲染。一个显着的区别是我们必须在上面的例子中用 useCallback 替换 useMemo

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

Node.js 与 React JS 的比较

发布时间:2023/03/27 浏览次数:137 分类:Node.js

本文比较和对比了两种编程语言,Node.js 和 React。React 和 Node.js 都是开源 JavaScript 库的示例。 这些库用于构建用户界面和服务器端应用程序。

在 React 中循环遍历对象数组

发布时间:2023/03/18 浏览次数:124 分类:React

在 React 中循环对象数组: 使用 map() 方法迭代数组。 我们传递给 map() 的函数会为数组中的每个元素调用。 该方法返回一个新数组,其中包含传入函数的结果。 export default function App (

获取 React 中元素的类名

发布时间:2023/03/18 浏览次数:162 分类:React

在 React 中使用 event.target 获取元素的类名 获取元素的类名: 将元素上的 onClick 属性设置为事件处理函数。 访问元素的类名作为 event.currentTarget.className 。 export default function App () { cons

如何将 key 属性添加到 React 片段

发布时间:2023/03/18 浏览次数:152 分类:React

使用更详细的片段语法将 key 属性添加到 React 片段,例如 React.Fragment key={key} 。 更冗长的语法实现了相同的结果对元素列表进行分组,而不向 DOM 添加额外的节点。 import React from react

如何在 React 中删除事件监听器

发布时间:2023/03/15 浏览次数:203 分类:React

在 React 中删除事件监听器: 在 useEffect 挂钩中添加事件侦听器。 从 useEffect 挂钩返回一个函数。 当组件卸载时,使用 removeEventListener 方法移除事件监听器。 import {useRef, useEffect} from r

React 中在 map() 中使用条件跳出map

发布时间:2023/03/15 浏览次数:198 分类:React

React 中在 map() 中使用条件: 在数组上调用 map() 方法。 使用 if 条件,如果条件满足则显式返回。 否则返回不同的值或返回 null 以不呈现任何内容。 export default function App () { const arr =

在 React 中调用多个 onClick 函数

发布时间:2023/03/15 浏览次数:160 分类:React

在 React 中调用多个 onClick 函数: 在元素上设置 onClick 属性。 在事件处理函数中调用其他函数。 事件处理函数可以根据需要调用尽可能多的其他函数。 export default function App () { const s

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便