概述
最近在开发的那个项目,需要将 iframe 放入组件,使用 v-for 把 n 个组件渲染出来,此外还需要随时调整组件的显示顺序,效果就像是 win10 里点击一个窗口将它置顶,其他窗口就会下移那样。
坑
这样的需求平常写起来很简单,但是加上 iframe 就麻烦了,因为 iframe 动不动就会重新加载,影响体验。
先从变更顺序这个点开始说,现在有个窗口列表,里面包含了所有应该显示的窗口,因为 v-for 是按顺序遍历的,所以列表的最后一个元素会显示在最上层,那么置顶操作就相当于”将一个元素移至末尾”。
听起来很简单是吧,那先写一段实现:
1 | handle (state, payload) { |
删除这个窗口,再把它放到列表末尾,看起来好像没问题,用起来就出问题了,交换位置的时候这个窗口中的 iframe 重新加载了。
那换一种方法,不删除元素,直接交换两个元素的位置:
1 | handle (state, payload) { |
元素位置确实变了,但是 vuex 也确实监听不到 list 的变化。
在方法 2 的基础上做一些修改,给 list 重新赋值:
1 | handle (state, payload) { |
强制更新了 list,vuex 也能检测到了,但是 iframe 重置了。
换一种更新的方法:
1 | handle (state, payload) { |
还是一样,list 更新,vuex 更新,iframe 也重置。
排除其他因素
这里要排除一种情况,为 v-for 正名,就是 v-for 在遇到顺序变更的情况并不会重载组件,只是更改顺序而已。
为了证明这一点,可以将 \{\{ +new Date() \}\}
放入 template 中,或者使用 beforeDestroy
钩子,事实上更改顺序之后组件的日期并不会更新,而且 beforeDestroy
也不会被触发。
所以这种”异常”的情况只是 iframe 自己的锅。
解决方案
既然变更顺序的排序方式不好使,那就换一种思路,在不更改元素 index 的前提下对元素位置进行改变,也就是让元素自己保留一个 order 属性去记录位置。每一次置顶,先过滤出比当前窗口 order 大的窗口列表,得到列表长度,并且让这些窗口的 order 减去 1,再让当前窗口的 order 加上列表长度。比方说 1 2 3 4 5 的窗口 2 要置顶,先将 3 4 5 分别减 1,再让 2 加上长度 3。
原因
原因暂时不清楚。