最近在做一个需求,使用了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值设置得不恰当引起的。

我给组件设置的keyseq是用来标识排列顺序的,而在拖拽过程中,组件的排列顺序持续在发生改变,所以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值是不可变的。