调用props传递的父函数时React useState更新失效问题
问题
我想在UI元素被选中时阻止重复点击,通过useState维护upArrowSelected和downArrowSelected两个布尔变量控制投票逻辑。但调用父组件的passVotes函数更新并排序列表后,这两个useState的状态更新逻辑就失效了——单独使用useState时逻辑正常,现在调用props.passVotes()后,updateDownArrowSelected()和updateUpArrowSelected()似乎没生效,我希望这两个操作都能执行。
子组件代码
const QuoteBubble = (props: Quote) =>{ const [upArrowSelected, updateUpArrowSelected] = useState(false); const [downArrowSelected, updateDownArrowSelected] = useState(false); const upVote = (event: React.MouseEvent)=>{ event.preventDefault(); if(upArrowSelected){ updateDownArrowSelected(false); updateUpArrowSelected(false); props.passVotes(props.votes - 1, props.quoteId); return; } updateDownArrowSelected(false); updateUpArrowSelected(true); props.passVotes(props.votes + 1, props.quoteId); } const downVote = (event: React.MouseEvent) =>{ event.preventDefault(); if(downArrowSelected){ props.passVotes(props.votes + 1, props.quoteId); updateUpArrowSelected(false); updateDownArrowSelected(false); return; } props.passVotes(props.votes - 1, props.quoteId); updateUpArrowSelected(false); updateDownArrowSelected(true); } return( <Card variant="outlined" className='quote-bubble'> <div className='quote-text quote-box'> <a href='' className={`btn ${upArrowSelected ? 'arrow-selected' : 'arrow-not-selected'}`} onClick={upVote}> <FontAwesomeIcon icon={faSquareCaretUp} className='up-arrow'/> </a> <h1>{props.quote}</h1> </div> <div className='quote-text author-box'> <a href='' className={`btn ${downArrowSelected ? 'arrow-selected' : 'arrow-not-selected'}`} onClick={downVote}> <FontAwesomeIcon icon={faSquareCaretDown} className='down-arrow'/> </a> <Chip label={props.votes} variant="outlined" className='votes-pill' /> <p>{props.author}</p> </div> </Card> ) }
父组件代码
const QuoteList = (props: QuoteListProps) =>{ const sortQuotes = (quotes: QuoteDataInput[]) => { quotes.sort((quoteA: QuoteDataInput, quoteB: QuoteDataInput) =>{ return quoteB.votes - quoteA.votes; }); props.setQuotes({quotes: [...quotes]}); return quotes; } const GetVotes = (votes: number, id: number) =>{ const updatedQuote = props.quoteList.find((quote) => quote.quoteId === id) if(updatedQuote){ updatedQuote.votes = votes; props.setQuotes({quotes:[...props.quoteList, updatedQuote]}); } sortQuotes(props.quoteList) } return( <ul className='quote-list'> {props.quoteList.map((quote: QuoteDataInput)=>( <li><QuoteBubble quote={quote.quote} author={quote.author} key={Math.random()} votes={quote.votes} passVotes={GetVotes} quoteId={quote.quoteId}/></li> ))} </ul> ) }
解决方案
问题根源在两个核心错误,修复后即可恢复状态逻辑:
1. 替换不稳定的组件Key
你用Math.random()作为QuoteBubble的key,会导致父组件更新时所有子组件被强制销毁重建,之前的useState状态直接丢失,看起来像是状态更新失效。必须用稳定唯一的key,比如quote.quoteId:
{props.quoteList.map((quote: QuoteDataInput)=>( <li key={quote.quoteId}> <QuoteBubble quote={quote.quote} author={quote.author} votes={quote.votes} passVotes={GetVotes} quoteId={quote.quoteId} /> </li> ))}
2. 修复父组件的列表更新逻辑
父组件GetVotes里直接修改原数组元素,还错误地把更新后的元素追加到数组末尾,导致数组出现重复项,排序逻辑也基于错误数组执行。正确做法是创建新数组替换目标元素:
const GetVotes = (votes: number, id: number) =>{ // 创建新数组,替换对应id的元素 const updatedQuotes = props.quoteList.map(quote => quote.quoteId === id ? {...quote, votes} : quote ); sortQuotes(updatedQuotes); } // 同时修改sortQuotes,基于新数组排序后更新状态 const sortQuotes = (quotes: QuoteDataInput[]) => { // 先拷贝数组再排序,避免修改原数据 const sortedQuotes = [...quotes].sort((quoteA, quoteB) => quoteB.votes - quoteA.votes); props.setQuotes({quotes: sortedQuotes}); }
可选优化:合并状态简化逻辑
可以把两个布尔状态合并成一个,用selectedVote表示当前选中的投票类型,让逻辑更简洁:
const [selectedVote, setSelectedVote] = useState<'up' | 'down' | null>(null); const upVote = (event: React.MouseEvent)=>{ event.preventDefault(); let newVotes = props.votes; if(selectedVote === 'up'){ newVotes -= 1; setSelectedVote(null); } else { // 如果之前选了down,需要抵消后再加1 newVotes += selectedVote === 'down' ? 2 : 1; setSelectedVote('up'); } props.passVotes(newVotes, props.quoteId); } const downVote = (event: React.MouseEvent) =>{ event.preventDefault(); let newVotes = props.votes; if(selectedVote === 'down'){ newVotes += 1; setSelectedVote(null); } else { // 如果之前选了up,需要抵消后再减1 newVotes -= selectedVote === 'up' ? 2 : 1; setSelectedVote('down'); } props.passVotes(newVotes, props.quoteId); }
对应的className也可以简化:
className={`btn ${selectedVote === 'up' ? 'arrow-selected' : 'arrow-not-selected'}`}
className={`btn ${selectedVote === 'down' ? 'arrow-selected' : 'arrow-not-selected'}`}
内容的提问来源于stack exchange,提问作者Baffled Code
相关产品推荐
相关产品推荐

