Vue3的變化
官網(wǎng)地址: https://v3.cn.vuejs.org/guide/migration/introduction.html
一、對比vue2的變化
1.優(yōu)點(diǎn)
- vue3支持vue2的大多數(shù)特性,實(shí)現(xiàn)對vue2的兼容
- vue3對比vue2具有明顯的性能提升
- 打包大小減少41%
- 初次渲染快55%,更新快133%
- 內(nèi)存使用減少54%
- 更好的支持TypeScript
- 使用Proxy代替defineProperty實(shí)現(xiàn)響應(yīng)式數(shù)據(jù)
2.性能提升的原因
- 靜態(tài)標(biāo)記
- vue2從根節(jié)點(diǎn)開始對虛擬dom進(jìn)行全量對比(每個節(jié)點(diǎn)不論寫死的還是動態(tài)的都會一層一層比較)
- vue3新增了靜態(tài)標(biāo)記 與上次虛擬dom對比的時候,只對比帶有 patchFlags 的節(jié)點(diǎn)。跳過一些靜態(tài)節(jié)點(diǎn)對比(下圖編譯結(jié)果中-1跟1就屬于靜態(tài)標(biāo)記)
patchFlags配置文件地址 https://github.com/vuejs/core/blob/main/packages/shared/src/patchFlags.ts
<div id='app'> <div>helloWorld</div> <div>{{msg}}</div> </div> <!--編譯后--> <script> const _hoisted_1 = { id: "app" } const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, "helloWorld", -1 /* HOISTED */) export function render(...) { return ( _openBlock(), _createBlock('div', _hoisted_1, [ _hoisted_2, _createElementVNode('div',null,_toDisplayString(_ctx.msg),1 /* TEXT */), ]) ) } </script> - hoistStatic 靜態(tài)提升
- vue2里每當(dāng)觸發(fā)更新的時候,不管元素是否參與更新,每次都會重新創(chuàng)建
- vue3為了避免每次渲染的時候都要重新創(chuàng)建這些對象,會把不參與更新的元素保存起來,只創(chuàng)建一次,每次復(fù)用。比如上面_hoisted_1,_hoisted_2被提升到渲染函數(shù)render之外,
- cacheHandlers 事件緩存
- vue2里綁定事件都要重新生成新的function去更新
- vue3會自動生成一個內(nèi)聯(lián)函數(shù),同時生成一個靜態(tài)節(jié)點(diǎn)。onclick時會讀取緩存,如果緩存沒有的話,就把傳入的事件存到緩存里
<div @click="handleClick">點(diǎn)擊</div> <!--編譯后--> <script> export function render(...) { return (_openBlock()._createElementVNode('div',{onClick: _ctx.todo}, '點(diǎn)擊')) ) } </script> <script> export function render(...) { return (_openBlock()._createElementVNode('div',{ onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.todo(...args))) },'點(diǎn)擊')) } </script>
3.響應(yīng)式數(shù)據(jù)的變化
- vue2的響應(yīng)式(實(shí)現(xiàn)地址:https://github.com/jiaomengyuan)
- 核心
- Object.defineProperty 來劫持各個屬性的setter、getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)
- 對象:遞歸調(diào)用defineProperty對對象已有屬性值的讀取和修改進(jìn)行攔截
- 數(shù)組:重寫數(shù)組更新數(shù)組一系列更新元素的方法來實(shí)現(xiàn)元素修改的劫持
- 問題
- 不能監(jiān)聽到對象屬性的添加和刪除,需要Vue.set()來添加和刪除。
- 不能通過下標(biāo)替換元素或更新length
- 核心
- vue3的響應(yīng)式(實(shí)現(xiàn)地址:https://github.com/jiaomengyuan)
- 核心
- 通過Proxy(代理): 攔截對data任意屬性的任意(13種)操作, 包括屬性值的讀寫, 屬性的添加, 屬性的刪除等
- 通過 Reflect(反射): 動態(tài)對被代理對象的相應(yīng)屬性進(jìn)行特定的操作
- 核心
二、 新增特性
1.Composition (組合) API
-
Option API:vue2創(chuàng)建組件時,會把數(shù)據(jù)放到data,計(jì)算屬性放到computed,事件處理放到methods,監(jiān)聽改變放到watch;共同處理頁面邏輯- 組件功能越來越多,邏輯功能點(diǎn)分散,不易閱讀(新增或修改一個需求,就需要分別在data,methods等...進(jìn)行修改,功能多時,滾動條來回滾動 )
- 可以通過Mixins重用邏輯代碼,但是數(shù)據(jù)來源模糊還會有Mixins命名沖突的問題
-
Composition API:將零散的data,methods代碼重新組合,一個功能的代碼放一塊兒,并且可以單出拆分出函數(shù)- 兼容Option API,還可繼續(xù)使用
- 利于代碼重用,沒有對this的使用,減少了this指向不明的情況
- 幾乎是函數(shù),編輯器可以幫我們進(jìn)行類型檢查和建議
2. setup
- setup函數(shù)是一個新的option,在初始化時執(zhí)行一次,可以理解為使用Composition API 的入口點(diǎn)。
- 這個函數(shù)的返回一個對象,對象里的屬性和方法,可以直接在模版中使用
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
setup () {
const msg = 'hello World'
return {
msg
}
}
}
</script>
注意:
- 在beforeCreate之前創(chuàng)建,因此,這個函數(shù)中沒有this。因此不能訪問data,methods等。但methods中可以訪問setup提供的屬性和方法
- return中返回的屬性跟data合并,返回的方法跟methods里的方法合并;如有重名,setup優(yōu)先
- setup不能是一個async函數(shù),使用async后返回值不是return的對象,而是promise。非要使用,需要使用
<suspense>包裹組件 - setup接收兩個參數(shù)
setup(props, context)||setup(props, {attrs, slots, emit})不能解構(gòu)props,否則會失去響應(yīng)式
1) ref
- 定義一個基本數(shù)據(jù)類型的響應(yīng)式引用
<template>
<div>{{num}}</div>
<button @click="addNum">添加</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
const num = ref(1)
function addNum () {
num.value = num.value + 1
}
return {
num,
addNum
}
}
}
</script>
注意:
- 在setup中使用ref定義的響應(yīng)式引用需要
.value(內(nèi)部通過給value屬性添加getter、setter實(shí)現(xiàn)對數(shù)據(jù)的劫持),在模版中不需要(解析模板時會自動添加.value) - ref常用來處理基本數(shù)據(jù)類型,如果用ref定義引用數(shù)據(jù)類型, 內(nèi)部會自動將對象,數(shù)組轉(zhuǎn)換為reactive的代理對象
2) reactive
- 定義多個數(shù)據(jù)的響應(yīng)式引用
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<button @click="updateObj">修改</button>
</template>
<script>
import { reactive } from 'vue'
export default {
setup () {
const obj = reactive({ name: "張三", age: 25 });
const updateObj = () => {
obj.name = "王五";
obj.age = 21;
};
return {
obj,
updateObj,
};
}
}
</script>
3) toRef(obj,key) 、 toRefs(obj)
- reactive定義的響應(yīng)式數(shù)據(jù),解構(gòu)后進(jìn)行使用,數(shù)據(jù)就不是響應(yīng)式的,使用
toRef或toRefs將解決這個問題 -
toRef跟toRefs都是復(fù)制reactive里的屬性然后轉(zhuǎn)成ref。因?yàn)槭菧\拷貝,所以修改復(fù)制出來的值時原來reactive里的數(shù)據(jù)也會跟著更新 -
toRef是復(fù)制reactive里的單個key轉(zhuǎn)成ref,toRefs復(fù)制reactive里的所有key轉(zhuǎn)成ref - 也可以用來將props接收的數(shù)據(jù)進(jìn)行解構(gòu)響應(yīng)式
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<div>{{ name }}</div>
<div>{{ age }}</div>
<button @click="updateObj">修改</button>
</template>
<script>
import { reactive, toRefs, toRef } from "vue";
export default {
props: {
msg: String,
},
setup (props) {
const obj = reactive({ name: "張三", age: 25 });
// const name = toRef(obj, "name");
// const { msg } = toRefs(props)
const { name, age } = toRefs(obj);
const updateObj = () => {
obj.name = "王五";
obj.age = 21;
};
return {
obj,
name,
age,
updateObj,
};
}
}
</script>
4) shallowRef、triggerRef 與 shallowReactive
-
ref和reactive是把數(shù)據(jù)變?yōu)轫憫?yīng)式,無論層級多深,始終都會對數(shù)據(jù)進(jìn)行深度監(jiān)聽。shallowRef和shallowReactive可以解決這個 -
shallowRef只處理了.value的響應(yīng)式(只監(jiān)聽.value的變化)。因此如果需要修改應(yīng)該xxx.value={}或者使用shallowRef將數(shù)據(jù)強(qiáng)制更新到頁面 - 注:
shallowReactive的數(shù)據(jù)使用shallowRef強(qiáng)制更新無效
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<div>{{ obj.c.d }}</div>
<button @click="updateObj">修改</button>
</template>
<script>
import { shallowRef, triggerRef } from "vue";
export default {
setup() {
const obj = shallowRef({ name: "張三", age: 25, c: { d: 2 } });
const updateObj = () => {
// 這些都不會修改成功,obj對象會變,不會更新到模版
// obj.value.name = "王五";
// obj.value.age = 21;
// obj.value.c.d = 4;
// 調(diào)用此方法,上方數(shù)據(jù)會更新到頁面
// triggerRef(obj);
// 如要修改,要用此方法
obj.value={ name: "王五", age: 21, c: { d: 4 } }
};
return {
obj,
updateObj,
};
},
};
</script>
-
shallowReactive只處理對象最外層屬性的響應(yīng)式(淺響應(yīng)式),默認(rèn)情況下只能監(jiān)聽第一次的數(shù)據(jù)。如要修改則需要先改第一層的數(shù)據(jù),然后再去更改其他層的數(shù)據(jù)
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<div>{{ obj.c.d }}</div>
<button @click="updateObj">修改</button>
</template>
<script>
import { shallowReactive } from "vue";
export default {
setup() {
const obj = shallowReactive({ name: "張三", age: 25, c: { d: 2 } });
const updateObj = () => {
// 這樣不會監(jiān)聽到修改,obj會變,不會更新到模版
// obj.c.d = 4;
// 如要修改需要用下面方法,先改第一層
obj.name = "王五";
obj.age = 21;
obj.c.d = 4;
};
return {
obj,
updateObj,
};
},
};
</script>
5) readonly 與 shallowReadonly
-
readonly是讓一個響應(yīng)式數(shù)據(jù)只讀,深層只讀
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<div>{{ obj.c.d }}</div>
<button @click="updateObj">修改</button>
</template>
<script>
import { reactive, readonly } from "vue";
export default {
setup() {
let obj = reactive({ name: "張三", age: 25, c: { d: 2 } });
obj = readonly(obj);
//這里不會修改成功(還是proxy對象,但obj不會修改成功)
const updateObj = () => {
obj.name = "王五";
obj.age = 21;
obj.c.d = 4;
console.log(obj)// 這里obj還等于之前定義的值
};
return {
obj,
updateObj,
};
},
};
</script>
-
shallowReadonly是讓一個響應(yīng)式數(shù)據(jù)只讀,淺層只讀(響應(yīng)式對象的最外層只讀,但再深一層的屬性可以被修改)
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<div>{{ obj.c.d }}</div>
<button @click="updateObj">修改</button>
</template>
<script>
import { shallowReadonly, reactive } from "vue";
export default {
setup() {
let obj = reactive({ name: "張三", age: 25, c: { d: 2 } });
obj = shallowReadonly(obj);
const updateObj = () => {
//這個不會修改
obj.name = "王五";
//這個不會修改
obj.age = 21;
//這個會被修改
obj.c.d = 4;
};
return {
obj,
updateObj,
};
},
};
</script>
6) toRaw 、 markRaw
-
toRaw將一個響應(yīng)式對象(由 reactive定義的響應(yīng)式)轉(zhuǎn)換為普通對象
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<div>{{ obj.c.d }}</div>
<button @click="updateObj">修改</button>
</template>
<script>
import { toRaw, reactive } from "vue";
export default {
setup() {
let obj = reactive({ name: "張三", age: 25, c: { d: 2 } });
obj = toRaw(obj);// 使用此方法,obj則變?yōu)槠胀▽ο螅ǚ莗roxy)
const updateObj = () => {
obj.name = "王五";
obj.age = 21;
obj.c.d = 4;
};
return {
obj,
updateObj,
};
},
};
</script>
-
markRaw標(biāo)記一個對象,使其不能成為一個響應(yīng)式對象
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<div>{{ obj.c.d }}</div>
<button @click="updateObj">修改</button>
</template>
<script>
import { markRaw, reactive } from "vue";
export default {
setup() {
let data = { name: "張三", age: 25, c: { d: 2 } };
// 在此處標(biāo)記后,之后使用reactive則不生效(不會成為一個proxy對象),markRaw放到reactive之后則無效
data = markRaw(data);
let obj = reactive(data);
const updateObj = () => {
obj.name = "王五";
obj.age = 21;
obj.c.d = 4;
};
return {
obj,
updateObj,
};
},
};
</script>
7) isRef 、unref 、 isReactive 、isProxy 、isReadonly
-
isRef檢查值是否為一個 ref 對象。unref為val = isRef(val) ? val.value : val的語法糖
const obj = ref({ name: "張三", age: 25, c: { d: 2 } });
console.log(isRef(obj))
console.log(unref(obj))
-
isReactive檢查值是否為一個 reactive 對象
const obj = reactive({ name: "張三", age: 25, c: { d: 2 } });
console.log(isReactive(obj))
-
isProxy檢查對象是否是由 reactive 或 readonly 創(chuàng)建的 proxy
const obj = reactive({ name: "張三", age: 25, c: { d: 2 } });
console.log(isProxy(obj))
const obj = readonly({ name: "張三", age: 25, c: { d: 2 } });
console.log(isProxy(obj))
-
isReadonly檢查對象是否是由 readonly 創(chuàng)建的只讀代理
const obj = readonly({ name: "張三", age: 25, c: { d: 2 } });
console.log(isReadonly(obj))
8) customRef
- 創(chuàng)建一個自定義的ref,接受一個函數(shù)作為參數(shù),這個函數(shù)接受兩個參數(shù)
track(通知vue需要追蹤后續(xù)內(nèi)容的變化)trigger(通知vue重新解析模版)
<template>
<input type="text" v-model="inpValue">
<div>{{inpValue}}</div>
</template>
<script>
import {customRef} from "vue";
export default {
setup() {
// 自定義一個 myRef
function myRef(value) {
return customRef((track, trigger) => {
return {
get() {
track() // 追蹤后續(xù)數(shù)據(jù)的變化
return value
},
set(newValue) {
value = newValue
trigger() // 重新解析模板
}
}
})
}
let inpValue = myRef('hello')
return {
inpValue,
}
}
}
</script>
9) computed、watch、watchEffect
- 在setup中也有單獨(dú)的
computed和watch用法基本相同 -
watchEffect是監(jiān)視所有回調(diào)中使用的數(shù)據(jù),因?yàn)槊看纬跏蓟臅r候都會執(zhí)行一次回掉自動獲取依賴,并不用手動傳需要監(jiān)聽的對象。注:無法獲取到oldValue
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
<input type="text" v-model="userNews"/>
<input type="text" v-model="userNews2"/>
<input type="text" v-model="userNews3"/>
<input type="text" v-model="userNews4"/>
<button @click="updateObj">修改</button>
</template>
<script>
import { ref, reactive, computed, watch, watchEffect } from "vue";
export default {
props: {
msg: String,
},
setup (props) {
const obj = reactive({ name: "張三", age: 25 });
const updateObj = () => {
obj.name = "王五";
obj.age = 21;
};
// computed只有g(shù)et
const userNews = computed(() => {
return obj.name + "," + obj.age;
})
// computed同時有g(shù)et和set
const userNews2 = computed({
get() {
return obj.name + "," + obj.age;
},
set(value) {
const nameAge = value.split(",");
obj.name = nameAge[0];
obj.age = nameAge[1];
},
});
// watch
const userNews3 = ref("");
watch(
obj,
(newValue, oldValue) => {
userNews3.value = newValue.name + "," + newValue.age;
},
{
immediate: true,
deep: true,
}
);
// watchEffect
const userNews4 = ref("");
watchEffect(() => {
userNews4.value = obj.name + "," + obj.age;
});
return {
obj,
userNews,
userNews2,
userNews3,
userNews4,
updateObj,
};
}
}
</script>
注意:
當(dāng)使用 watch 選項(xiàng)偵聽數(shù)組時,只有在數(shù)組被替換時才會觸發(fā)回調(diào)。換句話說,在數(shù)組被改變時偵聽回調(diào)將不再被觸發(fā)。要想在數(shù)組被改變時觸發(fā)偵聽回調(diào),必須指定 deep 選項(xiàng)。
10) 生命周期
- setup中也有新的生命周期
onBeforeMount->onMounted->onBeforeUpdate->onUpdated->onBeforeUnmount->onUnmounted->onErrorCaptured跟options api混用時onBeforeMount在beforeMount之前,onMounted在mounted之前。。。之后都是 - vue中父子順序
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted在setup中聲明周期也適用
11) provide 、 inject
-
provide、inject提供依賴注入 ,實(shí)現(xiàn)祖孫級組件通信
<template>
<h1>父組件</h1>
<one />
</template>
<script>
import { provide, ref } from 'vue'
import one from './one.vue'
export default {
components: {
one
},
setup() {
const msg = ref('red')
provide('msg', msg)
return {
msg
}
}
}
</script>
<!--子組件(one)-->
<template>
<div>子組件</div>
<two />
</template>
<script>
import two from './two.vue'
export default {
components: {
two
},
}
</script>
<!--孫子組件(two)-->
<script>
import { inject } from 'vue'
export default {
setup() {
const msg = inject('msg')
return {
msg
}
}
}
</script>
12) $refs
- 在vue2中使用
this.$refs.XXX獲取,vue3中setup函數(shù)沒有this,所以也有單獨(dú)的獲取ref的方法
<template>
<input type="text" ref="inputRef" value="這是input的文本"/>
</template>
<script>
import { onMounted, ref } from "vue";
export default {
setup() {
const inputRef = ref(null);/// 本質(zhì)是reactive({value:null})
onMounted(() => {
console.log(inputRef.value.value);
});
console.log(inputRef.value);// null dom還沒形成
return {
inputRef,
};
},
};
</script>
13) 自定義hook函數(shù)
- 與mixin類似,抽離公共代碼
import { reactive } from "vue";
export default function hookTest() {
const obj = reactive({ name: "張三", age: 25 });
return { obj };
}
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
</template>
<script>
import hookTest from './hookTest'
export default {
setup (props) {
const {obj} = hookTest()
return { obj };
}
}
</script>
3. 其他新特性
1) 全局API
- 在vue2中的main.js中有以下代碼。如果使用全局api則是 Vue.directive、Vue.component、Vue.config、Vue.mixin、Vue.prototype等,都是掛載在Vue原型上
import Vue from 'vue'
import App from './App.vue'
const app = new Vue(App)
app.$mount()
- vue3提供的是實(shí)例api。通過createApp創(chuàng)建vue實(shí)例
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

全局API.png
2) v-if 與 v-for 的優(yōu)先級對比
- 在vue2中:當(dāng)v-if和v-for同時使用時,v-for的優(yōu)先級高于v-if(因此我們通常需要計(jì)算屬性先對數(shù)據(jù)進(jìn)行加工處理,以達(dá)到性能優(yōu)化的目的)
- 在vue3中:當(dāng)v-if和v-for同時使用時,v-if的優(yōu)先級高于v-for
3) v-for 中的 Ref 數(shù)組
- vue2中,在 v-for 語句中使用ref屬性,會生成refs數(shù)組插入$refs屬性中
<div v-for="item in 5" :key="item" :ref="item">
{{ item }}
</div>
this.$refs將會是個數(shù)組
- vue3中,在v-for中使用ref屬性,將不會自動在$refs里創(chuàng)建數(shù)組,而是將ref綁定到一個函數(shù)中,在函數(shù)中可以處理ref
<template>
<div v-for="item in 5" :key="item" :ref="setItemRef">
{{ item }}
</div>
</template>
<script>
import { onMounted } from "vue";
export default {
setup() {
const refArray = [];
const setItemRef = (e) => {
refArray.push(e);
};
onMounted(() => {
console.log(refArray);
});
return {
setItemRef,
};
},
};
</script>
4) v-bind合并行為
- vue2如果一個標(biāo)簽同時定義了動態(tài)屬性和一個相同的單獨(dú)的屬性,那么這個單獨(dú)的屬性總是會覆蓋動態(tài)屬性。說明單獨(dú)屬性優(yōu)先級高于動態(tài)屬性
<div id="red" :id="'blue'"></div>
<!--這里div只會綁上id等于red-->
- vue3將會吧動態(tài)屬性跟單獨(dú)屬性合并
<div id="red" :id="'blue'"></div>
<!--結(jié)果為-->
<div id='red blue'></div>
5) v-model
- vue2中,在組件上使用v-model默認(rèn)prop與event為
value和input。如要修改,通過子組件的model選項(xiàng)中的prop值和event值來指定屬性名和事件名。
<child v-model="pageTitle" />
<!--等同于-->
<child :value="pageTitle" @input="pageTitle = $event" />
export default {
model: {
prop: 'title',
event: 'change'
},
props: {
title: {
type: String,
default: 'Default title'
}
},
methods:{
handleClick(val) {
this.$emit('change', val)
}
}
}
- 除了使用上面方法對某一個prop進(jìn)行 ‘雙向綁定’ 還可以通過這種方式
v-bind.sync - vue3中v-bind 的 .sync 修飾符已移除。
:title.sync就要替換為v-model:title
<child :title="pageTitle" @update:title="pageTitle = $event" />
<!--等同于-->
<child :title.sync="pageTitle" />
this.$emit('update:title', newValue)
- vue3默認(rèn)prop與event為
modelValue和update:modelValue。如要修改,直接通過v-model后面參數(shù)v-model:title來指定屬性名,并且支持綁定多個v-model。
<child v-model="pageTitle" />
<!--等同于-->
<child :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />
- 如果需要修改model的名稱,我們可以為v-model傳遞一個參數(shù),作為子組件內(nèi)model選項(xiàng)的代替
<child v-model:title="pageTitle" />
<!--等同于-->
<child :title="pageTitle" @update:title="pageTitle = $event" />
6) $attrs
- vue2中使用
v-bind='$attrs'進(jìn)行將父組件不被認(rèn)為props特性綁定的屬性傳入子組件(不包含class以及style),配合interitAttrs一起使用,如果為true則將所有attribute添加到子組件的跟標(biāo)元素上。但如果為false時,因?yàn)閏lass以及style不屬于$attrs,所以仍會添加到組件的跟元素上 - vue3中$attrs包含所有傳遞給子組件的attribute,包含class以及style
7) emits選項(xiàng)
- vue2中子組件觸發(fā)父親組件的方法
this.$emit(方法名) - vue3提供了一個類似props的emits選項(xiàng),emits選項(xiàng)可以配置校驗(yàn)emit事件,為null的時候不校驗(yàn)。校驗(yàn)時會把參數(shù)攜帶過去,當(dāng)校驗(yàn)不通過,控制臺會發(fā)出一個警告,但emit事件還會繼續(xù)執(zhí)行
- 官方建議我們在組件中所有的emit事件都能在組件的emits選項(xiàng)中聲明
<template>
<one @open="open" />
</template>
<script>
import one from "./one.vue";
export default {
components: {
one,
},
setup() {
open = () => {
console.log(1);
};
return {
open,
};
},
};
</script>
<!--子組件-->
<template>
<div @click="open">點(diǎn)擊</div>
</template>
<script>
export default {
emits: {
//open: null,
open: (value) => {
if (typeof value === "string") {
return true;
} else {
return false;
}
},
},
setup(props, { emit }) {
open = () => {
emit("open", 11);
};
return {
open,
};
},
};
</script>
7) 事件 API(eventBus)
- 移除
$on,$off和$once實(shí)例方法,組件實(shí)例不再實(shí)現(xiàn)事件觸發(fā)接口。
8) 函數(shù)式組件
- 函數(shù)式組件有兩個特性1、Stateless無狀態(tài):組件自身沒有狀態(tài) 2.Instanceless無實(shí)例:組件自身沒有實(shí)例,也就是沒有this
- vue2中,函數(shù)式組件通常用為性能優(yōu)化,它的初始化速度比有狀態(tài)組件快的多
Vue.component('typeButton',typeButton)
const typeButton = {
functional:true,//標(biāo)記,無狀態(tài)無實(shí)例
render(h , { props }){
const { type } = props
return <div class={ type }>{type}</div>
}
}
- vue3中不需要
functional定義 接收兩個參數(shù),props和context。context包含組件的attrs、slots和emitproperty
import { h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
9) 異步組件 defineAsyncComponent
const asyncModal = () => import('./Modal.vue')
//帶有選項(xiàng)
const asyncModal = {
component: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}
- vue3中函數(shù)式組件被定義為純函數(shù),因此異步組件需要包裹在
defineAsyncComponent中
import { defineAsyncComponent } from 'vue'
const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
//帶有選項(xiàng)
const asyncModalWithOptions = defineAsyncComponent({
loader: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
10) data選項(xiàng)
- vue2中,可以通過Object或者function定義data選項(xiàng)
<!-- Object -->
<script>
const app = new Vue({
data: {
num: 1
}
})
</script>
<!-- Function -->
<script>
const app = new Vue({
data() {
return {
num: 2
}
}
})
</script>
- vue3中,data只接受返回object的function
<script>
import { createApp } from 'vue'
createApp({
data() {
return {
num: 1
}
}
}).mount('#app')
</script>
- 另外于mixin合并操作將被淺層次執(zhí)行(data和mixin中聲明同樣的對象,vue2會把對象中的合并,vue3將只取mixin中)
三、 新組件
1. setup語法糖
- setup這個option,暴露變量必須都return出來,模板中才能使用。vue3也提供了相關(guān)語法糖,不用export ,也不需要寫setup函數(shù)
- 需要將 setup attribute 添加到
<script>代碼塊上.變量跟函數(shù)都不需要return,即可在模板中使用 - 引入組件可以直接使用,不需要通過components進(jìn)行注冊
<template>
<div>{{ obj.name }}</div>
<div>{{ obj.age }}</div>
</template>
<script setup>
import { ref } from "vue";
const obj = ref({ name: "張三", age: 25, c: { d: 2 } });
</script>
2. style
3. Fragment(片斷)
- 在vue2中組件必須要有一個根標(biāo)簽
- vue3組件里可以沒有根標(biāo)簽,vue3內(nèi)部會把多個標(biāo)簽放在一個Fragment虛擬元素中
4. Teleport(瞬移)
-
<teleport>用于移動dom到指定元素 - 還可配置
disabled屬性,禁用teleport的功能,不會移動到任何位置,但他仍會出現(xiàn)在正常位置下(里面的所有元素依然保持正常狀態(tài))
<teleport to="#some-id" />
<teleport to=".some-class" />
<teleport to="[data-teleport]" />
<teleport to="body" disabled=‘true’>
<div></div>
</teleport>
5. Suspense -- 實(shí)驗(yàn)
- 組件渲染被渲染之前,可能存在異步操作,
<suspense>幫助我們創(chuàng)建一個平滑的用戶體驗(yàn) -
<suspense>有兩個插槽default、fallback。如果default里的標(biāo)簽(任意深度,所有的后代組件)顯示不出來,則展示fallback - fallback被觸發(fā)的機(jī)制還有上面說的setup是一個async函數(shù)。這樣return的則是promise
<template>
<suspense>
<template #default>
<todo-list />
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</suspense>
</template>
<script>
export default {
components: {
TodoList: defineAsyncComponent(() => import('./TodoList.vue'))
}
}
</script>
四、 廢除屬性
1. $children
- 在vue2中,可以使用
this.$children訪問當(dāng)前實(shí)例的子組件。vue3中已移除,推薦使用$refs
2. 過濾器filter
- 在vue2中,可以使用過濾器來處理數(shù)據(jù)格式。vue3中已移除,推薦使用方法調(diào)用或計(jì)算屬性
3. $listeners
- vue2可以使用
v-bind='$attrs'訪問父組件不被認(rèn)為的props特性綁定的屬性傳入子組件,還可以通過在中間組件添加v-on=‘$listeners’使其子組件可以通過emits觸發(fā)其父組件的方法 - vue3將移除這一方法,因?yàn)楝F(xiàn)在事件監(jiān)聽器是
$attrs的一部分(相當(dāng)于父組件的@init=‘init’。不用在中間組件添加v-on=‘$listeners’,只需要v-bind='$attrs',由$attrs負(fù)責(zé)傳遞方法。即可在其子組件訪問)
4. inline-template
- 當(dāng) inline-template 出現(xiàn)在一個子組件上時,這個組件里面的內(nèi)容作為模版,而不是將其作為被分發(fā)的內(nèi)容
- vue3將移除這一屬性。可以通過默認(rèn)插槽進(jìn)行實(shí)現(xiàn)
5. propsData
- vue2中使用propsData用于在創(chuàng)建Vue實(shí)例的過程中傳入prop
- vue3移除了這一屬性。如果需要傳遞,則需要createApp的第二個參數(shù)
const Comp = Vue.extend({
props: ['username'],
template: '<div>{{ username }}</div>'
})
new Comp({
propsData: {
username: 'Evan'
}
})
//vue3中需要這樣
const app = createApp(
{
props: ['username'],
template: '<div>{{ username }}</div>'
},
{ username: 'Evan' }
)