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

以下文章來(lái)源于程序員成長(zhǎng)指北 ,作者koala

程序員成長(zhǎng)指北

前言

Vue3.0 在去年9月正式發(fā)布了端朵,也有許多小伙伴都熱情的擁抱Vue3.0好芭。去年年底我們新項(xiàng)目使用Vue3.0來(lái)開發(fā),這篇文章就是在使用后的一個(gè)總結(jié)冲呢, 包含Vue3新特性的使用以及一些用法上的變更舍败。

VUE3.0

為什么要升級(jí)Vue3

使用Vue2.x的小伙伴都熟悉,Vue2.x中所有數(shù)據(jù)都是定義在data中,方法定義在methods中的邻薯,并且使用this來(lái)調(diào)用對(duì)應(yīng)的數(shù)據(jù)和方法裙戏。那Vue3.x中就可以不這么玩了, 具體怎么玩我們后續(xù)再說(shuō)厕诡, 先說(shuō)一下Vue2.x版本這么寫有什么缺陷累榜,所以才會(huì)進(jìn)行升級(jí)變更的。

回顧Vue2.x實(shí)現(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 {
      count: 0,
    };
  },

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

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

上面代碼只是實(shí)現(xiàn)了對(duì)count的加減以及顯示倍數(shù)灵嫌, 就需要分別在data壹罚、methods、computed中進(jìn)行操作寿羞,當(dāng)我們?cè)黾右粋€(gè)需求猖凛,就會(huì)出現(xiàn)下圖的情況:

圖片

當(dāng)我們業(yè)務(wù)復(fù)雜了就會(huì)大量出現(xiàn)上面的情況, 隨著復(fù)雜度上升稠曼,就會(huì)出現(xiàn)這樣一張圖形病, 每個(gè)顏色的方塊表示一個(gè)功能:

圖片

甚至一個(gè)功能還有會(huì)依賴其他功能,全攪合在一起霞幅。

當(dāng)這個(gè)組件的代碼超過(guò)幾百行時(shí)漠吻,這時(shí)增加或者修改某個(gè)需求, 就要在data司恳、methods途乃、computed以及mounted中反復(fù)的跳轉(zhuǎn),這其中的的痛苦寫過(guò)的都知道扔傅。

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

圖片

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

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

關(guān)于上面經(jīng)常出現(xiàn)的問(wèn)題我就不一一舉例了段磨,使用過(guò)的小伙伴多多少少都會(huì)遇到她肯。

所以,我們Vue3.x就推出了Composition API主要就是為了解決上面的問(wèn)題,將零散分布的邏輯組合在一起來(lái)維護(hù)儒洛,并且還可以將單獨(dú)的功能邏輯拆分成單獨(dú)的文件粹舵。接下來(lái)我們就重點(diǎn)認(rèn)識(shí)Composition API属百。

Composition API

image.png

setup

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

setup執(zhí)行時(shí)機(jī)

我在學(xué)習(xí)過(guò)程中看到很多文章都說(shuō)setup 是在 beforeCreatecreated之間慢叨, 這個(gè)結(jié)論是錯(cuò)誤的纽匙。實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn), 于是自己去檢驗(yàn)了一下:

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

setup 執(zhí)行時(shí)機(jī)是在beforeCreate之前執(zhí)行拍谐,詳細(xì)的可以看后面生命周期講解烛缔。

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

setup 參數(shù)

使用setup時(shí)践瓷,它接受兩個(gè)參數(shù):

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

setup中接受的props是響應(yīng)式的毅弧, 當(dāng)傳入新的props 時(shí),會(huì)及時(shí)被更新当窗。由于是響應(yīng)式的, 所以不可以使用ES6解構(gòu)寸宵,解構(gòu)會(huì)消除它的響應(yīng)式崖面。

錯(cuò)誤代碼示例, 這段代碼會(huì)讓props不再支持響應(yīng)式:

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

那在開發(fā)中我們想要使用解構(gòu)梯影,還能保持props的響應(yīng)式巫员,有沒有辦法解決呢?大家可以思考一下甲棍,在后面toRefs學(xué)習(xí)的地方為大家解答简识。

接下來(lái)我們來(lái)說(shuō)一下setup接受的第二個(gè)參數(shù)context,我們前面說(shuō)了setup中不能訪問(wèn)Vue2中最常用的this對(duì)象感猛,所以context中就提供了this中最常用的三個(gè)屬性:attrs七扰、slotemit,分別對(duì)應(yīng)Vue2.x中的 $attr屬性陪白、slot插槽 和$emit發(fā)射事件颈走。并且這幾個(gè)屬性都是自動(dòng)同步最新的值,所以我們每次使用拿到的都是最新值咱士。

reactive立由、ref與toRefs

在vue2.x中, 定義數(shù)據(jù)都是在data中序厉, 但是Vue3.x 可以使用reactiveref來(lái)進(jìn)行數(shù)據(jù)定義锐膜。

那么refreactive他們有什么區(qū)別呢?分別什么時(shí)候使用呢弛房?說(shuō)到這里道盏,我又不得不提一下,看到很多網(wǎng)上文章說(shuō)(reactive用于處理對(duì)象的雙向綁定庭再,ref則處理js基礎(chǔ)類型的雙向綁定)捞奕。我其實(shí)不太贊同這樣的說(shuō)法,這樣很容易初學(xué)者認(rèn)為ref就能處理js基本類型拄轻, 比如ref也是可以定義對(duì)象的雙向綁定的啊颅围, 上段代碼:

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

我們將obj.countobj.name綁定到頁(yè)面上也是可以的;但是reactive函數(shù)確實(shí)可以代理一個(gè)對(duì)象恨搓, 但是不能代理基本類型院促,例如字符串筏养、數(shù)字、boolean等常拓。

接下來(lái)使用代碼展示一下ref渐溶、reactive的使用:

圖片

運(yùn)行效果:

圖片

上面的代碼中,我們綁定到頁(yè)面是通過(guò)user.name,user.age弄抬;這樣寫感覺很繁瑣茎辐,我們能不能直接將user中的屬性解構(gòu)出來(lái)使用呢?答案是不能直接對(duì)user進(jìn)行結(jié)構(gòu), 這樣會(huì)消除它的響應(yīng)式掂恕, 這里就和上面我們說(shuō)props不能使用ES6直接解構(gòu)就呼應(yīng)上了拖陆。那我們就想使用解構(gòu)后的數(shù)據(jù)怎么辦,解決辦法就是使用toRefs懊亡。

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

<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", age: 26, gender: "女" });
    setInterval(() =>{
        year.value ++
        user.age ++
    }, 1000)
    return {
        year,
        // 使用reRefs
        ...toRefs(user)
    }
  },
});
</script>

生命周期鉤子

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

圖片

從圖中我們可以看到Vue3.0新增了setup,這個(gè)在前面我們也詳細(xì)說(shuō)了店枣, 然后是將Vue2.x中的beforeDestroy名稱變更成beforeUnmount; destroyed 表更為 unmounted速警,作者說(shuō)這么變更純粹是為了更加語(yǔ)義化,因?yàn)橐粋€(gè)組件是一個(gè)mountunmount的過(guò)程鸯两。其他Vue2中的生命周期仍然保留闷旧。

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

圖片

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

下面我們簡(jiǎn)單使用幾個(gè)鉤子, 方便大家學(xué)習(xí)如何使用机错,Vue3.x中的鉤子是需要從vue中導(dǎo)入的:

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);
    })
  },
});

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

watch 與 watchEffect 的用法

watch 函數(shù)用來(lái)偵聽特定的數(shù)據(jù)源弱匪,并在回調(diào)函數(shù)中執(zhí)行副作用青瀑。默認(rèn)情況是惰性的,也就是說(shuō)僅在偵聽的源數(shù)據(jù)變更時(shí)才執(zhí)行回調(diào)萧诫。

watch(source, callback, [options])

參數(shù)說(shuō)明:

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

接下來(lái)我會(huì)分別介紹這個(gè)三個(gè)參數(shù)都是如何使用的帘饶, 如果你對(duì)watch的使用不明白的請(qǐng)往下看:

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

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

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

    // 修改age值時(shí)會(huì)觸發(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);
})

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

上面兩個(gè)例子中哑诊,我們分別使用了兩個(gè)watch, 當(dāng)我們需要偵聽多個(gè)數(shù)據(jù)源時(shí), 可以進(jìn)行合并及刻, 同時(shí)偵聽多個(gè)數(shù)據(jù):

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

偵聽復(fù)雜的嵌套對(duì)象

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

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

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

前面我們提到骆莹,默認(rèn)情況下,watch是惰性的, 那什么情況下不是惰性的担猛, 可以立即執(zhí)行回調(diào)函數(shù)呢幕垦?其實(shí)使用也很簡(jiǎn)單, 給第三個(gè)參數(shù)中設(shè)置immediate: true即可傅联。關(guān)于flush配置智嚷,還在學(xué)習(xí),后期會(huì)補(bǔ)充

stop 停止監(jiān)聽

我們?cè)诮M件中創(chuàng)建的watch監(jiān)聽纺且,會(huì)在組件被銷毀時(shí)自動(dòng)停止。如果在組件銷毀之前我們想要停止掉某個(gè)監(jiān)聽稍浆, 可以調(diào)用watch()函數(shù)的返回值载碌,操作如下:

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

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

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

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

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

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

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

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

從上面的代碼可以看出猾漫, 并沒有像watch一樣需要先傳入依賴,watchEffect會(huì)自動(dòng)收集依賴, 只要指定一個(gè)回調(diào)函數(shù)感凤。在組件初始化時(shí)悯周, 會(huì)先執(zhí)行一次來(lái)收集依賴, 然后當(dāng)收集到的依賴中數(shù)據(jù)發(fā)生變化時(shí)陪竿, 就會(huì)再次執(zhí)行回調(diào)函數(shù)禽翼。所以總結(jié)對(duì)比如下:

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

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

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

其實(shí)我們也能進(jìn)行自定義封裝礁哄。

自定義 Hooks

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

useCount.ts 實(shí)現(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,
    };
}

接下來(lái)看一下在組件中使用useCount這個(gè) 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實(shí)現(xiàn)花枫,分散在data,method,computed等刻盐, 如果剛接手項(xiàng)目,實(shí)在無(wú)法快速將data字段和method關(guān)聯(lián)起來(lái)劳翰,而Vue3的方式可以很明確的看出敦锌,將count相關(guān)的邏輯聚合在一起, 看起來(lái)舒服多了佳簸, 而且useCount還可以擴(kuò)展更多的功能乙墙。

項(xiàng)目開發(fā)完之后,后續(xù)還會(huì)寫一篇總結(jié)項(xiàng)目中使用到的「自定義Hooks的文章」生均,幫助大家更高效的開發(fā)听想, 關(guān)于Composition API和自定義Hooks就介紹到這里, 接下來(lái)簡(jiǎn)單介紹一下vue2.x與vue3響應(yīng)式對(duì)比马胧。

簡(jiǎn)單對(duì)比vue2.x與vue3.x響應(yīng)式

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

沒有無(wú)緣無(wú)故的愛佩脊,也沒有無(wú)緣無(wú)故的恨蛙粘。為何要將Object.defineProperty換掉呢,咋們可以簡(jiǎn)單聊一下威彰。

我剛上手Vue2.x的時(shí)候就經(jīng)常遇到一個(gè)問(wèn)題出牧,數(shù)據(jù)更新了啊,為何頁(yè)面不更新呢歇盼?什么時(shí)候用$set更新舔痕,什么時(shí)候用$forceUpdate強(qiáng)制更新,你是否也一度陷入困境豹缀。后來(lái)的學(xué)習(xí)過(guò)程中開始接觸源碼伯复,才知道一切的根源都是 Object.defineProperty

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

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

由于Object.defineProperty只能劫持對(duì)象屬性鸣剪,需要遍歷對(duì)象的每一個(gè)屬性组底,如果屬性值也是對(duì)象,就需要遞歸進(jìn)行深度遍歷筐骇。但是Proxy直接代理對(duì)象债鸡, 不需要遍歷操作

  1. Object.defineProperty對(duì)新增屬性需要手動(dòng)進(jìn)行Observe

因?yàn)?code>Object.defineProperty劫持的是對(duì)象的屬性,所以新增屬性時(shí)铛纬,需要重新遍歷對(duì)象厌均, 對(duì)其新增屬性再次使用Object.defineProperty進(jìn)行劫持。也就是Vue2.x中給數(shù)組和對(duì)象新增屬性時(shí)告唆,需要使用$set才能保證新增的屬性也是響應(yīng)式的, $set內(nèi)部也是通過(guò)調(diào)用Object.defineProperty去處理的棺弊。

Teleport

Teleport是Vue3.x新推出的功能晶密, 沒聽過(guò)這個(gè)詞的小伙伴可能會(huì)感到陌生;翻譯過(guò)來(lái)是傳送的意思模她,可能還是覺得不知所以稻艰,沒事下邊我就給大家形象的描述一下。

Teleport 是什么呢侈净?

Teleport 就像是哆啦A夢(mèng)中的「任意門」尊勿,任意門的作用就是可以將人瞬間傳送到另一個(gè)地方。有了這個(gè)認(rèn)識(shí)畜侦,我們?cè)賮?lái)看一下為什么需要用到Teleport的特性呢元扔,看一個(gè)小例子:

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

Dialog從用戶感知的層面擅羞,應(yīng)該是一個(gè)獨(dú)立的組件,從dom結(jié)構(gòu)應(yīng)該完全剝離Vue頂層組件掛載的DOM鲁森;同時(shí)還可以使用到Vue組件內(nèi)的狀態(tài)(data或者props)的值。簡(jiǎn)單來(lái)說(shuō)就是,即希望繼續(xù)在組件內(nèi)部使用Dialog,又希望渲染的DOM結(jié)構(gòu)不嵌套在組件的DOM中振惰。

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

接下來(lái)就舉個(gè)小例子痛垛,看看Teleport的使用方式

Teleport的使用

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

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

定義一個(gè)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>

最后在一個(gè)子組件Header.vue中使用Dialog組件,這里主要演示 Teleport的使用桶蛔,不相關(guān)的代碼就省略了匙头。header組件

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

Dom渲染效果如下:

圖片

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

Suspense

Suspense是Vue3.x中新增的特性电抚, 那它有什么用呢?別急竖共,我們通過(guò)Vue2.x中的一些場(chǎng)景來(lái)認(rèn)識(shí)它的作用蝙叛。

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

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

在前后端交互獲取數(shù)據(jù)時(shí), 是一個(gè)異步過(guò)程公给,一般我們都會(huì)提供一個(gè)加載中的動(dòng)畫借帘,當(dāng)數(shù)據(jù)返回時(shí)配合v-if來(lái)控制數(shù)據(jù)顯示蜘渣。

如果你使用過(guò)vue-async-manager這個(gè)插件來(lái)完成上面的需求, 你對(duì)Suspense可能不會(huì)陌生肺然,Vue3.x感覺就是參考了vue-async-manager.

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

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

asyncComponent.vue:

<template>
<div>
    <h4>這個(gè)是一個(gè)異步加載數(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>

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

片段(Fragment)

在 Vue2.x 中倦沧, template中只允許有一個(gè)根節(jié)點(diǎn):

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

但是在 Vue3.x 中,你可以直接寫多個(gè)根節(jié)點(diǎn)它匕, 是不是很爽:

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

更好的 Tree-Shaking

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

// vue2.x
import Vue from "vue"

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

Vue.nextTick() 是一個(gè)從 Vue 對(duì)象直接暴露出來(lái)的全局 API豫柬,其實(shí) $nextTick() 只是 Vue.nextTick() 的一個(gè)簡(jiǎn)易包裝告希,只是為了方便而把后者的回調(diào)函數(shù)的 this 綁定到了當(dāng)前的實(shí)例。雖然我們借助webpacktree-shaking,但是不管我們實(shí)際上是否使用Vue.nextTick(),最終都會(huì)進(jìn)入我們的生產(chǎn)代碼烧给, 因?yàn)?Vue實(shí)例是作為單個(gè)對(duì)象導(dǎo)出的燕偶, 打包器無(wú)法堅(jiān)持出代碼總使用了對(duì)象的哪些屬性。

在 Vue3.x中改寫成這樣:

import { nextTick } from "vue"

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

受影響的 API

這是一個(gè)比較大的變化础嫡, 因?yàn)橐郧暗娜?API 現(xiàn)在只能通過(guò)具名導(dǎo)入指么,這一更改會(huì)對(duì)以下API有影響:

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

內(nèi)置工具

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

:::warning 重要

以上僅適用于 ES Modules builds榴鼎,用于支持 tree-shaking 的綁定器——UMD 構(gòu)建仍然包括所有特性伯诬,并暴露 Vue 全局變量上的所有內(nèi)容 (編譯器將生成適當(dāng)?shù)妮敵觯允褂萌滞獾?api 而不是導(dǎo)入)巫财。:::

前面都是Vue3.0的一些新特性盗似,后面著重介紹一下相對(duì)于Vue2.x來(lái)說(shuō), 有什么變更呢平项?

變更

slot 具名插槽語(yǔ)法

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

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

在父組件中使用:

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

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

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

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

Vue3.0中v-slot

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

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

此處可擴(kuò)展查看之前的文章

自定義指令

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

// 注冊(cè)一個(gè)全局自定義指令 `v-focus`
Vue.directive('focus', {
  // 當(dāng)被綁定的元素插入到 DOM 中時(shí)……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

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

  • bind:只調(diào)用一次扔涧,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置。
  • inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在枯夜,但不一定已被插入文檔中)弯汰。
  • update:所在組件的 VNode 更新時(shí)調(diào)用,但是可能發(fā)生在其子 VNode 更新之前湖雹。指令的值可能發(fā)生了改變咏闪,也可能沒有。但是你可以通過(guò)比較更新前后的值來(lái)忽略不必要的模板更新 (詳細(xì)的鉤子函數(shù)參數(shù)見下)摔吏。
  • componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用鸽嫂。
  • unbind:只調(diào)用一次,指令與元素解綁時(shí)調(diào)用征讲。

在Vue 3 中對(duì)自定義指令的 API進(jìn)行了更加語(yǔ)義化的修改据某, 就如組件生命周期變更一樣, 都是為了更好的語(yǔ)義化诗箍, 變更如下:

圖片

所以在Vue3 中癣籽, 可以這樣來(lái)自定義指令:

const { createApp } from "vue"

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

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

<input v-focus />

v-model 升級(jí)

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

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

有點(diǎn)懵埂材?別著急,往下看 在Vue2 中汤求, 在組件上使用 v-model其實(shí)就相當(dāng)于傳遞了value屬性俏险, 并觸發(fā)了input事件:

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

<!-- 相當(dāng)于 -->
<search-input :value="searchValue" @input="searchValue=$event"><search-input>

這時(shí)v-model只能綁定在組件的value屬性上,那我們就不開心了首昔, 我們就像給自己的組件用一個(gè)別的屬性寡喝,并且我們不想通過(guò)觸發(fā)input來(lái)更新值糙俗,在.async出來(lái)之前勒奇,Vue 2 中這樣實(shí)現(xiàn):

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

修改后, searchInput 組件使用v-model就相當(dāng)于這樣:

<search-input v-model="searchValue"><search-input>
<!-- 相當(dāng)于 -->
<search-input :search="searchValue" @change="searchValue=$event"><search-input>

但是在實(shí)際開發(fā)中巧骚,有些場(chǎng)景我們可能需要對(duì)一個(gè) prop 進(jìn)行“雙向綁定”赊颠, 這里以最常見的 modal為例子:modal挺合適屬性雙向綁定的,外部可以控制組件的visible顯示或者隱藏劈彪,組件內(nèi)部關(guān)閉可以控制 visible屬性隱藏竣蹦,同時(shí)visible 屬性同步傳輸?shù)酵獠俊=M件內(nèi)部沧奴, 當(dāng)我們關(guān)閉modal時(shí), 在子組件中以u(píng)pdate:PropName模式觸發(fā)事件:

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

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

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

此時(shí)我們也可以使用v-bind.async來(lái)簡(jiǎn)化實(shí)現(xiàn):

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

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

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

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

<!-- 相當(dāng)于 -->
<modal :modelValue="isVisible" @update:modelValue="isVisible = $event"></modal>

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

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

<!-- 相當(dāng)于 -->
<modal 
    :visible="isVisible"
    :content="content"
    @update:visible="isVisible"
    @update:content="content"
/>

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

異步組件

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

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

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

export default {
  components: {
    // 無(wú)配置項(xiàng)異步組件
    AsyncPage: defineAsyncComponent(() => import("./NextPage.vue")),
    
    // 有配置項(xiàng)異步組件
    AsyncPageWithOptions: defineAsyncComponent({
   loader: () => import(".NextPage.vue"),
   delay: 200, 
   timeout: 3000,
   errorComponent: () => import("./ErrorComponent.vue"),
   loadingComponent: () => import("./LoadingComponent.vue"),
 })
  },
}
</script>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市庇麦,隨后出現(xiàn)的幾起案子计技,更是在濱河造成了極大的恐慌,老刑警劉巖女器,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酸役,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡驾胆,警方通過(guò)查閱死者的電腦和手機(jī)涣澡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丧诺,“玉大人入桂,你說(shuō)我怎么就攤上這事〔笛郑” “怎么了抗愁?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)呵晚。 經(jīng)常有香客問(wèn)我蜘腌,道長(zhǎng),這世上最難降的妖魔是什么饵隙? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任撮珠,我火速辦了婚禮,結(jié)果婚禮上金矛,老公的妹妹穿的比我還像新娘芯急。我一直安慰自己,他們只是感情好驶俊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布娶耍。 她就那樣靜靜地躺著,像睡著了一般饼酿。 火紅的嫁衣襯著肌膚如雪榕酒。 梳的紋絲不亂的頭發(fā)上胚膊,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音想鹰,去河邊找鬼澜掩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛杖挣,可吹牛的內(nèi)容都是我干的肩榕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼惩妇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼株汉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起歌殃,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乔妈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后氓皱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體路召,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年波材,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了股淡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡廷区,死狀恐怖唯灵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情隙轻,我是刑警寧澤埠帕,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站玖绿,受9級(jí)特大地震影響敛瓷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斑匪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一呐籽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秤标,春花似錦绝淡、人聲如沸宙刘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悬包。三九已至衙猪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垫释。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工丝格, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棵譬。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓显蝌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親订咸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子曼尊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容