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