React hook必须要知道的知识: useEffect的cleanup

React useEffect的cleanup

  • 可以避免应用程序出现内存泄漏等问题
  • 可以优化应用程序的性能

开始这篇文章前,您应该对什么useEffect是有一个基本的了解,包括可以使用它来获取数据。

本文将解释useEffectHook的cleanup,希望在本文结束时,您应该能够舒适地使用。

什么是useEffectcleanup?

顾名思义,useEffectcleanup 是Hook中的一个函数,它允许我们在卸载组件之前整理代码。

useEffect挂钩可以返回一个函数。

cleanup可防止内存泄漏并删除一些不必要和不需要的行为。

useEffect(() => {
        effect
        return () => {
            cleanup
        }
    }, [input])

为什么useEffectcleanup很有用?

如前所述,useEffectcleanup可帮助开发人员清理防止不需要的行为并优化应用程序的性能。

但是,需要注意的是,useEffectcleanup不仅在组件想要卸载时运行,也可以在某些属性和状态改变后运行终止副作用。

看看这个场景:假设我们通过特定用户id的 fetch数据,在获取完成之前,我们改变主意尝试获取另一个用户。此时id更新,而前一个 fetch 请求仍在进行中。

使用清理函数中止请求,应用程序就不会内存泄漏。

useEffect什么时候应该使用清理?

假设我们有一个 React 组件来获取和呈现数据。如果组件在清理之前卸载,useEffect尝试更新状态(在未安装的组件上)会得到如下所示的错误:

为了修复这个错误,我们使用了清理函数来解决它。

根据 React 的官方文档,“React 在组件卸载时执行清理。但是……副作用会在每次渲染时运行,而不仅仅是一次。这就是为什么 React 还会在下次运行副作用执行之前清除上一次渲染中的副作用。”

清理通常用于取消所有订阅和取消获取请求。

清理订阅

开始清理订阅,我们必须首先取消订阅,因为我们不想让应用程序内存泄漏,我们想优化应用程序。

在卸载组件之前取消订阅,将变量 , 设置isApiSubscribedtrue,然后我们可以false在卸载时将其设置为:

useEffect(() => {
    // set our variable to true
    let isApiSubscribed = true;
    axios.get(API).then((response) => {
        if (isApiSubscribed) {
            // handle success
        }
    });
    return () => {
        // cancel the subscription
        isApiSubscribed = false;
    };
}, []);

在上面的代码中,我们将变量设置isApiSubscribedtrue,然后将其用作处理我们成功请求的条件。然而,我们设置变量isApiSubscribedfalse当我们卸载组件。

取消请求

取消 fetch 请求调用的方法:AbortController Axios 取消令牌。

使用AbortController,我们必须使用构造函数创建一个控制器。然后,当 fetch 请求启动时,我们将作为一个选项传递到请求的对象中。AbortController() AbortSignaloption

这将控制器和信号与获取请求相关联,并使用以下命令取消它:AbortController.abort() 

>useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

        fetch(API, {
            signal: signal
        })
        .then((response) => response.json())
        .then((response) => {
            // handle success
        });
    return () => {
        // cancel the request before component unmounts
        controller.abort();
    };
}, []);

我们可以更进一步,在 catch 中添加一个错误条件,这样 fetch 请求中止时就不会抛出错误。原因是,在卸载时,我们仍会在处理错误时尝试更新状态。

我们能做的就是写一个条件,知道我们会得到什么样的错误;

如果我们收到中止错误,那么我们不更新状态:

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

   fetch(API, {
      signal: signal
    })
    .then((response) => response.json())
    .then((response) => {
      // handle success
      console.log(response);
    })
    .catch((err) => {
      if (err.name === 'AbortError') {
        console.log('successfully aborted');
      } else {
        // handle error
      }
    });
  return () => {
    // cancel the request before component unmounts
    controller.abort();
  };
}, []);

即使我们在请求完成之前导航到另一个页面,我们也不会再次收到该错误,因为请求将在组件卸载之前中止。如果我们收到中止错误,状态也不会更新。

所以,如何使用 Axios 的取消选项,Axios 取消令牌来做同样的事情,

我们首先将来自 Axios 的数据存储在一个名为 source 的常量中,将令牌作为 Axios 选项传递,然后使用以下命令取消请求:CancelToken.source()source.cancel()

useEffect(() => {
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();
  axios
    .get(API, {
      cancelToken: source.token
    })
    .catch((err) => {
      if (axios.isCancel(err)) {
        console.log('successfully aborted');
      } else {
        // handle error
      }
    });
  return () => {
    // cancel the request before component unmounts
    source.cancel();
  };
}, []);

就像我们对AbortErrorin所做的那样AbortController,Axios 给了我们一个调用的方法isCancel,它允许我们检查错误的原因并知道如何处理错误。

如果请求 Axios 源中止或取消而失败,那么我们不想更新状态。

如何使用useEffectcleanup

看一个示例,说明何时会发生上述错误以及在发生时如何使用cleanup。

创建两个文件:PostApp.

// Post component

import React, { useState, useEffect } from "react";
export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;
    fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => setError(err));
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

这是一个简单的 post 组件,它在每次渲染时获取 post 并处理获取错误。

我们在主组件中导入 post 组件,单击按钮时显示post ,再次单击按钮隐藏post ,即挂载和卸载post 组件:

// App component

import React, { useState } from "react";
import Post from "./Post";
const App = () => {
  const [show, setShow] = useState(false);
  const showPost = () => {
    // toggles posts onclick of button
    setShow(!show);
  };
  return (
    <div>
      <button onClick={showPost}>Show Posts</button>
      {show && <Post />}
    </div>
  );
};
export default App;

在Post 呈现之前单击该按钮,再次单击该按钮,我们会在控制台中收到错误消息。

这是因为 ReactuseEffect仍在运行并尝试在后台获取 API。完成获取 API 后,它会尝试更新状态,但这次是在未安装的组件上,因此会引发此错误:

要清除此错误并阻止内存泄漏,我们必须使用上述解决方案来实现cleanup。在这篇文章中,我们将使用AbortController

// Post component

import React, { useState, useEffect } from "react";
export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;
    fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => {
        setError(err);
      });
    return () => controller.abort();
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

我们在控制台中看到,即使在清除函数中中止请求后,卸载也会引发错误。

正如我们之前所讨论的,当我们中止 fetch 调用时会发生此错误。

useEffect在 catch 块中捕获 fetch 错误,然后尝试更新错误状态,然后抛出错误。要停止此更新,我们可以使用条件并检查我们得到的错误类型。if else

如果中止错误,我们不需要更新状态,否则我们处理错误:

// Post component

import React, { useState, useEffect } from "react";

export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

      fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => {
        if (err.name === "AbortError") {
          console.log("successfully aborted");
        } else {
          setError(err);
        }
      });
    return () => controller.abort();
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

注意这里只是在使用 fetch时调用方式

当使用 Axios 时应该判断:err.name === "AbortError" axios.isCancel()

完成!

结论

如何使用useEffectHookcleanup来防止内存泄漏和优化应用程序非常重要。

希望本文对您有所帮助。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是不会选择做一个普通人的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值