1. Vue3.0 新特性以及使用變更總結(實際工作用到的)

        共 30639字,需瀏覽 62分鐘

         ·

        2023-09-25 14:54


        前言

        Vue3.0 在2020年9月正式發(fā)布了,也有許多小伙伴都熱情的擁抱Vue3.0。去年年底我們新項目使用Vue3.0來開發(fā),這篇文章就是在使用后的一個總結, 包含Vue3新特性的使用以及一些用法上的變更。

        圖片.png

        為什么要升級Vue3

        使用Vue2.x的小伙伴都熟悉,Vue2.x中所有數(shù)據(jù)都是定義在data中,方法定義在methods中的,并且使用this來調(diào)用對應的數(shù)據(jù)和方法。那Vue3.x中就可以不這么玩了, 具體怎么玩我們后續(xù)再說, 先說一下Vue2.x版本這么寫有什么缺陷,所以才會進行升級變更的。

        回顧Vue2.x實現(xiàn)加減

        <template>
          <div class="homePage">
            <p>count: {{ count }}</p>
            <p>倍數(shù): {{ multiple }}</p>
            <div>
                <button style="margin-right:10px" @click="increase">加1</button>
                <button @click="decrease">減一</button>
            </div>
          </div>
        </template>

        <script>
        export default {
          data() {
            return {
              count0,
            };
          },

          computed: {
            multiple() {
              return 2 * this.count;
            },
          },

          methods: {
            increase() {
              this.count++;
            },
            decrease() {
              this.count++;
            },
          },
        };
        </script>

        上面代碼只是實現(xiàn)了對count的加減以及顯示倍數(shù), 就需要分別在data、methods、computed中進行操作,當我們增加一個需求,就會出現(xiàn)下圖的情況:

        當我們業(yè)務復雜了就會大量出現(xiàn)上面的情況, 隨著復雜度上升,就會出現(xiàn)這樣一張圖, 每個顏色的方塊表示一個功能:

        甚至一個功能還有會依賴其他功能,全攪合在一起。

        當這個組件的代碼超過幾百行時,這時增加或者修改某個需求, 就要在data、methods、computed以及mounted中反復的跳轉(zhuǎn),這其中的的痛苦寫過的都知道。

        那我們就想啊, 如果可以按照邏輯進行分割,將上面這張圖變成下邊這張圖,是不是就清晰很多了呢, 這樣的代碼可讀性和可維護性都更高:

        那么vue2.x版本給出的解決方案就是Mixin, 但是使用Mixin也會遇到讓人苦惱的問題:

        1. 命名沖突問題
        2. 不清楚暴露出來的變量的作用
        3. 邏輯重用到其他 component 經(jīng)常遇到問題

        關于上面經(jīng)常出現(xiàn)的問題我就不一一舉例了,使用過的小伙伴多多少少都會遇到。文章的重點不是Mixin,如果確實想知道的就留言啦~

        所以,我們Vue3.x就推出了Composition API主要就是為了解決上面的問題,將零散分布的邏輯組合在一起來維護,并且還可以將單獨的功能邏輯拆分成單獨的文件。接下來我們就重點認識Composition API。

        Composition API

        setup

        setup 是Vue3.x新增的一個選項, 他是組件內(nèi)使用 Composition API的入口。

        setup執(zhí)行時機

        我在學習過程中看到很多文章都說setup 是在 beforeCreatecreated之間, 這個結論是錯誤的。實踐是檢驗真理的唯一標準, 于是自己去檢驗了一下:

        export default defineComponent ({
            beforeCreate() {
                console.log("----beforeCreate----");
            },
            created() {
                console.log("----created----");
            },
            setup() {
                console.log("----setup----");
            },
        })

        setup 執(zhí)行時機是在beforeCreate之前執(zhí)行,詳細的可以看后面生命周期講解。

        ::: warning 由于在執(zhí)行setup 時尚未創(chuàng)建組件實例,因此在 setup 選項中沒有 this。:::

        setup 參數(shù)

        使用setup時,它接受兩個參數(shù):

        1. props: 組件傳入的屬性
        2. context

        setup中接受的props是響應式的, 當傳入新的props 時,會及時被更新。由于是響應式的, 所以不可以使用ES6解構,解構會消除它的響應式。

        錯誤代碼示例, 這段代碼會讓props不再支持響應式:

        // demo.vue
        export default defineComponent ({
            setup(props, context) {
                const { name } = props
                console.log(name)
            },
        })

        那在開發(fā)中我們想要使用解構,還能保持props的響應式,有沒有辦法解決呢?大家可以思考一下,在后面toRefs學習的地方為大家解答。

        接下來我們來說一下setup接受的第二個參數(shù)context,我們前面說了setup中不能訪問Vue2中最常用的this對象,所以context中就提供了this中最常用的三個屬性:attrsslotemit,分別對應Vue2.x中的 $attr屬性、slot插槽 和$emit發(fā)射事件。并且這幾個屬性都是自動同步最新的值,所以我們每次使用拿到的都是最新值。

        reactive、ref與toRefs

        在vue2.x中, 定義數(shù)據(jù)都是在data中, 但是Vue3.x 可以使用reactiveref來進行數(shù)據(jù)定義。

        那么refreactive他們有什么區(qū)別呢?分別什么時候使用呢?說到這里,我又不得不提一下,看到很多網(wǎng)上文章說(reactive用于處理對象的雙向綁定,ref則處理js基礎類型的雙向綁定)。我其實不太贊同這樣的說法,這樣很容易初學者認為ref就能處理js基本類型, 比如ref也是可以定義對象的雙向綁定的啊, 上段代碼:

         setup() {
            const obj = ref({count:1name:"張三"})
            setTimeout(() =>{
                obj.value.count = obj.value.count + 1
                obj.value.name = "李四"
            }, 1000)
            return{
                obj
            }
          }

        我們將obj.countobj.name綁定到頁面上也是可以的;但是reactive函數(shù)確實可以代理一個對象, 但是不能代理基本類型,例如字符串、數(shù)字、boolean等。

        接下來使用代碼展示一下ref、reactive的使用:

        運行效果:

        上面的代碼中,我們綁定到頁面是通過user.name,user.age;這樣寫感覺很繁瑣,我們能不能直接將user中的屬性解構出來使用呢?答案是不能直接對user進行結構, 這樣會消除它的響應式, 這里就和上面我們說props不能使用ES6直接解構就呼應上了。那我們就想使用解構后的數(shù)據(jù)怎么辦,解決辦法就是使用toRefs。

        toRefs用于將一個reactive對象轉(zhuǎn)化為屬性全部為ref對象的普通對象。具體使用方式如下:

        <template>
          <div class="homePage">
            <p>第 {{ year }} 年</p>
            <p>姓名: {{ nickname }}</p>
            <p>年齡: {{ age }}</p>
          </div>
        </template>

        <script>
        import { defineComponent, reactive, ref ,toRefs} from "vue";
        export default defineComponent({
          setup() {
            const year = ref(0);
            const user = reactive({ nickname"xiaofan"age26gender"女" });
            setInterval(() =>{
                year.value ++
                user.age ++
            }, 1000)
            return {
                year,
                // 使用reRefs
                ...toRefs(user)
            }
          },
        });
        </script>

        生命周期鉤子

        我們可以直接看生命周期圖來認識都有哪些生命周期鉤子(圖片是根據(jù)官網(wǎng)翻譯后繪制的):

        從圖中我們可以看到Vue3.0新增了setup,這個在前面我們也詳細說了, 然后是將Vue2.x中的beforeDestroy名稱變更成beforeUnmount; destroyed 表更為 unmounted,作者說這么變更純粹是為了更加語義化,因為一個組件是一個mountunmount的過程。其他Vue2中的生命周期仍然保留。

        上邊生命周期圖中并沒包含全部的生命周期鉤子, 還有其他的幾個, 全部生命周期鉤子如圖所示:

        我們可以看到beforeCreatecreatedsetup替換了(但是Vue3中你仍然可以使用, 因為Vue3是向下兼容的, 也就是你實際使用的是vue2的)。其次,鉤子命名都增加了on; Vue3.x還新增用于調(diào)試的鉤子函數(shù)onRenderTriggeredonRenderTricked

        下面我們簡單使用幾個鉤子, 方便大家學習如何使用,Vue3.x中的鉤子是需要從vue中導入的:

        import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate,onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered
        from "vue";

        export default defineComponent({
          // beforeCreate和created是vue2的
          beforeCreate() {
            console.log("------beforeCreate-----");
          },
          created() {
            console.log("------created-----");
          },
          setup() {
            console.log("------setup-----");

            // vue3.x生命周期寫在setup中
            onBeforeMount(() => {
              console.log("------onBeforeMount-----");
            });
            onMounted(() => {
              console.log("------onMounted-----");
            });
            // 調(diào)試哪些數(shù)據(jù)發(fā)生了變化
            onRenderTriggered((event) =>{
                console.log("------onRenderTriggered-----",event);
            })
          },
        });

        關于生命周期相關的內(nèi)容就介紹到這里,下面我們介紹一下Vue3.x中watch有什么不同。

        watch 與 watchEffect 的用法

        watch 函數(shù)用來偵聽特定的數(shù)據(jù)源,并在回調(diào)函數(shù)中執(zhí)行副作用。默認情況是惰性的,也就是說僅在偵聽的源數(shù)據(jù)變更時才執(zhí)行回調(diào)。

        watch(source, callback, [options])

        參數(shù)說明:

        • source:可以支持string,Object,Function,Array; 用于指定要偵聽的響應式變量
        • callback: 執(zhí)行的回調(diào)函數(shù)
        • options:支持deep、immediate 和 flush 選項。

        接下來我會分別介紹這個三個參數(shù)都是如何使用的, 如果你對watch的使用不明白的請往下看:

        偵聽reactive定義的數(shù)據(jù)

        import { defineComponent, ref, reactive, toRefs, watch } from "vue";
        export default defineComponent({
          setup() {
            const state = reactive({ nickname"xiaofan"age20 });

            setTimeout(() =>{
                state.age++
            },1000)

            // 修改age值時會觸發(fā) watch的回調(diào)
            watch(
              () => state.age,
              (curAge, preAge) => {
                console.log("新值:", curAge, "老值:", preAge);
              }
            );

            return {
                ...toRefs(state)
            }
          },
        });

        偵聽ref定義的數(shù)據(jù)

        const year = ref(0)

        setTimeout(() =>{
            year.value ++ 
        },1000)

        watch(year, (newVal, oldVal) =>{
            console.log("新值:", newVal, "老值:", oldVal);
        })

        偵聽多個數(shù)據(jù)

        上面兩個例子中,我們分別使用了兩個watch, 當我們需要偵聽多個數(shù)據(jù)源時, 可以進行合并, 同時偵聽多個數(shù)據(jù):

        watch([() => state.age, year], ([curAge, preAge], [newVal, oldVal]) => {
            console.log("新值:", curAge, "老值:", preAge);
            console.log("新值:", newVal, "老值:", oldVal);
        });

        偵聽復雜的嵌套對象

        我們實際開發(fā)中,復雜數(shù)據(jù)隨處可見, 比如:

        const state = reactive({
            room: {
            id100,
            attrs: {
                size"140平方米",
                type:"三室兩廳"
            },
            },
        });
        watch(() => state.room, (newType, oldType) => {
            console.log("新值:", newType, "老值:", oldType);
        }, {deep:true});

        如果不使用第三個參數(shù)deep:true, 是無法監(jiān)聽到數(shù)據(jù)變化的。

        前面我們提到,默認情況下,watch是惰性的, 那什么情況下不是惰性的, 可以立即執(zhí)行回調(diào)函數(shù)呢?其實使用也很簡單, 給第三個參數(shù)中設置immediate: true即可。關于flush配置,還在學習,后期會補充

        stop 停止監(jiān)聽

        我們在組件中創(chuàng)建的watch監(jiān)聽,會在組件被銷毀時自動停止。如果在組件銷毀之前我們想要停止掉某個監(jiān)聽, 可以調(diào)用watch()函數(shù)的返回值,操作如下:

        const stopWatchRoom = watch(() => state.room, (newType, oldType) => {
            console.log("新值:", newType, "老值:", oldType);
        }, {deep:true});

        setTimeout(()=>{
            // 停止監(jiān)聽
            stopWatchRoom()
        }, 3000)

        還有一個監(jiān)聽函數(shù)watchEffect,在我看來watch已經(jīng)能滿足監(jiān)聽的需求,為什么還要有watchEffect呢?雖然我沒有get到它的必要性,但是還是要介紹一下watchEffect,首先看看它的使用和watch究竟有何不同。

        import { defineComponent, ref, reactive, toRefs, watchEffect } from "vue";
        export default defineComponent({
          setup() {
            const state = reactive({ nickname"xiaofan"age20 });
            let year = ref(0)

            setInterval(() =>{
                state.age++
                year.value++
            },1000)

            watchEffect(() => {
                console.log(state);
                console.log(year);
              }
            );

            return {
                ...toRefs(state)
            }
          },
        });

        執(zhí)行結果首先打印一次stateyear值;然后每隔一秒,打印stateyear值。

        從上面的代碼可以看出, 并沒有像watch一樣需要先傳入依賴,watchEffect會自動收集依賴, 只要指定一個回調(diào)函數(shù)。在組件初始化時, 會先執(zhí)行一次來收集依賴, 然后當收集到的依賴中數(shù)據(jù)發(fā)生變化時, 就會再次執(zhí)行回調(diào)函數(shù)。所以總結對比如下:

        1. watchEffect 不需要手動傳入依賴
        2. watchEffect 會先執(zhí)行一次用來自動收集依賴
        3. watchEffect 無法獲取到變化前的值, 只能獲取變化后的值

        ::: danger 留一個思考題:如果定義一個非響應式的值, watch和watchEffect可以監(jiān)聽到值的變化嗎?:::

        上面介紹了Vue3 Composition API的部分內(nèi)容,還有很多非常好用的API, 建議直接查看官網(wǎng)composition-api。

        其實我們也能進行自定義封裝。

        自定義 Hooks

        開篇的時候我們使用Vue2.x寫了一個實現(xiàn)加減的例子, 這里可以將其封裝成一個hook, 我們約定這些「自定義 Hook」以 use 作為前綴,和普通的函數(shù)加以區(qū)分。

        useCount.ts 實現(xiàn):

        import { ref, Ref, computed } from "vue";

        type CountResultProps = {
            count: Ref<number>;
            multiple: Ref<number>;
            increase: (delta?: number) => void;
            decrease: (delta?: number) => void;
        };

        export default function useCount(initValue = 1): CountResultProps {
            const count = ref(initValue);

            const increase = (delta?: number): void => {
                if (typeof delta !== "undefined") {
                    count.value += delta;
                } else {
                    count.value += 1;
                }
            };
            const multiple = computed(() => count.value *2 )

            const decrease = (delta?: number): void => {
                if (typeof delta !== "undefined") {
                    count.value -= delta;
                } else {
                    count.value -= 1;
                }
            };

            return {
                count,
                multiple,
                increase,
                decrease,
            };
        }

        接下來看一下在組件中使用useCount這個 hook:

        <template>
          <p>count: {{ count }}</p>
          <p>倍數(shù): {{ multiple }}</p>
          <div>
            <button @click="increase()">加1</button>
            <button @click="decrease()">減一</button>
          </div>
        </template>

        <script lang="ts">
        import useCount from "../hooks/useCount";
         setup() {
            const { count, multiple, increase, decrease } = useCount(10);
                return {
                    count,
                    multiple,
                    increase,
                    decrease,
                };
            },
        </script>

        開篇Vue2.x實現(xiàn),分散在data,method,computed等, 如果剛接手項目,實在無法快速將data字段和method關聯(lián)起來,而Vue3的方式可以很明確的看出,將count相關的邏輯聚合在一起, 看起來舒服多了, 而且useCount還可以擴展更多的功能。

        項目開發(fā)完之后,后續(xù)還會寫一篇總結項目中使用到的「自定義Hooks的文章」,幫助大家更高效的開發(fā), 關于Composition API和自定義Hooks就介紹到這里, 接下來簡單介紹一下vue2.x與vue3響應式對比。

        簡單對比vue2.x與vue3.x響應式

        其實在Vue3.x 還沒有發(fā)布beta的時候, 很火的一個話題就是Vue3.x 將使用Proxy 取代Vue2.x 版本的 Object.defineProperty。

        沒有無緣無故的愛,也沒有無緣無故的恨。為何要將Object.defineProperty換掉呢,咋們可以簡單聊一下。

        我剛上手Vue2.x的時候就經(jīng)常遇到一個問題,數(shù)據(jù)更新了啊,為何頁面不更新呢?什么時候用$set更新,什么時候用$forceUpdate強制更新,你是否也一度陷入困境。后來的學習過程中開始接觸源碼,才知道一切的根源都是 Object.defineProperty。

        對這塊想要深入了解的小伙伴可以看這篇文章 為什么Vue3.0不再使用defineProperty實現(xiàn)數(shù)據(jù)監(jiān)聽?要詳細解釋又是一篇文章,這里就簡單對比一下Object.defineProperty 與Proxy

        1. Object.defineProperty只能劫持對象的屬性, 而Proxy是直接代理對象

        由于Object.defineProperty只能劫持對象屬性,需要遍歷對象的每一個屬性,如果屬性值也是對象,就需要遞歸進行深度遍歷。但是Proxy直接代理對象, 不需要遍歷操作

        1. Object.defineProperty對新增屬性需要手動進行Observe

        因為Object.defineProperty劫持的是對象的屬性,所以新增屬性時,需要重新遍歷對象, 對其新增屬性再次使用Object.defineProperty進行劫持。也就是Vue2.x中給數(shù)組和對象新增屬性時,需要使用$set才能保證新增的屬性也是響應式的, $set內(nèi)部也是通過調(diào)用Object.defineProperty去處理的。

        Teleport

        Teleport是Vue3.x新推出的功能, 沒聽過這個詞的小伙伴可能會感到陌生;翻譯過來是傳送的意思,可能還是覺得不知所以,沒事下邊我就給大家形象的描述一下。

        Teleport 是什么呢?

        Teleport 就像是哆啦A夢中的「任意門」,任意門的作用就是可以將人瞬間傳送到另一個地方。有了這個認識,我們再來看一下為什么需要用到Teleport的特性呢,看一個小例子:


        在子組件Header中使用到Dialog組件,我們實際開發(fā)中經(jīng)常會在類似的情形下使用到 Dialog ,此時Dialog就被渲染到一層層子組件內(nèi)部,處理嵌套組件的定位、z-index和樣式都變得困難。

        Dialog從用戶感知的層面,應該是一個獨立的組件,從dom結構應該完全剝離Vue頂層組件掛載的DOM;同時還可以使用到Vue組件內(nèi)的狀態(tài)(data或者props)的值。簡單來說就是,即希望繼續(xù)在組件內(nèi)部使用Dialog,又希望渲染的DOM結構不嵌套在組件的DOM中。

        此時就需要Teleport上場,我們可以用<Teleport>包裹Dialog, 此時就建立了一個傳送門,可以將Dialog渲染的內(nèi)容傳送到任何指定的地方。

        接下來就舉個小例子,看看Teleport的使用方式

        Teleport的使用

        我們希望Dialog渲染的dom和頂層組件是兄弟節(jié)點關系, 在index.html文件中定義一個供掛載的元素:

        <body>
        <div id="app"></div>
        <div id="dialog"></div>
        </body>

        定義一個Dialog組件Dialog.vue, 留意 to 屬性, 與上面的id選擇器一致:

        <template>
            <teleport to="#dialog">
                <div class="dialog">
                    <div class="dialog_wrapper">
                        <div class="dialog_header" v-if="title">
                            <slot name="header">
                                <span>{{title}}</span>
                            </slot>
                        </div>
                    </div>
                    <div class="dialog_content">
                        <slot></slot>
                    </div>
                    <div class="dialog_footer">
                        <slot name="footer"></slot>
                    </div>
                </div>
            </teleport>
        </template>

        最后在一個子組件Header.vue中使用Dialog組件,這里主要演示 Teleport的使用,不相關的代碼就省略了。header組件

        <div class="header">
            ...
            <navbar />
        +    <Dialog v-if="dialogVisible"></Dialog>
        </div>
        ...

        Dom渲染效果如下:

        圖片.png

        可以看到,我們使用 teleport 組件,通過 to 屬性,指定該組件渲染的位置與 <div id="app"></div> 同級,也就是在 body 下,但是 Dialog 的狀態(tài) dialogVisible 又是完全由內(nèi)部 Vue 組件控制.

        Suspense

        Suspense是Vue3.x中新增的特性, 那它有什么用呢?別急,我們通過Vue2.x中的一些場景來認識它的作用。

        Vue2.x中應該經(jīng)常遇到這樣的場景:

        <template>
        <div>
            <div v-if="!loading">
                ...
            </div>
            <div v-if="loading">
                加載中...
            </div>
        </div>
        </template>

        在前后端交互獲取數(shù)據(jù)時, 是一個異步過程,一般我們都會提供一個加載中的動畫,當數(shù)據(jù)返回時配合v-if來控制數(shù)據(jù)顯示。

        如果你使用過vue-async-manager這個插件來完成上面的需求, 你對Suspense可能不會陌生,Vue3.x感覺就是參考了vue-async-manager.

        Vue3.x新出的內(nèi)置組件Suspense, 它提供兩個template slot, 剛開始會渲染一個fallback狀態(tài)下的內(nèi)容, 直到到達某個條件后才會渲染default狀態(tài)的正式內(nèi)容, 通過使用Suspense組件進行展示異步渲染就更加的簡單。:::warning 如果使用 Suspense, 要返回一個promise :::Suspense 組件的使用:

         <Suspense>
                <template #default>
                    <async-component></async-component>
                </template>
                <template #fallback>
                    <div>
                        Loading...
                    </div>
                </template>
            </Suspense>

        asyncComponent.vue:

        <template>
        <div>
            <h4>這個是一個異步加載數(shù)據(jù)</h4>
            <p>用戶名:{{user.nickname}}</p>
            <p>年齡:{{user.age}}</p>
        </div>
        </template>

        <script>
        import { defineComponent } from "vue"
        import axios from "axios"
        export default defineComponent({
            setup(){
                const rawData = await axios.get("http://xxx.xinp.cn/user")
                return {
                    user: rawData.data
                }
            }
        })
        </script>

        從上面代碼來看,Suspense 只是一個帶插槽的組件,只是它的插槽指定了defaultfallback 兩種狀態(tài)。

        片段(Fragment)

        在 Vue2.x 中, template中只允許有一個根節(jié)點:

        <template>
            <div>
                <span></span>
                <span></span>
            </div>
        </template>

        但是在 Vue3.x 中,你可以直接寫多個根節(jié)點, 是不是很爽:

        <template>
            <span></span>
            <span></span>
        </template>

        更好的 Tree-Shaking

        Vue3.x 在考慮到 tree-shaking的基礎上重構了全局和內(nèi)部API, 表現(xiàn)結果就是現(xiàn)在的全局API需要通過 ES Module的引用方式進行具名引用, 比如在Vue2.x中,我們要使用 nextTick:

        // vue2.x
        import Vue from "vue"

        Vue.nextTick(()=>{
            ...
        })

        Vue.nextTick() 是一個從 Vue 對象直接暴露出來的全局 API,其實 $nextTick() 只是 Vue.nextTick() 的一個簡易包裝,只是為了方便而把后者的回調(diào)函數(shù)的 this 綁定到了當前的實例。雖然我們借助webpacktree-shaking,但是不管我們實際上是否使用Vue.nextTick(),最終都會進入我們的生產(chǎn)代碼, 因為 Vue實例是作為單個對象導出的, 打包器無法堅持出代碼總使用了對象的哪些屬性。

        在 Vue3.x中改寫成這樣:

        import { nextTick } from "vue"

        nextTick(() =>{
            ...
        })

        受影響的 API

        這是一個比較大的變化, 因為以前的全局 API 現(xiàn)在只能通過具名導入,這一更改會對以下API有影響:

        • Vue.nextTick
        • Vue.observable(用 Vue.reactive 替換)
        • Vue.version
        • Vue.compile(僅限完整版本時可用)
        • Vue.set(僅在 2.x 兼容版本中可用)
        • Vue.delete(與上同)

        內(nèi)置工具

        出來上面的 API外, 還有許多內(nèi)置的組件

        :::warning 重要

        以上僅適用于 ES Modules builds,用于支持 tree-shaking 的綁定器——UMD 構建仍然包括所有特性,并暴露 Vue 全局變量上的所有內(nèi)容 (編譯器將生成適當?shù)妮敵?,以使用全局外?api 而不是導入)。:::

        前面都是Vue3.0的一些新特性,后面著重介紹一下相對于Vue2.x來說, 有什么變更呢?

        變更

        slot 具名插槽語法

        在Vue2.x中, 具名插槽的寫法:

        <!--  子組件中:-->
        <slot name="title"></slot>

        在父組件中使用:

        <template slot="title">
            <h1>歌曲:成都</h1>
        <template>

        如果我們要在slot上面綁定數(shù)據(jù),可以使用作用域插槽,實現(xiàn)如下:

        // 子組件 
        <slot name="content" :data="data"></slot>
        export default {
            data(){
                return{
                    data:["走過來人來人往","不喜歡也得欣賞","陪伴是最長情的告白"]
                }
            }
        }
        <!-- 父組件中使用 -->
        <template slot="content" slot-scope="scoped">
            <div v-for="item in scoped.data">{{item}}</div>
        <template>

        在Vue2.x中具名插槽和作用域插槽分別使用slotslot-scope來實現(xiàn), 在Vue3.0中將slotslot-scope進行了合并同意使用。

        Vue3.0中v-slot

        <!-- 父組件中使用 -->
         <template v-slot:content="scoped">
           <div v-for="item in scoped.data">{{item}}</div>
        </template>

        <!-- 也可以簡寫成: -->
        <template #content="{data}">
            <div v-for="item in data">{{item}}</div>
        </template>

        自定義指令

        首先回顧一下 Vue 2 中實現(xiàn)一個自定義指令:

        // 注冊一個全局自定義指令 `v-focus`
        Vue.directive('focus', {
          // 當被綁定的元素插入到 DOM 中時……
          insertedfunction (el{
            // 聚焦元素
            el.focus()
          }
        })

        在Vue 2 中, 自定義指令通過以下幾個可選鉤子創(chuàng)建:

        • bind:只調(diào)用一次,指令第一次綁定到元素時調(diào)用。在這里可以進行一次性的初始化設置。
        • inserted:被綁定元素插入父節(jié)點時調(diào)用 (僅保證父節(jié)點存在,但不一定已被插入文檔中)。
        • update:所在組件的 VNode 更新時調(diào)用,但是可能發(fā)生在其子 VNode 更新之前。指令的值可能發(fā)生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新 (詳細的鉤子函數(shù)參數(shù)見下)。
        • componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用。
        • unbind:只調(diào)用一次,指令與元素解綁時調(diào)用。

        在Vue 3 中對自定義指令的 API進行了更加語義化的修改, 就如組件生命周期變更一樣, 都是為了更好的語義化, 變更如下:

        所以在Vue3 中, 可以這樣來自定義指令:

        const { createApp } from "vue"

        const app = createApp({})
        app.directive('focus', {
            mounted(el) {
                el.focus()
            }
        })

        然后可以在模板中任何元素上使用新的 v-focus指令, 如下:

        <input v-focus />

        v-model 升級

        在使用Vue 3 之前就了解到 v-model 發(fā)生了很大的變化, 使用過了之后才真正的get到這些變化, 我們先縱觀一下發(fā)生了哪些變化, 然后再針對的說一下如何使用:

        • 變更:在自定義組件上使用v-model時, 屬性以及事件的默認名稱變了
        • 變更:v-bind.sync修飾符在 Vue 3 中又被去掉了, 合并到了v-model
        • 新增:同一組件可以同時設置多個 v-model
        • 新增:開發(fā)者可以自定義 v-model修飾符

        有點懵?別著急,往下看 在Vue2 中, 在組件上使用 v-model其實就相當于傳遞了value屬性, 并觸發(fā)了input事件:

        <!-- Vue 2 -->
        <search-input v-model="searchValue"><search-input>

        <!-- 相當于 -->
        <search-input :value="searchValue" @input="searchValue=$event"><search-input>

        這時v-model只能綁定在組件的value屬性上,那我們就不開心了, 我們就像給自己的組件用一個別的屬性,并且我們不想通過觸發(fā)input來更新值,在.async出來之前,Vue 2 中這樣實現(xiàn):

        // 子組件:searchInput.vue
        export default {
            model:{
                prop'search',
                event:'change'
            }
        }

        修改后, searchInput 組件使用v-model就相當于這樣:

        <search-input v-model="searchValue"><search-input>
        <!-- 相當于 -->
        <search-input :search="searchValue" @change="searchValue=$event"><search-input>

        但是在實際開發(fā)中,有些場景我們可能需要對一個 prop 進行“雙向綁定”, 這里以最常見的 modal為例子:modal挺合適屬性雙向綁定的,外部可以控制組件的visible顯示或者隱藏,組件內(nèi)部關閉可以控制 visible屬性隱藏,同時visible 屬性同步傳輸?shù)酵獠?。組件內(nèi)部, 當我們關閉modal時, 在子組件中以update:PropName模式觸發(fā)事件:

        this.$emit('update:visible'false)

        然后在父組件中可以監(jiān)聽這個事件進行數(shù)據(jù)更新:

        <modal :visible="isVisible" @update:visible="isVisible = $event"></modal>

        此時我們也可以使用v-bind.async來簡化實現(xiàn):

        <modal :visible.async="isVisible"></modal>

        上面回顧了 Vue2 中v-model實現(xiàn)以及組件屬性的雙向綁定,那么在Vue 3 中應該怎樣實現(xiàn)的呢?

        在Vue3 中,在自定義組件上使用v-model,相當于傳遞一個modelValue 屬性, 同時觸發(fā)一個update:modelValue事件:

        <modal v-model="isVisible"></modal>

        <!-- 相當于 -->
        <modal :modelValue="isVisible" @update:modelValue="isVisible = $event"></modal>

        如果要綁定屬性名, 只需要給v-model傳遞一個參數(shù)就行, 同時可以綁定多個v-model

        <modal v-model:visible="isVisible" v-model:content="content"></modal>

        <!-- 相當于 -->
        <modal 
            :visible="isVisible"
            :content="content"
            @update:visible="isVisible"
            @update:content="content"
        />

        不知道你有沒有發(fā)現(xiàn),這個寫法完全沒有.async什么事兒了, 所以啊,Vue 3 中又拋棄了.async寫法, 統(tǒng)一使用v-model

        異步組件

        Vue3 中 使用 defineAsyncComponent 定義異步組件,配置選項 component 替換為 loader ,Loader 函數(shù)本身不再接收 resolve 和 reject 參數(shù),且必須返回一個 Promise,用法如下:

        <template>
          <!-- 異步組件的使用 -->
          <AsyncPage />
        </tempate>

        <script>
        import { defineAsyncComponent } from "vue";

        export default {
          components: {
            // 無配置項異步組件
            AsyncPage: defineAsyncComponent(() => import("./NextPage.vue")),
            
            // 有配置項異步組件
            AsyncPageWithOptions: defineAsyncComponent({
           loader() => import(".NextPage.vue"),
           delay200
           timeout3000,
           errorComponent() => import("./ErrorComponent.vue"),
           loadingComponent() => import("./LoadingComponent.vue"),
         })
          },
        }
        </script>

        參考文章:

        vue3全家桶入門指南

        學習一波Vue3新特性

        了不起的Vue3(上)

            
            

        瀏覽 910
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
          
          

            1. 久草a在线 | 侵犯双性稚嫩小少爷h视频 | 黄色香蕉视频在线观看 | 91精品国产综合久久久久久 | 四川女人一级毛片视频 |