React子组件向父组件传递更新后的状态为何失效?
问题:React子组件状态同步父组件时切换顺序混乱
我切换子组件状态后能实现父组件UI更新,但总觉得实现方式有问题。我基于event.target.value更新单选按钮组件的状态,再通过onToggle属性传递给父组件WebsitePicker(子组件是SitePageToggle)。按道理应该用更新后的selectedRadioBtn状态传递,但这么做时,'Website'和'Page'的切换顺序会混乱颠倒;但在子组件内部使用这个更新后的状态却没问题。我知道状态更新可能是异步的,但用useState的回调形式也没解决。
父组件代码:
import styles from "./WebsitePicker.module.css"; import { useState } from "react"; import SitePageToggle from "./SitePageToggle"; import SubmitButton from "./Buttons/SubmitButton"; function WebsitePicker() { const [QAObject, setQAObject] = useState("Website"); const { label, input__url } = styles; const toggleWebsitePageHandler = toggleData => { if (toggleData === 'Website') { setQAObject("Website"); } else { setQAObject("Page"); } }; return ( <> <SitePageToggle onToggle={toggleWebsitePageHandler} /> <form onSubmit={formSubmissionHandler}> <label className={label} htmlFor="picker"> Enter the URL of the {QAObject} you wish to QA. </label> <br /> <input id="picker" type="url" className={input__url} /> <SubmitButton>QA my page</SubmitButton> </form> </> ); } export default WebsitePicker;
子组件代码:
import RadioButton from "./Buttons/RadioButton"; function SitePageToggle({ onToggle }) { const [selectedRadioBtn, setSelectedRadioBtn] = useState("Website"); const isRadioSelected = (btnValue) => selectedRadioBtn === btnValue; const handleToggle = event => { setSelectedRadioBtn(event.target.value); onToggle(event.target.value); }; return ( <ul> <li> <RadioButton label="Website" name="react-radio-btn" value="Website" checked={isRadioSelected("Website")} onChange={handleToggle} /> </li> <li> <RadioButton label="Page" name="react-radio-btn" value="Page" checked={isRadioSelected("Page")} onChange={handleToggle} /> </li> </ul> ); } export default SitePageToggle;
解决方案
问题根源
setSelectedRadioBtn是异步操作,调用后selectedRadioBtn不会立刻更新成新值。当你在handleToggle里先调用setSelectedRadioBtn,紧接着就把selectedRadioBtn传给onToggle,此时传递的还是更新前的旧值,导致父组件拿到的状态和子组件最终状态不一致,出现切换顺序颠倒的问题。而event.target.value是事件触发时的实时值,是同步的,所以传递它不会有问题。
方法1:继续使用event.target.value传递(最简单)
保持当前的handleToggle写法即可,event.target.value能准确传递当前选中的选项:
const handleToggle = event => { setSelectedRadioBtn(event.target.value); onToggle(event.target.value); };
方法2:用useEffect监听子组件状态变化
如果一定要基于selectedRadioBtn传递给父组件,可以用useEffect监听状态变化,等状态更新完成后再同步给父组件:
import { useState, useEffect } from "react"; import RadioButton from "./Buttons/RadioButton"; function SitePageToggle({ onToggle }) { const [selectedRadioBtn, setSelectedRadioBtn] = useState("Website"); const isRadioSelected = (btnValue) => selectedRadioBtn === btnValue; const handleToggle = event => { setSelectedRadioBtn(event.target.value); }; // 监听状态变化,同步给父组件 useEffect(() => { onToggle(selectedRadioBtn); }, [selectedRadioBtn, onToggle]); return ( <ul> <li> <RadioButton label="Website" name="react-radio-btn" value="Website" checked={isRadioSelected("Website")} onChange={handleToggle} /> </li> <li> <RadioButton label="Page" name="react-radio-btn" value="Page" checked={isRadioSelected("Page")} onChange={handleToggle} /> </li> </ul> ); } export default SitePageToggle;
方法3:改为受控组件(推荐,符合React单向数据流)
把子组件的状态提升到父组件,子组件不再维护自己的状态,完全由父组件控制,从根源避免状态不一致问题:
修改后的父组件
import styles from "./WebsitePicker.module.css"; import { useState } from "react"; import SitePageToggle from "./SitePageToggle"; import SubmitButton from "./Buttons/SubmitButton"; function WebsitePicker() { const [QAObject, setQAObject] = useState("Website"); const { label, input__url } = styles; const toggleWebsitePageHandler = toggleData => { setQAObject(toggleData); }; // 补充表单提交处理函数(原代码缺失) const formSubmissionHandler = (e) => { e.preventDefault(); // 这里添加表单提交逻辑 }; return ( <> <SitePageToggle selectedValue={QAObject} onToggle={toggleWebsitePageHandler} /> <form onSubmit={formSubmissionHandler}> <label className={label} htmlFor="picker"> Enter the URL of the {QAObject} you wish to QA. </label> <br /> <input id="picker" type="url" className={input__url} /> <SubmitButton>QA my page</SubmitButton> </form> </> ); } export default WebsitePicker;
修改后的子组件
import RadioButton from "./Buttons/RadioButton"; function SitePageToggle({ selectedValue, onToggle }) { const isRadioSelected = (btnValue) => selectedValue === btnValue; const handleToggle = event => { onToggle(event.target.value); }; return ( <ul> <li> <RadioButton label="Website" name="react-radio-btn" value="Website" checked={isRadioSelected("Website")} onChange={handleToggle} /> </li> <li> <RadioButton label="Page" name="react-radio-btn" value="Page" checked={isRadioSelected("Page")} onChange={handleToggle} /> </li> </ul> ); } export default SitePageToggle;
内容的提问来源于stack exchange,提问作者JG Trindade
相关产品推荐
相关产品推荐

