我使用vue 做前端开发,现在我有一个脚本树,想要只是搜索功能,这个搜索是在前端完成的,但是当脚本树的数据和层次结构较深时,输入搜索后,可能需要1-2秒的时间响应,这时就希望在搜索开始的时候加入loading效果,结束后取消loading。
<Spin size="large" fix v-show="dataLoading"></Spin>
点击搜索后我的搜索方法如下;
onSearchTreeData (searchContent) {
// this.searchText = searchContent
this.dataLoading = true
// 搜索逻辑
this.userScriptTreeMap.forEach((treeItem, menuTag) => {
let isSearchMatch = false
if (!searchContent) {
isSearchMatch = false
} else {
isSearchMatch = treeItem.text.indexOf(searchContent) !== -1
}
// 各个子节点会观测$isSearchMatch 属性值
this.$set(treeItem, '$isSearchMatch', isSearchMatch)
if (isSearchMatch) {
while (treeItem.parent) {
if (!treeItem.parent.opened) {
treeItem.parent.opened = treeItem
}
treeItem = treeItem.parent
}
}
})
// 设置组件结束搜索
this.dataLoading = false
console.info('3333')
},```’
其实这段代码很简单,不用关注搜索逻辑的话,也就是三步,第一步:搜索之前把 dataLoading设置为true,这时候希望Spin组件显示,出现loading效果。第二步: 执行搜索逻辑;第三步搜索完成后:把dataLoading设置为false,loading效果结束,然而测试时,发现不会出现loading效果,也就是vue组件绑定的值变化并没有引起界面的变化。思考原因:一开始觉得搜索很快就完成了,就是dataLoading的值变化的太快,Spin快速的显示和隐藏,视觉上感觉不到而已。然后我加了个空的for循环,时间足够长,这次输入后明显感觉页面卡了,但是也没出现loading效果。
从代码逻辑上来看好像没有啥错误,但是为什么数据值改变了,vue没有重新渲染界面了?这好像和MVVM理念不一样啊。思考了一会想起关键一句话:js 是单线程的。vue只是Js框架而已,数据值的改变,重新渲染都需要js线程去执行,那么我执行搜索方法时是同步的,虽然值改变了,但是vue需要等整个search方法结束后才会把变化的值渲染到组件UI,然而等这整个search方法执行完后,dataLoading相当于没变,自然就不会有Loading效果了。那么关键点就是希望能让搜索的逻辑能够异步执行。js 要咋样异步了?反正想到一个函数setTimeout 貌似是异步的,于是改成如下代码:
// 第一步页面Loading
this.dataLoading = true
// 第二步搜索
setTimeout(() => {
this.userScriptTreeMap.forEach((treeItem, menuTag) => {
this.$set(treeItem, '$isSearchMatch', false)
treeItem.opened = false
})
this.userScriptTreeMap.forEach((treeItem, menuTag) => {
let isSearchMatch = false
if (!searchContent) {
isSearchMatch = false
} else {
isSearchMatch = treeItem.text.indexOf(searchContent) !== -1
}
// 各个子节点会观测$isSearchMatch 属性值
this.$set(treeItem, '$isSearchMatch', isSearchMatch)
if (isSearchMatch) {
while (treeItem.parent) {
if (!treeItem.parent.opened) {
treeItem.parent.opened = treeItem
}
treeItem = treeItem.parent
}
}
})
// 设置组件结束搜索
this.dataLoading = false
}, 100)
这里看到我的执行逻辑是在timeout里面执行的。跑一下效果,果然出现了loading效果。这里可以看到timeout是异步的,即 第一步dataLoading值发生改变并且vue已经监测到值重新渲染UI后,在执行setTimeout里面的搜索逻辑,搜索结束后dataLoading设置为false,loading结束,搜索值出现。
好的,问题解决了,提交代码完工了!开心!
但是这里有个疑惑,为啥setTimeout可以呢?setTimeout 到底做了啥?是不是有其他的办法?带着疑问问了下百度粑粑,发现引出js event loop 知识点。
参考大神门的文章如下:
总的来说需要清楚js 事件循环的机制才能明白解决办法和方案。
看完js 事件循环后,发现Promise 的then 方法回调其实也是异步的,只不过它比setTimeout的时间要早,于是想能不能把搜索方法放到then里面去执行:尝试如下:
onSearchTreeData (searchContent) {
// this.searchText = searchContent
this.dataLoading = true
new Promise(resolve => {
console.info('1111')
resolve()
}).then(() => {
console.info('22222')
// 选重置状态
for (let i = 0; i < 9000000000; i++) {
}
this.userScriptTreeMap.forEach((treeItem, menuTag) => {
this.$set(treeItem, '$isSearchMatch', false)
treeItem.opened = false
})
this.userScriptTreeMap.forEach((treeItem, menuTag) => {
let isSearchMatch = false
if (!searchContent) {
isSearchMatch = false
} else {
isSearchMatch = treeItem.text.indexOf(searchContent) !== -1
}
// 各个子节点会观测$isSearchMatch 属性值
this.$set(treeItem, '$isSearchMatch', isSearchMatch)
if (isSearchMatch) {
while (treeItem.parent) {
if (!treeItem.parent.opened) {
treeItem.parent.opened = treeItem
}
treeItem = treeItem.parent
}
}
})
// 设置组件结束搜索
this.dataLoading = false
})
console.info('3333')
结果发现是不行的,这里看到我打印了执行顺序,执行顺序是 “1111”-》“3333”-》“2222”,从执行顺序来说符合事件循环模型,但是不成功的原因依然是vue js需要监听值的变化,then 被回调的时间太短,vue还未监听执行渲染逻辑,then 逻辑就被执行,js单线程的原因导致必须等then方法执行完后,在执行vue的监听。这个只是我个人推测。