vue3.4的更新,保證你看的明明白白

南风晚来晚相识發表於2024-06-25

defineModel 同學已經轉正

defineModel 在vue3.3中還是一個實驗性功能,
但是經過一個學期的努力,該同學已經轉正。

defineModel的簡單介紹

defineModel() 返回的值是一個 ref。
它可以像其他 ref 一樣被訪問以及修改。
它能起到在父元件和當前變數之間的雙向繫結的作用。
它的 .value 和父元件的 v-model 的值同步。
當它被子元件改變時,會觸發父元件繫結的值一起更新。

我們都知道 props 的設計原則是單項資料流。
子元件預設情況下是無法更改父元件傳遞過來的資料。
如果要更改vue3.3以前是透過 $emit 來實現的。
下面我們來對比一下使用 $emit 和 defineModel 來更新資料

使用 $emit更新父元件傳遞過來的資料(vue3.2)

// 父頁面
<template>
  <div>
    <div class="father-box">
      <p>我是父頁面-此時:son元件的值{{ flag }}</p>
      <button @click="openHandler"> 顯示子元件</button>
    </div>
    <!-- 控制子元件是否顯示 -->
    <son v-model:flag="flag"></son>
  </div>
</template>
<script setup lang="ts">
// vue3.2開始元件引入後,就不需要註冊啦。變數也不需要暴露出去啦
import son from '@/components/son.vue'
import { ref } from 'vue';
let flag = ref(false)
// 點選事件,更改值,讓元件顯示出來
const openHandler = ()=>{
  flag.value = true;
}
</script>
<style>
.father-box{
  background: palegreen;
}
</style>
// son元件
<template>
  <div class="son-box" v-if="flag">
    <h1 >我是son元件</h1>
    <button @click="hideHandler"> 關閉元件:更改父元件傳遞過來的值</button>
  </div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
// 接收傳遞過來的值
defineProps({
  flag: {
    type: Boolean,
    default: false,
  }
});
// 註冊事件
const emits = defineEmits(["update:flag"]);
// 去更新父元件中的flag值,更改為false
const hideHandler = () => {
  emits("update:flag", false)
}
</script>
<style>
.son-box{
   background: pink;
}
</style>

使用 defineModel 更新父元件傳遞過來的資料(vue3.4)

// 父頁面程式碼
<template>
  <div>
    <div class="father-box">
      <p>我是父頁面-此時:son1元件的值{{ flag }}</p>
      <button @click="openHandler"> 顯示子元件</button>
    </div>
    <!-- 控制子元件是否顯示 -->
    <son1 v-model:flag="flag"></son1>
  </div>
</template>
<script setup lang="ts">
// vue3.2開始元件引入後,就不需要註冊啦。變數也不需要暴露出去啦
import son1 from '@/components/son1.vue'
import { ref } from 'vue';
let flag = ref(false)
// 點選事件,更改值,讓元件顯示出來
const openHandler = ()=>{
  flag.value = true;
}
</script>
<style>
.father-box{
  background: palegreen;
}
</style>
// son1元件
<template>
  <div class="son-box" v-if="flagBool">
    <h1 >我是son1元件</h1>
    <button @click="hideHandler"> 關閉元件:更改父元件傳遞過來的值</button>
  </div>
</template>

<script setup lang="ts">
import { defineModel } from "vue";
// defineModel('flag')中的flag必須與傳遞過來的屬性保持一致
// flagBool 是接受的控制變數,可以是任意的
const flagBool = defineModel('flag')
const hideHandler = () => {
  flagBool.value = false
}
</script>
<style>
.son-box{
   background: pink;
}
</style>

採訪一下:使用 defineModel 後的感覺

現在使用 defineModel 進行資料的雙向繫結更加友好。
比原來更加方便了。爽歪歪!
原來需要再一個合適的時機(事件觸發)寫上:
emits("update:flag", false)
而現在直接寫上 const 變數名 = defineModel('雙向繫結的值')
不需要考慮時機

defineModel 傳遞多個v-model

// 父頁面
<template>
  <div>
    <div class="father-box">
      <p>我是父頁面-此時:son3元件的值{{ titleName }} {{ address }}</p>
    </div>
    <!-- defineModel 傳遞多個v-model -->
    <son3 v-model:titleName="titleName" v-model:address="address"></son3>
  </div>
</template>
<script setup lang="ts">
// vue3.2開始元件引入後,就不需要註冊啦。變數也不需要暴露出去啦
import son3 from '@/components/son3.vue'
import { ref } from 'vue'
let titleName = ref('少玩手機多看報')
let address = ref('我在這個紅綠燈旁邊')
</script>
<style>
.father-box{
  background: palegreen;
}
</style>
// 元件son3
<template>
  <div class="son-box">
    <h1 >我是son1元件</h1>
    <input type="text" v-model="titleName"> 
    <input type="text" v-model="address"> 
  </div>
</template>

<script setup lang="ts">
import { defineModel } from "vue";
const titleName = defineModel('titleName')
const address = defineModel('address')
</script>
<style>
.son-box{
   background: pink;
}
</style>

defineModel 設定預設值

// 父頁面
<template>
  <div>
    <div class="father-box">
      <!-- 這裡獲取不到子元件的值 -->
      <p>我是父頁面-此時:son3元件的值{{ obj }} </p>
    </div>
    <!-- 沒有給子元件傳遞值 -->
    <son3></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
import { ref } from 'vue'
const obj = ref()
</script>
<style>
.father-box{
  background: palegreen;
}
</style>
<template>
  <div class="son-box">
    <h1 >我是son3元件</h1>
    <h2>{{  detailsObj.name }}</h2>
    <h2>{{  detailsObj.age }}</h2>
    <button @click="changeHandler">改變值</button>
  </div>
</template>

<script setup lang="ts">
import { defineModel } from "vue";
// 定義 引數如型別、預設值
let detailsObj = defineModel('obj', {
  // 初始渲染的時候會顯示預設值
  default: { name :'張三', age: 10},
  type: Object,
});
// 改變值
const changeHandler = ()=>{
  console.log(detailsObj)
  detailsObj.value.name = '我是王五',
  detailsObj.value.age = 20
}
</script>
.son-box{
   background: pink;
}
</style>


發現2個問題:父子資料不同步,子元件資料不更新

我們透過給defineModel設定了預設值。
子元件也正確顯示了預設值,但是父頁面獲取不到子元件的值。
這導致導致父元件與子元件之間的資料不同步。這個是我們發現的第1個問題
第2個問題是:設定預設值後,子元件資料在檢視中不更新。
這裡我們大膽的猜想,是不是跟資料型別有關?
引用資料型別不更新,基本資料型別會更新。

驗證: 子元件引用資料型別不更新,基本資料型別會更新

// 父元件
<template>
  <div>
    <div class="father-box">
      <!-- 這裡獲取不到子元件的值 -->
      <p>我是父頁面-此時:son3元件的值{{ age }} </p>
    </div>
    <!-- 沒有給子元件傳遞值 -->
    <son3></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
import { ref } from 'vue'
const age = ref()
</script>
// 子元件
<template>
  <div class="son-box">
    <h1 >我是son3元件</h1>
    <h2>{{  ageValue }}</h2>
    <button @click="changeHandler">改變值</button>
  </div>
</template>
<script setup lang="ts">
import { defineModel } from "vue";
// 這次是一個基本資料型別
let ageValue = defineModel('age', {
  // 初始渲染的時候會顯示預設值
  default: 10,
  type: Number,
});
// 改變值,頁面會更新嗎?
const changeHandler = ()=>{
  console.log(ageValue)
  ageValue.value = 100
}
</script>

總結: defineModel使用預設值會造成2個影響

defineModel使用預設值後:
1.導致父元件與子元件之間的資料不同步
2.如何預設值是應用資料型別,子元件資料在檢視中不更新,
  如果預設值是基本資料型別,子元件資料在檢視中會更新。
ps: 儘量不要在defineModel中使用預設值。

處理 v-model 修飾符

我們知道了 v-model 有一些內建的修飾符。
例如 .trim, .number, .lazy
有些時候,我們想自定義修飾符。
如:將v-model 繫結輸入的字串值第一個字母轉為大寫。
我們可以使用 defineModel 的 get 和 set 選項。

v-model 修飾符實現:第一個字母轉為大寫

// 父元件
<template>
  <div>
    <div class="father-box">
      <!-- 這裡獲取不到子元件的值 -->
      <p>我是父頁面-此時:child元件的值{{ surName }} </p>
    </div>
    <!-- 修飾符 titleCase 在 defineModel 解構的第2個引數中可以拿到 -->
    <child  v-model.titleCase="surName"></child>
  </div>
</template>
<script setup lang="ts">
import child from '@/components/child.vue'
import { ref } from 'vue'
const surName = ref()
</script>
<style>
.father-box{
  background: palegreen;
}
</style>
// 子元件
<template>
  <input type="text" v-model="modelValue" />
</template>
<script setup>
import { defineModel } from 'vue'
// 解構
const [modelValue, modifiers] = defineModel({
  set(value) {
    // 正則表達輸入的是否是26個英文
    const regex = /^[a-zA-Z]+$/
    if(regex.test(value) && modifiers.titleCase){
      return value.charAt(0).toUpperCase() + value.slice(1)
    } else {
      // 如果不符合要求返回空字元
      return value
    }
  }
})
console.log(modelValue)
console.log(modifiers)
</script>
<style>
.son-box{
   background: pink;
}
</style>

v-bind的簡寫語法

// 以前的
<img  :src="src" :alt="alt">
//3.4版本可以簡寫為
<img  :src :alt>

v-bind的簡寫語法用在元件傳遞值上

<template>
  <div>
    // 簡寫語法
    <son3 :userName :age></son3>
    <!-- 等價與以前這樣寫 -->
    <!-- <son3 :userName="userName" :age="age"></son3> -->
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
import { ref } from 'vue'
const userName = ref('李四')
const age = ref(18)
</script>

更高效的反應性系統 watchEffect

<template>
  <div>
    <h1>分數{{ fraction }}</h1>
    <button @click="onChangeFraction">努力學習,改變分數</button>
  </div>
</template>
<script setup lang="ts">
import { ref ,watchEffect} from 'vue'
const fraction = ref(540)
// 現在fraction的值不發生變化
const onChangeFraction = () => {
  fraction.value = 540
}
// 不會觸發watchEffect的回撥
watchEffect(() => console.log(fraction.value))
</script>
在 vue3.4 之前,即使計算結果(fraction)保持不變
每次 fraction.value 都將觸發 watchEffect 的回撥。
而現在只要fraction的值不變化,不會觸發watchEffect的回撥

編譯器效能最佳化

解析速度提高一倍
解析器從頭開始重寫,速度快了一倍。
與舊模板相比,它在一半的時間內解析相同的模板。
舊的解析器是一個遞迴下降解析器,它使用大量正規表示式和低效的前瞻搜尋。
新的解析器使用 htmlparser2。
它以線性方式迭代輸入,具有最少的前瞻和回溯。
並在很大程度上消除了對正規表示式的依賴。

刪除了已棄用的功能

1.全域性 JSX 名稱空間
從 3.4 開始,Vue 預設不再註冊全域性 JSX 名稱空間。
這是避免與 React 發生全域性名稱空間衝突,
以便兩個庫的 TSX 可以共存於同一個專案中。
這應該不會影響使用最新版本的 Volar 的 SFC 的使用者。

如果您使用的是 TSX,則有兩種選擇:
第1種:在升級到 3.4 之前,
在tsconfig.json 中將 jsxImportSource 顯式設定為 'vue'。
您還可以透過在檔案頂部新增 /* @jsxImportSource vue */ 註釋來選擇加入每個檔案。

第2種:如果您的程式碼依賴於全域性 JSX 名稱空間的存在,
例如使用 JSX.Element 型別等.
則可以透過顯式引用 vue/jsx 來保留 3.4 之前的確切全域性行為,
這將註冊全域性 JSX 名稱空間。
2.其他已刪除的功能
1,反應性轉換在 3.3 中被標記為不推薦使用,現在在 3.4 中被刪除。
2,app.config.unwrapInjectedRef 已被刪除。
3,@vnode-xx模板中的事件偵聽器現在是編譯器錯誤,而不是棄用警告。請改用 @vue:XXX 偵聽器。
4,v-is 指令已被刪除。它在 3.3 中已棄用。請改用帶 vue: 字首的 is 屬性。

監聽子元件的生命週期:@vnode-xx更改為@vue:xxx

在已經刪除的功能中,第2點:@vnode-xx更改為@vue:xxx。
有些時候,我們需要去監聽子元件的生命週期。
有2種辦法:第1種,在子元件的各個生命週期中使用emit丟擲方法,然後父元件呼叫
缺點:第3方元件必須如果沒有提供emit的話,我們就可以使用下面這一種
第2種: 在元件中使用@vnode-xx(3.4之前)
現在@vnode--更改為@vue:xxx

監聽子元件的生命(vue3.4之前)

<template>
  <div>
    <!-- 在vue3.4之前監聽子元件的生命週期可以使用 @vnode-mounted="fn" -->
    <son3 @vnode-mounted="sonMounted"></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
const sonMounted = () =>{
  console.log('元件dom渲染完成')
}
</script>

監聽子元件的生命(vue3.4)

<template>
  <div>
    <!-- 現在使用@vue:mounted="fn" @vue:後面是生命週期-->
    <son3 @vue:mounted="mountedDoThing" ></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
const mountedDoThing = () =>{
  console.log('子元件的掛載階段完成')
}
</script>

監聽子元件的生命-奇怪的地方

細心的小夥伴剛剛可能發現了
<son3 @vue:mounted="mountedDoThing" ></son3>
@vue:後面的生命週期是原來vue2的mounted。
為啥不使用vue3的onMounted呢?
因為:如果使用的是onMounted的話,
將無法監聽子元件(son3)是否在頁面中被掛載了。
這裡大家是否會覺得奇怪?
我想了很久,也沒有找到原因。機智的小夥伴可以幫我解惑一下
下面我們看下使用vue3的生命週期是否會出發

奇怪的地方:如果使用vue3的生命週期將不會被觸發

<template>
  <div>
    <!-- 這裡是vue3的onMounted生命週期,
      onMountedDoThing函式將不會被觸發
        如果使用的是mounted將會被觸發  -->
    <son3  @vue:onMounted="onMountedDoThing"></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
const onMountedDoThing = () => {
  console.log('onMounted不會被觸發')
}
</script>

v-is 指令已被刪除,改用帶vue:字首的is屬性

在vue3.4中,v-is 指令已被刪除。
它在 3.3 中已棄用。請改用 is="vue:想替換成的標籤"
有些時候,我們想替換某個原生元素。
這個時候我們就可以is來實現。
下面我們將tr標籤和p標籤替換成li標籤
<template>
  <div>
    <ul>
      <tr is="vue:li">tr變成li標籤</tr>
      <p is="vue:li">p變成li標籤</p>
    </ul>
  </div>
</template>

Vue 3.4 釋出地址

https://blog.vuejs.org/posts/vue-3-4#removed-deprecated-features

尾聲

如果你覺得我寫的不錯的話,
請給我點一個推薦,
週末都在寫這個,有能力可以給我打賞(手動狗頭)
最近想吃親嘴燒,最好可以喝一瓶水,因為辣條有點辣(手動狗頭)

相關文章