- Published on
- 约 1140 字
一次Antd form.setFieldsValue卡死对应的解决经历
- Authors
- Name
- 小辉辉
现象
一次树组件的开发中,有个需求是监听树的双击事件,拿到这个值后关闭加载树的对话框,同时将双击的节点设置到当前页面form下的一个表单字段中,我初步看了下树的doubleclick事件,有两个入参,一个是事件event,第二个是节点node,嗯,很好,感觉这个需求可以分分钟搞定了。
当我满怀欣喜写好了逻辑后,想进行另外一个功能开发的时候,竟然发现界面上居然点击不了了,仔细看了下代码,有这么一个逻辑,在双击的时候调用了form表单的onchange事件,同时这个form还有一个全局的onchange监听事件,这个全局的事件会去重新组织下当前form的数据中,最后生成一个新的data,传入到form.setFieldsValue方法中。
概括起来就是表单的onchange改变以后,会再去手动调用一次form的setFieldsValue方法,我注意到如果把这个方法注释掉,界面就不会卡住。
解决方案
起初让我疑惑的地方在这里,两份数据应该都是一样的,如果说是双击事件第二个参数有问题的话,那么onchange的时候应该也会卡死才对啊,然后并没有,而是在setFieldsValue的时候卡死了,于是决定打断点调试看看到底是哪里有循环了。
最终就发现有个出自rc-util模块下的set方法频繁调用,一看路径,居然是一个长度为12的数组,可以把这个数组看作一个对象的层级,我的天呐,这个层级也太深了吧,在细看这个层级发现都来源于props参数下面,回过头来看双击事件第二个入场,props居然是一个getter定义方法,直接取了当前节点对应react实例的信息,在这个数实例下面,又自带了很多的引用属性,估计是antd tree组件在设计时为了性能考虑特意加上的。
讲到这里解决方案就很明确了,我只需要取出第二个参数里面需要的字段就行了,马上试了试,正常。
后续
在这之前我怀疑过第二个参数是不是引用对象,不能直接传入的原因,直接对其做个了简单拷贝,用的是...
展开运算符,发现的确可以解决问题,现在看来真正原因并不是引用的关系,而是在拷贝过程中省略了props的getter,下面的代码示例也验证了这个问题。
const data = {
b: 1
};
Object.defineProperty(data, 'a', {
get() {
return 'a';
}
})
console.log(data);
// 输出结果
/*
{
b: 1,
a: (...),
get a: ƒ get()
}
*/
const copyData = {...data};
console.log(copyData);
// 输出结果
/*
{
b: 1
}
*/
可以发现使用defineProperty定义的getter在使用展开运算符后会直接被忽略掉
但是,如果直接使用字面的get来定义getter的话,结果又不一样:
const data = {
get a(){
return 'a';
},
b: 1
}
console.log(data);
// 输出结果
/*
{
b: 1,
a: (...),
get a: ƒ get()
}
*/
const copyData = {...data};
console.log(copyData);
// 输出结果
/*
{
a: "a",
b: 1
}
*/
发现不一样的地方了吗,使用字面get定义的getter对应的对象在使用展开运算符时属性是能被继承过来的,那会到antd的tree组件双击事件第二个入参值对应的props就是使用defineProperty来定义getter的,所以在使用展开运算符的时候props属性直接就被忽略了。
那为什么上面两种getter会有不同的结果呢,其实要回到展开运算符本身的规则,这个运算符只会复制默认可用枚举的属性,也就是enumerable属性为true的情况,属性是否可枚举可以通过Object.getOwnPropertyDescriptor
方法获取到。
通过该方法,我们分别试了下上述定义方法的枚举值就发现,使用Object.defineProperty方法定义的getter它的enumerable属性为false,而使用字面get定义的getter它的enumerable属性是true,这就刚刚能解释上面不同表现结果了。
我们再去查看下文档说明,会发现默认情况下,使用 Object.defineProperty() 添加的属性是不可写、不可枚举和不可配置的,嗯,完美,和我们预想的一模一样。