You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何抽象React中多个相似的Provider函数组件?

最佳重构方案:基于自定义Hook的通用CRUD Context抽象

完全懂你这种重复写CRUD Provider的痛苦!不用转类组件——我们可以用自定义Hook + 动态创建Context的方式实现通用抽象,既保留函数组件和hooks的全部能力,又彻底消除冗余代码。

核心思路

把通用的CRUD逻辑抽成可复用的自定义Hook,再通过工厂函数快速生成不同模型的Context、Provider和useHook,每个业务模型只需要一行代码就能搞定。

第一步:封装通用CRUD自定义Hook

这个Hook接收API基础地址作为参数,封装所有通用的状态管理和CRUD操作:

import { useState, useCallback } from 'react';

// 通用CRUD Hook,支持所有基础增删改查逻辑
export const useCrud = (baseApiUrl) => {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  // 获取列表
  const fetchItems = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(`${baseApiUrl}`);
      if (!response.ok) throw new Error('获取数据失败');
      const data = await response.json();
      setItems(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [baseApiUrl]);

  // 更新单个项
  const updateItem = useCallback(async (id, updatedData) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(`${baseApiUrl}/${id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatedData),
      });
      if (!response.ok) throw new Error('更新数据失败');
      const data = await response.json();
      // 本地状态同步更新
      setItems(prev => prev.map(item => item.id === id ? data : item));
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [baseApiUrl]);

  // 创建新项
  const createItem = useCallback(async (newData) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(`${baseApiUrl}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newData),
      });
      if (!response.ok) throw new Error('创建数据失败');
      const data = await response.json();
      setItems(prev => [...prev, data]);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [baseApiUrl]);

  // 删除项
  const deleteItem = useCallback(async (id) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(`${baseApiUrl}/${id}`, { method: 'DELETE' });
      if (!response.ok) throw new Error('删除数据失败');
      setItems(prev => prev.filter(item => item.id !== id));
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [baseApiUrl]);

  // 获取单个项
  const fetchItem = useCallback(async (id) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(`${baseApiUrl}/${id}`);
      if (!response.ok) throw new Error('获取单条数据失败');
      return await response.json();
    } catch (err) {
      setError(err.message);
      throw err; // 抛出错误让调用者自行处理
    } finally {
      setLoading(false);
    }
  }, [baseApiUrl]);

  return {
    items,
    loading,
    error,
    fetchItems,
    updateItem,
    createItem,
    deleteItem,
    fetchItem,
    clearError: () => setError(null),
  };
};

第二步:创建通用Provider和useHook工厂

通过工厂函数快速生成不同模型的Context、Provider和自定义Hook:

import { createContext, useContext } from 'react';

// 生成对应模型的Provider组件
export const createCrudProvider = (Context, baseApiUrl) => {
  return ({ children }) => {
    const crudState = useCrud(baseApiUrl);
    return (
      <Context.Provider value={crudState}>
        {children}
      </Context.Provider>
    );
  };
};

// 生成对应模型的useHook(带上下文校验)
export const createUseCrud = (Context) => {
  return () => {
    const context = useContext(Context);
    if (!context) {
      throw new Error('该useHook必须在对应的Provider组件内部使用');
    }
    return context;
  };
};

第三步:生成业务模型的Provider和useHook

现在每个模型只需要几行代码就能完成,彻底告别重复逻辑:

// Companies 相关
export const CompaniesContext = createContext(null);
export const CompaniesProvider = createCrudProvider(CompaniesContext, '/api/companies');
export const useCompanies = createUseCrud(CompaniesContext);

// Technologies 相关
export const TechnologiesContext = createContext(null);
export const TechnologiesProvider = createCrudProvider(TechnologiesContext, '/api/technologies');
export const useTechnologies = createUseCrud(TechnologiesContext);

方案优势

  1. 保留hooks能力:全程使用函数组件,useState、useCallback等hooks特性完全保留,没有类组件的限制
  2. 零重复代码:每个业务模型的Provider和useHook只需要一行代码生成
  3. 高扩展性:如果某个模型需要特殊逻辑,可以在对应的useHook中扩展,或者给通用Hook添加可选参数支持定制
  4. 类型安全(可选):如果用TypeScript,可以给通用Hook添加泛型,让items和数据类型严格对应:
    export const useCrud = <T>(baseApiUrl: string) => {
      const [items, setItems] = useState<T[]>([]);
      // 其余函数的返回值也可指定为T类型
    };
    
    // 使用时指定类型
    export const useCompanies = createUseCrud<Company>(CompaniesContext);
    

内容的提问来源于stack exchange,提问作者AlbertMunichMar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 17:45:38