vue 組件綁定的值發生變化後,組件UI效果不生效

問題場景

我使用vue 做前端開發,現在我有一個腳本樹,想要只是搜索功能,這個搜索是在前端完成的,但是當腳本樹的數據和層次結構較深時,輸入搜索後,可能需要1-2秒的時間響應,這時就希望在搜索開始的時候加入loading效果,結束後取消loading。

  • 腳本樹長這樣:
  • 在這裏插入圖片描述
  • 點擊搜索後希望使用iview 的 Spin 組件, 期望結果如下:
    在這裏插入圖片描述
    也就是加個遮罩層,有個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 知識點。

參考大神門的文章如下:

總的來說需要清楚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的監聽。這個只是我個人推測。

發佈了39 篇原創文章 · 獲贊 31 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章