Vue自定義指令&插槽&作用域插槽&具名插槽
一、學習目標
1.自定義指令
- 基本語法(全局、局部注冊)
- 指令的值
- v-loading的指令封裝
2.插槽
- 默認插槽
- 具名插槽
- 作用域插槽
3.綜合案例:商品列表
- MyTag組件封裝
- MyTable組件封裝
4.路由入門
- 單頁應用程序
- 路由
- VueRouter的基本使用
二、自定義指令
1.指令介紹
-
內置指令:v-html、v-if、v-bind、v-on… 這都是Vue給咱們內置的一些指令,可以直接使用
-
自定義指令:同時Vue也支持讓開發者,自己注冊一些指令。這些指令被稱為自定義指令
每個指令都有自己各自獨立的功能
2.自定義指令
概念:自己定義的指令,可以封裝一些DOM操作,擴展額外的功能
3.自定義指令語法
-
全局注冊
//在main.js中 Vue.directive('指令名', {"inserted" (el) {// 可以對 el 標簽,擴展額外功能el.focus()} })
-
局部注冊
//在Vue組件的配置項中 directives: {"指令名": {inserted () {// 可以對 el 標簽,擴展額外功能el.focus()}} }
-
使用指令
注意:在使用指令的時候,一定要先注冊,再使用,否則會報錯
使用指令語法: v-指令名。如:注冊指令時不用加v-前綴,但使用時一定要加v-前綴
4.指令中的配置項介紹
inserted:被綁定元素插入父節點時調用的鉤子函數
el:使用指令的那個DOM元素
5.代碼示例
需求:當頁面加載時,讓元素獲取焦點(autofocus在safari瀏覽器有兼容性)
App.vue
<div><h1>自定義指令</h1><input v-focus ref="inp" type="text"></div>
6.總結
1.自定義指令的作用是什么?
2.使用自定義指令的步驟是哪兩步?
三、自定義指令-指令的值
1.需求
實現一個 color 指令 - 傳入不同的顏色, 給標簽設置文字顏色
2.語法
1.在綁定指令時,可以通過“等號”的形式為指令 綁定 具體的參數值
<div v-color="color">我是內容</div>
2.通過 binding.value 可以拿到指令值,指令值修改會 觸發 update 函數
directives: {color: {inserted (el, binding) {el.style.color = binding.value},update (el, binding) {el.style.color = binding.value}}
}
3.代碼示例
App.vue
<template><div><!--顯示紅色--> <h2 v-color="color1">指令的值1測試</h2><!--顯示藍色--> <h2 v-color="color2">指令的值2測試</h2><button>改變第一個h1的顏色</button></div>
</template><script>
export default {data () {return {color1: 'red',color2: 'blue'}}
}
</script><style></style>
四、自定義指令-v-loading指令的封裝
1.場景
實際開發過程中,發送請求需要時間,在請求的數據未回來時,頁面會處于空白狀態 => 用戶體驗不好
2.需求
封裝一個 v-loading 指令,實現加載中的效果
3.分析
1.本質 loading效果就是一個蒙層,蓋在了盒子上
2.數據請求中,開啟loading狀態,添加蒙層
3.數據請求完畢,關閉loading狀態,移除蒙層
4.實現
1.準備一個 loading類,通過偽元素定位,設置寬高,實現蒙層
2.開啟關閉 loading狀態(添加移除蒙層),本質只需要添加移除類即可
3.結合自定義指令的語法進行封裝復用
.loading:before {content: "";position: absolute;left: 0;top: 0;width: 100%;height: 100%;background: #fff url("./loading.gif") no-repeat center;
}
5.準備代碼
<template><div class="main"><div class="box"><ul><li v-for="item in list" :key="item.id" class="news"><div class="left"><div class="title">{{ item.title }}</div><div class="info"><span>{{ item.source }}</span><span>{{ item.time }}</span></div></div><div class="right"><img :src="item.img" alt=""></div></li></ul></div> </div>
</template><script>
// 安裝axios => yarn add axios || npm i axios
import axios from 'axios'// 接口地址:http://hmajax.itheima.net/api/news
// 請求方式:get
export default {data () {return {list: [],isLoading: false,isLoading2: false}},async created () {// 1. 發送請求獲取數據const res = await axios.get('http://hmajax.itheima.net/api/news')setTimeout(() => {// 2. 更新到 list 中,用于頁面渲染 v-forthis.list = res.data.data}, 2000)}
}
</script><style>
.loading:before {content: '';position: absolute;left: 0;top: 0;width: 100%;height: 100%;background: #fff url('./loading.gif') no-repeat center;
}.box2 {width: 400px;height: 400px;border: 2px solid #000;position: relative;
}.box {width: 800px;min-height: 500px;border: 3px solid orange;border-radius: 5px;position: relative;
}
.news {display: flex;height: 120px;width: 600px;margin: 0 auto;padding: 20px 0;cursor: pointer;
}
.news .left {flex: 1;display: flex;flex-direction: column;justify-content: space-between;padding-right: 10px;
}
.news .left .title {font-size: 20px;
}
.news .left .info {color: #999999;
}
.news .left .info span {margin-right: 20px;
}
.news .right {width: 160px;height: 120px;
}
.news .right img {width: 100%;height: 100%;object-fit: cover;
}
</style>
五、插槽-默認插槽
1.作用
讓組件內部的一些 結構 支持 自定義
2.需求
將需要多次顯示的對話框,封裝成一個組件
3.問題
組件的內容部分,不希望寫死,希望能使用的時候自定義。怎么辦
4.插槽的基本語法
- 組件內需要定制的結構部分,改用****占位
- 使用組件時, ****標簽內部, 傳入結構替換slot
- 給插槽傳入內容時,可以傳入純文本、html標簽、組件
5.代碼示例
MyDialog.vue
<template><div class="dialog"><div class="dialog-header"><h3>友情提示</h3><span class="close">??</span></div><div class="dialog-content">您確定要進行刪除操作嗎?</div><div class="dialog-footer"><button>取消</button><button>確認</button></div></div>
</template><script>
export default {data () {return {}}
}
</script><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
<template><div><MyDialog></MyDialog></div>
</template><script>
import MyDialog from './components/MyDialog.vue'
export default {data () {return {}},components: {MyDialog}
}
</script><style>
body {background-color: #b3b3b3;
}
</style>
6.總結
場景:組件內某一部分結構不確定,想要自定義怎么辦
使用:插槽的步驟分為哪幾步?
六、插槽-后備內容(默認值)
1.問題
通過插槽完成了內容的定制,傳什么顯示什么, 但是如果不傳,則是空白
能否給插槽設置 默認顯示內容 呢?
2.插槽的后備內容
封裝組件時,可以為預留的 <slot>
插槽提供后備內容(默認內容)。
3.語法
在 標簽內,放置內容, 作為默認顯示內容
4.效果
-
外部使用組件時,不傳東西,則slot會顯示后備內容
-
外部使用組件時,傳東西了,則slot整體會被換掉
5.代碼示例
App.vue
<template><div><MyDialog></MyDialog><MyDialog>你確認要退出么</MyDialog></div>
</template><script>
import MyDialog from './components/MyDialog.vue'
export default {data () {return {}},components: {MyDialog}
}
</script><style>
body {background-color: #b3b3b3;
}
</style>
七、插槽-具名插槽
1.需求
一個組件內有多處結構,需要外部傳入標簽,進行定制
上面的彈框中有三處不同,但是默認插槽只能定制一個位置,這時候怎么辦呢?
2.具名插槽語法
-
多個slot使用name屬性區分名字
-
template配合v-slot:名字來分發對應標簽
3.v-slot的簡寫
v-slot寫起來太長,vue給我們提供一個簡單寫法 v-slot —> #
4.總結
- 組件內 有多處不確定的結構 怎么辦?
- 具名插槽的語法是什么?
- v-slot:插槽名可以簡化成什么?
八、作用域插槽
1.插槽分類
-
默認插槽
-
具名插槽
插槽只有兩種,作用域插槽不屬于插槽的一種分類
2.作用
定義slot 插槽的同時, 是可以傳值的。給 插槽 上可以 綁定數據,將來 使用組件時可以用
3.場景
封裝表格組件
4.使用步驟
-
給 slot 標簽, 以 添加屬性的方式傳值
<slot :id="item.id" msg="測試文本"></slot>
-
所有添加的屬性, 都會被收集到一個對象中
{ id: 3, msg: '測試文本' }
-
在template中, 通過
#插槽名= "obj"
接收,默認插槽名為 default<MyTable :list="list"><template #default="obj"><button @click="del(obj.id)">刪除</button></template> </MyTable>
5.代碼示例
MyTable.vue
<template><table class="my-table"><thead><tr><th>序號</th><th>姓名</th><th>年紀</th><th>操作</th></tr></thead><tbody><tr><td>1</td><td>趙小云</td><td>19</td><td><button>查看 </button></td></tr><tr><td>1</td><td>張小花</td><td>19</td><td><button>查看 </button></td></tr><tr><td>1</td><td>孫大明</td><td>19</td><td><button>查看 </button></td></tr></tbody></table>
</template><script>
export default {props: {data: Array}
}
</script><style scoped>
.my-table {width: 450px;text-align: center;border: 1px solid #ccc;font-size: 24px;margin: 30px auto;
}
.my-table thead {background-color: #1f74ff;color: #fff;
}
.my-table thead th {font-weight: normal;
}
.my-table thead tr {line-height: 40px;
}
.my-table th,
.my-table td {border-bottom: 1px solid #ccc;border-right: 1px solid #ccc;
}
.my-table td:last-child {border-right: none;
}
.my-table tr:last-child td {border-bottom: none;
}
.my-table button {width: 65px;height: 35px;font-size: 18px;border: 1px solid #ccc;outline: none;border-radius: 3px;cursor: pointer;background-color: #ffffff;margin-left: 5px;
}
</style>
App.vue
<template><div><MyTable :data="list"></MyTable><MyTable :data="list2"></MyTable></div>
</template><script>import MyTable from './components/MyTable.vue'export default {data () {return {list: [{ id: 1, name: '張小花', age: 18 },{ id: 2, name: '孫大明', age: 19 },{ id: 3, name: '劉德忠', age: 17 },],list2: [{ id: 1, name: '趙小云', age: 18 },{ id: 2, name: '劉蓓蓓', age: 19 },{ id: 3, name: '姜肖泰', age: 17 },]}},components: {MyTable}}
</script>
6.總結
1.作用域插槽的作用是什么?
2.作用域插槽的使用步驟是什么?
九、綜合案例 - 商品列表-MyTag組件抽離
1.需求說明
- my-tag 標簽組件封裝
? (1) 雙擊顯示輸入框,輸入框獲取焦點
? (2) 失去焦點,隱藏輸入框
? (3) 回顯標簽信息
? (4) 內容修改,回車 → 修改標簽信息
- my-table 表格組件封裝
? (1) 動態傳遞表格數據渲染
? (2) 表頭支持用戶自定義
? (3) 主體支持用戶自定義
2.代碼準備
<template><div class="table-case"><table class="my-table"><thead><tr><th>編號</th><th>名稱</th><th>圖片</th><th width="100px">標簽</th></tr></thead><tbody><tr><td>1</td><td>梨皮朱泥三絕清代小品壺經典款紫砂壺</td><td><img src="https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg" /></td><td><div class="my-tag"><!-- <input class="input"type="text"placeholder="輸入標簽"/> --><div class="text">茶具</div></div></td></tr><tr><td>1</td><td>梨皮朱泥三絕清代小品壺經典款紫砂壺</td><td><img src="https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg" /></td><td><div class="my-tag"><!-- <inputref="inp"class="input"type="text"placeholder="輸入標簽"/> --><div class="text">男靴</div></div></td></tr></tbody></table></div>
</template><script>
export default {name: 'TableCase',components: {},data() {return {goods: [{id: 101,picture:'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',name: '梨皮朱泥三絕清代小品壺經典款紫砂壺',tag: '茶具',},{id: 102,picture:'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',name: '全防水HABU旋鈕牛皮戶外徒步鞋山寧泰抗菌',tag: '男鞋',},{id: 103,picture:'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',name: '毛茸茸小熊出沒,兒童羊羔絨背心73-90cm',tag: '兒童服飾',},{id: 104,picture:'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',name: '基礎百搭,兒童套頭針織毛衣1-9歲',tag: '兒童服飾',},],}},
}
</script><style lang="less" scoped>
.table-case {width: 1000px;margin: 50px auto;img {width: 100px;height: 100px;object-fit: contain;vertical-align: middle;}.my-table {width: 100%;border-spacing: 0;img {width: 100px;height: 100px;object-fit: contain;vertical-align: middle;}th {background: #f5f5f5;border-bottom: 2px solid #069;}td {border-bottom: 1px dashed #ccc;}td,th {text-align: center;padding: 10px;transition: all 0.5s;&.red {color: red;}}.none {height: 100px;line-height: 100px;color: #999;}}.my-tag {cursor: pointer;.input {appearance: none;outline: none;border: 1px solid #ccc;width: 100px;height: 40px;box-sizing: border-box;padding: 10px;color: #666;&::placeholder {color: #666;}}}
}
</style>
3.my-tag組件封裝-創建組件
MyTag.vue
<template><div class="my-tag"><!-- <inputclass="input"type="text"placeholder="輸入標簽" /> --><div class="text">茶具</div></div>
</template><script>
export default {}
</script><style lang="less" scoped>
.my-tag {cursor: pointer;.input {appearance: none;outline: none;border: 1px solid #ccc;width: 100px;height: 40px;box-sizing: border-box;padding: 10px;color: #666;&::placeholder {color: #666;}}
}
</style>
App.vue
<template>...<tbody><tr>....<td><MyTag></MyTag></td></tr></tbody>...
</template>
<script>
import MyTag from './components/MyTag.vue'
export default {name: 'TableCase',components: {MyTag,},....</script>
十、綜合案例-MyTag組件控制顯示隱藏
MyTag.vue
<template><div class="my-tag"><inputv-if="isEdit"v-focusref="inp"class="input"type="text"placeholder="輸入標簽" @blur="isEdit = false" /><div v-else@dblclick="handleClick"class="text">茶具</div></div>
</template><script>
export default {data () {return {isEdit: false}},methods: {handleClick () {this.isEdit = true}}
}
</script>
main.js
// 封裝全局指令 focus
Vue.directive('focus', {// 指令所在的dom元素,被插入到頁面中時觸發inserted (el) {el.focus()}
})
十一、綜合案例-MyTag組件進行v-model綁定
App.vue
<MyTag v-model="tempText"></MyTag>
<script>export default {data(){tempText:'水杯'}}
</script>
MyTag.vue
<template><div class="my-tag"><inputv-if="isEdit"v-focusref="inp"class="input"type="text"placeholder="輸入標簽":value="value"@blur="isEdit = false"@keyup.enter="handleEnter"/><div v-else@dblclick="handleClick"class="text">{{ value }}</div></div>
</template><script>
export default {props: {value: String},data () {return {isEdit: false}},methods: {handleClick () {this.isEdit = true},handleEnter (e) {// 非空處理if (e.target.value.trim() === '') return alert('標簽內容不能為空')this.$emit('input', e.target.value)// 提交完成,關閉輸入狀態this.isEdit = false}}
}
</script>
十二、綜合案例-封裝MyTable組件-動態渲染數據
App.vue
<template><div class="table-case"><MyTable :data="goods"></MyTable></div>
</template><script>
import MyTable from './components/MyTable.vue'
export default {name: 'TableCase',components: { MyTable},data(){return {....}},
}
</script>
MyTable.vue
<template><table class="my-table"><thead><tr><th>編號</th><th>名稱</th><th>圖片</th><th width="100px">標簽</th></tr></thead><tbody><tr v-for="(item, index) in data" :key="item.id"><td>{{ index + 1 }}</td><td>{{ item.name }}</td><td><img:src="item.picture"/></td><td>標簽內容<!-- <MyTag v-model="item.tag"></MyTag> --></td></tr></tbody></table>
</template><script>
export default {props: {data: {type: Array,required: true}}
};
</script><style lang="less" scoped>.my-table {width: 100%;border-spacing: 0;img {width: 100px;height: 100px;object-fit: contain;vertical-align: middle;}th {background: #f5f5f5;border-bottom: 2px solid #069;}td {border-bottom: 1px dashed #ccc;}td,th {text-align: center;padding: 10px;transition: all .5s;&.red {color: red;}}.none {height: 100px;line-height: 100px;color: #999;}
}</style>
十三、綜合案例-封裝MyTable組件-自定義結構
App.vue
<template><div class="table-case"><MyTable :data="goods"><template #head><th>編號</th><th>名稱</th><th>圖片</th><th width="100px">標簽</th></template><template #body="{ item, index }"><td>{{ index + 1 }}</td><td>{{ item.name }}</td><td><img:src="item.picture"/></td><td><MyTag v-model="item.tag"></MyTag></td></template></MyTable></div>
</template><script>
import MyTag from './components/MyTag.vue'
import MyTable from './components/MyTable.vue'
export default {name: 'TableCase',components: {MyTag,MyTable},data () {return {....}
}
</script>
MyTable.vue
<template><table class="my-table"><thead><tr><slot name="head"></slot></tr></thead><tbody><tr v-for="(item, index) in data" :key="item.id"><slot name="body" :item="item" :index="index" ></slot></tr></tbody></table>
</template><script>
export default {props: {data: {type: Array,required: true}}
};
</script>