Skip to content

useEffect中实现异步操作详解 ​

在 React 组件中,我们经常需要在 useEffect 中处理异步操作,比如获取数据、订阅事件等。本文将详细介绍在 useEffect 中实现异步操作的几种方法及其注意事项。

实现方式 ​

1. 使用 async/await ​

这是最现代和推荐的方式,代码清晰易读:

js
import { useState, useEffect } from "react";
import axios from "axios";

function DataFetcher() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 定义异步函数
    async function fetchData() {
      try {
        setLoading(true);
        const response = await axios.get("https://api.example.com/data");
        setData(response.data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchData(); // 调用异步函数

    // 清理函数
    return () => {
      // 在组件卸载时进行清理
      setData(null);
    };
  }, []); // 空依赖数组表示只在组件挂载时执行

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误:{error}</div>;
  return <div>{data && JSON.stringify(data)}</div>;
}

2. 使用 Promise ​

传统的 Promise 链式调用方式:

js
import { useState, useEffect } from "react";
import axios from "axios";

function DataFetcher() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    axios.get("https://api.example.com/data")
      .then(response => {
        setData(response.data);
      })
      .catch(err => {
        setError(err.message);
      });
      
    return () => {
      setData(null);
    };
  }, []);

  return (
    <div>
      {error ? (
        <div>错误:{error}</div>
      ) : (
        <div>{data ? JSON.stringify(data) : "加载中..."}</div>
      )}
    </div>
  );
}

注意事项 ​

  1. 不要直接在 useEffect 中使用 async
js
// ❌ 错误示例
useEffect(async () => {
  const data = await fetchData();
}, []);

// ✅ 正确示例
useEffect(() => {
  const fetchData = async () => {
    const data = await fetchData();
  };
  fetchData();
}, []);
  1. 处理竞态条件
js
function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isSubscribed = true;

    async function fetchData() {
      const response = await axios.get("https://api.example.com/data");
      if (isSubscribed) {
        setData(response.data);
      }
    }

    fetchData();

    return () => {
      isSubscribed = false;
    };
  }, []);
}
  1. 错误处理:始终添加错误处理机制,可以使用 try/catch 或 .catch()

  2. 加载状态:添加加载状态指示器提升用户体验

  3. 清理函数:在必要时添加清理函数,防止内存泄漏

最佳实践 ​

  1. 使用 loading 状态管理加载过程
  2. 实现错误处理机制
  3. 添加清理函数防止内存泄漏
  4. 处理竞态条件
  5. 合理使用依赖数组
  6. 考虑使用自定义 Hook 封装异步逻辑

自定义 Hook 示例 ​

js
function useAsyncData(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isSubscribed = true;

    async function fetchData() {
      try {
        setLoading(true);
        const response = await axios.get(url);
        if (isSubscribed) {
          setData(response.data);
        }
      } catch (err) {
        if (isSubscribed) {
          setError(err.message);
        }
      } finally {
        if (isSubscribed) {
          setLoading(false);
        }
      }
    }

    fetchData();

    return () => {
      isSubscribed = false;
    };
  }, [url]);

  return { data, loading, error };
}

这样的封装可以让代码更加可复用且易于维护。