Vue3

Vue2->Vue3

如果有Vue2的基礎(chǔ),并在此基礎(chǔ)上學(xué)習(xí)Vue3,并不需要把完整的官網(wǎng)看完,我們只需要關(guān)注一些新功能和非兼容的變化即可進(jìn)行開(kāi)發(fā)。

Vue3變化

  • 同一元素上使用的v-ifv-for優(yōu)先級(jí)已更改,但不推薦同時(shí)使用 v-ifv-for。
  • 組件事件需要在emits選項(xiàng)中聲明
  • destroyed 生命周期選項(xiàng)被重命名為 unmounted
  • beforeDestroy 生命周期選項(xiàng)被重命名為 beforeUnmount
  • 自定義指令的API已更改為與組件生命周期一致
  • 新增了三個(gè)組件:Fragment 支持多個(gè)根節(jié)點(diǎn)、Suspense 可以在組件渲染之前的等待時(shí)間顯示指定內(nèi)容、Teleport 可以讓子組件能夠在視覺(jué)上跳出父組件(如父組件overflow:hidden)
  • 新增指令 v-memo,可以緩存 html 模板,比如 v-for 列表不會(huì)變化的就緩存,簡(jiǎn)單說(shuō)就是用內(nèi)存換時(shí)間。
  • Proxy 代替 Object.defineProperty 重構(gòu)了響應(yīng)式系統(tǒng),可以監(jiān)聽(tīng)到數(shù)組下標(biāo)變化,及對(duì)象新增屬性,因?yàn)楸O(jiān)聽(tīng)的不是對(duì)象屬性,而是對(duì)象本身,還可攔截 apply、has 等13種方法
  • 重構(gòu)了虛擬 DOM,在編譯時(shí)會(huì)將事件緩存、將 slot 編譯為 lazy 函數(shù)、保存靜態(tài)節(jié)點(diǎn)直接復(fù)用(靜態(tài)提升)、以及添加靜態(tài)標(biāo)記、Diff 算法使用 最長(zhǎng)遞增子序列 優(yōu)化了對(duì)比流程,使得虛擬 DOM 生成速度提升 200%
  • 支持在 <style></style> 里使用 v-bind,給 CSS 綁定 JS 變量(color: v-bind(str))
  • 新增 Composition API 可以更好的邏輯復(fù)用和代碼組織,同一功能的代碼不至于像以前一樣太分散,雖然 Vue2 中可以用 mixin 來(lái)實(shí)現(xiàn)復(fù)用代碼,但也存在問(wèn)題,比如方法或?qū)傩悦麜?huì)沖突,代碼來(lái)源也不清楚等
  • 全局函數(shù) setdelete 以及實(shí)例方法 $set$delete移除?;诖淼淖兓瘷z測(cè)已經(jīng)不再需要它們了
  • 畢竟 Vue3 是用 TS 寫(xiě)的,所以對(duì) TS 的支持度更好
  • Vue3 不兼容 IE11
  • $on,$off$once 實(shí)例方法已被移除,組件實(shí)例不再實(shí)現(xiàn)事件觸發(fā)接口。

三、組合式API

原有的組件選項(xiàng)(data、computed、methods、watch) 的方式來(lái)組織組件代碼通常是非常有效的,但是也存在一些不好的地方,例如把原有的關(guān)聯(lián)邏輯按照選項(xiàng)劃分開(kāi)來(lái),掩蓋了原有潛在的邏輯問(wèn)題,這個(gè)時(shí)候我們就必須要不斷地上下滾動(dòng)代碼來(lái)找到響應(yīng)的代碼塊來(lái)查找,這樣帶來(lái)了極大的不便,特別是一開(kāi)始沒(méi)有編寫(xiě)過(guò)這組件的人來(lái)說(shuō),這導(dǎo)致組件難以閱讀和理解。

所以針對(duì)上述的情況,Vue3提出了新的組織組件代碼的方式---組合式API。組合式API需要一個(gè)可以實(shí)際使用的地方,那就是setup。

setup的觸發(fā)時(shí)機(jī)是在組件創(chuàng)建之前執(zhí)行的。需要注意,在setup中你應(yīng)該避免使用this,因?yàn)檫@個(gè)時(shí)候this并不代表組件實(shí)例。setup的調(diào)用發(fā)生在data、computedmethods被解析之前,所以它們沒(méi)法在setup中被獲取。

當(dāng)然,我們依然可以在Vue3使用選項(xiàng)(Option API)的方式來(lái)組織代碼,這個(gè)和Vue2沒(méi)有區(qū)別,但是不建議這樣寫(xiě)。

Vue3.x組件的選項(xiàng)(Option API)寫(xiě)法(不建議)

<script>
  import { defineComponent, ref } from 'vue';
  export default defineComponent({
    setup() {},
    mounted() {
      console.log('生命周期mounted')
    },
    components: {},
    methods: {},
    watch: {},
    computed: {}
  });
</script>

Vue3.x組合式寫(xiě)法(推薦)

<script>
  import { defineComponent, ref, onMounted } from 'vue';
  export default defineComponent({
    setup(){
      let num = ref(0);
      let fn = () => {};
      onMounted(():void => {
                console.log('生命周期mounted');
      });
      return {
        num,
        fn
      }
    }
  })
</script>

下面例子代碼都是在Typescript的環(huán)境下進(jìn)行的,所以需要Typescript基礎(chǔ)。

1. 生命周期

通過(guò)在生命周期鉤子前面加上 “on” 來(lái)訪問(wèn)組件的生命周期鉤子。

下表包含如何在 Option API 和 setup() 內(nèi)部調(diào)用生命周期鉤子

Option API setup
beforeCreate -
created -
beforeMount OnBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
activated onActivated
deactivated onDeactivated

因?yàn)?setup 是在 beforeCreatecreated 生命周期鉤子前運(yùn)行的,所以不需要顯式地定義它們。換句話說(shuō),在這些鉤子中編寫(xiě)的任何代碼都應(yīng)該直接在 setup 函數(shù)中編寫(xiě)。

<template>
    <div id="div">123</div>
</template>

<script lang="ts">
import { defineComponent, onMounted } from "vue";
export default defineComponent({
  setup() {
    console.log("setup");
    onMounted((): void => {
      console.log("onMounted");
      console.log(document.getElementById('div')?.innerHTML);
    });
  },
});
</script>

2. ref、reactive、toRefs響應(yīng)式和methods

Vue2.x默認(rèn)寫(xiě)在data的值,初始化的時(shí)候內(nèi)部會(huì)完成值的數(shù)據(jù)的響應(yīng)式(getset綁定),但是Vue3要手動(dòng)調(diào)用內(nèi)置方法實(shí)現(xiàn),那么接下來(lái)看看常用的實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式方法都有哪些。

ref不僅可以用在數(shù)據(jù)的響應(yīng)式,還可以綁定DOM元素

<template>  
    <div>
        <div>{{ num }}</div>
    <button @click="add1">num++</button>
    <p>-------------------------------</p>
    <div>{{ state.count }}</div>
    <button @click="add2">state.count++</button>
    <p>-------------------------------</p>
    <div>{{ count }}</div>
    <button @click="add3">count++</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, toRefs } from "vue";
export default defineComponent({
  setup() {
    interface ObjItf {
      count: number;
    }

    // ref聲明響應(yīng)式數(shù)據(jù),用于聲明基本數(shù)據(jù)類(lèi)型
    let num = ref<number>(1);

    let obj = {
      count: 1,
    };

    // reactive聲明響應(yīng)式數(shù)據(jù),用于聲明引用數(shù)據(jù)類(lèi)型
    let state = reactive<ObjItf>(obj);

    // toRefs解構(gòu)響應(yīng)式數(shù)據(jù)
    let { count } = toRefs<ObjItf>(state);

    const add1 = (): void => {
      num.value++; // 注意通過(guò)ref聲明的變量,所以js要修改對(duì)應(yīng)的值是要通過(guò).value訪問(wèn)才可以,template模板不需要通過(guò).value訪問(wèn)
    };

    const add2 = (): void => {
      state.count++; // 通過(guò)reactive聲明的遍歷,不需要通過(guò).value訪問(wèn)值
    };

    const add3 = (): void => {
      count.value++; // 通過(guò)toRefs結(jié)構(gòu)的值和ref聲明的變量一樣,需要通過(guò).value訪問(wèn)其值
    };

    return {
      num,
      state,
      count,
      add1,
      add2,
      add3
    };
  },
});
</script>

注意:

  1. reactive可以傳遞基礎(chǔ)數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型,基礎(chǔ)數(shù)據(jù)類(lèi)型不會(huì)被包裝成響應(yīng)式數(shù)據(jù)
  2. reactive返回的響應(yīng)式數(shù)據(jù)本質(zhì)是Proxy對(duì)象,對(duì)象里面每一層都會(huì)被包裝成Proxy對(duì)象
  3. reactive返回的響應(yīng)式數(shù)據(jù)和原始的數(shù)據(jù)會(huì)相互影響
  4. ref可以傳遞基礎(chǔ)數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型。如果是基礎(chǔ)數(shù)據(jù)類(lèi)型,那么這個(gè)基礎(chǔ)數(shù)據(jù)值保存在返回的響應(yīng)式數(shù)據(jù)的.value上;如果是對(duì)象,響應(yīng)式數(shù)據(jù)在.value上。
  5. ref本質(zhì)是將一個(gè)數(shù)據(jù)變成一個(gè)對(duì)象,這個(gè)對(duì)象具有響應(yīng)式特點(diǎn)

為什么需要toRefs和toRef?

ref不一樣的是,toReftoRefs整兩個(gè)方法,它們不創(chuàng)造響應(yīng)式,而是延續(xù)響應(yīng)式。創(chuàng)造響應(yīng)式一般由ref和reactive來(lái)解決,而toRef和toRefs則把對(duì)象的數(shù)據(jù)進(jìn)行分解和擴(kuò)散,其這個(gè)對(duì)象針對(duì)的是響應(yīng)式對(duì)象(reactive)而非普通對(duì)象。

3. watch

語(yǔ)法:watch(監(jiān)聽(tīng)源|[多個(gè)], (val, oldVal) => {}, {immediate?: false, deep: false})

watch寫(xiě)法上支持一個(gè)或者多個(gè)監(jiān)聽(tīng)源,這些監(jiān)聽(tīng)源必須只能是getter/effect函數(shù),ref數(shù)據(jù),reactive對(duì)象,或者是數(shù)組類(lèi)型

<template>
  <div>
    <div>{{ count }}</div>
    <button @click="add">count++</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs, watch } from "vue";
export default defineComponent({
  setup() {
    interface ObjItf {
      count: number;
    }

    let obj = {
      count: 1,
    };

    // reactive聲明響應(yīng)式數(shù)據(jù),用于聲明引用數(shù)據(jù)類(lèi)型
    let state = reactive<ObjItf>(obj);

    // toRefs解構(gòu)響應(yīng)式數(shù)據(jù)
    let { count } = toRefs<ObjItf>(state);

    const add = (): void => {
      count.value++;
    };

    watch(count, (newVal: number, oldVal: number): void => {
      console.log(newVal, oldVal);
    });

    return {
      count,
      add,
    };
  },
});
</script>

4. watchEffect

它立即執(zhí)行傳入的一個(gè)函數(shù),同時(shí)響應(yīng)式追蹤其依賴(lài),并在其依賴(lài)變更時(shí)重新運(yùn)行該函數(shù)。

<script>
    let num = ref(0);
  watchEffect(() => {
    console.log(num.value);
  });
  setTimeout(() => {
    num.value++;
  }, 1000);
</script>

watch 和 watchEffect的區(qū)別

  • 兩者都可以監(jiān)聽(tīng) data 屬性變化;
  • watch 需要明確監(jiān)聽(tīng)哪個(gè)屬性;
  • watchEffect 會(huì)根據(jù)其中的屬性,自動(dòng)監(jiān)聽(tīng)其變化。

5. computed

<template>
    <div>{{ count }}</div>
  <button @click="state.count++">state.count++</button>
</template>
<script lang="ts">
import { defineComponent, reactive, computed } from "vue";
export default defineComponent({
  setup() {
    interface ObjItf {
      count: number;
    }

    let obj = {
      count: 1,
      arr: [1, 2, 3]
    };

    const state = reactive<ObjItf>(obj);

    let count = computed((): number => {
      return state.count;
    });

    return {
      count,
      state
    };
  },
});
</script>

6. 組件

全局組件:

const app = Vue.createApp({...})

app.component('my-component-name', {
  /* ... */
})

局部組件:

子組件

<template>
  <div>{{ aname }}</div>
</template>


<script lang="ts">
import { defineComponent, toRefs } from "vue";
export default defineComponent({
  props: {
    aname: {
      type: String,
      default: "張三",
    },
  },
  emits: ["uname"],
  setup(props, { emit }) {
    let { aname } = toRefs(props);
    const updateName = () => {
      emit('uname', 'a改變后的名字')
    };
    return {
      aname,
      updateName,
    };
  },
});
</script>

父組件

<template>
    <mychild :aname="aname" @uname="updateName"></mychild>
</template>

<script lang="ts">
import { defineComponent, toRefs, ref } from "vue";
import mychild from './mychild.vue';
  export default defineComponent({
    components: {
      mychild
    },
    setup() {
      let aname = ref('李四');
      const updateName = (p: string) => {
        aname.value = p;
      }
      return {
        aname,
        updateName
      }
    }
  })
</script>

setup語(yǔ)法糖組件

子組件

<!--  -->
<template>
  <div>{{ aname }}</div>
  <button @click="updateName">修改姓名</button>
</template>

<script lang="ts" setup>
// setup語(yǔ)法糖下defineProps,defineEmits 不需要引入
let emit = defineEmits(["updatename"]);
let props = defineProps({
  aname: {
    type: String,
    default: "李四",
  },
});
const updateName = (): void => {
    emit('updatename', '修改后的名字')
};
</script>

<style lang="less" scoped></style>

父組件

<!--  -->
<template>
  <div></div>
  <mychild :aname="aname" @updatename="updateName"></mychild>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import mychild from "./b.vue";
let aname = ref("張三");
let updateName = (name: string): void => {
  aname.value = name;
};
</script>

<style lang="less" scoped></style>

7. v-model

子組件

<!--  -->
<template>
  <div>{{ name }}{{ age }}</div>
  <button @click="updateName">修改姓名</button>
</template>

<script lang="ts" setup>
let props = defineProps({
  age: Number,
  name: String,
});
let emits = defineEmits(["update:name", "update:age"]);
let updateName = () => {
  emits("update:age", 30);
  emits("update:name", "李四");
};
</script>

<style lang="less" scoped></style>

父組件

<!--  -->
<template>
  <div></div>
  <mychild v-model:name="state.name" v-model:age="state.age"></mychild>
</template>

<script lang="ts" setup>
import { reactive } from "vue";
import mychild from "./b.vue";
let info = {
  name: '張三',
  age: 20
}
let state = reactive(info);

</script>

<style lang="less" scoped></style>

8. 插槽

子組件

<!--  -->
<template>
  <slot></slot>
  <slot name="title"></slot>
  <slot name="footer" :user="state.user" :d="state.d"></slot>
</template>

<script lang="ts" setup>
import { reactive } from "vue";
let state = reactive({
  user: { a: 1, b: 2 },
  d: 1,
});
</script>

<style lang="less" scoped></style>

父組件

<!--  -->
<template>
  <div></div>
  <mychild>
    <div>匿名插槽</div>
    <template #title>
      <div>具名插槽</div>
    </template>
    <template #footer="scope">
      <div>作用域插槽{{ scope.user }} {{ scope.d }}</div>
    </template>
  </mychild>
</template>

<script lang="ts" setup>
import mychild from "./child.vue";
</script>

<style lang="less" scoped></style>

9. await支持

不必再配合 async 就可以直接使用 await 了,這種情況下,組件的 setup 會(huì)自動(dòng)變成 async setup 。

<script setup>
  const post = await fetch('/api').then(() => {})
</script>

10. style scoped

<style scoped>
/* 修改第三方組件樣式 */
::v-deep(.foo) {}
/* 簡(jiǎn)寫(xiě) */
:deep(.foo) {}

/* 修改插槽內(nèi)容樣式 */
::v-slotted(.foo) {}
/* 簡(jiǎn)寫(xiě) */
:slotted(.foo) {}

/* 修改全局樣式 */
::v-global(.foo) {}
/* 簡(jiǎn)寫(xiě) */
:global(.foo) {}
</style>

11. teleport

這個(gè)組件可以把組件進(jìn)行傳送,to屬性就是要傳送的位置目標(biāo),用css選擇器

<template>
  <div class="header"></div>
  <teleport :to="target">
    <p>哈哈哈哈哈</p>
  </teleport>
  <button @click="changeTarget">點(diǎn)擊切換</button>
  <p>-------------------------------</p>
  <div class="footer"></div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
let target = ref<string>(".header");
const changeTarget = (): void => {
  target.value = ".footer";
};
</script>

<style lang="less" scoped></style>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容