React中当props变化时使用useEffect获取新状态是否合理?
示例代码
const Parent = () => { const [parentVal, setParentVal] = useState(0); const updateParentVal = () => { setParentVal((v) => v + 1); }; return ( <div style={{ border: "1px solid red", padding: 10 }}> <span style={{ marginRight: 10 }}>parentVal: {parentVal}</span> <button onClick={updateParentVal}>increment</button> <Child parentVal={parentVal} /> </div> ); };
const Child = ({ parentVal }) => { const [childVal, setChildVal] = useState(-2); const fetchNextChildVal = (currChildVal) => Promise.resolve(currChildVal + 2); useEffect(() => { (async () => { const nextChildVal = await fetchNextChildVal(childVal); setChildVal(nextChildVal); })(); }, [parentVal]); return ( <div style={{ border: "1px solid blue", margin: 30, padding: 10 }}> <span>childVal: {childVal}</span> </div> ); };
export default function App() { return <Parent />; }
我的困惑
我看完React官方文档《You Might Not Need an Effect》后仍有疑问。文档多处不建议用useEffect更新状态,这让我觉得正确做法应该是在updateParentVal函数里获取数据再传给Child组件。特别是文档“Sharing logic between event handlers”章节提到:
当你不确定某些代码应该放在Effect还是事件处理函数中时,问问自己这段代码为什么需要运行。仅当代码需要因组件展示给用户而运行时才使用Effect。在这个例子中,通知应该因为用户点击按钮而出现,而不是因为页面展示!删除Effect,将共享逻辑放入一个由两个事件处理函数调用的函数中:
我知道这章讲的是多事件处理函数共享逻辑,但感觉它传递了一个更宽泛的观点——在事件处理函数里获取数据更合适。不过文档末尾的“Fetching data”章节又说响应props变化获取数据是合理的,所以我不确定最佳实践是什么。
解答
核心结论
响应props变化用useEffect获取/更新状态在很多场景下是合理的,但要分情况讨论,关键看数据依赖的触发源和组件职责划分。
分场景分析
你的示例场景:存在潜在问题
你写的Child组件里,useEffect依赖parentVal,但内部使用了childVal状态——这里有个隐患:当parentVal变化时,childVal可能是旧值(因为React状态更新是异步的)。比如连续点击按钮,parentVal多次变化,fetchNextChildVal可能拿到的是初始的-2或者中间旧值,导致最终childVal不符合预期。正确的修复方式是使用函数式更新,确保拿到最新的
childVal:useEffect(() => { (async () => { setChildVal(async prev => await fetchNextChildVal(prev)); })(); }, [parentVal]);什么时候适合用useEffect响应props获取数据?
当数据的获取是因为组件的props变化导致视图需要更新时,这种场景完全符合官方文档的建议:- 列表组件接收
categoryIdprops,需要根据新分类ID请求对应列表数据 - 详情组件接收
itemIdprops,需要重新请求该物品的详情信息
这类场景中,props变化是数据更新的直接触发条件,且数据是组件自身需要的状态,用
useEffect监听props并发起请求是合理的。- 列表组件接收
什么时候更适合在事件处理函数里处理?
当数据的获取是直接由用户操作触发,且数据需要在父组件层面共享时,逻辑应该放在事件处理函数里:- 用户点击“加载更多”按钮,父组件发起请求后把数据传给子组件
- 用户提交表单,父组件处理请求后更新子组件的状态
这种情况的触发源是用户操作,不是组件渲染,符合文档中“不要用Effect处理用户操作触发的逻辑”的建议。
回到你的示例
如果childVal的更新逻辑只和parentVal变化强相关,且是Child组件自身的状态,那保留useEffect是合理的,但要修复状态依赖的问题;如果childVal的更新其实是和用户点击按钮这个操作直接绑定的,那把逻辑移到父组件的updateParentVal里,再把childVal作为props传给Child会更清晰,也更符合“用户操作触发逻辑”的原则。
内容的提问来源于stack exchange,提问作者Adam Zerner

