Vue3的基本用法

語言: CN / TW / HK

一、前言

vue3已經發布一年了,今天和大家聊聊vue2和vue3。

在這之前,想問大家幾個問題?大家可以帶着這兩個問題聽我的分享

  1. 不言而喻vue3的誕生,肯定是為了解決vue2存在的問題,那麼究竟解決了什麼問題?
  2. vue3相比vue2性能上有哪些提升?

二、vue2和vue3的區別:

vue2基於Object.defineProperty實現,初始化數據在data中,業務邏輯處理放在(computed、methods、watch)以及各個生命週期函數中。

缺點:

  1. 無法監聽到添加和刪除的屬性
  2. 無法監控到數組和對象的變化
  3. 業務邏輯分佈在data、computed、methods、watch以及各個生命週期函數中,代碼聚合性弱,不便於閲讀

1.jpg

vue3基於Proxy實現,把所有的業務邏輯都放在setup函數中進行處理。

優點:

  1. 解決了vue2存在的問題
  2. 在diff算法上使用了靜態標記的方式,大大提升了vue的執行效率
  3. 通過關注點分離可以解決單個文件代碼宂餘的問題(下面會專門講這個問題)
  4. 靜態提升
  5. 事件偵聽器緩存
  6. 更好的支持TS
  7. 高內聚、低耦合

```js

```

三、vue3新特性

組合式API(Composition API)

組合式API實就是一個setup函數,把相關的業務邏輯放在一起解決了之前vue2代碼分散的問題,最後返回需要用的變量(響應式變量、非響應式變量)和函數。

```js import { ref,defineComponent } from 'vue'

export default defineComponent({ setup() { const count = ref(0) const handleClick = () => {

  }
  return {
    count,
    handleClick
  }
}

}) ``` 最簡單的setup由定義的數據和函數組成,最終在return中返回,供模板中使用。接下來具體講一下setup由哪些內容組成:

1、ref() 響應式函數

js import { ref } from 'vue' const count = ref(0) ref接受一個參數,返回的是一個帶有value屬性的對象,通過value屬性可以修改對應的值。

```js import { ref,defineComponent } from 'vue'

export default defineComponent({ setup() { const count = ref(0) console.log(count.value); //0 //修改value的值 count.value++; console.log(count.value); //1 } }) ``` 問題:

ref接受一個參數之後,為什麼返回的是一個對象?

ref就是把傳入的值和對象統一轉化成Proxy對象,因為Proxy只支持引用對象,所以對於值對象會轉化成{ value: "值"} 再轉化成Proxy對象,此時就可以監聽到value值的變化。

為什麼要用ref?

相當vue2中data定義的數據,這樣vue底層就知道了它是響應式變量。

2、reactive 返回對象的響應式副本

const person = reactive({ name: "天下" }) 返回的person是一個響應式對象,和原對象並不相等,因此建議只用proxy對象。

```js import { reactive,defineComponent } from 'vue'

export default defineComponent({ setup() { const obj = { name: "天下" } const person = reactive(obj)

  console.log(obj === person) //false

  //修改值
  setTimeout(()=>{
    person.name = "曉";
    person.age = 18;
  },1000)
}

}) ```

3、在setup如何用生命週期函數

組合式API和選項式AP聲明週期函數基本一致,區別是組合式API加了前綴"on",有如下生命週期函數 Vue2 | Vue3 | | --------------- | ----------------- | | beforeCreate | | | created | | | beforeMount | onBeforeMount | | mounted | onMounted | | beforeUpdate | onBeforeUpdate | | updated | onUpdated | | beforeMount | onBeforeUnmount | | unmounted | onUnmounted | | errorCaptured | onErrorCaptured | | renderTracked | onRenderTracked | | renderTriggered | onRenderTirggered | | activated | onActivated | | deactivated | onDeactivated

```js import { onMounted,defineComponent } from "vue";

export default defineComponent({ setup() { onMounted(()=>{ console.log("onMounted"); }) } }) ```

4、watch 監聽

接受三個參數:

  • 想要偵聽的響應式引用
  • 一個回調
  • 可選的配置選項

```js import { ref,watch,defineComponent } from "vue";

export default defineComponent({ setup() { const num = ref(0); watch(num,(newNum,oldNum)=>{ console.log("newNum",newNum); //666 console.log("oldNum",oldNum); //0 }) setTimeout(()=>{ num.value = 666; },1000) } }) ```

5、computed

```js import { ref,computed,defineComponent } from "vue";

export default defineComponent({ setup() { const str = ref("Hello"); const splitVal = computed(()=>{ return str.value.split(""); }) console.log("splitVal.value",splitVal.value); //['H', 'e', 'l', 'l', 'o'] } }) ```

6、setup 函數

參數:

  • props
  • context

props 是響應式的,傳入新的prop時會被更新。

html //父組件 <test-setup title="北京歡迎你"></test-setup>

js //子組件 export default defineComponent({ props: { title: String }, setup(props,context) { console.log("--- setup ---"); console.log("props",props); //Proxy {title: '北京歡迎你'} console.log("context",context); } }) 注意:因為props是響應式的,因此不能用ES6解構,它會消除prop的響應式。

如果需要解構可以在setup函數中使用Refs。

```js //失去了響應式 const { message } = props; console.log("message",message); //父組件信息

const { title } = toRefs(props); console.log("title",title); //ObjectRefImpl {_object: Proxy, _key: 'title', __v_isRef: true} ``` 如果title是可選的prop,則傳入的prop可能沒有此屬性,在這種情況下toRefs不會創建一個ref,需要用toRef代替。

```js import { toRef,defineComponent } from "vue";

export default defineComponent({ setup() { const otherMessage = toRef(props,"otherMessage"); console.log("otherMessage",otherMessage.value); //undefined } }) ``` Context 是一個普通的js對象,它暴露了組件的3個屬性

  • attrs 屬性
  • slots 插槽
  • emit 觸發事件

context對象不是響應式對象,因此可以解構

```js import { defineComponent } from "vue";

export default defineComponent({ setup(props,{ attrs, slots, emit}) { console.log("attrs",attrs); console.log("slots",slots); console.log("emit",emit); } }) ```

7、ref和reactive的區別:

ref和reactive都可以實現變量響應式,ref主要用於單個變量,reactive主要用於表單對象中多一些。

兩者在使用上也有差別:ref之後的變量通過.value值的方式訪問,reactive直接可以通過[data].[property]的方式訪問。

四、關注點分離

html <a-form-item class="width200"> <a-select placeholder="請選擇小程序" v-model:value="formState.applet" :options="appletData" @change="appletHandle" > </a-select> </a-form-item>

```js import useAppletData from "./market-data-helper/useAppletData";

export default defineComponent({ setup() { const { appletData } = useAppletData(); return { appletData } } }) ``` 相關業務邏輯代碼分離出去

下面useAppletData.ts文件的主要作用是把請求到的下拉數據分離出去,請求到數據之後return出去,然後在上面的列表頁引入使用。

```js //useAppletData.ts import { ref, onMounted } from "vue"; import { appletList } from "../../../../api/common";

interface IOptions { label: string; value: string; } const appletDataOptions = [{ label: "全部小程序", value: "" }]; export default function useAppletData() { const appletData = ref>(appletDataOptions); const getAppletList = async () => { let data = { page: 1, pageSize: 999999 }; let res = await appletList(data); if (res.code === 0) { appletData.value = res; } }; return { appletData }; } ``` 這樣可以大大提高閲讀的速度,代碼也不會宂餘。

五、在vue-router中的用法

在vue2中,我們通過this.$router這種方式進行路由跳轉的,但是在vue3中沒有this這一概念,因此我們是這樣使用的:

```js import { defineComponent } from "vue"; import { useRouter } from "vue-router";

export default defineComponent({ const count = ref(0); const router = useRouter(); const handleClick = () => { router.push({ name: "AddApplet" }); } return { count, handleClick } }) ```

六、在vuex中的用法

```js import { useStore } from 'vuex'

export default defineComponent({ setup() { const store = useStore() const count = store.state.count; return { count, } } }) ```

七、對於TypeScript的支持

從列表最初數據的定義到請求接口再到返回數據,都進行了約束。

列表:

```ts //列表定義的數據 interface Applet { id?: number; code: string; createAt: String, logo: string, merchantName: string, name: string; serviceName: string; tag: string; tagId: string; }

const appletData = ref>([]); ```

```ts //返回值 data中的值定義 interface AppletListResponse { count: number; list: Array; page: number | string; pageSize: number | string; }

//列表返回的值定義 interface AppletListRes { code: number; msg: string; data: AppletListResponse; }

appletList(params).then((res: AppletListRes) => { if (res.code === 0) { appletData.value = res.data.list; total.value = res.data.count; } }); ```

```ts //參數 interface AppletParams { page: number | string; pageSize: number | string; name?: string; storeName?: string; serviceName?: string; tag?: string; }

//接口請求 export const appletList = (params: AppletParams): Promise => { return Http.get("/applet/list", params); }; ```

八、script setup語法糖

大家可能發現了,雖然我們可以進行關注點分離代碼,但是setup函數裏面的代碼依舊看起來很多,那麼有沒有什麼方法可以讓setup變的更加簡短一些呢?下面我就介紹一下vue3 3.2版本正式發佈的script setup語法糖,可真是用起來特別甜。

1、setup基本用法:

使用起來特別簡單,只需要在script標籤上加上setup關鍵字即可。

```js

```

2、組件自動註冊

在script setup中,引入的組件,無需在components註冊。

```js

```

3、組件核心API的使用

(1)使用defineProps

通過 defineProps 指定當前props類型,獲得上下文的props對象。

```js

```

(2)使用emits

父組件:

```js

```

```js

``` (3)獲取slots和attrs

通過useAttrs、useSlots分別獲取slots和attrs。

```js

```

(4)屬性和方法無需返回,直接使用

之前,setup函數中的變量和函數都要return,才能在模板中使用,現在在script setup中定義無需return,直接可以使用,有沒有感覺到意外的驚喜?

```js

``` 大家有沒有覺得簡單多了。

最後我為大家總結一下:

vue3不論在底層還是邏輯上都有很大的優勢,採用Proxy代替了Object.defineProperty方法的用法,解決了vue之前在處理數組和對象的缺陷。通過優化diff算法、靜態提升、事件偵聽器緩存,極大的提升了vue的性能。

從使用層面,vue3使用組合式API,拋棄了選項式(data、methods、watch、mounted)隔離這種方式,組合式API講究的是代碼的高內聚低耦合。當代碼過多的時候用了關注點分離的方式大大提高了代碼的可讀性。最後vue3最近還使用了script setup語法糖使得代碼更加簡潔精煉。