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語法糖使得程式碼更加簡潔精煉。

「其他文章」