为何通过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} />; }
问题原因
- 状态不同步:List组件中
renderedOptions的初始值仅在组件首次挂载时被设置为当时的options(空数组)。当父组件异步加载完成后,options更新为完整列表,但renderedOptions没有自动同步更新,仍保持初始空数组状态。 - 触发条件缺失:虽然
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
相关产品推荐
相关产品推荐

