如何抽象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);
方案优势
- 保留hooks能力:全程使用函数组件,useState、useCallback等hooks特性完全保留,没有类组件的限制
- 零重复代码:每个业务模型的Provider和useHook只需要一行代码生成
- 高扩展性:如果某个模型需要特殊逻辑,可以在对应的useHook中扩展,或者给通用Hook添加可选参数支持定制
- 类型安全(可选):如果用TypeScript,可以给通用Hook添加泛型,让items和数据类型严格对应:
export const useCrud = <T>(baseApiUrl: string) => { const [items, setItems] = useState<T[]>([]); // 其余函数的返回值也可指定为T类型 }; // 使用时指定类型 export const useCompanies = createUseCrud<Company>(CompaniesContext);
内容的提问来源于stack exchange,提问作者AlbertMunichMar
相关产品推荐
相关产品推荐

