接到新項目,需要用vue3讓我瞅瞅有什麼新特性?

語言: CN / TW / HK

highlight: an-old-hope

1.Vue3簡介

2.vue3--你知道哪些

2.1 性能的提升

  • 打包大小減少41%

  • 初次渲染快55%, 更新渲染快133%

  • 內存減少54%

    ......

2.源碼的升級

  • 使用Proxy代替defineProperty實現響應式

  • 重寫虛擬DOM的實現和Tree-Shaking

    ......

3.擁抱TypeScript

  • Vue3可以更好的支持TypeScript

4.新的特性

  1. Composition API(組合API)

    • setup配置
    • ref與reactive
    • watch與watchEffect
    • provide與inject
    • ......
  2. 新的內置組件

    • Fragment
    • Teleport
    • Suspense
  3. 其他改變

    • 新的生命週期鈎子
    • data 選項應始終被聲明為一個函數
    • 移除keyCode支持作為 v-on 的修飾符
    • ......

5. Composition API 的優勢

  1. Options API 存在的問題

    使用傳統OptionsAPI中,新增或者修改一個需求,就需要分別在data,methods,computed裏修改 。

  2. Composition API 的優勢

    我們可以更加優雅的組織我們的代碼,函數。讓相關功能的代碼更加有序的組織在一起。

20220508124639.gif

3.常用 Composition API

3.1 setup

理解:Vue3.0中一個新的配置,值為一個函數

  1. 組件中所用到的:數據、方法等等,均要配置在setup中

  2. setup函數的兩種返回值:

    1. 若返回一個對象,則對象中的屬性、方法, 在模板中均可以直接使用。(重點關注!)
    2. 若返回一個渲染函數:則可以自定義渲染內容。(瞭解)
  3. steup 執行時機

    1. 在beforeCreate之前執行一次,this是undefined。
  4. setup的參數

    • props:值為對象,包含:組件外部傳遞過來,且組件內部聲明接收了的屬性。

    • context:上下文對象

      • attrs: 值為對象,包含:組件外部傳遞過來,但沒有在props配置中聲明的屬性, 相當於 this.$attrs
      • slots: 收到的插槽內容, 相當於 this.$slots
      • emit: 分發自定義事件的函數, 相當於 this.$emit
  5. 注意點:

    1. 儘量不要與Vue2.x配置混用

      • Vue2.x配置(data、methos、computed...)中可以訪問到setup中的屬性、方法。
      • 但在setup中不能訪問到Vue2.x配置(data、methos、computed...)。
      • 如果有重名, setup優先。
    2. setup不能是一個async函數,因為返回值不再是return的對象,而是promise,模板看不到return對象中的屬性。(後期也可以返回一個Promise實例,但需要Suspense和異步組件的配合)

3.2 ref

  1. 作用:定義一個響應式的數據

  2. 語法: const xxx = ref(initValue)

    • 創建一個包含響應式數據的引用對象(reference對象,簡稱ref對象)
    • JS中操作數據: xxx.value
    • 模板中讀取數據: 不需要.value,直接:<div>{{xxx}}</div>
  3. 其他

    • 接收的數據可以是:基本類型、也可以是對象類型。
    • 基本類型的數據:響應式依然是靠Object.defineProperty()getset完成的。
    • 對象類型的數據:內部 “ 求助 ” 了Vue3.0中的一個新函數—— reactive函數(也就是ES6的Proxy 代理)。

    3.3 reactive函數

  4. 作用: 定義一個對象類型的響應式數據(基本類型不要用它,要用ref函數)

  5. 語法:const 代理對象= reactive(源對象)接收一個對象(或數組),返回一個代理對象(Proxy的實例對象,簡稱proxy對象)
  6. reactive定義的響應式數據是“深層次的”。
  7. 內部基於 ES6 的 Proxy 實現,通過代理對象操作源對象內部數據進行操作

3.4 computed函數

  • 配置與vue2.x中computed配置功能一致

  • 寫法

    js import {computed} from 'vue' ​ setup(){    ... //計算屬性——簡寫(沒有考慮寫的情況)    let fullName = computed(()=>{        return person.firstName + '-' + person.lastName   })    //計算屬性——完整(考慮讀和寫)    let fullName = computed({        get(){            return person.firstName + '-' + person.lastName       },        set(value){            const nameArr = value.split('-')            person.firstName = nameArr[0]            person.lastName = nameArr[1]       }   }) }

3.5 watch函數

  • 與vue2.x中的watch配置功能一致

  • 支持三個參數

  • 兩個小坑

    • 監視reactive定義的響應式數據時:oldValue無法正確獲取,強制開啟嘗試監視
    • 監視reactive內某個屬性時:deep配置有效,(如對象類型有意義,基本數據類型無意義)

    js import {ref,reactive,watch} from "vue" ​ setup(){       let person=reactive({         name:"張三",         age:18,         job:{           j1:{             salary:20           }         }       })     //情況一:監視ref定義的響應式數據 支持第三個參數      watch(sum,(newValue,oldValue)=>{        console.log('sum變化了',newValue,oldValue)     },{immediate:true}) ​      //情況二:監視多個ref定義的響應式數據      watch([sum,msg],(newValue,oldValue)=>{        console.log('sum或msg變化了',newValue,oldValue)     },) ​      /* 情況三:監視reactive定義的響應式數據           若watch監視的是reactive定義的響應式數據,則無法正確獲得oldValue!!           若watch監視的是reactive定義的響應式數據,則強制開啟了深度監視     */      watch(person,(newValue,oldValue)=>{        console.log('person變化了',newValue,oldValue)     },{immediate:true,deep:false}) //此處的deep配置不再奏效 ​      //情況四:監視reactive定義的響應式數據中的某個屬性      watch(()=>person.job,(newValue,oldValue)=>{        console.log('person的job變化了',newValue,oldValue)     },{immediate:true,deep:true}) ​      //情況五:監視reactive定義的響應式數據中的某些屬性      watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{        console.log('person的job變化了',newValue,oldValue)     },{immediate:true,deep:true}) ​      //特殊情況      watch(()=>person.job,(newValue,oldValue)=>{          console.log('person的job變化了',newValue,oldValue)     },{deep:true}) //此處由於監視的是reactive素定義的對象中的某個屬性,所以deep配置有效 }

3.6 watchEffect函數

  • watch的套路是:既要指明監視的屬性,也要指明監視的回調

  • watchEffect的套路是:不用指明監視哪個屬性,監視的回調中用到哪個屬性,那就監視哪個屬性。

  • watchEffect 有點像computed):

    • computed注重的計算出來的值(回調函數的返回值),所以必須要寫返回值。
    • 而watchEffect更注重的是過程(回調函數的函數體),所以不用寫返回值。

    js //watchEffect所指定的回調中用到的數據只要發生變化,則直接重新執行回調。 watchEffect(()=>{    const x1 = sum.value    const x2 = person.age    console.log('watchEffect配置的回調執行了') })

3.7 hook函數

  • 什麼是hook?—— 本質是一個函數,把setup函數中使用的Composition API進行了封裝。
  • 類似於vue2.x中的mixin。缺點是組件的data,methods會覆蓋mixins裏的同名data,methods
  • 自定義hook的優勢: 複用代碼, 讓setup中的邏輯更清楚易懂。
  • Vue3 hook 庫Get Started | VueUse

案例 本地圖片轉 Base64

```js import { onMounted } from 'vue'

type Options = {    el: string }

type Return = {    Baseurl: string | null } export default function (option: Options): Promise {

return new Promise((resolve) => {        onMounted(() => {            const file: HTMLImageElement = document.querySelector(option.el) as HTMLImageElement;            file.onload = ():void => {                resolve({                    Baseurl: toBase64(file)               })           }

})

const toBase64 = (el: HTMLImageElement): string => {            const canvas: HTMLCanvasElement = document.createElement('canvas')            const ctx = canvas.getContext('2d') as CanvasRenderingContext2D            canvas.width = el.width            canvas.height = el.height            ctx.drawImage(el, 0, 0, canvas.width,canvas.height)            console.log(el.width);                        return canvas.toDataURL('image/png')

}   })

} ```

使用

js import useBase64 from './hooks' ​ useBase64({el:'#img'}).then(res=>{  console.log(res) })

3.8 toRef

  • 作用:創建一個 ref 對象,其value值指向另一個對象中的某個屬性。

  • 語法:const name = toRef(obj, 'bar')

  • 應用: 要將響應式對象中的某個屬性單獨提供給外部使用時。

    ```js

    ```

3.9 toRefs

  • 擴展:toRefstoRef功能一致,但可以批量創建多個 ref 對象,語法:toRefs(person)

  • 應用:批量創建ref對象主要 是方便我們解構使用

    ```js import { reactive, toRefs } from 'vue' const obj = reactive({   foo: 1,   bar: 1 })

    let { foo, bar } = toRefs(obj)

    foo.value++ console.log(foo, bar); ```

4 . 其他 Composition API

4.1shallowReactive 與 shallowRef

  • shallowReactive:只處理對象最外層屬性的響應式(淺響應式)

  • shallowRef:只處理基本數據類型的響應式, 不進行對象的響應式處理。

  • 什麼時候使用?

    • 如果有一個對象數據,結構比較深,但變化 時只是外層屬性變化==>

      shallowReactive

    • 如果有一個對象數據,後續功能不會修改該對象中的屬性,而是生新的對象來替換===> shallowRef

4.2 readonly 與shallowReadonly

  • readonly: 讓一個響應式數據變為只讀的(深只讀)。
  • shallowReadonly:讓一個響應式數據變為只讀的(淺只讀)。
  • 應用場景: 不希望數據被修改時。

4.3 toRaw 與 markRaw

  • toRaw

    • 作用:將一個由 reactive 生成的 響應式對象 轉為 普通對象
    • 使用場景:用於讀取響應式對象=>對應的普通對象,對這個普通對象的所有操作,不會引起頁面的更新
  • markRaw

    • 作用:標記一個對象,使用其永遠不會再成為響應式對象

    • 應用場景:

      • 有些值不應被設置為響應式的,例如複雜的第三方類庫
      • 當渲染具有不可變數據源的大列表時,跳過響應式轉換可以提高性能

4.4 customRef

  • 作用:創建一個自定義的ref、並對其依賴項跟蹤和更新觸發進行顯示控制

  • 實現防抖效果:

    js <template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> ​ <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue準備好的內置ref //自定義一個myRef function myRef(value,delay){ let timer //通過customRef去實現自定義 return customRef((track,trigger)=>{ return{ get(){ track() //告訴Vue這個value值是需要被“追蹤”的 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告訴Vue去更新界面 },delay) } } }) } let keyword = myRef('hello',500) //使用程序員自定義的ref return { keyword } } } </script>

4.5 provide 與 inject

  • 作用:實現 祖與後代組件間通信 (如是 父子 之間通信 建議使用props)

  • 套路: 父組件有一個 provide 選項來提供數據,後代組件有一個 inject 選項來開始使用這些數據

  • 具體寫法:

    注意:如果傳遞普通的值 是不具有響應式的 需要通過ref reactive 添加響應式

  • 祖組件中:

    js setup(){ ......    let car = reactive({name:'奔馳',price:'40萬'})    provide('car',car) //可以用來傳遞響影數據    ...... } 0. 後代組件中:

    js setup(props,context){ ......    const car = inject('car')    return {car} ...... }

4.6響應式數據的判斷

  • isRef: 檢查一個值是否為一個 ref 對象
  • isReactive: 檢查一個對象是否是由 reactive 創建的響應式代理
  • isReadonly: 檢查一個對象是否是由 readonly 創建的只讀代理
  • isProxy: 檢查一個對象是否是由 reactive 或者 readonly 方法創建的代理

5.語法糖

5.1 setup

```js

```

  1. <script setup> 包裹 的任何在內部聲明的頂級綁定(包括變量,函數聲明和導入)都可以直接在模板中使用

  2. setup() 函數返回的值 ,引用在模板中引用時會自動展開

    js <script setup> // variable const msg = 'Hello!' ​ // functions function log() {  console.log(msg) } </script> ​ <template>  <div @click="log">{{ msg }}</div> </template>

  3. 使用組件

    建議PascalCase 命名的組件標籤以保持一致性,它有助於區分原生自定義元素

    js <script setup> import MyComponent from './MyComponent.vue' </script> ​ <template>  <MyComponent /> //不使用my-Component是因為區別是否為原生元素 </template>

  1. 動態組件

    在setup的語法糖的便攜下,由於組件被引用為變量而不是在字符串鍵下注冊,因此

    :is 在內部使用動態組件時應該使用動態綁定<script setup>

    js <script setup> import Foo from './Foo.vue' import Bar from './Bar.vue' </script> ​ <template>  <component :is="Foo" />  <component :is="someCondition ? Foo : Bar" /> </template>

  2. 組件之間的傳值

    要聲明具有完整類型推斷支持的選項propsemits我們可以使用definePropsdefineEmitsAPI,它們在內部自動可用<script setup>

    js <script setup> const props = defineProps({  foo: String }) ​ const emit = defineEmits(['change', 'delete']) // setup code </script>

  1. 頂層 await

    頂層await可以在裏面使用<script setup>。生成的代碼將編譯為async setup()

    js <script setup> const post = await fetch(`/api/post/1`).then((r) => r.json()) </script>

  1. useSlots()&useAttrs()

    • 插槽 useSlots() ==setupContext.slots
    • useAttrs() 相當於 setupContext.attrs

    您可以直接在模板$slots中訪問它們。$attrs在您確實需要它們的極少數情況下,請分別使用useSlotsuseAttrs助手:

  2. 對外暴露屬性 (defineExpose)

    在vue3.x的setup語法糖中定義的變量默認不會暴露出去,需要使用defineExpose({}) 來暴露組件內部屬性給父組件使用為了在