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

React字典应用:如何确保组件在数据获取完成后再渲染?

问题根源与解决方案

核心问题是你违反了React钩子的调用规则——useFetch作为自定义钩子,不能在async函数fetchData里调用,必须放在组件的顶层作用域中。这种错误会导致钩子的状态更新完全混乱,比如loading、isError的状态和实际请求进度不同步,明明数据还没加载完成,loading就被设为false,导致Word组件提前渲染,而此时term还是null,最终在Definitions组件里触发报错。

以下是具体修复步骤:


1. 重构useFetch钩子,让它自主管理状态

把原来依赖外部setter的钩子,改成标准的自定义钩子,自己内部管理请求相关的状态:

import { useState, useEffect } from 'react';
import nProgress from 'nprogress';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);

  useEffect(() => {
    if (!url) return;
    
    const fetchData = async () => {
      setLoading(true);
      setError(false);
      nProgress.start();
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error(res.status);
        const responseData = await res.json();
        setData(responseData);
      } catch (err) {
        setError(true);
      } finally {
        setLoading(false);
        nProgress.done();
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

2. 在App组件顶层调用useFetch,依赖输入值自动触发请求

删掉原来的fetchData函数,直接在组件顶层调用钩子,让请求随input的变化自动发起:

function App() {
  const [queryParams, setQueryParams] = useSearchParams();
  const initialInput = queryParams.get("term") ?? "hello";
  const [input, setInput] = useState(initialInput);
  const [isValid, setIsValid] = useState(true);

  // 顶层调用useFetch,url随input变化自动发起请求
  const { data, loading, error } = useFetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${input}`);
  // 安全获取第一个词条数据
  const term = data?.[0];

  useEffect(() => {
    setInput(initialInput);
  }, [initialInput]);

  const handleChange = (e) => {
    setInput(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (input.length < 1) {
      setIsValid(false);
      return;
    }
    setIsValid(true);
    setQueryParams({ term: input });
  };

  // 强化条件渲染:除了loading和error,还要确保term存在
  return (
    // ...其他组件结构
    {!loading && !error && term && <Word data={term} darkTheme={darkTheme} />}
    // ...其他组件结构
  );
}

3. 在Definitions组件添加防御性检查

即使父组件传入了数据,也要确保meanings存在后再执行映射,避免API返回结构异常的情况:

// Definitions组件内的映射逻辑
{data?.meanings?.map((meaning) => (
  <div key={meaning.partOfSpeech}>
    {/* 其他JSX内容 */}
    {meaning.definitions?.map((definition, idx) => (
      <li key={idx} className="marker:text-purple-600 pt-4">
        {definition.definition}
      </li>
    ))}
  </div>
))}

补充说明

React钩子必须遵守顶层调用规则:只能在组件或自定义钩子的顶层调用,不能嵌套在函数、循环、条件语句中。你之前把useFetch放在async函数里,会导致React无法追踪钩子的执行顺序,状态更新完全混乱——比如loading可能在请求未完成时就被设为false,此时term还是null,但你的条件渲染!loading && !isError会通过,直接渲染Word组件引发报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:53:19