<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Vue Composition API 陷阱

          共 6323字,需瀏覽 13分鐘

           ·

          2020-09-05 00:04

          編者按:本文轉(zhuǎn)載自XxjzZ的掘金文章,快樂來一起學(xué)習(xí)吧!

          前言

          自從React Hooks出現(xiàn)之后,批評的聲音不斷,很多人說它帶來了心智負(fù)擔(dān),因為相比傳統(tǒng)的Class寫法,useState/useEffect的依賴于執(zhí)行順序的特點讓人捉摸不透。與此相對的,在Vue3 Composition API RFC 中,我們看到Vue3官方描述CompositionAPI是一個基于已有的"響應(yīng)式"心智模型的更好方案,這讓我們覺得好像不需要任何心智模型的切換就可以迅速投入到Compositoin API的開發(fā)中去。但在我嘗試了一段時間后,發(fā)現(xiàn)事實并非如此,我們依然需要一些思維上的變化來適應(yīng)新的Compsition API。

          Setup陷阱

          簡單陷阱

          先看一個Vue2簡單例子:

          <template>

          ??<div id="app">

          ????{{count}}

          ????<button @click="addCount"></button>

          ??</div>

          </template>

          <script>?export?default?{

          ??data()?{

          ???return?{

          ?????count:?0

          ???}

          ??},

          ??methods:?{

          ???addCount()?{

          ?????this.count?+=?1

          ???}

          ??}

          };?</script>

          復(fù)制代碼

          在Vue2的心智模型中,我們總會在data中返回一個對象,我們并不關(guān)心對象的值是簡單類型還是引用類型,因為它們都能很好的被響應(yīng)式系統(tǒng)處理,就像上面這個例子一樣。但是,如果我們不作任何心智模型的變化,就開始使用Composition API,我們就容易寫出這樣的代碼:

          <template>

          ??<div id="app">

          ????{{count}}

          ????<button @click="addCount"></button>

          ??</div>

          </template>

          <script>?import?{?reactive?}?from?'@vue/runtime-dom'

          export?default?{

          ??setup()?{

          ????const?data?=?reactive({

          ??????count:?0

          ????})

          ????function?addCount()?{

          ??????data.count?+=?1

          ????}

          ????return?{

          ??????count:?data.count,

          ??????addCount

          ????}

          ??}

          };?</script>

          復(fù)制代碼

          實際上,這段代碼不能正常運作,當(dāng)你點擊button時,視圖不會響應(yīng)數(shù)據(jù)變化。原因是,我們先將data中的count取了出來,再合并到this.$data中,但是一旦count被取出來,它就是一個單純的簡單類型數(shù)據(jù),響應(yīng)式就丟了。

          復(fù)雜陷阱

          數(shù)據(jù)結(jié)構(gòu)越復(fù)雜,我們就越容易落入陷阱,在這里我們把一段業(yè)務(wù)邏輯抽離到自定義hooks里,如下:

          // useSomeData.js

          import?{?reactive,?onMounted?}?from?'@vue/runtime-dom'

          export?default?function?useSomeData()?{

          ??const?data?=?reactive({

          ????userInfo:?{

          ??????name:?'default_name',

          ??????role:?'default_role'

          ????},

          ????projectList:?[]

          ??})


          ??onMounted(()?=>?{

          ????// 異步獲取數(shù)據(jù)

          ????fetch(...).then(result?=>?{

          ??????const?{?userInfo,?projectList?}?=?result

          ??????data.userInfo?=?userInfo

          ??????data.projectList?=?projectList

          ????})

          ??})


          ??return?data

          }

          復(fù)制代碼

          然后像往常一樣,我們在業(yè)務(wù)組件中去使用:

          // App.vue

          <template>

          ??<div>

          ????{{name}}

          ????{{role}}

          ????{{list}}

          ??</div>

          </template>

          <script>?import?useSomeData?from?'./useSomeData'

          export?default?{

          ??setup()?{

          ????const?{?userInfo,?projectList?}?=?useSomeData()

          ????return?{

          ??????name:?userInfo.name?// 響應(yīng)式斷掉

          ??????role:?userInfo.role,?// 響應(yīng)式斷掉

          ??????list:?projectList?// 響應(yīng)式還是斷掉

          ????}

          ??}

          }?</script>

          復(fù)制代碼

          我們看到,不管我們從響應(yīng)式數(shù)據(jù)里取出什么(簡單類型 or 引用類型),都會導(dǎo)致響應(yīng)式斷掉,進而無法更新視圖。

          所有這些問題的根源都是:setup只會執(zhí)行一次。

          遷移到新的心智模型

          1. 時刻記住setup只會執(zhí)行一次

          2. 永遠(yuǎn)不要直接使用簡單類型

          3. 解構(gòu)可能有風(fēng)險,優(yōu)先使用引用本身,而不是解構(gòu)它

          4. 可以通過一些手段讓解構(gòu)變得安全

          使用新心智模型來解決問題

          簡單陷阱:永遠(yuǎn)不要直接使用簡單類型

          <template>

          ??<div id="app">

          ????{{count}}

          ????<button @click="addCount"></button>

          ??</div>

          </template>

          <script>?import?{?reactive,?ref?}?from?'@vue/runtime-dom'

          export?default?{

          ??setup()?{

          ????const?count?=?ref(0)?// 在這里使用ref包裹一層引用容器

          ????function?addCount()?{

          ??????count.value?+=?1

          ????}

          ????return?{

          ??????count,

          ??????addCount

          ????}

          ??}

          };?</script>

          復(fù)制代碼

          復(fù)雜陷阱-方案1:解構(gòu)可能有風(fēng)險,優(yōu)先使用引用本身,而不是解構(gòu)它

          // useSomeData.js

          ...

          // App.vue

          <template>

          ??<div>

          ????{{someData.userInfo.name}}

          ????{{someData.userInfo.role}}

          ????{{someData.projectList}}

          ??</div>

          </template>

          <script>?import?useSomeData?from?'./useSomeData'

          export?default?{

          ??setup()?{

          ????const?someData?=?useSomeData()

          ????return?{

          ??????someData

          ????}

          ??}

          }?</script>

          復(fù)制代碼

          復(fù)雜陷阱-方案2:可以通過computed讓解構(gòu)變得安全

          // useSomeData.js

          import?{?reactive,?onMounted,?computed?}?from?'@vue/runtime-dom'

          export?default?function?useSomeData()?{

          ??const?data?=?reactive({

          ????userInfo:?{

          ??????name:?'default_user',

          ??????role:?'default_role'

          ????},

          ????projectList:?[]

          ??})


          ??onMounted(()?=>?{

          ????// 異步獲取數(shù)據(jù)

          ????fetch(...).then(result?=>?{

          ??????const?{?userInfo,?projectList?}?=?result

          ??????data.userInfo?=?userInfo

          ??????data.projectList?=?projectList

          ????})

          ??})


          ??const?userName?=?computed(()?=>?data.userInfo.name)

          ??const?userRole?=?computed(()?=>?data.userinfo.role)

          ??const?projectList?=?computed(()?=>?data.projectList)


          ??return?{

          ????userName,

          ????userRole,

          ????projectList

          ??}

          }

          復(fù)制代碼

          // App.vue

          export?default?{

          ??setup()?{

          ????const?{?userName,?userRole,?projectList?}?=?useSomeData()

          ????return?{

          ??????name:?userName?// 是計算屬性,響應(yīng)式不會斷掉

          ??????role:?userRole,?// 是計算屬性,響應(yīng)式不會斷掉

          ??????list:?projectList?// 是計算屬性,響應(yīng)式不會斷掉

          ????}

          ??}

          }

          復(fù)制代碼

          復(fù)雜陷阱-方案3:方案2需要額外寫一些computed屬性,比較麻煩,我們還可以通過toRefs讓解構(gòu)變得安全

          // useSomeData.js

          import?{?reactive,?onMounted?}?from?'@vue/runtime-dom'

          export?default?function?useSomeData()?{

          ??const?data?=?reactive({

          ????userInfo:?{

          ??????name:?'default_user',

          ??????role:?'default_role'

          ????},

          ????projectList:?[]

          ??})


          ??onMounted(()?=>?{

          ????// 異步獲取數(shù)據(jù)

          ????fetch(...).then(result?=>?{

          ??????const?{?userInfo,?projectList?}?=?result

          ??????data.userInfo?=?userInfo

          ??????data.projectList?=?projectList

          ????})

          ??})

          ??// 使用toRefs

          ??return?toRefs(data)

          }

          復(fù)制代碼

          // App.vue

          export?default?{

          ??setup()?{

          ????// 現(xiàn)在userInfo和projectList都已經(jīng)被ref包裹了一層

          ????// 這層包裹會在template中自動解開

          ????const?{?userInfo,?projectList?}?=?useSomeData()

          ????return?{

          ??????name:?userInfo.value.name,?// ???好了嗎

          ??????role:?userInfo.value.role,?// ???好了嗎

          ??????list:?projectList?// ???好了嗎

          ????}

          ??}

          }

          復(fù)制代碼

          你以為這樣就好了嗎?其實這里有一個陷阱中的陷阱:projectList好了,但是name和role依然是響應(yīng)式斷開的狀態(tài),因為toRefs只會”淺“包裹,實際上useSomeData返回的結(jié)果是這樣的:

          const?someData?=?useSomeData()

          {

          ??userInfo:?{

          ????value:?{

          ??????name:?'...',?// 依然是簡單類型,沒有被包裹

          ??????role:?'...'?// 依然是簡單類型,沒有被包裹

          ????}

          ??},

          ??projectList:?{

          ????value:?[...]

          ??}

          }

          復(fù)制代碼

          因此,我們的useSomeData如果想要通過toRefs實現(xiàn)真正的解構(gòu)安全,需要這樣寫:

          // useSomeData.js

          import?{?reactive,?onMounted?}?from?'@vue/runtime-dom'

          export?default?function?useSomeData()?{

          ??...

          ??// 讓每一層級都套一層ref

          ??return?toRefs({

          ????projectList:?data.projectList,

          ????userInfo:?toRefs(data.userInfo)

          ??})

          }

          復(fù)制代碼

          建議:使用自定義hooks返回數(shù)據(jù)的時候,如果數(shù)據(jù)的層級比較簡單,可以直接使用toRefs包裹;如果數(shù)據(jù)的層級比較復(fù)雜,建議使用computed。

          繞過陷阱

          上述操作其實是Vue官方使用CompositionAPI的標(biāo)準(zhǔn)方式,因為CompositionAPI完全就是按照setup只會執(zhí)行一次進行設(shè)計的。但是不可否認(rèn)的是,這的確帶來了許多心智負(fù)擔(dān),因為我們不得不時刻關(guān)注響應(yīng)式數(shù)據(jù)到底能不能解構(gòu),不然一不小心就容易調(diào)到坑里。

          其實所有這些問題都出在setup只會執(zhí)行一次,那么有沒有辦法解決呢?有的,可以使用JSX或h的寫法,繞過setup只會執(zhí)行一次的問題:

          還是這個存在安全隱患的自定義hooks:

          // useSomeData.js

          import?{?reactive,?onMounted?}?from?'@vue/runtime-dom'

          export?default?function?useSomeData()?{

          ??const?data?=?reactive({

          ????userInfo:?{

          ??????name:?'default_name',

          ??????role:?'default_role'

          ????},

          ????projectList:?[]

          ??})


          ??onMounted(()?=>?{

          ????// 異步獲取數(shù)據(jù)

          ????fetch(...).then(result?=>?{

          ??????const?{?userInfo,?projectList?}?=?result

          ??????data.userInfo?=?userInfo

          ??????data.projectList?=?projectList

          ????})

          ??})


          ??return?data

          }

          復(fù)制代碼

          使用JSX或h

          import?useSomeData?from?'./useSomeData'

          export?default?{

          ????setup()?{

          ??????const?someData?=?useSomeData()

          ??????return?()?=>?{

          ????????const?{userInfo:?{name,?role},?projectList}?=?someData

          ????????return?(

          ??????????<div>

          ??????????????{name}

          ??????????????{role}

          ??????????????{projectList}

          ??????????</div>

          ????????)

          ??????}

          ??}

          }

          復(fù)制代碼

          在使用JSX或h的時候,setup需要返回一個函數(shù),這個函數(shù)其實就是render函數(shù),它在數(shù)據(jù)變化時會重新執(zhí)行,所以我們只需要把解構(gòu)的邏輯放到render函數(shù)里,那么就解決了setup只會執(zhí)行一次的問題。

          后記

          我們可能需要一些約定,來約束自定義hooks的使用方式。但是官方并沒有給出,這將導(dǎo)致我們hooks會寫的五花八門,并且漏洞百出。目前來看,”不要解構(gòu)“是最安全的方式。

          我專門就這個問題請教了yyx大佬,大佬給出了一個”約定”,那就是盡量少使用“解構(gòu)”。這我也很無奈。其實我是希望官方能夠給出一個工具,讓我們減少在自定義hooks中犯錯誤的可能性。(toRefs其實就是這樣的一個工具,但是它并不能解決所有問題)



          推薦閱讀




          我的公眾號能帶來什么價值?(文末有送書規(guī)則,一定要看)

          每個前端工程師都應(yīng)該了解的圖片知識(長文建議收藏)

          為什么現(xiàn)在面試總是面試造火箭?

          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲色情免费 | 日本中文字幕不卡 | 乱伦中文字幕网 | 最新中文字幕av专区 | 俺去俺来也在线www色 |