v-for 遇到 iframe 时的一些坑

概述

最近在开发的那个项目,需要将 iframe 放入组件,使用 v-for 把 n 个组件渲染出来,此外还需要随时调整组件的显示顺序,效果就像是 win10 里点击一个窗口将它置顶,其他窗口就会下移那样。

这样的需求平常写起来很简单,但是加上 iframe 就麻烦了,因为 iframe 动不动就会重新加载,影响体验。

先从变更顺序这个点开始说,现在有个窗口列表,里面包含了所有应该显示的窗口,因为 v-for 是按顺序遍历的,所以列表的最后一个元素会显示在最上层,那么置顶操作就相当于”将一个元素移至末尾”

听起来很简单是吧,那先写一段实现:

1
2
3
4
5
6
7
8
handle (state, payload) {
if (!state.list.includes(payload)) {
return
}
const index = state.list.indexOf(payload)
state.list.splice(index, 1)
state.list.push(payload)
}

删除这个窗口,再把它放到列表末尾,看起来好像没问题,用起来就出问题了,交换位置的时候这个窗口中的 iframe 重新加载了。

那换一种方法,不删除元素,直接交换两个元素的位置:

1
2
3
4
5
6
7
8
9
10
handle (state, payload) {
if (!state.list.includes(payload)) {
return
}
const index = state.list.indexOf(payload)
const lastIndex = state.list.length - 1
const temp = state.list[index]
state.list[index] = state.list[lastIndex]
state.list[lastIndex] = temp
}

元素位置确实变了,但是 vuex 也确实监听不到 list 的变化。

在方法 2 的基础上做一些修改,给 list 重新赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
handle (state, payload) {
if (!state.list.includes(payload)) {
return
}
const index = state.list.indexOf(payload)
const lastIndex = state.list.length - 1
const temp = state.list[index]
state.list[index] = state.list[lastIndex]
state.list[lastIndex] = temp

const tempList = state.list
state.list = []
state.list = tempList
}

强制更新了 list,vuex 也能检测到了,但是 iframe 重置了。

换一种更新的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
handle (state, payload) {
if (!state.list.includes(payload)) {
return
}
const index = state.list.indexOf(payload)
const lastIndex = state.list.length - 1
const temp = state.list[index]
state.list[index] = state.list[lastIndex]
state.list[lastIndex] = temp

state.push(undefined)
state.pop()
}

还是一样,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。

原因

原因暂时不清楚。