1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Vue Composition API 陷阱

        共 6323字,需瀏覽 13分鐘

         ·

        2020-09-05 00:04

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

        前言

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

        Setup陷阱

        簡(jiǎn)單陷阱

        先看一個(gè)Vue2簡(jiǎn)單例子:

        <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的心智模型中,我們總會(huì)在data中返回一個(gè)對(duì)象,我們并不關(guān)心對(duì)象的值是簡(jiǎn)單類型還是引用類型,因?yàn)樗鼈兌寄芎芎玫谋豁憫?yīng)式系統(tǒng)處理,就像上面這個(gè)例子一樣。但是,如果我們不作任何心智模型的變化,就開始使用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ù)制代碼

        實(shí)際上,這段代碼不能正常運(yùn)作,當(dāng)你點(diǎn)擊button時(shí),視圖不會(huì)響應(yīng)數(shù)據(jù)變化。原因是,我們先將data中的count取了出來(lái),再合并到this.$data中,但是一旦count被取出來(lái),它就是一個(gè)單純的簡(jiǎn)單類型數(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ù)制代碼

        然后像往常一樣,我們?cè)跇I(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ù)里取出什么(簡(jiǎn)單類型 or 引用類型),都會(huì)導(dǎo)致響應(yīng)式斷掉,進(jìn)而無(wú)法更新視圖。

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

        遷移到新的心智模型

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

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

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

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

        使用新心智模型來(lái)解決問(wèn)題

        簡(jiǎn)單陷阱:永遠(yuǎn)不要直接使用簡(jiǎ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)險(xiǎn),優(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:可以通過(guò)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?// 是計(jì)算屬性,響應(yīng)式不會(huì)斷掉

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

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

        ????}

        ??}

        }

        復(fù)制代碼

        復(fù)雜陷阱-方案3:方案2需要額外寫一些computed屬性,比較麻煩,我們還可以通過(guò)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包裹了一層

        ????// 這層包裹會(huì)在template中自動(dòng)解開

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

        ????return?{

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

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

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

        ????}

        ??}

        }

        復(fù)制代碼

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

        const?someData?=?useSomeData()

        {

        ??userInfo:?{

        ????value:?{

        ??????name:?'...',?// 依然是簡(jiǎn)單類型,沒(méi)有被包裹

        ??????role:?'...'?// 依然是簡(jiǎn)單類型,沒(méi)有被包裹

        ????}

        ??},

        ??projectList:?{

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

        ??}

        }

        復(fù)制代碼

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

        // useSomeData.js

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

        export?default?function?useSomeData()?{

        ??...

        ??// 讓每一層級(jí)都套一層ref

        ??return?toRefs({

        ????projectList:?data.projectList,

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

        ??})

        }

        復(fù)制代碼

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

        繞過(guò)陷阱

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

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

        還是這個(gè)存在安全隱患的自定義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的時(shí)候,setup需要返回一個(gè)函數(shù),這個(gè)函數(shù)其實(shí)就是render函數(shù),它在數(shù)據(jù)變化時(shí)會(huì)重新執(zhí)行,所以我們只需要把解構(gòu)的邏輯放到render函數(shù)里,那么就解決了setup只會(huì)執(zhí)行一次的問(wèn)題。

        后記

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

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



        推薦閱讀




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

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

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

        瀏覽 49
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            口述三个男人躁我一个爽漫画 | 永久成人毛片免费观看 | 激情五月天开心网 | 色色网站免费观看 | 夜色AV秘 无码一区二区三va | 欧美日韩无码 | 亚 洲 成 人 视 频 在 线 | 看学生妹毛片 | 轻轻搞| 乱人XXXX国语对白91 |