大家好, Capybara 继续与大家一起学习Vue框架。坚持就是胜利。
day05
自定义指令
指令注册
directive 指令
main.js(全局注册)
// inserted 会在 指令所在的元素,被插入到页面中时触发
import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false // // 1. 全局注册指令 Vue.directive('focus', { // inserted 会在 指令所在的元素,被插入到页面中时触发 inserted (el) { // el 就是指令所绑定的元素 console.log(el); el.focus() } }) new Vue({ render: h => h(App), }).$mount('#app')
App.vue(局部注册)
directives
<template> <div> <h1>自定义指令</h1> <input v-focus ref="inp" type="text"> </div> </template> <script> export default { // mounted () { // this.$refs.inp.focus() // } // 2. 局部注册指令 directives: { // 指令名:指令的配置项 focus: { inserted (el) { el.focus() } } } } </script> <style> </style>
指令的值
实操:
(1)没有传入指令的值
(2)传入指令的值
(3)传入指令的值(并用于修改元素文字颜色)
// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑
<template> <div> <h1 v-color="color1">指令的值1测试</h1> <h1 v-color="color2">指令的值2测试</h1> </div> </template> <script> export default { data () { return { color1: 'red', color2: 'orange' } }, directives: { color: { // 1. inserted 提供的是元素被添加到页面中时的逻辑 inserted (el, binding) { // console.log(el, binding.value); // binding.value 就是指令的值 el.style.color = binding.value }, // 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑 update (el, binding) { console.log('指令的值修改了'); el.style.color = binding.value } } } } </script> <style> </style>
修改指令的值:
页面发生变化
(打印多次,是内部更新机制问题)
自定义指令 - v-loading 指令封装
代码:
<template> <div class="main"> <div class="box" v-loading="isLoading"> <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 class="box2" v-loading="isLoading2"></div> </div> </template> <script> // 安装axios => yarn add axios import axios from 'axios' // 接口地址:http://hmajax.itheima.net/api/news // 请求方式:get export default { data () { return { list: [], isLoading: true, isLoading2: true } }, async created () { // 1. 发送请求获取数据 const res = await axios.get('http://hmajax.itheima.net/api/news') setTimeout(() => { // 2. 更新到 list 中,用于页面渲染 v-for this.list = res.data.data this.isLoading = false }, 2000) }, directives: { loading: { inserted (el, binding) { binding.value ? el.classList.add('loading') : el.classList.remove('loading') }, update (el, binding) { binding.value ? el.classList.add('loading') : el.classList.remove('loading') } } } } </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>
效果:
总结:
插槽
默认插槽
基本语法
代码演示:
不使用插槽时(组件内容一样、不可变、固定):
使用插槽:
MyDialog.vue
<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> <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> <!-- 2. 在使用组件时,组件标签内填入内容 --> <MyDialog> <div>你确认要删除么</div> </MyDialog> <MyDialog> <p>你确认要退出么</p> </MyDialog> </div> </template> <script> import MyDialog from './components/MyDialog.vue' export default { data () { return { } }, components: { MyDialog } } </script> <style> body { background-color: #b3b3b3; } </style>
效果:
后备内容
slot标签里面的内容会作为默认显示内容:
没有默认内容时(不显示任何内容):
往slot标签内部,编写内容,可以作为后备内容(默认值)
同时使用:
总结:
具名插槽
既想定制标题,也要定制内容:
用法:
template标签包裹内容,配合v-slot:名字来分发对应标签
v-slot可以简写成#
使用具名插槽:
总结:
作用域插槽
作用域插槽:是插槽的一个传参语法
使用步骤:
父组件 无法跨组件 拿到子组件的item
使用作用域插槽:
MyTable.vue
<template> <table class="my-table"> <thead> <tr> <th>序号</th> <th>姓名</th> <th>年纪</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item, index) in data" :key="item.id"> <td>{{ index + 1 }}</td> <td>{{ item.name }}</td> <td>{{ item.age }}</td> <td> <!-- 1. 给slot标签,添加属性的方式传值 --> <slot :row="item" msg="测试文本"></slot> <!-- 2. 将所有的属性,添加到一个对象中 --> <!-- { row: { id: 2, name: '孙大明', age: 19 }, msg: '测试文本' } --> </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"> <!-- 3. 通过template #插槽名="变量名" 接收 --> <template #default="obj"> <button @click="del(obj.row.id)"> 删除 </button> </template> </MyTable> <MyTable :data="list2"> <template #default="{ row }"> <button @click="show(row)">查看</button> </template> </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 }, ] } }, methods: { del (id) { this.list = this.list.filter(item => item.id !== id) }, show (row) { // console.log(row); alert(`姓名:${row.name}; 年纪:${row.age}`) } }, components: { MyTable } } </script>
总结:
个人认为作用域插槽传值,比组件通信-子传父-$emit 会更加简洁方便。
综合案例:商品列表
MyTag.vue
<template> <div class="my-tag"> <input v-if="isEdit" v-focus ref="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 () { // 双击后,切换到显示状态 (Vue是异步dom更新) this.isEdit = true // // 等dom更新完了,再获取焦点 // this.$nextTick(() => { // // 立刻获取焦点 // this.$refs.inp.focus() // }) }, handleEnter (e) { // 非空处理 if (e.target.value.trim() === '') return alert('标签内容不能为空') // 子传父,将回车时,[输入框的内容] 提交给父组件更新 // 由于父组件是v-model,触发事件,需要触发 input 事件 this.$emit('input', e.target.value) // 提交完成,关闭输入状态 this.isEdit = false } } } </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>
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> <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>
效果:
总结
路由入门
单页应用程序
对比:
总结:
路由概念
生活中的路由
Vue中的路由:
小结:
VueRouter 的基本使用
介绍:
使用步骤5+2
Vue版本对应关系:2 3 3 3 4 4
2个核心步骤
route
router
小结:
组件目录存放问题
页面组件和复用组件:
分类分开存放,更容易维护
练习1:
练习2:
小结: