
01-為什么學(xué)vue3
Vue3現(xiàn)狀:
- vue-next (opens new window)2020年09月18日,正式發(fā)布vue3.0版本。但是由于剛發(fā)布周邊生態(tài)不支持,大多數(shù)開發(fā)者處于觀望。
- 現(xiàn)在主流組件庫都已經(jīng)發(fā)布了支持vue3.0的版本,其他生態(tài)也在不斷地完善中,這是趨勢。
- element-plus (opens new window)基于 Vue 3.0 的桌面端組件庫
- vant (opens new window)vant3.0版本,有贊前端團(tuán)隊(duì)開源移動端組件庫
- ant-design-vue (opens new window)Ant Design Vue 2.0版本,社區(qū)根據(jù)螞蟻 ant design 開發(fā)
Vue3優(yōu)點(diǎn):
- 最火框架,它是國內(nèi)最火的前端框架之一,官方文檔 (opens new window)中文文檔(opens new window)
- 性能提升,運(yùn)行速度事vue2.x的1.5倍左右
- 體積更小,按需編譯體積比vue2.x要更小
- 類型推斷,更好的支持Ts(typescript)這個也是趨勢
- 高級給予,暴露了更底層的API和提供更先進(jìn)的內(nèi)置組件
- ★組合API (composition api) ,能夠更好的組織邏輯,封裝邏輯,復(fù)用邏輯
02-選項(xiàng)API和組合API
什么是選項(xiàng)API寫法:Options ApI
咱們在vue2.x項(xiàng)目中使用的就是 選項(xiàng)API 寫法
- 代碼風(fēng)格:data選項(xiàng)寫數(shù)據(jù),methods選項(xiàng)寫函數(shù)...,一個功能邏輯的代碼分散。
- 優(yōu)點(diǎn):易于學(xué)習(xí)和使用,寫代碼的位置已經(jīng)約定好
- 缺點(diǎn):代碼組織性差,相似的邏輯代碼不便于復(fù)用,邏輯復(fù)雜代碼多了不好閱讀。
- 補(bǔ)充:雖然提供mixins用來封裝邏輯,但是出現(xiàn)數(shù)據(jù)函數(shù)覆蓋的概率很大,不好維護(hù)。
<template>
<div class="container">
<div>鼠標(biāo)位置:</div>
<div>X軸:{{x}}</div>
<div>Y軸:{{y}}</div>
<hr>
<div>{{count}} <button @click="add()">自增</button></div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
x: 0,
y: 0,
count: 0
}
},
mounted() {
document.addEventListener('mousemove', this.move)
},
methods: {
move(e) {
this.x = e.pageX
this.y = e.pageY
},
add () {
this.count++
}
},
destroyed() {
document.removeEventListener('mousemove', this.move)
}
}
</script>
什么是組合API寫法:Compositon API
咱們在vue3.0項(xiàng)目中將會使用 組合API 寫法
- 代碼風(fēng)格:一個功能邏輯的代碼組織在一起(包含數(shù)據(jù),函數(shù)...)
- 優(yōu)點(diǎn):功能邏輯復(fù)雜繁多情況下,各個功能邏輯代碼組織再一起,便于閱讀和維護(hù)
- 缺點(diǎn):需要有良好的代碼組織能力和拆分邏輯能力,PS:大家沒問題。
- 補(bǔ)充:為了能讓大家較好的過渡到vue3.0的版本來,也支持vue2.x選項(xiàng)API寫法
<template>
<div class="container">
<div>鼠標(biāo)位置:</div>
<div>X軸:{{x}}</div>
<div>Y軸:{{y}}</div>
<hr>
<div>{{count}} <button @click="add()">自增</button></div>
</div>
</template>
<script>
import { onMounted, onUnmounted, reactive, ref, toRefs } from 'vue'
export default {
name: 'App',
setup () {
// 鼠標(biāo)移動邏輯
const mouse = reactive({
x: 0,
y: 0
})
const move = e => {
mouse.x = e.pageX
mouse.y = e.pageY
}
onMounted(()=>{
document.addEventListener('mousemove',move)
})
onUnmounted(()=>{
document.removeEventListener('mousemove',move)
})
// 累加邏輯
const count = ref(0)
const add = () => {
count.value ++
}
// 返回?cái)?shù)據(jù)
return {
...toRefs(mouse),
count,
add
}
}
}
</script>
03-組合API-setup函數(shù)
setup 是一個新的組件選項(xiàng),作為組件中使用組合API的起點(diǎn)。
從組件生命周期來看,它的執(zhí)行在組件實(shí)例創(chuàng)建之前vue2.x的beforeCreate執(zhí)行。
這就意味著在setup函數(shù)中 this 還不是組件實(shí)例,this 此時是 undefined
在模版中需要使用的數(shù)據(jù)和函數(shù),需要在 setup 返回。
演示代碼:
<template>
<div class="container">
<h1 @click="say()">{{msg}}</h1>
</div>
</template>
<script>
export default {
setup () {
console.log('setup執(zhí)行了')
console.log(this)
// 定義數(shù)據(jù)和函數(shù)
const msg = 'hi vue3'
const say = () => {
console.log(msg)
}
return { msg , say}
},
beforeCreate() {
console.log('beforeCreate執(zhí)行了')
console.log(this)
}
}
</script>
04-組合API-生命周期
回顧vue2.x生命周期鉤子函數(shù):
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
認(rèn)識vue3.0生命周期鉤子函數(shù)
- setup 創(chuàng)建實(shí)例前
- onBeforeMount 掛載DOM前
- onMounted 掛載DOM后
- onBeforeUpdate 更新組件前
- onUpdated 更新組件后
- onBeforeUnmount 卸載銷毀前
- onUnmounted 卸載銷毀后
演示代碼:
<template>
<div class="container">
container
</div>
</template>
<script>
import { onBeforeMount, onMounted } from 'vue'
export default {
setup () {
onBeforeMount(()=>{
console.log('DOM渲染前',document.querySelector('.container'))
})
onMounted(()=>{
console.log('DOM渲染后1',document.querySelector('.container'))
})
onMounted(()=>{
console.log('DOM渲染后2',document.querySelector('.container'))
})
},
}
</script>
05-組合API-reactive函數(shù)
定義響應(yīng)式數(shù)據(jù):
- reactive是一個函數(shù),它可以定義一個復(fù)雜數(shù)據(jù)類型,成為響應(yīng)式數(shù)據(jù)。
演示代碼:
<template>
<div class="container">
<div>{{obj.name}}</div>
<div>{{obj.age}}</div>
<button @click="updateName">修改數(shù)據(jù)</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup () {
// 普通數(shù)據(jù)
// const obj = {
// name: 'ls',
// age: 18
// }
const obj = reactive({
name: 'ls',
age: 18
})
// 修改名字
const updateName = () => {
console.log('updateName')
obj.name = 'zs'
}
return { obj ,updateName}
}
}
</script>
06-組合API-toRef函數(shù)
定義響應(yīng)式數(shù)據(jù):
- toRef是函數(shù),轉(zhuǎn)換響應(yīng)式對象中某個屬性為單獨(dú)響應(yīng)式數(shù)據(jù),并且值是關(guān)聯(lián)的。
演示代碼:
<template>
<div class="container">
{{name}} <button @click="updateName">修改數(shù)據(jù)</button>
</div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
name: 'App',
setup () {
// 1. 響應(yīng)式數(shù)據(jù)對象
const obj = reactive({
name: 'ls',
age: 10
})
console.log(obj)
// 2. 模板中只需要使用name數(shù)據(jù)
// 注意:從響應(yīng)式數(shù)據(jù)對象中解構(gòu)出的屬性數(shù)據(jù),不再是響應(yīng)式數(shù)據(jù)
// let { name } = obj 不能直接解構(gòu),出來的是一個普通數(shù)據(jù)
const name = toRef(obj, 'name')
// console.log(name)
const updateName = () => {
console.log('updateName')
// toRef轉(zhuǎn)換響應(yīng)式數(shù)據(jù)包裝成對象,value存放值的位置
name.value = 'zs'
}
return {name, updateName}
}
}
</script>
<style scoped lang="less"></style>
07-組合API-toRefs函數(shù)
定義響應(yīng)式數(shù)據(jù):
- toRefs是函數(shù),轉(zhuǎn)換響應(yīng)式對象中所有屬性為單獨(dú)響應(yīng)式數(shù)據(jù),對象成為普通對象,并且值是關(guān)聯(lián)的
演示代碼:
<template>
<div class="container">
<div>{{name}}</div>
<div>{{age}}</div>
<button @click="updateName">修改數(shù)據(jù)</button>
</div>
</template>
<script>
import { reactive, toRef, toRefs } from 'vue'
export default {
name: 'App',
setup () {
// 1. 響應(yīng)式數(shù)據(jù)對象
const obj = reactive({
name: 'ls',
age: 10
})
console.log(obj)
// 2. 解構(gòu)或者展開響應(yīng)式數(shù)據(jù)對象
// const {name,age} = obj
// console.log(name,age)
// const obj2 = {...obj}
// console.log(obj2)
// 以上方式導(dǎo)致數(shù)據(jù)就不是響應(yīng)式數(shù)據(jù)了
const obj3 = toRefs(obj)
console.log(obj3)
const updateName = () => {
// obj3.name.value = 'zs'
obj.name = 'zs'
}
return {...obj3, updateName}
}
}
</script>
<style scoped lang="less"></style>
08-組合API-ref函數(shù)
定義響應(yīng)式數(shù)據(jù):
- ref函數(shù),常用于簡單數(shù)據(jù)類型定義為響應(yīng)式數(shù)據(jù)
再修改值,獲取值的時候,需要.value
在模板中使用ref申明的響應(yīng)式數(shù)據(jù),可以省略.value
演示代碼:
<template>
<div class="container">
<div>{{name}}</div>
<div>{{age}}</div>
<button @click="updateName">修改數(shù)據(jù)</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup () {
// 1. name數(shù)據(jù)
const name = ref('ls')
console.log(name)
const updateName = () => {
name.value = 'zs'
}
// 2. age數(shù)據(jù)
const age = ref(10)
// ref常用定義簡單數(shù)據(jù)類型的響應(yīng)式數(shù)據(jù)
// 其實(shí)也可以定義復(fù)雜數(shù)據(jù)類型的響應(yīng)式數(shù)據(jù)
// 對于數(shù)據(jù)未之的情況下 ref 是最適用的
// const data = ref(null)
// setTimeout(()=>{
// data.value = res.data
// },1000)
return {name, age, updateName}
}
}
</script>
09-組合API-computed函數(shù)
定義計(jì)算屬性:
- computed函數(shù),是用來定義計(jì)算屬性的,計(jì)算屬性不能修改。
基本使用:
<template>
<div class="container">
<div>今年:{{age}}歲</div>
<div>后年:{{newAge}}歲</div>
</div>
</template>
<script>
import { computed, ref } from 'vue'
export default {
name: 'App',
setup () {
// 1. 計(jì)算屬性:當(dāng)你需要依賴現(xiàn)有的響應(yīng)式數(shù)據(jù),根據(jù)一定邏輯得到一個新的數(shù)據(jù)。
const age = ref(16)
// 得到后年的年齡
const newAge = computed(()=>{
// 該函數(shù)的返回值就是計(jì)算屬性的值
return age.value + 2
})
return {age, newAge}
}
}
</script>
高級用法:
<template>
<div class="container">
<div>今年:{{age}}歲</div>
<div>后年:{{newAge}}歲</div>
<!-- 使用v-model綁定計(jì)算屬性 -->
<input type="text" v-model="newAge">
</div>
</template>
<script>
import { computed, ref } from 'vue'
export default {
name: 'App',
setup () {
// 1. 計(jì)算屬性:當(dāng)你需要依賴現(xiàn)有的響應(yīng)式數(shù)據(jù),根據(jù)一定邏輯得到一個新的數(shù)據(jù)。
const age = ref(16)
// 得到后年的年齡
// const newAge = computed(()=>{
// // 該函數(shù)的返回值就是計(jì)算屬性的值
// return age.value + 2
// })
// 計(jì)算屬性高級用法,傳人對象
const newAge = computed({
// get函數(shù),獲取計(jì)算屬性的值
get(){
return age.value + 2
},
// set函數(shù),當(dāng)你給計(jì)算屬性設(shè)置值的時候觸發(fā)
set (value) {
age.value = value - 2
}
})
return {age, newAge}
}
}
</script>
目的:讓計(jì)算屬性支持雙向數(shù)據(jù)綁定。
總結(jié):計(jì)算屬性兩種用法
- 給computed傳入函數(shù),返回值就是計(jì)算屬性的值
- 給computed傳入對象,get獲取計(jì)算屬性的值,set監(jiān)聽計(jì)算屬性改變。
10-組合API-watch函數(shù)
定義計(jì)算屬性:
- watch函數(shù),是用來定義偵聽器的
監(jiān)聽ref定義的響應(yīng)式數(shù)據(jù)
監(jiān)聽多個響應(yīng)式數(shù)據(jù)數(shù)據(jù)
監(jiān)聽reactive定義的響應(yīng)式數(shù)據(jù)
監(jiān)聽reactive定義的響應(yīng)式數(shù)據(jù),某一個屬性
深度監(jiān)聽
默認(rèn)執(zhí)行
<template>
<div class="container">
<div>
<p>count的值:{{count}}</p>
<button @click="add">改數(shù)據(jù)</button>
</div>
<hr>
<div>
<p>{{obj.name}}</p>
<p>{{obj.age}}</p>
<p>{{obj.brand.name}}</p>
<button @click="updateName">改名字</button>
<button @click="updateBrandName">改品牌名字</button>
</div>
</div>
</template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
name: 'App',
setup () {
const count = ref(0)
const add = () => {
count.value++
}
// 當(dāng)你需要監(jiān)聽數(shù)據(jù)的變化就可以使用watch
// 1. 監(jiān)聽一個ref數(shù)據(jù)
// 1.1 第一個參數(shù) 需要監(jiān)聽的目標(biāo)
// 1.2 第二個參數(shù) 改變后觸發(fā)的函數(shù)
// watch(count, (newVal,oldVal)=>{
// console.log(newVal,oldVal)
// })
const obj = reactive({
name: 'ls',
age: 10,
brand: {
id: 1,
name: '寶馬'
}
})
const updateName = () => {
obj.name = 'zs'
}
const updateBrandName = () => {
obj.brand.name = '奔馳'
}
// 2. 監(jiān)聽一個reactive數(shù)據(jù)
watch(obj, ()=>{
console.log('數(shù)據(jù)改變了')
})
watch(()=>obj.brand, ()=>{
console.log('brand數(shù)據(jù)改變了')
},{
// 5. 需要深度監(jiān)聽
deep: true,
// 6. 想默認(rèn)觸發(fā)
immediate: true
})
// 3. 監(jiān)聽多個數(shù)據(jù)的變化
// watch([count, obj], ()=>{
// console.log('監(jiān)聽多個數(shù)據(jù)改變了')
// })
// 4. 此時監(jiān)聽對象中某一個屬性的變化 例如:obj.name
// 需要寫成函數(shù)返回該屬性的方式才能監(jiān)聽到
// watch(()=>obj.name,()=>{
// console.log('監(jiān)聽obj.name改變了')
// })
return {count, add, obj, updateName, updateBrandName}
}
}
</script>
11-組合API-ref屬性
獲取DOM或者組件實(shí)例可以使用ref屬性,寫法和vue2.0需要區(qū)分開
獲取單個DOM或者組件
<template>
<div class="container">
<!-- vue2.0 獲取單個元素 -->
<!-- 1. 通過ref屬性綁定該元素 -->
<!-- 2. 通過this.$refs.box獲取元素 -->
<!-- <div ref="box">我是box</div> -->
<!-- vue2.0 獲取v-for遍歷的多個元素 -->
<!-- 1. 通過ref屬性綁定被遍歷元素 -->
<!-- 2. 通過this.$refs.li 獲取所有遍歷元素 -->
<!-- <ul>
<li v-for="i in 4" :key="i" ref="li">{{i}}</li>
</ul> -->
<!-- 單個元素 -->
<div ref="dom">我是box</div>
<!-- 被遍歷的元素 -->
<ul>
<li v-for="i in 4" :key="i" :ref="setDom">第{{i}}LI</li>
</ul>
</div>
</template>
<script>
import { onMounted, ref } from 'vue'
export default {
name: 'App',
setup () {
// 1. 獲取單個元素
// 1.1 先定義一個空的響應(yīng)式數(shù)據(jù)ref定義的
// 1.2 setup中返回該數(shù)據(jù),你想獲取那個dom元素,在該元素上使用ref屬性綁定該數(shù)據(jù)即可。
const dom = ref(null)
onMounted(()=>{
console.log(dom.value)
})
}
}
</script>
<style scoped lang="less"></style>
獲取v-for遍歷的DOM或者組件
// 2. 獲取v-for遍歷的元素
// 2.1 定義一個空數(shù)組,接收所有的LI
// 2.2 定義一個函數(shù),往空數(shù)組push DOM
const domList = []
const setDom = (el) => {
domList.push(el)
}
onMounted(()=>{
console.log(domList)
})
return {dom, setDom}
總結(jié):
- 單個元素:先申明ref響應(yīng)式數(shù)據(jù),返回給模版使用,通過ref綁定數(shù)據(jù)
- 遍歷的元素:先定義一個空數(shù)組,定一個函數(shù)獲取元素,返回給模版使用,通過ref綁定這個函數(shù)
有一個邊界問題:組件更新的時候會重復(fù)的設(shè)置dom元素給數(shù)組:
12-組合API-父子通訊
父傳子:
<template>
<div class="container">
<h1>父組件</h1>
<p>{{money}}</p>
<hr>
<Son :money="money" />
</div>
</template>
<script>
import { ref } from 'vue'
import Son from './Son.vue'
export default {
name: 'App',
components: {
Son
},
// 父組件的數(shù)據(jù)傳遞給子組件
setup () {
const money = ref(100)
return { money }
}
}
</script>
<template>
<div class="container">
<h1>子組件</h1>
<p>{{money}}</p>
</div>
</template>
<script>
import { onMounted } from 'vue'
export default {
name: 'Son',
// 子組件接收父組件數(shù)據(jù)使用props即可
props: {
money: {
type: Number,
default: 0
}
},
setup (props) {
// 獲取父組件數(shù)據(jù)money
console.log(props.money)
}
}
</script>
子傳父:
<template>
<div class="container">
<h1>父組件</h1>
<p>{{money}}</p>
<hr>
+ <Son :money="money" @change-money="updateMoney" />
</div>
</template>
<script>
import { ref } from 'vue'
import Son from './Son.vue'
export default {
name: 'App',
components: {
Son
},
// 父組件的數(shù)據(jù)傳遞給子組件
setup () {
const money = ref(100)
+ const updateMoney = (newMoney) => {
+ money.value = newMoney
+ }
+ return { money , updateMoney}
}
}
</script>
<template>
<div class="container">
<h1>子組件</h1>
<p>{{money}}</p>
+ <button @click="changeMoney">花50元</button>
</div>
</template>
<script>
import { onMounted } from 'vue'
export default {
name: 'Son',
// 子組件接收父組件數(shù)據(jù)使用props即可
props: {
money: {
type: Number,
default: 0
}
},
// props 父組件數(shù)據(jù)
// emit 觸發(fā)自定義事件的函數(shù)
+ setup (props, {emit}) {
// 獲取父組件數(shù)據(jù)money
console.log(props.money)
// 向父組件傳值
+ const changeMoney = () => {
// 消費(fèi)50元
// 通知父組件,money需要變成50
+ emit('change-money', 50)
+ }
+ return {changeMoney}
}
}
</script>
擴(kuò)展:
- 在vue2.x的時候 .sync 除去v-model實(shí)現(xiàn)雙向數(shù)據(jù)綁定的另一種方式
<!-- <Son :money='money' @update:money="fn" /> -->
<Son :money.sync='money' />
- 在vue3.0的時候,使用 v-model:money="money" 即可
<!-- <Son :money="money" @update:money="updateMoney" /> -->
<Son v-model:money="money" />
總結(jié):
- 父傳子:在setup種使用props數(shù)據(jù) setup(props){ // props就是父組件數(shù)據(jù) }
- 子傳父:觸發(fā)自定義事件的時候emit來自 setup(props,{emit}){ // emit 就是觸發(fā)事件函數(shù) }
- 在vue3.0中 v-model 和 .sync 已經(jīng)合并成 v-model 指令
13-組合API-依賴注入
使用場景:有一個父組件,里頭有子組件,有孫組件,有很多后代組件,共享父組件數(shù)據(jù)。
演示代碼:
<template>
<div class="container">
<h1>父組件 {{money}} <button @click="money=1000">發(fā)錢</button></h1>
<hr>
<Son />
</div>
</template>
<script>
import { provide, ref } from 'vue'
import Son from './Son.vue'
export default {
name: 'App',
components: {
Son
},
setup () {
const money = ref(100)
const changeMoney = (saleMoney) => {
console.log('changeMoney',saleMoney)
money.value = money.value - saleMoney
}
// 將數(shù)據(jù)提供給后代組件 provide
provide('money', money)
// 將函數(shù)提供給后代組件 provide
provide('changeMoney', changeMoney)
return { money }
}
}
</script>
<style scoped lang="less"></style>
<template>
<div class="container">
<h2>子組件 {{money}}</h2>
<hr>
<GrandSon />
</div>
</template>
<script>
import { inject } from 'vue'
import GrandSon from './GrandSon.vue'
export default {
name: 'Son',
components: {
GrandSon
},
setup () {
// 接收祖先組件提供的數(shù)據(jù)
const money = inject('money')
return { money }
}
}
</script>
<style scoped lang="less"></style>
<template>
<div class="container">
<h3>孫組件 {{money}} <button @click="fn">消費(fèi)20</button></h3>
</div>
</template>
<script>
import { inject } from 'vue'
export default {
name: 'GrandSon',
setup () {
const money = inject('money')
// 孫組件,消費(fèi)50,通知父組件App.vue組件,進(jìn)行修改
// 不能自己修改數(shù)據(jù),遵循單選數(shù)據(jù)流原則,大白話:數(shù)據(jù)誰定義誰修改
const changeMoney = inject('changeMoney')
const fn = () => {
changeMoney(20)
}
return {money, fn}
}
}
</script>
<style scoped lang="less"></style>
總結(jié):
- provide函數(shù)提供數(shù)據(jù)和函數(shù)給后代組件使用
- inject函數(shù)給當(dāng)前組件注入provide提供的數(shù)據(jù)和函數(shù)