首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >React实现Promise封装

React实现Promise封装

原创
作者头像
小焱写作
发布2025-11-09 18:39:37
发布2025-11-09 18:39:37
1090
举报
文章被收录于专栏:javajava

在 React 中封装 Promise,核心是结合 React 的生命周期/ Hooks 特性(如 ​​useEffect​​​ 处理异步时机、​​useState​​ 管理异步状态),封装通用的异步请求逻辑(如 API 调用),同时解决重复请求、错误处理、加载状态管理等问题。以下是从「基础封装」到「通用 Hooks 封装」的完整实现方案,适配 React 函数组件(主流开发模式)。

一、核心需求

  1. 统一管理异步状态:加载中(loading)、成功(data)、失败(error);
  2. 支持请求参数传递、请求取消(避免内存泄漏);
  3. 防止重复请求(如快速点击按钮多次触发同一请求);
  4. 适配 React 生命周期(组件卸载时取消未完成的请求);
  5. 简洁的调用方式,减少冗余代码。

二、基础封装:Promise + useEffect (无 Hooks 依赖)

先实现最基础的 Promise 封装,直接在组件中结合 ​​useEffect​​ 处理异步逻辑,适合简单场景(如单个组件的独立请求)。

1. 封装思路
  • 用 ​​useState​​ 管理 ​​loading​​、​​data​​、​​error​​ 状态;
  • 用 ​​useEffect​​ 触发异步请求(依赖项变化时重新请求);
  • 用 ​​AbortController​​ 实现请求取消(组件卸载时终止未完成请求)。
2. 代码实现
代码语言:javascript
复制
import React, { useState, useEffect } from 'react';

// 模拟 API 请求(实际项目中替换为 axios/fetch 真实请求)
const fetchData = (params, signal) => {
  // 返回 Promise 对象
  return new Promise((resolve, reject) => {
    // 模拟网络延迟
    const timer = setTimeout(() => {
      if (params?.id === 1) {
        resolve({ code: 200, data: { name: 'React Promise 封装', age: 3 } });
      } else {
        reject(new Error('请求失败:参数错误'));
      }
    }, 1000);

    // 监听取消信号(组件卸载时触发)
    signal.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(new Error('请求已取消'));
    });
  });
};

const BasicPromiseDemo = () => {
  // 管理异步状态
  const [state, setState] = useState({
    loading: false,
    data: null,
    error: null,
  });

  // 触发请求的函数
  const fetchDataHandler = async (params = { id: 1 }) => {
    // 重置状态 + 开启加载
    setState(prev => ({ ...prev, loading: true, error: null }));
    
    // 创建取消控制器
    const controller = new AbortController();
    const signal = controller.signal;

    try {
      // 执行 Promise 请求
      const response = await fetchData(params, signal);
      setState({ loading: false, data: response.data, error: null });
    } catch (err) {
      // 忽略取消请求的错误(组件卸载时触发,无需展示)
      if (err.message !== '请求已取消') {
        setState({ loading: false, data: null, error: err.message });
      }
    }

    // 返回取消函数(供组件卸载时调用)
    return () => controller.abort();
  };

  // 组件挂载时触发请求,卸载时取消请求
  useEffect(() => {
    const cancelRequest = fetchDataHandler();
    // 组件卸载时取消请求(清理副作用)
    return () => cancelRequest();
  }, []);

  // 手动触发请求(如按钮点击)
  const handleRetry = () => {
    fetchDataHandler({ id: 1 });
  };

  const { loading, data, error } = state;

  return (
    <div style={{ padding: '20px' }}>
      <h3>基础 Promise 封装示例</h3>
      {loading && <div>加载中...</div>}
      {error && <div style={{ color: 'red' }}>错误:{error}</div>}
      {data && (
        <div>
          <p>名称:{data.name}</p>
          <p>版本:{data.age}</p>
        </div>
      )}
      <button onClick={handleRetry} disabled={loading}>
        重新请求
      </button>
    </div>
  );
};

export default BasicPromiseDemo;

三、通用 Hooks 封装:usePromise (复用性强)

对于多个组件需要异步请求的场景,封装成通用 ​​usePromise​​ Hooks,统一管理异步逻辑,减少重复代码。

1. 封装思路
  • Hooks 接收「请求函数」和「依赖项数组」;
  • 内部管理 ​​loading​​/​​data​​/​​error​​ 状态;
  • 支持手动触发请求、请求取消、防止重复请求;
  • 适配 React 严格模式(避免重复渲染导致的重复请求)。
2. 完整实现(usePromise.js)
代码语言:javascript
复制
import { useState, useRef, useEffect } from 'react';

/**
 * 通用 Promise 异步请求 Hooks
 * @param {Function} requestFn - 异步请求函数(需返回 Promise)
 * @param {Array} deps - 依赖项数组(依赖变化时自动重新请求)
 * @param {Object} options - 配置项(manual: 是否手动触发,默认 false)
 * @returns {Object} { loading, data, error, run, cancel } - 状态和操作函数
 */
const usePromise = (requestFn, deps = [], options = {}) => {
  const { manual = false } = options;

  // 状态管理
  const [state, setState] = useState({
    loading: false,
    data: null,
    error: null,
  });

  // 存储取消控制器(用于组件卸载或重复请求时取消上一次请求)
  const controllerRef = useRef(null);
  // 防止重复请求的锁
  const requestLock = useRef(false);

  // 取消请求的函数
  const cancel = () => {
    if (controllerRef.current) {
      controllerRef.current.abort();
      controllerRef.current = null;
    }
  };

  // 核心请求函数(支持传递参数)
  const run = async (...args) => {
    // 防止重复请求
    if (requestLock.current) return;
    requestLock.current = true;

    // 重置状态 + 开启加载
    setState(prev => ({ ...prev, loading: true, error: null }));

    try {
      // 创建新的取消控制器(取消上一次未完成的请求)
      cancel();
      const controller = new AbortController();
      const signal = controller.signal;
      controllerRef.current = controller;

      // 执行请求函数(传递参数和取消信号)
      const result = await requestFn(...args, signal);
      setState({ loading: false, data: result, error: null });
      return result; // 返回结果供外部使用
    } catch (error) {
      // 忽略取消请求的错误
      if (error.name !== 'AbortError') {
        setState({ loading: false, data: null, error: error.message || '请求失败' });
        return Promise.reject(error); // 外部可捕获错误
      }
    } finally {
      requestLock.current = false; // 释放锁
    }
  };

  // 依赖变化时自动请求(非手动模式下)
  useEffect(() => {
    if (!manual) {
      run();
    }

    // 组件卸载时取消请求
    return () => {
      cancel();
    };
  }, deps); // 依赖项变化时重新请求

  return {
    ...state,
    run, // 手动触发请求
    cancel, // 手动取消请求
  };
};

export default usePromise;
3. Hooks 用法示例(组件中调用)
代码语言:javascript
复制
import React from 'react';
import usePromise from './usePromise';

// 1. 定义真实请求函数(如 API 调用,支持 axios/fetch)
// 示例 1:Fetch API(原生)
const fetchUser = async (userId, signal) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`, { signal });
  if (!response.ok) throw new Error('用户数据请求失败');
  return response.json();
};

// 示例 2:Axios(需安装 axios)
// import axios from 'axios';
// const fetchUser = async (userId, signal) => {
//   const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`, {
//     signal: signal, // Axios 支持 AbortController 信号
//   });
//   return response.data;
// };

const UserComponent = () => {
  // 2. 使用 usePromise Hooks 封装请求
  const {
    loading,
    data: user,
    error,
    run: fetchUserById,
  } = usePromise(
    fetchUser, // 请求函数
    [], // 依赖项:空数组 = 组件挂载时只请求一次(类似 useEffect([]))
    { manual: true } // 手动触发模式(不自动请求)
  );

  // 3. 手动触发请求(如按钮点击)
  const handleFetchUser = () => {
    fetchUserById(1); // 传递参数给请求函数(userId=1)
  };

  return (
    <div style={{ padding: '20px' }}>
      <h3>用户信息</h3>
      <button onClick={handleFetchUser} disabled={loading}>
        {loading ? '请求中...' : '获取用户数据'}
      </button>

      {error && <div style={{ color: 'red', marginTop: '10px' }}>{error}</div>}
      {user && (
        <div style={{ marginTop: '10px' }}>
          <p>姓名:{user.name}</p>
          <p>邮箱:{user.email}</p>
          <p>电话:{user.phone}</p>
        </div>
      )}
    </div>
  );
};

export default UserComponent;

四、高级扩展:支持并发请求、缓存

在 ​​usePromise​​ 基础上扩展,支持并发请求、结果缓存,满足更复杂场景(如列表查询、频繁切换参数请求)。

1. 扩展 Hooks(usePromiseAdvance.js)
代码语言:javascript
复制
import { useState, useRef, useEffect } from 'react';

const usePromiseAdvance = (requestFn, deps = [], options = {}) => {
  const { manual = false, cache = false } = options;
  const [state, setState] = useState({ loading: false, data: null, error: null });
  const controllerRef = useRef(null);
  const requestLock = useRef(false);
  const cacheRef = useRef({}); // 缓存结果(key: 参数序列化,value: 数据)

  // 取消请求
  const cancel = () => {
    if (controllerRef.current) {
      controllerRef.current.abort();
      controllerRef.current = null;
    }
  };

  // 序列化参数(作为缓存 key)
  const serializeParams = (...args) => JSON.stringify(args);

  // 核心请求函数
  const run = async (...args) => {
    if (requestLock.current) return;
    requestLock.current = true;

    // 缓存key
    const cacheKey = serializeParams(...args);

    // 有缓存且开启缓存模式,直接返回缓存数据
    if (cache && cacheRef.current[cacheKey]) {
      setState({ loading: false, data: cacheRef.current[cacheKey], error: null });
      requestLock.current = false;
      return cacheRef.current[cacheKey];
    }

    setState(prev => ({ ...prev, loading: true, error: null }));

    try {
      cancel();
      const controller = new AbortController();
      const signal = controller.signal;
      controllerRef.current = controller;

      const result = await requestFn(...args, signal);
      // 缓存结果
      if (cache) cacheRef.current[cacheKey] = result;
      setState({ loading: false, data: result, error: null });
      return result;
    } catch (error) {
      if (error.name !== 'AbortError') {
        setState({ loading: false, data: null, error: error.message || '请求失败' });
        return Promise.reject(error);
      }
    } finally {
      requestLock.current = false;
    }
  };

  // 自动请求
  useEffect(() => {
    if (!manual) {
      run();
    }
    return () => cancel();
  }, deps);

  // 清除缓存的方法
  const clearCache = (key) => {
    if (key) {
      delete cacheRef.current[serializeParams(key)];
    } else {
      cacheRef.current = {};
    }
  };

  return {
    ...state,
    run,
    cancel,
    clearCache, // 暴露清除缓存的方法
  };
};

export default usePromiseAdvance;
2. 扩展用法(缓存示例)
代码语言:javascript
复制
const CacheDemo = () => {
  const { loading, data, run, clearCache } = usePromiseAdvance(
    fetchUser,
    [],
    { manual: true, cache: true } // 开启缓存
  );

  return (
    <div>
      <button onClick={() => run(1)} disabled={loading}>
        获取用户1(缓存)
      </button>
      <button onClick={() => clearCache(1)} style={{ marginLeft: '10px' }}>
        清除用户1缓存
      </button>
      {data && <p>{data.name}</p>}
    </div>
  );
};

五、核心特性总结

特性

基础封装

usePromise Hooks

高级扩展 Hooks

加载/错误/数据状态

请求取消(防内存泄漏)

防止重复请求

手动/自动触发请求

依赖项变化重新请求

结果缓存

清除缓存

六、使用注意事项

  1. 请求函数要求: 传递给 Hooks 的 requestFn 需支持接收 signal 参数(用于取消请求),Axios v0.22+、Fetch API 原生支持 AbortController
  2. 依赖项数组: 类似 useEffectdeps 数组中的变量变化时会自动重新请求,避免依赖项缺失导致的闭包问题。
  3. 手动触发模式: 当 manual: true 时,组件挂载不会自动请求,需通过 run 方法手动触发(适合按钮点击、表单提交等场景)。
  4. 缓存使用场景: 缓存适合「参数不变、数据不频繁更新」的请求(如用户详情、字典数据),避免重复请求浪费资源。

七、替代方案(成熟库)

如果项目需要更复杂的异步管理(如请求拦截、响应统一处理、并发控制),可直接使用成熟库:

  • SWR/React Query:React 官方推荐的异步数据请求库,内置缓存、重试、聚焦重新验证等功能;
  • axios:配合 ​​axios.create​​ 封装全局请求拦截器,统一处理请求头、错误码;
  • ahooks/useRequest:阿里封装的 Hooks 库,提供更丰富的配置(防抖、节流、轮询等)。

本教程的封装方案适合学习 Promise 原理和 React Hooks 实战,或中小型项目无需引入额外库的场景。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、核心需求
  • 二、基础封装:Promise + useEffect (无 Hooks 依赖)
    • 1. 封装思路
    • 2. 代码实现
  • 三、通用 Hooks 封装:usePromise (复用性强)
    • 1. 封装思路
    • 2. 完整实现(usePromise.js)
    • 3. Hooks 用法示例(组件中调用)
  • 四、高级扩展:支持并发请求、缓存
    • 1. 扩展 Hooks(usePromiseAdvance.js)
    • 2. 扩展用法(缓存示例)
  • 五、核心特性总结
  • 六、使用注意事项
  • 七、替代方案(成熟库)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档