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

为何通过setTimeout更新状态后子组件无法渲染正确Props?

问题描述

编写了一个List组件用于渲染项目列表,支持输入框过滤,加载或无数据时显示对应提示。在App组件中用setTimeout模拟异步请求加载数据后,List组件无法正确显示新的项目列表。

List组件代码:

import { useState } from "react";

export const List = ({ loading, options }) => {
  const _options = options ?? [];
  const [renderedOptions, setRenderedOptions] = useState(_options);
  const [inputValue, setInputValue] = useState("");

  function handleChange(event) {
    setInputValue(event.target.value);
    const filteredOptions = _options.filter((option) =>
      option.toLowerCase().includes(event.target.value.toLowerCase())
    );
    setRenderedOptions(filteredOptions);
  }

  return (
    <div>
      <input type="text" value={inputValue} onChange={handleChange} />
      <ul>
        {renderedOptions.length > 0 ? (
          renderedOptions.map((option) => <li key={option}>{option}</li>)
        ) : loading ? (
          <li>Loading...</li>
        ) : (
          <li>Nothing to show</li>
        )}
      </ul>
    </div>
  );
};

App组件代码:

import { List } from "./List";
import { useState, useEffect } from "react";

const ITEMS = ["list_1", "list_2", "list_3", "list_4", "list_5"];

export default function App() {
  const [asyncOptions, setAsyncOptions] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    setIsLoading(true);
    const timeoutId = setTimeout(() => {
      setIsLoading(false);
      setAsyncOptions(ITEMS);
    }, 2000);

    return () => clearTimeout(timeoutId);
  }, []);
  return <List options={asyncOptions} loading={isLoading} />;
}
问题原因
  1. 状态不同步:List组件中renderedOptions的初始值仅在组件首次挂载时被设置为当时的options(空数组)。当父组件异步加载完成后,options更新为完整列表,但renderedOptions没有自动同步更新,仍保持初始空数组状态。
  2. 触发条件缺失:虽然handleChange中会根据_options过滤列表,但只有用户触发输入框变化时才会重新计算。异步数据加载完成后,输入框值为空,不会触发handleChange,因此renderedOptions始终是空数组,最终显示"Nothing to show"。
解决方案

方案一:监听props变化同步更新状态

使用useEffect监听options和inputValue的变化,每当这两个值改变时,重新计算并更新renderedOptions,确保状态与props同步:

import { useState, useEffect } from "react";

export const List = ({ loading, options }) => {
  const _options = options ?? [];
  const [renderedOptions, setRenderedOptions] = useState(_options);
  const [inputValue, setInputValue] = useState("");

  useEffect(() => {
    // 每次options或inputValue变化时,重新过滤列表
    const filteredOptions = _options.filter((option) =>
      option.toLowerCase().includes(inputValue.toLowerCase())
    );
    setRenderedOptions(filteredOptions);
  }, [_options, inputValue]);

  function handleChange(event) {
    setInputValue(event.target.value);
  }

  return (
    <div>
      <input type="text" value={inputValue} onChange={handleChange} />
      <ul>
        {renderedOptions.length > 0 ? (
          renderedOptions.map((option) => <li key={option}>{option}</li>)
        ) : loading ? (
          <li>Loading...</li>
        ) : (
          <li>Nothing to show</li>
        )}
      </ul>
    </div>
  );
};

方案二:实时计算过滤结果(推荐)

去掉renderedOptions状态,直接在渲染时根据当前的options和inputValue计算过滤后的列表,避免维护额外状态带来的同步问题:

import { useState } from "react";

export const List = ({ loading, options }) => {
  const _options = options ?? [];
  const [inputValue, setInputValue] = useState("");

  // 实时计算过滤后的列表
  const renderedOptions = _options.filter((option) =>
    option.toLowerCase().includes(inputValue.toLowerCase())
  );

  function handleChange(event) {
    setInputValue(event.target.value);
  }

  return (
    <div>
      <input type="text" value={inputValue} onChange={handleChange} />
      <ul>
        {renderedOptions.length > 0 ? (
          renderedOptions.map((option) => <li key={option}>{option}</li>)
        ) : loading ? (
          <li>Loading...</li>
        ) : (
          <li>Nothing to show</li>
        )}
      </ul>
    </div>
  );
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 12:24:19