一文學(xué)會使用Vue3
本文適合Vue
初學(xué)者,或者Vue2
遷移者,當(dāng)然還是建議Vue3
官網(wǎng)完全過一遍。不適合精通原理,源碼的大佬們。
先推薦兩個vscode插件
Volar
首先推薦Volar
,使用vscode
開發(fā)Vue
項(xiàng)目的小伙伴肯定都認(rèn)識Vetur
這個神級插件,有了它可以讓我們得開發(fā)如魚得水。 那么Volar
可以理解為Vue3
版本的Vetur
,代碼高亮,語法提示,基本上Vetur
有的它都有。
特色功能
當(dāng)然作為新的插件出山,肯定有它獨(dú)有的功能。
多個根節(jié)點(diǎn)編輯器不會報(bào)錯
Vue3
是允許我們有多個根節(jié)點(diǎn)的,但是我們?nèi)绻褂肰etur就會報(bào)錯,不會影響運(yùn)行,但是看起來就很煩。 所以當(dāng)我們轉(zhuǎn)向Volar
那么就不會出現(xiàn)這個問題了。
編輯器分隔
即便Vue
的組件化開發(fā),可以將單文件的代碼長度大幅縮短,但還是動輒幾百行甚是上千行。那么我們切換template
,script
和style
的時(shí)候就要頻繁上下翻,雖然有的插件可以直接定位到css
,但是你回不去啊!所以這個功能簡直是太人性化了。
安裝完Volar
以后,打開一個.vue
文件,看vscode
的右上角,有這么一個圖標(biāo),點(diǎn)一下。
它就會自動給你分隔成三個頁面,分別對應(yīng)template
,script
和style
,這樣就太舒服了有沒有。
Vue 3 Snippets
推薦的第二個插件叫做Vue 3 Snippets
,同樣的,他也有自己的Vue2
版本。它是干什么的呢,可以看一下下面這張圖,我只輸入了“v3”
,它有很多提示,我們就先選擇v3computed
,選中回車即可。
然后它就給自動給我們寫了如下代碼
是不是超級省事,摸魚的時(shí)間又增加了!還有更多有趣的使用方式,小伙伴們自行探索吧。
創(chuàng)建Vue3項(xiàng)目
那么正式開始學(xué)習(xí)我們的Vue3
,先從創(chuàng)建項(xiàng)目開始。
使用 vue-cli 創(chuàng)建
輸入下面的命令然后選擇配置項(xiàng)進(jìn)行安裝即可,這里注意vue-cli
的版本一定要在4.5.0
以上
// 安裝或者升級
npm install -g @vue/cli
//查看版本 保證 vue cli 版本在 4.5.0 以上
vue --version
// 創(chuàng)建項(xiàng)目
vue create my-project
//然后根據(jù)提示一步一步傻瓜式操作就行了
...
使用 Vite 創(chuàng)建
都說Vue3.0
和Vite2
更配,各種優(yōu)化,各種快,但都不屬于本文的內(nèi)容,本文的目的我們只需要知道它特別好用,怎么用就行了。我這里是多選擇了TS
,每行都有注釋,一目了然。
// 初始化viete項(xiàng)目
npm init vite-app <project-name>
// 進(jìn)入項(xiàng)目文件夾
cd <project-name>
// 安裝依賴
npm install
//啟動項(xiàng)目
npm run dev
創(chuàng)建完以后我們先來看看入口文件main.ts
// 引入createApp函數(shù),創(chuàng)建對應(yīng)的應(yīng)用,產(chǎn)生應(yīng)用的實(shí)例對象
import { createApp } from 'vue';
// 引入app組件(所有組件的父級組件)
import App from './App.vue';
// 創(chuàng)建app應(yīng)用返回對應(yīng)的實(shí)例對象,調(diào)用mount方法進(jìn)行掛載 掛載到#app節(jié)點(diǎn)上去
createApp(App).mount('#app');
然后看看根組件app.vue
//Vue2組件中的html模板中必須要有一對根標(biāo)簽,Vue3組件的html模板中可以沒有根標(biāo)簽
<template>
<img alt="Vue logo" src="./assets/logo.png">
<!-- 使用子級組件 -->
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</template>
<script lang="ts"> // 這里可以書寫TS代碼
// defineComponent函數(shù),目的是定義一個組件 內(nèi)部可以傳入一個配置對象
import { defineComponent } from 'vue';
//引入子級組件
import HelloWorld from './components/HelloWorld.vue';
// 暴露出去一個定義好的組件
export default defineComponent({
// 當(dāng)前組件的名字
name: 'App',
// 注冊組件
components: {
// 注冊一個子級組件
HelloWorld,
},
}); </script>
Composition API
接下來到了重頭戲,Vue3
的招牌特性,Composition API
Composition API
可以更方便的抽取共通邏輯,但是不要過于在意邏輯代碼復(fù)用,以功能提取代碼也是一種思路。
順便提一句,Vue3
兼容大部分Vue2
語法,所以在Vue3
中書寫Vue2
語法是沒有問題的(廢除的除外),但是既然我們已經(jīng)升級Vue3
了,不建議混合使用,除非一些大型特殊項(xiàng)目需要兼容兩個版本。
setup
setup
是組合Composition API
中的入口函數(shù),也是第一個要使用的函數(shù)。
setup
只在初始化時(shí)執(zhí)行一次,所有的Composition API
函數(shù)都在此使用。
setup() {
console.log('我執(zhí)行了') //我執(zhí)行了
},
可以通過console.log
看到setup
是在beforeCreate
生命周期之前執(zhí)行的(只執(zhí)行一次)
beforeCreate() {
console.log('beforeCreate執(zhí)行了');
},
setup() {
console.log('setup執(zhí)行了');
return {};
},
//setup執(zhí)行了
//beforeCreate執(zhí)行了
由此可以推斷出setup
執(zhí)行的時(shí)候,組件對象還沒有創(chuàng)建,組件實(shí)例對象this
還不可用,此時(shí)this
是undefined
, 不能通過this
來訪問data/computed/methods/props
。
返回對象中的屬性會與data
函數(shù)返回對象的屬性合并成為組件對象的屬性,返回對象中的方法會與methods
中的方法合并成功組件對象的方法,如果有重名, setup
優(yōu)先。因?yàn)樵?code>setup中this
不可用,methods
中可以訪問setup
提供的屬性和方法, 但在setup
方法中不能訪問data
和methods
里的內(nèi)容,所以還是不建議混合使用。
setup
函數(shù)如果返回對象, 對象中的 屬性 或 方法 , 模板 中可以直接使用
//templete
<div>{{number}}</div>
//JS
setup() {
const number = 18;
return {
number,
};
},
注意:setup
不能是一個async
函數(shù): 因?yàn)榉祷刂挡辉偈?code>return的對象, 而是promise
, 模板中就不可以使用return
中返回對象的數(shù)據(jù)了。
setup的參數(shù)(props,context)`
props: 是一個對象,里面有父級組件向子級組件傳遞的數(shù)據(jù),并且是在子級組件中使用props
接收到的所有的屬性
context:上下文對象,可以通過es6
語法解構(gòu) setup(props, {attrs, slots, emit})
-
attrs: 獲取當(dāng)前組件標(biāo)簽上所有沒有通過
props
接收的屬性的對象, 相當(dāng)于this.$attrs
-
slots: 包含所有傳入的插槽內(nèi)容的對象, 相當(dāng)于
this.$slots
-
emit: 用來分發(fā)自定義事件的函數(shù), 相當(dāng)于
this.$emit
演示attrs
和props
//父組件
<template>
<child :msg="msg" msg2='哈哈哈' />
</template>
<script lang='ts'> import { defineComponent, ref } from 'vue';
// 引入子組件
import Child from './components/Child.vue';
export default defineComponent({
name: 'App',
components: {
Child,
},
setup() {
const msg = ref('hello,vue3');
return {
msg,
};
},
}); </script>
//子組件
<template>
<h2>子組件</h2>
<h3>msg:{{ msg }}</h3>
</template>
<script lang='ts'> import { defineComponent } from 'vue';
export default defineComponent({
name: 'Child',
props: ['msg'],
setup(props, {attrs, slots, emit}) {
console.log('props:', props);//msg: "hello,vue3"
console.log('attrs:', attrs);//msg2: "哈哈哈"
return {};
},
}); </script>
演示emit
//父組件
<template>
<child @show="show" />
</template>
<script lang='ts'> setup() {
const show = () => {
console.log('name:', 'hzw');
};
return {
show,
};
}, </script>
//子組件
<template>
<button>事件分發(fā)</button>
</template>
<script lang='ts'> import { defineComponent } from 'vue';
export default defineComponent({
name: 'Child',
setup(props, { emit }) {
const emitFn = () => {
emit('show');
};
return {
emitFn,
};
},
}); </script>
ref
作用
定義一個響應(yīng)式的數(shù)據(jù)(一般用來定義一個基本類型的響應(yīng)式數(shù)據(jù)Undefined
、Null
、Boolean
、Number
和String
)
語法
const xxx = ref(initValue):
注意:script
中操作數(shù)據(jù)需要使用xxx.value
的形式,而模板中不需要添加.value
用一個例子來演示:實(shí)現(xiàn)一個按鈕,點(diǎn)擊可以增加數(shù)字
<template>
<div>{{count}}</div>
<button @click='updateCount'>增加</button>
</template>
在Vue2中
data() {
return {
conunt: 0,
};
},
methods: {
updateCount() {
this.conunt++;
},
},
在Vue3中
setup() {
// ref用于定義一個響應(yīng)式的數(shù)據(jù),返回的是一個Ref對象,對象中有一個value屬性
//如果需要對數(shù)據(jù)進(jìn)行操作,需要使用該Ref對象的value屬性
const count = ref(0);
function updateCount() {
count.value++;
}
return {
count,
updateCount,
};
},
在Vue2
中我們通過this.$refs
來獲取dom
節(jié)點(diǎn),Vue3
中我們通過ref
來獲取節(jié)點(diǎn)
首先需要在標(biāo)簽上添加ref='xxx'
,然后再setup
中定義一個初始值為null
的ref
類型,名字要和標(biāo)簽的ref
屬性一致
const xxx = ref(null)
注意:一定要在setup
的return
中返回,不然會報(bào)錯。
還是用一個例子來演示:讓輸入框自動獲取焦點(diǎn)
<template>
<h2>App</h2>
<input type="text">---
<input type="text" ref="inputRef">
</template>
<script lang="ts">
import { onMounted, ref } from 'vue'
/*
ref獲取元素: 利用ref函數(shù)獲取組件中的標(biāo)簽元素
功能需求: 讓輸入框自動獲取焦點(diǎn)
*/
export default {
setup() {
const inputRef = ref<HTMLElement|null>(null)
onMounted(() => {
inputRef.value && inputRef.value.focus()
})
return {
inputRef
}
},
}
</script>
reactive
語法
const proxy = reactive(obj)
作用
定義多個數(shù)據(jù)的響應(yīng)式,接收一個普通對象然后返回該普通對象的響應(yīng)式代理器對象(Proxy)
,響應(yīng)式轉(zhuǎn)換是“深層的”:會影響對象內(nèi)部所有嵌套的屬性,所有的數(shù)據(jù)都是響應(yīng)式的。
代碼演示
<template>
<h3>姓名:{{user.name}}</h3>
<h3>年齡:{{user.age}}</h3>
<h3>wife:{{user.wife}}</h3>
<button @click="updateUser">更新</button>
</template>
setup() {
const user = reactive({
name: 'hzw',
age: 18,
wife: {
name: 'xioaohong',
age: 18,
books: ['紅寶書', '設(shè)計(jì)模式', '算法與數(shù)據(jù)結(jié)構(gòu)'],
},
});
const updateUser = () => {
user.name = '小紅';
user.age += 2;
user.wife.books[0] = '西游記';
};
return {
user,
updateUser,
};
},
computed函數(shù):
與Vue2
中的computed
配置功能一致,返回的是一個ref
類型的對象
計(jì)算屬性的函數(shù)中如果只傳入一個回調(diào)函數(shù) 表示的是get
操作
import { computed } from 'vue';
const user = reactive({
firstName: '韓',
lastName: '志偉',
});
const fullName1 = computed(() => {
return user.firstName + user.lastName;
});
return {
user,
fullName1,
};
計(jì)算屬性的函數(shù)中可以傳入一個對象,可以包含set
和get
函數(shù),進(jìn)行讀取和修改的操作
const fullName2 = computed({
get() {
return user.firstName + '_' + user.lastName;
},
set(val: string) {
const names = val.split('_');
user.firstName = names[0];
user.lastName = names[1];
},
});
return {
user,
fullName2,
};
watch函數(shù):
與Vue2
中的watch
配置功能一致,
- 參數(shù)1:要監(jiān)聽的數(shù)據(jù)
- 參數(shù)2:回調(diào)函數(shù)
- 參數(shù)3:配置
作用
監(jiān)視指定的一個或多個響應(yīng)式數(shù)據(jù), 一旦數(shù)據(jù)變化, 就自動執(zhí)行監(jiān)視回調(diào)
默認(rèn)初始時(shí)不執(zhí)行回調(diào), 但可以通過配置immediate
為true
, 來指定初始時(shí)立即執(zhí)行第一次
通過配置deep
為true
, 來指定深度監(jiān)視
import { watch, ref } from 'vue';
const user = reactive({
firstName: '韓',
lastName: '志偉',
});
const fullName3 = ref('');
watch(
user,
({ firstName, lastName }) => {
fullName3.value = firstName + '_' + lastName;
},
{ immediate: true, deep: true }
);
return {
user,
fullName3,
};
watch
監(jiān)聽多個數(shù)據(jù),使用數(shù)組
watch
監(jiān)聽非響應(yīng)式數(shù)據(jù)的時(shí)候需要使用回調(diào)函數(shù)的形式
watch([()=>user.firstName,()=>user.lastName,fullName3],()=>{console.log('我執(zhí)行了')})
watchEffect函數(shù):
作用
監(jiān)視數(shù)據(jù)發(fā)生變化時(shí)執(zhí)行回調(diào),不用直接指定要監(jiān)視的數(shù)據(jù), 回調(diào)函數(shù)中使用的哪些響應(yīng)式數(shù)據(jù)就監(jiān)視哪些響應(yīng)式數(shù)據(jù),默認(rèn)初始時(shí)就會執(zhí)行第一次, 從而可以收集需要監(jiān)視的數(shù)據(jù)。
import { watchEffect, ref } from 'vue';
const user = reactive({
firstName: '韓',
lastName: '志偉',
});
const fullName4 = ref('');
watchEffect(() => {
fullName4.value = user.firstName + '_' + user.lastName;
});
return {
user,
fullName4,
};
watchEffect可以實(shí)現(xiàn)計(jì)算屬性set方法
watchEffect(() => {
const names = fullName3.value.split('_');
user.firstName = names[0];
user.lastName = names[1];
});
生命周期對比:
注意:3.0
中的生命周期鉤子要比2.X
中相同生命周期的鉤子要快
Composition API
還新增了以下調(diào)試鉤子函數(shù):但是不怎么常用
- onRenderTracked
- onRenderTriggered
代碼演示
setup() {
onBeforeMount(() => {
console.log('--onBeforeMount')
})
onMounted(() => {
console.log('--onMounted')
})
onBeforeUpdate(() => {
console.log('--onBeforeUpdate')
})
onUpdated(() => {
console.log('--onUpdated')
})
onBeforeUnmount(() => {
console.log('--onBeforeUnmount')
})
onUnmounted(() => {
console.log('--onUnmounted')
})
}
toRefs
作用
把一個響應(yīng)式對象轉(zhuǎn)換成普通對象,該普通對象的每個屬性都是一個 ref
應(yīng)用
我們使用reactive
創(chuàng)建的對象,如果想在模板中使用,就必須得使用xxx.xxx
的形式,如果大量用到的話還是很麻煩的,但是使用es6
解構(gòu)以后,會失去響應(yīng)式,那么toRefs
的作用就體現(xiàn)在這,,利用toRefs
可以將一個響應(yīng)式 reactive
對象的所有原始屬性轉(zhuǎn)換為響應(yīng)式的ref
屬性。當(dāng)然小伙伴們可以自行開發(fā)更多應(yīng)用場景。
代碼演示
<template>
<div>
name:{{name}}
</div>
</template>
<script lang='ts'> import { defineComponent, reactive, toRefs } from 'vue';
export default defineComponent({
name: '',
setup() {
const state = reactive({
name: 'hzw',
});
const state2 = toRefs(state);
setInterval(() => {
state.name += '===';
}, 1000);
return {
//通過toRefs返回的對象,解構(gòu)出來的屬性也是響應(yīng)式的
...state2,
};
},
}); </script>
provide 與 inject
作用
實(shí)現(xiàn)跨層級組件(祖孫)間通信
代碼演示
父組件
<template>
<h1>父組件</h1>
<p>當(dāng)前顏色: {{color}}</p>
<button @click="color='red'">紅</button>
<button @click="color='yellow'">黃</button>
<button @click="color='blue'">藍(lán)</button>
<hr>
<Son />
</template>
<script lang="ts"> import { provide, ref } from 'vue'
import Son from './Son.vue'
export default {
name: 'ProvideInject',
components: {
Son
},
setup() {
const color = ref('red')
provide('color', color)
return {
color
}
}
} </script>
子組件
<template>
<div>
<h2>子組件</h2>
<hr>
<GrandSon />
</div>
</template>
<script lang="ts"> import GrandSon from './GrandSon.vue'
export default {
components: {
GrandSon
},
} </script>
孫子組件
<template>
<h3 :style="{color}">孫子組件: {{color}}</h3>
</template>
<script lang="ts"> import { inject } from 'vue'
export default {
setup() {
const color = inject('color')
return {
color
}
}
} </script>
其他特性
Teleport(瞬移)
作用
Teleport
提供了一種干凈的方法, 讓組件的html
在父組件界面外的特定標(biāo)簽(很可能是body
)下插入顯示 換句話說就是可以把 子組件 或者 dom節(jié)點(diǎn) 插入到任何你想插入到的地方去。
語法
使用to
屬性 引號內(nèi)使用選擇器
<teleport to="body">
</teleport>
代碼演示
//父組件
<template>
<div class="father">
<h2>App</h2>
<modal-button></modal-button>
</div>
</template>
<script lang="ts"> import ModalButton from './components/ModalButton.vue'
export default {
setup() {
return {}
},
components: {
ModalButton,
},
} </script>
//子組件
<template>
<div class="son">
<button @click="modalOpen = true">
點(diǎn)我打開對話框
</button>
<teleport to="body">
<div v-if="modalOpen"
class="looklook">
看看我出現(xiàn)在了哪里
<button @click="modalOpen = false">
Close
</button>
</div>
</teleport>
</div>
</template>
<script> import { ref } from 'vue'
export default {
name: 'modal-button',
setup() {
const modalOpen = ref(false)
return {
modalOpen,
}
},
} </script>
可以看到在子組件中的looklook
元素跑到了body
下面,而之前的位置默認(rèn)出現(xiàn)了兩行注釋
Suspense(不確定的)
作用
它們允許我們的應(yīng)用程序在等待異步組件時(shí)渲染一些后備內(nèi)容,可以讓我們創(chuàng)建一個平滑的用戶體驗(yàn)
語法
<Suspense>
<template v-slot:default>
<!-- 異步組件 -->
<AsyncComp />
</template>
<template v-slot:fallback>
<!-- 后備內(nèi)容 -->
<h1>LOADING...</h1>
</template>
</Suspense>
vue3
中引入異步組件的方式
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
代碼演示
父組件
<template>
<Suspense>
<!-- v-slot:defaul可以簡寫成#defaul -->
<template v-slot:default>
<AsyncComp/>
</template>
<template v-slot:fallback>
<h1>LOADING...</h1>
</template>
</Suspense>
</template>
<script lang="ts"> import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
setup() {
return {
}
},
components: {
AsyncComp,
}
} </script>
子組件
<template>
<h2>AsyncComp22</h2>
<p>{{msg}}</p>
</template>
<script lang="ts"> export default {
name: 'AsyncComp',
setup () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
msg: 'abc'
})
}, 2000)
})
}
} </script>
通過下圖可以看到在異步組件加載出來之前,顯示的是fallback
中的內(nèi)容
響應(yīng)式數(shù)據(jù)的判斷
作用
-
isRef: 檢查一個值是否為一個
ref
對象 -
isReactive: 檢查一個對象是否是由
reactive
創(chuàng)建的響應(yīng)式代理 -
isReadonly: 檢查一個對象是否是由
readonly
創(chuàng)建的只讀代理 -
isProxy: 檢查一個對象是否是由
reactive
或者readonly
方法創(chuàng)建的代理
代碼演示
setup() {
const state1 = ref(1);
console.log('isref:', isRef(state1));//isref: true
const state2 = reactive({});
console.log('isReactive:', isReactive(state2));//isReactive: true
const state3 = readonly({});
console.log('isReadonly:', isReadonly(state3));//isReadonly: true
const state4 = reactive({});
console.log('isProxy:', isProxy(state2));//isProxy: true
console.log('isProxy:', isProxy(state4));//isProxy: true
return {};
},
其他不常用特性
還有很多很多不常用的新特性,我在日常開發(fā)中是沒有用到的,很多都是用來做優(yōu)化的,感興趣的小伙伴們自行去官網(wǎng)查看,或者大佬們可以介紹一下應(yīng)用場景。
- shallowReactive
- shallowRef
- readonly
- shallowReadonly
- markRaw
- customRef
- ...
語法糖
雖然Composition API
用起來已經(jīng)非常方便了,但是我們還是有很煩的地方,比如
- 組件引入了還要注冊
- 屬性和方法都要在
setup
函數(shù)中返回,有的時(shí)候僅一個return
就十幾行甚至幾十行 - ...
- 不想寫啊怎么辦
好辦,Vue3
官方提供了script setup
語法糖
只需要在script
標(biāo)簽中添加setup
,組件只需引入不用注冊,屬性和方法也不用返回,setup
函數(shù)也不需要,甚至export default
都不用寫了,不僅是數(shù)據(jù),計(jì)算屬性和方法,甚至是自定義指令也可以在我們的template
中自動獲得。
但是這么過癮的語法糖,還是稍微添加了一點(diǎn)點(diǎn)心智負(fù)擔(dān),因?yàn)闆]有了setup
函數(shù),那么props
,emit
,attrs
怎么獲取呢,就要介紹一下新的語法了。
setup script
語法糖提供了三個新的API
來供我們使用:defineProps
、defineEmit
和useContext
-
defineProps 用來接收父組件傳來的值
props
。 - defineEmit 用來聲明觸發(fā)的事件表。
-
useContext 用來獲取組件上下文
context
。
代碼演示
父組件
<template>
<div>
<h2>我是父組件!</h2>
<Child msg="hello"
@child-click="handleClick" />
</div>
</template>
<script setup> import Child from './components/Child.vue'
const handleClick = (ctx) => {
console.log(ctx)
} </script>
子組件
<template>
<span @click="sonClick">msg: {{ props.msg }}</span>
</template>
<script setup> import { useContext, defineProps, defineEmit } from 'vue'
const emit = defineEmit(['child-click'])
const ctx = useContext()
const props = defineProps({
msg: String,
})
const sonClick = () => {
emit('child-click', ctx)
} </script>
我們點(diǎn)擊一下子組件
可以看到context
被打印了出來,其中的attrs
、emit
、slots
、expose
屬性和方法依然可以使用。 props
也可以輸出在頁面上,事件也成功派發(fā)。
其他知識點(diǎn)
接下來介紹一下我使用Vue3
過程中遇到的問題或者小技巧,不全面,想起什么就寫什么吧
script setup語法糖請注意
如果在父組件中通過ref='xxx'
的方法來獲取子組件實(shí)例,子組件使用了script setup
語法糖,那么子組件的數(shù)據(jù)需要用expose的方式導(dǎo)出,否則會因?yàn)楂@取不到數(shù)據(jù)而報(bào)錯。
代碼演示
父組件
<template>
<div>
<h2>我是父組件!</h2>
<Child ref='son' />
</div>
</template>
<script setup> import Child from './components/Child.vue'
import { ref } from 'vue'
const son = ref(null)
console.log('????~ son:', son) </script>
子組件先不使用語法糖
<template>
<div>
我是子組件{{msg}}
</div>
</template>
<script > import { ref } from 'vue'
export default {
setup() {
const msg = ref('hello')
return {
msg,
}
},
}
可以看到是可以獲取到我們在子組件中定義的msg
屬性的
現(xiàn)在把子組件換成script setup
語法糖再來試一試
<template>
<div>
我是子組件{{msg}}
</div>
</template>
<script setup> import { ref } from 'vue'
const msg = ref('hello') </script>
可以看到現(xiàn)在是獲取不到子組件定義的msg
屬性的
我們可以看看尤大大怎么說
Emit派發(fā)事件可以對參數(shù)進(jìn)行驗(yàn)證
父組件
<template>
<div>
<h2>我是父組件!</h2>
<Child @sonClick='sonClick' />
</div>
</template>
<script setup> import Child from './components/Child.vue'
import { ref } from 'vue'
const sonClick = (value) => {
console.log(value)
} </script>
子組件
<template>
<div>
我是子組件{{ msg }}
</div>
<button @click="handleClick(1)">我是按鈕1</button>
<button @click="handleClick(2)">我是按鈕2</button>
</template>
<script> import { ref } from 'vue'
export default {
name: '',
emits: {
sonClick: (value) => {
if (value === 1) {
return true
} else {
return false
}
},
},
setup(props, { emit }) {
const msg = ref('hello')
const handleClick = (value) => {
emit('sonClick', value)
}
return {
msg,
handleClick,
}
},
} </script>
我們分別點(diǎn)一下按鈕1和按鈕2,可以看到當(dāng)我們點(diǎn)了按鈕2的時(shí)候,控制臺會發(fā)出警告,但是程序會繼續(xù)執(zhí)行,還沒想到什么適合的應(yīng)用場景,但是要知道這個知識點(diǎn),小伙伴們可以在這搞事情。
跨組件通訊mitt.js
Vue2
中怎么實(shí)現(xiàn)跨組件通訊呢,很多人第一想法就是event bus
。但是Vue3
移除了$on
,$once
,$off
導(dǎo)致不能使用這個方法。但是Vue
官方給大家推薦了mitt.js
,它的原理就是event bus
。
代碼演示
先安裝
npm i mitt -s
然后封裝一個hook
//mitt.js
import mitt from 'mitt'
const emitter = mitt();
export default emitter;
父組件
<template>
<div>
<h2>我是父組件!</h2>
<Child1 />
<Child2 />
</div>
</template>
<script setup> import Child1 from './components/Child1.vue'
import Child2 from './components/Child2.vue' </script>
子組件1
<template>
<div>
我是子組件1
<h1>{{msg}}</h1>
</div>
</template>
<script> import { ref, onUnmounted } from 'vue'
import emitter from '../mitt'
export default {
name: '',
setup() {
//初始化
const msg = ref('hello')
const changeMsg = () => {
msg.value = 'world'
}
// 監(jiān)聽事件,更新數(shù)據(jù)
emitter.on('change-msg', changeMsg)
// 顯式卸載
onUnmounted(() => {
emitter.off('change-msg', changeMsg)
})
return {
msg,
changeMsg,
}
},
} </script>
子組件2
<template>
<div>
我是子組件2
</div>
<button @click='changeMsg'>點(diǎn)擊修改msg</button>
</template>
<script> import { ref } from 'vue'
import emitter from '../mitt'
export default {
name: '',
setup() {
const changeMsg = () => {
emitter.emit('change-msg')
}
return {
changeMsg,
}
},
} </script>
演示
自定義指令
先看看Vue2
自定義指令的鉤子
- bind:當(dāng)指令綁定在對應(yīng)元素時(shí)觸發(fā)。只會觸發(fā)一次。
-
inserted:當(dāng)對應(yīng)元素被插入到
DOM
的父元素時(shí)觸發(fā)。 - update:當(dāng)元素更新時(shí),這個鉤子會被觸發(fā)(此時(shí)元素的后代元素還沒有觸發(fā)更新)。
- componentUpdated:當(dāng)整個組件(包括子組件)完成更新后,這個鉤子觸發(fā)。
- unbind:當(dāng)指令被從元素上移除時(shí),這個鉤子會被觸發(fā)。也只觸發(fā)一次。
在 Vue3
中,官方為了更有助于代碼的可讀性和風(fēng)格統(tǒng)一,把自定義指令的鉤子名稱改的更像是組件生命周期,盡管他們是兩回事
- bind => beforeMount
- inserted => mounted
- beforeUpdate: 新的鉤子,會在元素自身更新前觸發(fā)
- update => 移除!
- componentUpdated => updated
- beforeUnmount: 新的鉤子,當(dāng)元素自身被卸載前觸發(fā)
- unbind => unmounted
過渡動畫
這個沒有什么大的改動,只是修改了兩個class
名字,正是因?yàn)闆]有什么大的改動,導(dǎo)致我曾經(jīng)在這里栽了大跟頭,寫完了怎么都不對,后來查官網(wǎng)才知道。
以下是直接引用 官網(wǎng)的原文
- v-enter-from:定義進(jìn)入過渡的開始狀態(tài)。在元素被插入之前生效,在元素被插入之后的下一幀移除。
- v-enter-active:定義進(jìn)入過渡生效時(shí)的狀態(tài)。在整個進(jìn)入過渡的階段中應(yīng)用,在元素被插入之前生效,在過渡/動畫完成之后移除。這個類可以被用來定義進(jìn)入過渡的過程時(shí)間,延遲和曲線函數(shù)。
- v-enter-to:定義進(jìn)入過渡的結(jié)束狀態(tài)。在元素被插入之后下一幀生效 (與此同時(shí) v-enter-from 被移除),在過渡/動畫完成之后移除。
- v-leave-from:定義離開過渡的開始狀態(tài)。在離開過渡被觸發(fā)時(shí)立刻生效,下一幀被移除。
- v-leave-active:定義離開過渡生效時(shí)的狀態(tài)。在整個離開過渡的階段中應(yīng)用,在離開過渡被觸發(fā)時(shí)立刻生效,在過渡/動畫完成之后移除。這個類可以被用來定義離開過渡的過程時(shí)間,延遲和曲線函數(shù)。
- v-leave-to:離開過渡的結(jié)束狀態(tài)。在離開過渡被觸發(fā)之后下一幀生效 (與此同時(shí) v-leave-from 被刪除),在過渡/動畫完成之后移除。
特別注意的是v-enter
改成了v-enter-form
,v-leave
改成了v-leave-from
。
其他小知識
Vue3
移除了filter
獲取組件實(shí)例方法
getCurrentInstance()
這個方法可以獲取到當(dāng)前組件的實(shí)例,相當(dāng)于Vue2
中的this
,但是要注意在 開發(fā)環(huán)境 和 生產(chǎn)環(huán)境 的使用方法是不同的。
畢竟是個人總結(jié),難免會出現(xiàn)紕漏和錯誤,期待各路大神的補(bǔ)充和糾正。