使用Sortable.js拖拽后,视图更新有错,强制重新更新视图

391 阅读3分钟

问题

当使用了Sortable.js这种第三方库,拖拽之后,视图更新不正确。

更准确的说,在template中绑定的是questionSettings.pages,当拖拽操作的数据层面改变了questionSettings.pages,那么会触发视图更新,结果视图更新的结果不正确。

解决问题的尝试

我之前找过Sortable.js的Github,大致看了一下,只发现Sortable.js只说明了各个hook函数,并没有说明视图更新的问题。

在issue中也查找了视图更新的问题,确实有一些视图更新的反馈问题,但是官方其实更想要从Sortable.js本身来解决问题,视图更新的问题出现后,只能将这个bug作为下一个发布的版本前必须解决的问题,就我现在用的版本,不会解决这个问题。

而同样使用Sortable.js的跟踪回答视图更新问题的人,他们并没有提供一个直接适合我使用的场景的解决方案。

所以,在初步探索Sortable.js的Github之后,并没有找到直接的解决方案。

我认为查看第三方库的Github这个手段更适合用来切实的了解这个组件向外暴露的接口和属性,在版本更新之后,是否有变化。如果你正确使用了该组件,在具体的业务步骤上出现了bug,那么大概率上,查看第三方库的Github并不能让你找到解决bug的方法。

正确的解法

问题分析

既然在拖拽后,自动更新视图的结果不正确。是重新渲染拖拽后questionSettings.pages 这一步出了问题,那么将问题定位在数据层面。

不再执行自动更新,而是为了排除数据层面引发错误的因素,强制赋值为空,渲染空数组,然后再渲染拖拽后的数据。

实现解决方法

先保存拖拽后的questionSettings.pages,使用深拷贝确保引用变化。

const newPages = JSON.parse(JSON.stringify(questionSettings.pages));

强制赋值为空,渲染空数组。

questionSettings.pages = [];

视图渲染即DOM更新操作,在Vue中DOM更新操作属于微任务,需要在同步代码执行完后执行,所以在Vue检测到数据变化之后,不会立即执行更新操作,而是将更新操作加入更新队列。

我的目的是,在视图渲染为空数组后,渲染深拷贝的拖拽后的questionSettings.pages。使用nextTick这个hook函数,在DOM更新之后执行的函数。nextTick这个回调函数也是一个微任务,所以微任务队列中,前有DOM更新操作,后有nextTick回调函数,这样就满足了我在执行顺序上的要求。

nextTick的回调函数中只要简单的一句

questionSettings.pages = newPages

就能完成重新渲染拖拽后的questionSettings.pages

全部代码如下:

const newPages = JSON.parse(JSON.stringify(questionSettings.pages));
questionSettings.pages = [];
nextTick(() => {
  questionSettings.pages = newPages
});

执行顺序分析

  • 同步代码执行完毕
  • Vue 开始处理更新队列,执行 DOM 更新(此时页面确实会渲染为空)
  • DOM 更新完成后,执行nextTick回调
  • 在回调中设置新的 pages 数据,触发新一轮更新

注意

实际上页面是会短暂地渲染为空的,只是这个过程非常快,可能肉眼难以察觉。 nextTick 并不会阻止空数组的渲染,而是在空数组渲染完成后才执行。