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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 17:10:29