vue 组件绑定的值发生变化后,组件UI效果不生效

问题场景

我使用vue 做前端开发,现在我有一个脚本树,想要只是搜索功能,这个搜索是在前端完成的,但是当脚本树的数据和层次结构较深时,输入搜索后,可能需要1-2秒的时间响应,这时就希望在搜索开始的时候加入loading效果,结束后取消loading。

  • 脚本树长这样:
  • vue 组件绑定的值发生变化后,组件UI效果不生效_第1张图片
  • 点击搜索后希望使用iview 的 Spin 组件, 期望结果如下:
    vue 组件绑定的值发生变化后,组件UI效果不生效_第2张图片
    也就是加个遮罩层,有个Loaing,按照iview的组件文档只需要给Spin 组件绑定一个布尔类型的值,通过值的改变就能显示或者隐藏Spin组件, 前端代码如下:
  <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 知识点。

参考大神门的文章如下:

  • setTimeout究竟做了什么
  • 深入理解JavaScript事件循环机制
  • setTimeout和Promise的执行顺序

总的来说需要清楚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的监听。这个只是我个人推测。

你可能感兴趣的:(javascript,Vue,错误记录)