Published on
954

记录react-grid-layout和immer搭配使用导致只读报错

Authors
  • avatar
    Name
    小辉辉
    Twitter

前言

最近项目上有在做一个grid布局的功能,支持在一个大的容器里面拖入小的容器,这个小容器支持任意定位布局,类似与css grid layout,比flex布局要更加自由灵活。

由于时间紧没有精力自己实现一套,网上找了个具备类似功能的组件库react-grid-layout,它的特性正好满足我们的需求。

于是便马上把它引入到项目中,一切都十分顺利,拖拽功能也实现了,然而突然发现有再移动内部的小容器时,会有这么一瞬间定位会飞出当前视口,给我的反应应该是遇到报错了,看了下控台,果然,出现了好多报错:

Uncaught TypeError: Cannot assign to read only property 'x' of object '#'ht TypeError: Cannot assign to read only property 'x' of object '#'

分析

一般我们遇到错误肯定是先定位抛出错误的文件位置,定位如下:

// 文件 /react-grid-layout/build/utils.js
// moveElement方法里
function moveElement() {
  // This is quite a bit faster than extending the object
  if (typeof x === "number") l.x = x; // 报错代码位置

结合错误信息,我们就知道了l这是个只读对象,再翻看它来源于传入的layout参数,这时候就有点眉目了,因为这里的layout来自于外部使用的地方传入的,也就是说传入的地方参数有问题。

这时候马上就联想到我们现在这个项目用了immer进行处理,这里面就会默认开启对象只读的功能,那么自然而然的我们的解决方案就比较简单了,只需要在传入layout之前讲默认immer转换过的值进行一次转换,转换成一个正常的非只读对象即可,对应代码如下

const cloneLayout = (layout: any) => layout.map((l: any) => ({ ...l }));

原以为这一波操作就万事大吉了,马上进行测试发现上述报错的问题依旧存在,这会我就纳闷了?这是啥情况,难道react-layout-grid这个模块内部还有手动对对象进行只读设置操作吗?

拿着这个模块全局搜索了下设置只读的关键字Object.preventExtensions,Object.definePro都没有找到,想想也是,这个模块压根就没有必要添加这个限制啊。

这个时候想想还是有必要再理下整个过程了,假设grid模块本身没有冻结这个限制,那这个限制必定来自于外部,而整个过程是在拖动的时候发生的,会不会是在拖动的时候这个变化的值被外部的immer模块给重新设值了,而新的值还没有来的及作为layout传入?此时可以理解为grid内部的layout和传出外部onLayoutChange是同一个引用对象,所以导致在拖动过程中属性不能赋值的报错。

经过验证,只需在传入外部的onLayoutChange事件里使用上面的转换方法处理下,避免两者之间的引用,这样一来报错终于解决。

结论

相信每个开发在遇到奇奇怪怪的问题时,尤其是以前从来没有遇到过的,一开始都会很头疼,显得无从下手,我这边的经验给我的方案是“大胆假设,快速验证”,前端开发不同于后端的地方就是不需要编译,代码修改后可以直接调试,关键是你要能想到各个情况发生的可能性,这其实就离不开平时的日积月累,还有系统知识的学习,所以这也是这个博客当初建立的初衷。