一.組件及組件化
1.組件化的作用
由于之前的代碼全寫在一個App.vue這個文件里面,會到導致一個文件代碼過于多而且不易復用,所以有組件化的思想。
2.組件的使用
①創建
創建一個.vue文件,使用setup的簡寫方式會自動導出.vue文件
②導入
import 組件對象 from '相對路徑'// eg
import MyPanel from './components/MyPanel.vue'
③使用
把組件當做?定義標簽使?(單雙標簽均可)
<組件名></組件名>
<組件名 />// eg
<!-- ?駝峰標 雙標簽 -->
<MyPanel></MyPanel>
<!-- ?駝峰 ?閉合的單標簽 -->
<MyPanel />
<!-- 烤串法 雙標簽 -->
<my-panel></my-panel>
<!-- 烤串法 ?閉合的單標簽 -->
<my-panel />
3.組件的全局注冊
①特點
全局注冊的組件,在項?的任何組件中都能使?
②步驟
1. 創建.vue組件(三個組成部分)
2. main.js 中進?全局注冊
③使用方式
當成HTML標簽直接使?:
? 雙標簽: <組件名></組件名>? ?閉合的單標簽: <組件名 />
④在main.js中注冊的寫法
import MyPanel from './components/MyPanel.vue'
const app = createApp(App)
// 注冊全局組件
// app.component('組件名', 組件對象)eg:
// ?駝峰組件名
app.component('MyPanel', MyPanel)
// 烤串法組件名
app.component('my-panel', MyPanel)
二.組件生命周期
1.生命周期的介紹
組件的生命周期的四個階段:
1. 創建階段:創建響應式數據
2. 掛載階段:渲染模板
3. 更新階段:修改數據,更新視圖4. 卸載階段:卸載組件
2.組件生命周期的鉤子
介紹:
? ? ? ?每個 Vue 組件實例在創建時都需要經歷?系列的初始化步驟,?如設置好數據監聽,編譯模板,掛載實例到真實 DOM 樹上,以及在數據改變時更新 DOM。在此過程中,會 ?動運??些函數,這些函數被稱為【Vue?命周期鉤?】
鉤子的本質就是函數,只不過這些函數比較特殊,無需程序員調用,而是有vue3內部的執行機制自己調用。鉤子函數存在的意義就是給了程序員在特定的時機添加自己的代碼的機會,比如組件創建完畢了,就想發送ajax請求,那么可以在創建后的鉤子函數中編寫相關代碼;還比如頁面渲染完畢后,立刻讓輸入框自動聚焦,那么可以在掛載后的鉤子函數中編寫相關代碼
生命周期的八個鉤子:
分別是:
1. 創建階段: 1. beforeCreate (): 創建前 2. created (): 創建后
2. 掛載階段: 1. beforeMount (): 掛載前 2. mounted (): 掛載后
3. 更新階段: 1. beforeUpdate (): 更新前 2. updated (): 更新后
4. 卸載階段: 1. beforeUnmount (): 卸載前 2. unmounted (): 卸載后?
vue生命周期與鉤子的關系圖:
3.選項式API生命周期的鉤子寫法(vue2的寫法)
代碼示例:
App.vue 文件代碼
<script setup>
import LifeCycle from './components/LifeCycle.vue'
import { ref } from 'vue'
const isLive = ref(true)
</script>
<template><LifeCycle v-if="isLive" />
</template>
<style scoped></style>
LifeCycle.vue 文件代碼
<script>
export default {// 提供響應式數據data() {return {count: 0,}},// 提供?法/函數methods: {fn() {console.log('fn 函數執?了')},},setup() {console.log('0-setup')},// 創建階段(第?階段):Vue組件創建/出?階段:// 創建前:此時?法訪問 data 數據,也?法調? methods ?法beforeCreate() {console.log('1-beforeCreate')// console.log(this.count) // undefined// console.log(this.fn) // undefined},// 創建后:此時可以訪問 data 數據,也可以調? methods ?法created() {console.log('2-created')// console.log(this.count) // 0// console.log(this.fn)// 訪問到函數// this.fn()// 開啟定時器// 給當前組件實例新增了?個 timerId 屬性,保存了當前定時器的 id 值// this.timerId = setInterval(() => {// console.log(this.count)// }, 1000)// },},// 掛載階段(第?階段):模版渲染階段// 掛載前:此時寫在 template 下的標簽還沒有變成真實DOM,故??法獲取DOMbeforeMount() {console.log('3-beforeMount')// console.log(document.querySelector('p')) // null},// 掛載后:此時寫在 template 下的標簽已經變成了真實DOM,故?可以獲取DOM(是最早可以操作DOM的時機)mounted() {console.log('4-mounted')// console.log(document.querySelector('p')) // <p>0</p>// document.querySelector('p').style.color = 'red'},// 更新階段(第三階段):數據變了,組件重新渲染的過程// 更新前beforeUpdate() {console.log('5-beforeUpdate')// console.log(this.count)// console.log(document.querySelector('p').innerText) // 舊內容(以前的內容)},// 更新后updated() {console.log('6-updated')// console.log(this.count)// console.log(document.querySelector('p').innerText) // 新內容},// 卸載階段(第四階段):組件移除階段beforeUnmount() {console.log('7-beforeUnmount')},unmounted() {console.log('8-mounted')//關閉定時器// clearInterval(this.timerId)},
}
</script>
<template><div><p>{{ count }}</p><button @click="count++">+1</button></div>
</template><style scoped></style>
比較實用的應該就只有:created,mounted,unmounted 這三個鉤子。
4.組合式API生命周期鉤子
創建階段 | 掛載階段 | 更新階段 | 銷毀階段 | |
---|---|---|---|---|
Vue2 | beforeCreate created | beforeMount mounted | beforeUpdate updated | beforeUnmount unmounted |
Vue3 | setup (網絡請求) | onBeforeMount onMounted (操作 DOM) | onBeforeUpdate onUpdated | onBeforeUnmount onUnmounted (清理工作) |
代碼示例:
App.vue 文件代碼
<script setup>
import LifeCycle from './components/LifeCycle.vue'
import { ref } from 'vue'
const isLive = ref(true)
</script>
<template><LifeCycle v-if="isLive" />
</template>
<style scoped></style>
LifeCycle.vue 文件代碼
<script setup>
import { onMounted, ref } from 'vue'
onMounted(() => {console.log('渲染之后')document.querySelector('p').style.color = 'green'
})
let count = ref(0)
</script><template><div><p>{{ count }}</p><button @click="count++">+1</button></div>
</template><style scoped></style>
三.scoped
1.scoped作用
寫在組件中的樣式會 全局?效, 因此很容易造成多個組件之間的樣式沖突問題。
1. 全局樣式: 默認組件中的樣式會作?到全局,任何?個組件中都會受到此樣式的影響2. 局部樣式: 可以給組件加上scoped 屬性,可以讓樣式只作?于當前組件的標簽
2.scoped原理
1. 組件內所有標簽都被添加data-v-hash值 的?定義屬性
2. css選擇器都被添加 [data-v-hash值] 的屬性選擇器最終效果: 必須是當前組件的元素, 才會有這個?定義屬性, 從?保證了樣式只能作?到當前組件。
四.組件通信
1.介紹
作用:
之前把代碼寫在?起的,數據直接使?即可;現在是組件化開發,通過代碼拆分和組合的?式進?開發,這種情況下,還要達到和不拆分之前?樣的效果,這時組件之間難免需要數據傳遞,這就需要組件之間進?通信。
組件之間的關系:
1、??關系:誰被使?, 誰就是?組件, 當前組件就是?組件
2、???關系
2.父傳子(defineProps)
傳數據步驟:
1. ?組件通過 defineProps 接收數據(?接)
2. ?組件通過 ?定義屬性 傳遞數據 (?傳)
流程圖:
代碼案例:
App.vue 文件代碼
<script setup>
import MyGoods from './components/MyGoods.vue'
// 商品列表
const goodsList = [{id: '4001172',name: '稱?如意?搖咖啡磨?機咖啡?研磨機',price: 289,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001594',name: '?式?陶功夫茶組雙側把茶具禮盒裝',price: 288,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001009',name: '?制?泡茶盤正?形瀝?茶臺品茶盤',price: 109,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001874',name: '古法溫酒汝瓷酒具套裝?酒杯蓮花溫酒器',price: 488,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001649',name: '?師監制?泉?瓷茶葉罐',price: 139,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3997185',name: '與眾不同的?感汝瓷?酒杯套組1壺4杯',price: 108,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3997403',name: '??吹制更厚實?酒杯壺套裝6壺6杯',price: 100,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3998274',name: '德國百年?藝?端?晶玻璃紅酒杯2?裝',price: 139,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},
]
</script>
<template><div class="list"><!-- 循環遍歷、獨?傳值--><MyGoodsv-for="item in goodsList":key="item.id":imgUrl="item.picture":title="item.name":price="item.price"/></div>
</template>
<style lang="scss">
* {margin: 0;padding: 0;box-sizing: border-box;
}
.list {width: 1000px;margin: 0 auto;display: flex;flex-wrap: wrap;
}
</style>
MyGoods.vue 文件代碼
<script setup>
// 接受數據
defineProps(['imgUrl', 'title', 'price'])
</script>
<template><div class="item"><img :src="imgUrl" :alt="title" /><p class="name">{{ title }}</p><p class="price">{{ price }}.00</p></div>
</template>
<style lang="scss" scoped>
.item {width: 240px;margin-left: 10px;padding: 20px 30px;transition: all 0.5s;margin-bottom: 20px;.item:nth-child(4n) {margin-left: 0;}&:hover {box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);transform: translate3d(0, -4px, 0);cursor: pointer;}img {width: 100%;}.name {font-size: 18px;margin-bottom: 10px;color: #666;}.price {display: flex;align-items: center;font-size: 22px;color: firebrick;button {margin-left: 48px;font-size: 14px;outline: none;}}.price::before {content: '¥';font-size: 22px;}
}
</style>
3.子傳父(defineEmits)
數據傳輸步驟:
1. ?組件內,?組件上,綁定?定義事件,@?定義事件="?修改數據的函數" (?綁定)
2. ?組件恰當時機, 觸發?組件的?定義事件 , emit('?定義事件', 攜帶的參數...), 從?導致?組件修改函數的時候(?觸發)前提還需要拿到自定義事件的函數 emit? ?const emit = defineEmits()
代碼案例:
App.vue
<script setup>
import { ref } from 'vue'
// 導? MyGoods 商品組件
import MyGoods from './components/MyGoods2.vue'
// 商品列表
const goodsList = ref([{id: '4001172',name: '稱?如意?搖咖啡磨?機咖啡?研磨機',price: 289,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001594',name: '?式?陶功夫茶組雙側把茶具禮盒裝',price: 288,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001009',name: '?制?泡茶盤正?形瀝?茶臺品茶盤',price: 109,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001874',name: '古法溫酒汝瓷酒具套裝?酒杯蓮花溫酒器',price: 488,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001649',name: '?師監制?泉?瓷茶葉罐',price: 139,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3997185',name: '與眾不同的?感汝瓷?酒杯套組1壺4杯',price: 108,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3997403',name: '??吹制更厚實?酒杯壺套裝6壺6杯',price: 100,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3998274',name: '德國百年?藝?端?晶玻璃紅酒杯2?裝',price: 139,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},
])
// i: 下標,讓哪個對象的 price 減少
// price: 每次減少的價格
//提供修改的函數
const subPrice = (i, price) => {goodsList.value[i].price -= price
}
</script>
<template><div class="list"><MyGoodsv-for="(item, index) in goodsList":key="item.id":imgUrl="item.picture":title="item.name":price="item.price":idx="index"@ccc="subPrice"/></div>
</template>
<style lang="scss">
* {margin: 0;padding: 0;box-sizing: border-box;
}
.list {width: 1000px;margin: 0 auto;display: flex;flex-wrap: wrap;
}
</style>
MyGoods.vue
<script setup>
import { ref } from 'vue'
// 導? MyGoods 商品組件
import MyGoods from './components/MyGoods2.vue'
// 商品列表
const goodsList = ref([{id: '4001172',name: '稱?如意?搖咖啡磨?機咖啡?研磨機',price: 289,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001594',name: '?式?陶功夫茶組雙側把茶具禮盒裝',price: 288,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001009',name: '?制?泡茶盤正?形瀝?茶臺品茶盤',price: 109,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001874',name: '古法溫酒汝瓷酒具套裝?酒杯蓮花溫酒器',price: 488,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '4001649',name: '?師監制?泉?瓷茶葉罐',price: 139,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3997185',name: '與眾不同的?感汝瓷?酒杯套組1壺4杯',price: 108,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3997403',name: '??吹制更厚實?酒杯壺套裝6壺6杯',price: 100,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},{id: '3998274',name: '德國百年?藝?端?晶玻璃紅酒杯2?裝',price: 139,picture: 'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',},
])
// i: 下標,讓哪個對象的 price 減少
// price: 每次減少的價格
//提供修改的函數
const subPrice = (i, price) => {goodsList.value[i].price -= price
}
</script>
<template><div class="list"><MyGoodsv-for="(item, index) in goodsList":key="item.id":imgUrl="item.picture":title="item.name":price="item.price":idx="index"@ccc="subPrice"/></div>
</template>
<style lang="scss">
* {margin: 0;padding: 0;box-sizing: border-box;
}
.list {width: 1000px;margin: 0 auto;display: flex;flex-wrap: wrap;
}
</style>
4.祖先傳后代
5.任意兩個組件通信
五.props校驗
作用:
我們前面使用?props 傳輸數據的時候,參數的格式傳輸錯誤,在控制臺不會顯示,我們添加了props校驗之后,就會出現報錯了,幫助開發者,快速發現錯誤。
完整語法格式:
defineProps({屬性名: {type: 類型, // Number String Boolean ...required: true, // 是否必填default: 默認值, // 默認值// value: ?傳?的值validator (value) {// ?定義校驗邏輯return 布爾值}}
})
代碼示例:里面有一種簡單寫法一種完整寫法
App.vue 文件代碼:
<script setup>
import { ref } from 'vue'
import MyProgress from './components/MyProgress.vue'
const width = ref(20)
</script>
<template><div class="app"><MyProgress :width="width" /><MyProgress :width="80" /></div>
</template>
<style></style>
MyProgress.vue 文件代碼:
<script setup>
// 不驗證的寫法
// defineProps(['width'])// 驗證的簡易寫法
// 屬性:類型
// defineProps({
// width: Number,
// })// 驗證的完整寫法
defineProps({width: {type: Number,// required: true // 寫了這個default就不會生效了default: 50,validator: (value) => {if (value >= 0 && value <= 100) {return true} else {console.error('The width prop must be between 0 and 100')return false}},},
})
</script>
<template><div class="my-progress"><div class="inner" :style="{ width: width + '%' }"><span>{{ width }}%</span></div></div>
</template>
<style scoped>
.my-progress {height: 26px;width: 400px;border-radius: 15px;background-color: #272425;border: 3px solid #272425;box-sizing: border-box;margin-bottom: 30px;
}
.inner {position: relative;background: #379bff;border-radius: 15px;height: 25px;box-sizing: border-box;left: -3px;top: -2px;
}
.inner span {position: absolute;right: -30px;top: 26px;
}
</style>
六.v-model進階
1.原理
v-model本質上是?個語法糖。
例如應?在輸?框上,就是 value 屬性 和 input 事件的合寫
<script setup>import { ref } from 'vue'const msg = ref('')
</script>
<template><p>{{ msg }}</p><input type="text" v-model="msg"><input type="text" :value="msg" @input="msg = $event.target.value" >
</template>
?$event.target.value 意思是獲取當前輸入框的內容。
2.v-model作用在組件上
作用:
實現父組件和子組件數據的雙向綁定
代碼實現:
App.vue 的代碼實現
<script setup>
import { ref } from 'vue'
import BitSelect from './components/BitSelect.vue'
const activeId = ref('222')
</script>
<template><!-- 組件標簽上的 $event 指的是 emit 傳遞來的參數 --><BitSelect :modelValue="activeId" @update:modelValue="activeId = $event" /><!-- 自定義屬性 自定義事件 --><!-- 簡化寫法 --><!-- <BitSelect v-model="activeId" /> -->
</template>
?BitSelect.vue
<script setup>
// 接收 modelValue 是父傳子這里不能直接改,所以下面不能直接用 v-model
const props = defineProps({modelValue: {type: String,default: '',},
})
// 獲取 emit
const emit = defineEmits()
</script>
<template><!-- 原生標簽上的 $event 就是事件對象 --><!-- $event.target: select 原生DOM --><!-- $event.target.value: 選中的 option 的 value --><select :value="props.modelValue" @change="emit('update:modelValue', $event.target.value)"><option value="111">北京</option><option value="222">上海</option><option value="333">深圳</option><option value="444">杭州</option><option value="555">蘇州</option></select>
</template>
流程圖
簡化:
App.vue 文件的代碼
<script setup>
import { ref } from 'vue'
import BitSelect from './components/BitSelect.vue'
const activeId = ref('222')
</script>
<template><BitSelect v-model="activeId" />
</template>
??BitSelect.vue
<script setup>
// 子組件直接通過 defineModel() 來接收父組件傳遞的v-model值
// defineModel() 的返回值是一個 ref 數據,并且可以在子組件中直接操作這個 ref 數據,
// 并且可以在子組件中直接操作這個 ref 數據,會引起父組件的數據的同步更新
const model = defineModel()
</script>
<template><select v-model="model"><option value="111">北京</option><option value="222">上海</option><option value="333">深圳</option><option value="444">杭州</option><option value="555">蘇州</option></select>
</template>
這里的defineModel是個語法糖。?
七.ref屬性
1.ref是什么
是作用在標簽上的屬性,是 vue 新增的,原生不具備這個屬性的。
2.作用:
用來獲取原生 DOM 或 組件實例(進而調用組件提供的方法)。
3.獲取原生DOM
<!-- @format -->
<script setup>
import { onMounted, ref } from 'vue'// 準備 ref 響應式數據
const divRef = ref(null)// 組件掛載后
onMounted(() => {// 獲取原生 div// console.log(divRef.value)/// <div></div>// 以設置 div 字體顏色為例(DOM操作的代碼):divRef.value.style.color = 'red'
})
</script><template><!-- 給目標元素添加ref屬性并綁定數據 --><div ref="divRef">Some Text...</div>
</template><style scoped></style>
這個獲取是直接從自己這個組件上獲取,比原本從整棵DOM樹上獲取會快一點?
4.使用組件實例調用組件暴露的方法和屬性(defineExpose)
App.vue 文件代碼
<script setup>
import { ref } from 'vue'
import MyForm from './components/MyForm.vue'
const formRef = ref(null)
// 登錄
const onLogin = () => {console.log(formRef.value.ccc)console.log(formRef.value.validate())// 進?校驗// if (formRef.value.validate()) {// console.log('ok')// } else {// console.log('error')// }
}
</script>
<template><MyForm ref="formRef" /><button @click="onLogin">登 錄</button>
</template>
<style>
#app {width: 300px;margin: 100px auto;
}
</style>
MyForm.vue 文件代碼
<script setup>
// 表單校驗
const validate = () => {return Math.random() > 0.5 ? true : false
}
// 暴漏給組件, ?的是?組件可以通過 ref 可以拿到?組件的?法
defineExpose({ccc: 100,validate,
})
</script>
<template><div class="login-box">賬?:<input type="text" /><br /><br />密碼:<input type="password" /><br /><br /></div>
</template>
八.nextTick
1.作用
當數據變了,想獲取更新后的DOM,需要把代碼寫在這個方法的回調中。直接獲取是獲取不到的,因為DOM操作是異步的,獲取DOM會在更新DOM之前執行。
2.代碼案例
<script setup>
import { nextTick, ref } from 'vue'// 控制是否顯示輸入框
const isShowEdit = ref(false)// 點擊編輯按鈕
const onEdit = () => {isShowEdit.value = true// 問題:當數據變了,發現獲取DOM拿不到的// 原因:在 vue3 中,當數據變了,DOM的更新是異步的;// 也就是說,數據變了,想立即獲取最新的DOM是拿不到的,此時DOM并沒有更新// console.log(inputRef.value) // null// 解決:利用 nextTick() 這個方法,因為在這個方法的回調中,DOM更新完畢了nextTick(() => {inputRef.value.focus()})
}const inputRef = ref(null)
</script><template><div class="box"><h3>大標題</h3><button @click="onEdit">編輯</button></div><div v-if="isShowEdit"><input type="text" ref="inputRef" /><button>確認</button></div>
</template>
<style scoped>
.box {display: flex;justify-content: space-between;align-items: center;width: 200px;height: 40px;
}
</style>
九.自定義指令
1.作用
封裝?段 公共的DOM操作 代碼,便于復?。
2.基本使用?
①語法
注冊
// main.js 中
app.directive('指令名', {// 元素掛載后(成為真實DOM) ?動觸發?次?動執?mounted(el) {// el: 指令所在的DOM元素}
}
使用
<p v-指令名></p>
②代碼示例
main.js 文件代碼
// 按需導入 createApp 函數
import { createApp } from 'vue'
// 導入 App.vue
import App from './App.vue'
// 創建應用
const app = createApp(App)// 定義指令
app.directive('jujiao', {// 指令所在的標簽插入到真實DOM中自動執行一次mounted(el) {// el就是指令所在的DOM元素, 拿到 el 了,// 就可以對 el 做任何原生DOM操作// console.log('mounted', el)el.focus()}
})
// 指定渲染的位置
app.mount('#app')
App.vue 文件代碼
<template><input type="text" v-jujiao />
</template><script setup></script><style scoped></style>
3.綁定數據
①語法
1.在綁定指令時,可以通過“等號”的形式為指令 綁定 具體的參數值
<div v-color="colorStr">Some Text</div>
?2.通過 binding.value 可以拿到指令值,指令值修改會 觸發 updated 鉤?
app.directive('指令名', {// 掛載后?動觸發?次mounted(el, binding) { },// 數據更新, 每次都會執?updated(el, binding) { }
})
②代碼示例
main.js 文件代碼
// 按需導入 createApp 函數
import { createApp } from 'vue'
// 導入 App.vue
import App from './App.vue'
// 創建應用
const app = createApp(App)app.directive('color', {// 指令所在的DOM元素變成真實DOM了,會自動執行一次;// 換句話說,當指令所在表達式的值變了,該函數不會再次執行了mounted(el, binding) {// el: 指令所在元素// binding: 指令綁定的信息對象,binding.value 獲取指令表達式的結果el.style.color = binding.valueconsole.log('1-mounted')},// 每次指令綁定表達式的值變了,都會執行一次updated(el, binding) {el.style.color = binding.valueconsole.log('2-updated')}
})// 指定渲染的位置
app.mount('#app')
App.vue 文件代碼
<script setup>import { ref } from 'vue'// 提供的顏色響應式數據const colorStr = ref('red')
</script><template><!-- 使用自定義指令,并綁定表達式 --><div v-color="colorStr">Some Text</div>
</template><style scoped></style>
main.js 的簡化寫法?
對于自定義指令來說,一個很常見的情況是僅僅需要在 mounted 和 updated 上實現相同的行為。這種情況我們可以直接用一個函數的定義指令,如下所示:
app.directive('color', (el, binding) => {// 這會在 `mounted` 和 `updated` 時都調?el.style.color = binding.value
}
十.插槽
1.作用
讓組件內部的?些 結構 ?持 ?定義
2.默認插槽
①基本語法
1. 組件內需要定制的結構部分,改? <slot></slot> 占位
2. 使?組件時, <MyDialog></MyDialog>寫成雙標簽 , 包裹結構, 傳?替換slot
②代碼示例
MyDialog.vue 文件代碼
<script setup></script>
<template><div class="dialog"><div class="dialog-header"><h3>友情提?</h3><span class="close">?</span></div><div class="dialog-content"><!-- 1. 在組件內標簽不確定位置用 slot 組件占位 --><slot> </slot></div><div class="dialog-footer"><button>取消</button><button>確認</button></div></div>
</template>
<style scoped>
* {margin: 0;padding: 0;
}
.dialog {width: 470px;height: 230px;padding: 0 25px;background-color: #ffffff;margin: 40px auto;border-radius: 5px;
}
.dialog-header {height: 70px;line-height: 70px;font-size: 20px;border-bottom: 1px solid #ccc;position: relative;
}
.dialog-header .close {position: absolute;right: 0px;top: 0px;cursor: pointer;
}
.dialog-content {height: 80px;font-size: 18px;padding: 15px 0;
}
.dialog-footer {display: flex;justify-content: flex-end;
}
.dialog-footer button {width: 65px;height: 35px;background-color: #ffffff;border: 1px solid #e1e3e9;cursor: pointer;outline: none;margin-left: 10px;border-radius: 3px;
}
.dialog-footer button:last-child {background-color: #007acc;color: #fff;
}
</style>
App.vue 文件代碼
<script setup>
import MyDialog from './components/MyDialog.vue'
</script>
<template><!-- 2. 在使用組件的時候,給組件傳入想展示的結構,從而替換掉占位的 slot 組件 --><MyDialog> 你取認要刪除嗎 </MyDialog><MyDialog><span>我已經刪除了</span></MyDialog>
</template>
<style scoped>
body {background-color: #b3b3b3;
}
</style>
3.插槽默認值
通過插槽完成了內容的定制,傳什么顯?什么, 但是如果不傳,則是空?,如果我們需要有默認值在, <slot></slot> 標簽里面寫默認值就可以了,比較簡單這里就不代碼演示了。
4.具名插槽
①作用
一個組件有多個位置需要使用插槽是使用具名插槽。
②語法
?? 多個slot使?name屬性區分名字
? template配合v-slot:名字 來分發對應標簽
? v-slot寫起來太?,vue給我們提供?個簡單寫法 v-slot: 直接簡寫為 #
③代碼示例?
MyDialog.vue 文件代碼
<script setup></script>
<template><div class="dialog"><div class="dialog-header"><slot name="header"><h3>溫馨提?</h3></slot><span class="close">?</span></div><div class="dialog-content"><slot>我是主體內容</slot></div><div class="dialog-footer"><slot name="footer"><button>取消</button><button>確認</button></slot></div></div>
</template>
<style>
* {margin: 0;padding: 0;
}
.dialog {width: 470px;height: 230px;padding: 0 25px;background-color: #ffffff;margin: 40px auto;border-radius: 5px;
}
.dialog-header {height: 70px;line-height: 70px;font-size: 20px;border-bottom: 1px solid #ccc;position: relative;
}
.dialog-header .close {position: absolute;right: 0px;top: 0px;cursor: pointer;
}
.dialog-content {height: 80px;font-size: 18px;padding: 15px 0;
}
.dialog-footer {display: flex;justify-content: flex-end;
}
.dialog-footer button {width: 65px;height: 35px;background-color: #ffffff;border: 1px solid #e1e3e9;cursor: pointer;outline: none;margin-left: 10px;border-radius: 3px;
}
.dialog-footer button:last-child {background-color: #007acc;color: #fff;
}
</style>
App.vue 文件代碼
<script setup>
import MyDialog from './components/MyDialog.vue'
</script>
<template><!-- 1. 不傳、都顯?插槽默認內容 --><MyDialog /><!-- 2. 傳了 --><MyDialog><template v-slot:header><h3>友情提?</h3></template><template v-slot:default> 請輸?正確?機號 </template><template v-slot:footer><button>關閉</button></template></MyDialog><!-- 3. 傳了 --><MyDialog><template v-slot:header><h3>警告</h3></template><template v-slot:default> 你確認要退出么? </template><template v-slot:footer><button>確認</button><button>取消</button></template></MyDialog>
</template>
<style>
body {background-color: #b3b3b3;
}
</style>
5.作用域插槽
①作用
帶數據的插槽, 可以讓組件功能更強?、更靈活、復?性更?; ? slot 占位的同時, 還可以給 slot 綁定數據, 將來使?組件時, 不僅可以傳內容, 還能使? slot 帶來的數據。
②代碼示例
MyDialog.vue 文件代碼
<script setup></script><template><div class="dialog"><div class="dialog-header"><slot name="header"></slot><span class="close">×</span></div><div class="dialog-content"><slot name="body" account="xiaobit" password="123456"></slot></div><div class="dialog-footer"><button>取消</button><button>確認</button></div></div>
</template><style>
* {margin: 0;padding: 0;
}
.dialog {width: 470px;height: 230px;padding: 0 25px;background-color: #ffffff;margin: 40px auto;border-radius: 5px;
}
.dialog-header {height: 70px;line-height: 70px;font-size: 20px;border-bottom: 1px solid #ccc;position: relative;
}
.dialog-header .close {position: absolute;right: 0px;top: 0px;cursor: pointer;
}
.dialog-content {height: 80px;font-size: 18px;padding: 15px 0;
}
.dialog-footer {display: flex;justify-content: flex-end;
}
.dialog-footer button {width: 65px;height: 35px;background-color: #ffffff;border: 1px solid #e1e3e9;cursor: pointer;outline: none;margin-left: 10px;border-radius: 3px;
}
.dialog-footer button:last-child {background-color: #007acc;color: #fff;
}
</style>
App.vue 文件代碼
<script setup>
import MyDialog from './components/MyDialog.vue'
</script><template><MyDialog><template #header="obj"><h4>登錄</h4></template><template #body="obj"><!-- 默認接收的數據 obj 是個空對象 --><p>{{ obj }}</p>賬號:<input type="text" v-model="obj.account" /><br /><br />密碼:<input type="password" v-model="obj.password" /><br /><br /></template></MyDialog>
</template><style scoped></style>