需求:
用 Redux-form 管理表单数据, 并且需要持久化整个 state 到 localstorage 里面, 这样在页面跳转后, 后面能够随时取到前面页面表单的数据.
代码实现概要:
表单用 redux-form 包裹:
// 因为刷新页面后, state 就没有上一个页面数据了, 必须合并 localstorage 存好的 state // 其中 jso 是一个 npm 的库, 可以 override 前一个 object 的 element const combineStateWithLocalstorage = state => jso(state, JSON.parse(localstorage.getItem('app'))); const mapStateToProps = state => ({ // 这里是我们想要从前一个页面取的表单数据 // getFormValues 是 redux-form 的方法 previousFormValue: getFormValues('previousFormId')(combineStateWithLocalstorage(state)), }); export default reduxForm({ form: 'formId', })(connect(mapStateToProps)(MyForm))
问题来了
我们的存储是在页面跳转的时候, 触发存储的, 但通过日志可以看出, 命名 state 上有数据, 但是 localstorage 就是没有存下来?
通过 redux-logger 看到了原因
其实 redux-form 为了实现垃圾回收机制, 所谓的 GC, 会在 component on unmount 的时候, 销毁表单, 也就是说页面跳转后, state 上对应 form 的表单就会被销毁. 存的时候自然就拿不到值了, 解决方案也很简单, 加上一个 destroyOnUnmount: false 即可.
export default reduxForm({ form: 'formId', destroyOnUnmount: false, })(connect(mapStateToProps)(MyForm))
重点来了
一切都很美好, 刷新页面也能重新填充 state 的值, 在任何一个页面我们都能取到任何一个表单的数据. 也能正常前后跳转, 数据也能正常重新回显.
直到有一次, 我们从页面 A 填好数据, 进入下一个页面 B, 再回到页面 A, 发现…. 表单居然无法编辑了, 数据怎么改都不会变化, 也就是无法输入新的值???
找原因
一开始, 我们认为是 localstorage 存的数据有变化, 我们通过日志发现, redux-form 其实在页面跳转的时候会 unregisterField 的, 然后 localstorage 就存下了像这样的数据:
{ "app": { "formId": { "registerFields": { "someValue": { ... "count": 0 } }, "values": { ... } } } }
你会发现, 有一个 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(state, JSON.parse(localstorage.getItem('app')))
问题就出在这里, jso 是一个 用后一个 Object, 重新给前一个 Object赋值的方法, 就是合并2个 JSON Object. 仔细看源码发现有这样的代码
baseObject[key] = nextObject[key]
按照我们的用法, baseObject 就是 state, 也就是说 state 被改变了!!!!!
问题又来了
不是说改变 state, 需要用 setState 吗? 否则不生效!!!
这里却生效了, 有2个原因:
- 这里的 state 不是 React 的 state, 这个是由 Redux 管理的 state.
- 其实如果用引用修改这里的 state 是不生效的, 关键里面的代码是直接修改.
原因2 是这样的:
state = {someKey: someValue}; // 不生效 state.someKey = someValue; // 生效
到这里就找到原因了, 不用 jso 这个库即可解决问题!