React:函数作为state对象值存储时回调无法触发问题排查
React 兄弟组件传参回调不触发问题排查
问题背景
- 页面存在多个widget小组件,每个widget内部维护自身展示所需的
data(数据)和settings(设置项) - 点击widget按钮会唤起侧边栏:侧边栏是页面级子组件,和所有widget为兄弟节点,侧边栏内置表单控件,用于编辑当前被选中widget的数据和设置
- 原有实现逻辑:在widget和侧边栏的共同父组件中定义公共state,点击widget时更新该state,同时存入侧边栏需要的展示数据、以及修改widget数据的回调函数
- 异常表现:侧边栏可以正常展示选中widget的数据,但触发修改操作时,传入的回调函数始终不执行
相关代码
Widget组件代码
(...) const updatingInformations = (data, settings, config) => { console.log("updating informations : ", data, settings, config); data && setWidgetData(data); settings && setWidgetSettings(settings); config && setWidgetConfig(config); }; (...) <button onClick={(e)=>{ e.preventDefault(); setSidebarContent({ type: "widget", widget: widget, informations: { data: widgetData, config: widgetConfig, settings: widgetSettings, }, callback: updatingInformations, }); }} /> (...)
侧边栏组件代码
(...) const updatingWidget = (path, newValue) => { let stockInfo = { ...sidebarContent.informations }; stockInfo[path[0]][path[1]][path[2]][path[3]] = newValue; console.log( "updating widget : ", sidebarContent, stockInfo[path[0]][path[1]][path[2]][path[3]], ); sidebarContent.callback(null, stockInfo.settings, null); }; (...)
编辑输入触发操作时,控制台可输出
updatingWidget()内的日志,证明该函数已正常执行,但updatingInformations()回调始终未被调用。
问题根因
问题出在两个典型的React使用误区上:
- 不该把组件内定义的函数直接存入state做跨组件传递
updatingInformations是widget组件每次渲染时重新生成的函数,它的闭包只捕获当前渲染轮次的setWidgetData等方法引用。你只有点击按钮打开侧边栏的那一刻,才会把当时生成的updatingInformations存进父组件的sidebarContent状态,后续widget因为数据变化重渲染时,父组件state里存的callback永远是点击那一刻的旧版本,不会跟着更新,多widget场景下还很容易出现callback被其他widget操作覆盖的情况。 - 侧边栏拿到的state是陈旧闭包值
你在侧边栏的updatingWidget里直接读取sidebarContent,如果这个函数被useCallback包裹且没有把sidebarContent加入依赖数组,它拿到的永远是侧边栏首次渲染时的旧state值。这个旧值里的callback要么是undefined、要么是初始状态设置的空函数、要么是其他操作留下的旧回调,自然不会走到你当前选中widget的updatingInformations逻辑里。
另外你写的状态更新逻辑也有隐患:{ ...sidebarContent.informations }只做了第一层浅拷贝,深层的嵌套对象还是原引用,直接给深层属性赋值属于状态突变,会导致React检测不到状态变化,进一步加剧引用不一致的问题。
修复方案
- 最稳妥的改法是不要跨组件传回调:父组件只存当前选中的widget ID,所有widget的data、settings、更新方法全部提升到父组件统一维护,侧边栏修改值时直接调用父组件的统一更新方法,通过ID匹配到对应widget更新即可,从根源上避免闭包引用问题。
- 如果暂时不想调整状态架构:给
updatingInformations包一层useCallback做引用稳定,同时在widget的相关状态(data/settings/config)变化时,同步更新父组件sidebarContent里存的callback值,保证回调永远是最新版本;侧边栏里读取state时要保证拿到最新引用,更新嵌套状态时做完整拷贝,不要直接修改原对象。
内容的提问来源于stack exchange,提问作者FlowRan
相关产品推荐
相关产品推荐

