如何在React中复用自定义useRosterViewer Hook?
如何在自定义Hook中复用useRosterViewer获取排班数据
问题背景
我正在开发一个排班管理应用,已实现useRosterViewer Hook用于获取并管理排班数据,在排班查看组件中运行正常。现在要构建排班编辑器,希望复用useRosterViewer里的数据获取逻辑,避免重复编写异步请求代码,但尝试两种写法均出现问题:
写法一:初始状态同步失效
export function useRosterEditor(){ const { error, isLoading, roster } = useRosterViewer(); const [itemList, updateItemList] = useReducer(reducer, { error, isLoading, roster, }); return { error: itemList.error, isLoading: itemList.isLoading, roster: itemList.roster, } }
此写法中,useReducer的初始值仅在Hook首次渲染时获取useRosterViewer的初始状态,后续useRosterViewer数据更新后,itemList无法同步更新。
写法二:违反Hook调用规则
export function useRosterEditor(){ const [itemList, updateItemList] = useReducer(reducer, { error:null, isLoading:true, roster:null, }); useEffect(()=>{ let getData = async () => { const { error, isLoading, roster } = useRosterViewer(); } getData(); },[]) return { error: itemList.error, isLoading: itemList.isLoading, roster: itemList.roster, } }
触发React Hook规则错误:React Hook "useRosterViewer" is called in function "getData" that is neither a React function component nor a custom React Hook function,因为Hook只能在组件或自定义Hook的顶层调用,不能在异步函数、循环或条件判断内调用。
正确复用方案
方案一:结合useReducer同步状态
在useRosterEditor顶层调用useRosterViewer,通过useEffect监听数据变化并同步到编辑状态中,同时保留编辑专用的状态管理逻辑:
// 编辑专用reducer,处理排班修改、状态同步等逻辑 const editorReducer = (state, action) => { let result = { ...state }; switch (action.type) { case "sync-roster": result.roster = action.roster; result.error = action.error; result.isLoading = action.isLoading; break; // 添加编辑相关的action,比如修改单条排班 case "update-shift": result.roster = result.roster.map(shift => shift.id === action.id ? { ...shift, ...action.data } : shift ); break; default: break; } return result; }; export function useRosterEditor(){ // 顶层调用useRosterViewer,符合Hook规则 const { error, isLoading, roster } = useRosterViewer(); // 用useRosterViewer的初始值初始化编辑状态 const [itemList, updateItemList] = useReducer(editorReducer, { error, isLoading, roster, }); // 监听源数据变化,同步到编辑状态 useEffect(() => { updateItemList({ type: "sync-roster", roster, error, isLoading }); }, [error, isLoading, roster]); // 暴露编辑方法,比如保存排班 const saveRoster = async () => { try { await saveRosterApi(itemList.roster); // 保存成功后可触发数据刷新(若需要) } catch (err) { updateItemList({ type: "set-error", error: err.message }); } }; return { ...itemList, saveRoster }; }
方案二:简化复用(无复杂状态管理)
如果编辑逻辑简单,不需要额外的状态管理,可直接复用useRosterViewer的返回值,再添加编辑方法:
export function useRosterEditor(){ const viewerState = useRosterViewer(); const saveRoster = async (updatedRoster) => { await saveRosterApi(updatedRoster); // 若需要刷新数据,可给useRosterViewer添加refresh方法,或修改其依赖项触发重新请求 }; return { ...viewerState, saveRoster }; }
内容的提问来源于stack exchange,提问作者The KNVB
相关产品推荐
相关产品推荐

