API数据加载场景下如何合规使用useCallback与useMemo?
问题描述
我正在开发应用,需要从API获取数据,希望优化生命周期行为,但由于数据未加载时需返回加载/错误提示的条件渲染逻辑,后续使用useCallback或useMemo会触发Hook条件渲染警告/错误。以下是代码示例,请问该如何妥善处理?
const TranscriptController = (mode: 'single' | 'multi') => { const { setPlay } = useContext(VideoPlayerContext) as IVideoPlayerContext; const { transcript, translation, currSelectionIdx, setTranscript, setTranslation, handlePlayNext, handlePlayPrevious } = useContext(TranscriptContext) as ITranscriptContext; useEffect(() => { setTranscript(MOCK_TRANSCRIPT_DE); setTranslation(MOCK_TRANSLATE_EN); }, [setTranscript, setTranslation]); // * data doesnt load, show this message if (!transcript || !translation) { return <Typography>Resources not found</Typography>; } // * otherwise ... // * cannot use callback/memo hooks here or in downstream components const renderOnlyActiveSelection = () => ActiveSelection( transcript.selections[currSelectionIdx], translation.selections[currSelectionIdx], `active-selection`, true ); const renderMultipleSelections = transcript.selections.map((selection, i) => { if (i === currSelectionIdx) { return ActiveSelection( transcript.selections[currSelectionIdx], translation.selections[currSelectionIdx], `selection_${i}` ); } else { return InActiveSelection(selection, translation.selections[i], i); } }); // * output return mode === 'single' ? renderOnlyActiveSelection() : renderMultipleSelections; }; export default TranscriptController;
解决方案
核心问题是违反了React Hook的调用规则:所有Hook必须在组件顶层作用域调用,不能放在条件判断、循环或嵌套函数内部。你当前的提前返回逻辑在Hook之前执行,导致后续的useCallback/useMemo无法放在顶层,触发警告。
具体解决步骤:
- 调整代码顺序:将所有Hook(包括
useCallback/useMemo)放在条件返回逻辑之前,确保每次渲染时Hook的调用顺序完全一致。 - 缓存渲染逻辑:用
useCallback缓存渲染函数,用useMemo缓存列表渲染结果,减少不必要的重渲染。 - 正确设置依赖数组:确保缓存的Hook依赖所有用到的外部变量,避免出现 stale 数据。
修改后的代码示例
import { useCallback, useMemo } from 'react'; const TranscriptController = (mode: 'single' | 'multi') => { const { setPlay } = useContext(VideoPlayerContext) as IVideoPlayerContext; const { transcript, translation, currSelectionIdx, setTranscript, setTranslation, handlePlayNext, handlePlayPrevious } = useContext(TranscriptContext) as ITranscriptContext; useEffect(() => { setTranscript(MOCK_TRANSCRIPT_DE); setTranslation(MOCK_TRANSLATE_EN); }, [setTranscript, setTranslation]); // 用useCallback缓存单个激活项渲染函数,放在顶层 const renderOnlyActiveSelection = useCallback(() => { if (!transcript || !translation) return null; // 加安全判断 return ActiveSelection( transcript.selections[currSelectionIdx], translation.selections[currSelectionIdx], `active-selection`, true ); }, [transcript, translation, currSelectionIdx]); // 用useMemo缓存多选项渲染结果,放在顶层 const renderMultipleSelections = useMemo(() => { if (!transcript || !translation) return null; // 加安全判断 return transcript.selections.map((selection, i) => { if (i === currSelectionIdx) { return ActiveSelection( transcript.selections[currSelectionIdx], translation.selections[currSelectionIdx], `selection_${i}` ); } else { return InActiveSelection(selection, translation.selections[i], i); } }); }, [transcript, translation, currSelectionIdx]); // 条件返回放在所有Hook之后 if (!transcript || !translation) { return <Typography>Resources not found</Typography>; } // 输出渲染结果 return mode === 'single' ? renderOnlyActiveSelection() : renderMultipleSelections; }; export default TranscriptController;
关键说明:
- 即使数据未加载,
useCallback和useMemo依然会被调用,但内部加了安全判断,不会执行无效渲染逻辑。 - 依赖数组包含了
transcript、translation、currSelectionIdx,确保数据变化时缓存的内容能及时更新。 - 完全符合Hook调用规则,不会触发警告,同时实现了性能优化。
内容的提问来源于stack exchange,提问作者kevin
相关产品推荐
相关产品推荐

