最近在做一个需求,使用了Draglua这个拖拽库,在使用的时候出现了莫名的render错误:被拖拽的元素排序错乱,甚至是神秘消失或者突然增加……
问题描述
先放图:
我需要实现拖拽文件排序的功能,可以看到在拖拽过程中,数据项在疯狂乱跳……
原代码是这样的:
updateFileList(list) {
this.props.updateFile(this.updateSeq(list));
}
newFileList() {
const {
fileList
} = this.props;
return JSON.parse(JSON.stringify(fileList));
}
handleDrag = (carousleCard) => {
const self = this;
if (carousleCard) {
const options = {
accepts: function (el, target, source, sibling) {
if (sibling) {
self.handleChangeSeq(Number(el.id), Number(sibling.id));
} else {
self.handleChangeSeq(Number(el.id), undefined);
}
return true;
}
};
Dragula([carousleCard], options);
}
};
handleChangeSeq(sourceId, siblingId) {
const list = this.newFileList();
const item = list[sourceId - 1];
if (siblingId) {
if (siblingId === sourceId) {
return;
}
// 当把元素往前移动
if (sourceId > siblingId) {
list.splice(sourceId - 1, 1);
list.splice(siblingId - 1, 0, item);
} else {
list.splice(siblingId - 1, 0, item);
list.splice(sourceId - 1, 1);
}
} else {
list.splice(sourceId - 1, 1);
list.push(item);
}
this.updateFileList(list);
}
updateSeq = (list) => {
return list.map((item, index) => {
return {
...item,
seq: index + 1,
};
});
};
renderDetailCom = () => {
const {
fileList, disabled
} = this.props;
const res = fileList.map((item, index) => {
return (
<UploadedModule
key={item.seq}
id={item.seq}
fileName={item.fileName}
disabled={disabled}
onDelete={() => this.doDeleteFile(index)}
/>
);
});
return (
<div ref={this.handleDrag} className="multi-file-upload-list">
{res}
</div>
);
};
最初我考虑到列表状态值的改变和拖拽行为都会引起dom结构的改变,所以误认为是双数据源的问题。
但是我始终也找不到相应的解决方案,这令我头疼不已。
经过很长一段时间的尝试,还是无法解决这个问题,就去请教了一名经验丰富的同事。
把问题跟他说了一下,又给他看了代码,他一针见血的指出,我给元素设置的key
值是错误的。
虽然我对key
值的作用有所了解(react利用key
来识别组件,它是一种身份标识标识),但是我怎么也没想到,这个问题居然是key
值设置得不恰当引起的。
我给组件设置的key
值seq是用来标识排列顺序的,而在拖拽过程中,组件的排列顺序持续在发生改变,所以seq的值也持续在变化。这样的变化使得react在做diff
算法的时候出现了识别错误,从而引发了render错误的问题。
当问题的原因被发现了,那么解决问题就比较简单了,只需要修改一下key
值就可以了。我将原本设置的seq更换成了一个不会改变的key
值,刷新网页,发现问题果然被解决了!
更改后的函数:
renderDetailCom = () => {
const {
fileList, disabled
} = this.props;
const res = fileList.map((item, index) => {
return (
<UploadedModule
key={`${item.fileStorageId}${item.fileName}`}
id={item.seq}
fileName={item.fileName}
disabled={disabled}
onDelete={() => this.doDeleteFile(index)}
/>
);
});
return (
<div ref={this.handleDrag} className="multi-file-upload-list">
{res}
</div>
);
};
修改后的效果:
敲黑板
key值一定要恰当,必须要保证key值是不可变的。