记一次 React + Redux-form + Localstorage 返回到上一个页面无法编辑表单的 BUG

18 1月

需求:

用 Redux-form 管理表单数据, 并且需要持久化整个 state 到 localstorage 里面, 这样在页面跳转后, 后面能够随时取到前面页面表单的数据.

代码实现概要:

表单用 redux-form 包裹:

问题来了

我们的存储是在页面跳转的时候, 触发存储的, 但通过日志可以看出, 命名 state 上有数据, 但是 localstorage 就是没有存下来?

通过 redux-logger 看到了原因

其实 redux-form 为了实现垃圾回收机制, 所谓的 GC, 会在 component on unmount 的时候, 销毁表单, 也就是说页面跳转后, state 上对应 form 的表单就会被销毁. 存的时候自然就拿不到值了, 解决方案也很简单, 加上一个 destroyOnUnmount: false 即可.

 

重点来了

一切都很美好, 刷新页面也能重新填充 state 的值, 在任何一个页面我们都能取到任何一个表单的数据. 也能正常前后跳转, 数据也能正常重新回显.

直到有一次, 我们从页面 A 填好数据, 进入下一个页面 B, 再回到页面 A, 发现…. 表单居然无法编辑了, 数据怎么改都不会变化, 也就是无法输入新的值???

找原因

一开始, 我们认为是 localstorage 存的数据有变化, 我们通过日志发现, redux-form 其实在页面跳转的时候会 unregisterField 的, 然后 localstorage 就存下了像这样的数据:

你会发现, 有一个 count 是0, 正常情况下表单注册的字段, count 是1, 会不会是这个原因?

寻找有没有类似 NotUnregisterFieldsOnUnmount 的属性, 发现 redux-form 并没有. 只有 enableReinitialize 和 keepDirtyOnReinitialize 的属性, 但尝试了都无法解决问题.

尝试在各个 React component 的 lifesycle 方法重新 initialize 表单, 还是无法解决, 再次回到页面, 表单依旧无法再次编辑.

(n 小时鏖战…)

问题浮现

终于, 我们发现其实 在我们尝试编辑表单的时候, redux-form 会 dispatch 一个 CHANGE 的 action 出去, 这个 action 如果你断点跟着走, 会发现其实修改是成功了的, 但是又被 React-dom 的一个 JS 给强制回退回去了.

通过研究发现, React Redux 其实在每一次 action 被 dispatch 的时候, 都会重新执行一次 connect 里面的方法, 以保证 props 被修改后重新渲染页面.

那么问题就应该出在 connect 的方法里, 也就是我们写的 mapStateToProps 里面. 简单来说就是 redux-form 其实 dispatch 了 CHANGE 事件, 是成功修改了表单的, 但是紧接着又被 mapStateToProps 的什么东西给重新赋值回去了.

最终解决

我们发现 mapStateToProps 有这样的代码

问题就出在这里, jso 是一个 用后一个 Object, 重新给前一个 Object赋值的方法, 就是合并2个 JSON Object. 仔细看源码发现有这样的代码

按照我们的用法, baseObject 就是 state, 也就是说 state 被改变了!!!!!

问题又来了

不是说改变 state, 需要用 setState 吗? 否则不生效!!!

这里却生效了, 有2个原因:

  1. 这里的 state 不是 React 的 state, 这个是由 Redux 管理的 state.
  2. 其实如果用引用修改这里的 state 是不生效的, 关键里面的代码是直接修改.

原因2 是这样的:

到这里就找到原因了, 不用 jso 这个库即可解决问题!

发表评论

电子邮件地址不会被公开。