vue核心技术

196 阅读4分钟

vue核心技术与实战

自定义指令

image.png

自定义指令:自己定义的指令,可以封装一些dom操作,扩展额外功能

基本语法

全局注册:

image.png

// 1. 全局注册指令
Vue.directive('focus', {
  // inserted 会在 指令所在的元素,被插入到页面中时触发
  inserted (el) {
    // el 就是指令所绑定的元素
    // console.log(el);
    el.focus()
  }
})

局部注册:

image.png

 directives:{
    loading:{
      inserted(el,banding){
       banding.value ? el.classList.add('loading') : el.classList.remove('loading')
      },
      update(el,banding){
        banding.value ? el.classList.add('loading') : el.classList.remove('loading')
      }
    }
  }

需求:当页面加载时,让元素获得焦点

image.png

指令的值

需求:实现一个color指令-传入不同的颜色,给标签设置文字颜色

语法:

  1. 在绑定指令时,可以通过=的形式为指令绑定具体的参数值

image.png 2. 通过binding.value可以拿到指令值,指令值修改会触发update函数

image.png

image.png

v-loading指令封装

场景:实际开发中没发送请求需要时间,在请求的数据没有回来时,页面会处于空白状态,用户体验不好

需求:封装一个v-loading指令,实现加载中的效果

image.png

分析:

  1. 本质loading效果就是一个蒙层,盖在了盒子上
  2. 数据请求中,开启loading状态,添加蒙层
  3. 数据请求完毕,关闭loading状态,移除蒙层

实现:

  1. 准备一个loading类,通过伪元素定位,设置宽高,实现蒙层
.loading:before{
  content: '';
  position:absolute;
  left: 0;
  right: 0;
  width: 100%;
  height: 100%;
  background: #fff url(./loading.gif) no-repeat center;

}
  1. 开启关闭loading状态(添加移除蒙层),本质只需要添加移除类即可
<template>
  <div>
    <div class="box" v-loading="isLoading">
    <ul>
      <!-- v-for  v-if  v-else 用来循环数据,选择展示标签 -->
      <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>

定义v-if v-else具体展示哪个标签(控制loading类到底是添加还是移除)

data () {
    return {
      isLoading:true,
      isLoading2:true,
      list: []
    }
  },
  1. 结合具体使用哪个(私有还是全局)自定义指令的语法进行封装复用

inserted钩子中,binding.value判断指令的值,设置默认状态

update钩子中,binding.value判断指令的值,更新状态

//私有自定义指令
 directives:{
    loading:{
      inserted(el,banding){
       banding.value ? el.classList.add('loading') : el.classList.remove('loading')
      },
      update(el,banding){
        banding.value ? el.classList.add('loading') : el.classList.remove('loading')
      }
    }
  }
//全局自定义指令
Vue.directive('loading',{
  instered(el,binding){
    binding.value?el.classList.add('.loading'):el.classList.remove('loading')
  },
  update(el,bingding){
    binding.value?el.classList.add('.loading'):el.classList.remove('loading')
  }

插槽

默认插槽

作用:让组件内部的一些结构支持自定义

需求: 将需要多次显示的对话框,封装成一个组件

问题:组件的内容部分,不希望写死,希望能使用的时候自定义,怎么办?

插槽基本语法:

  1. 组件内需要定制结构部分改用<slot></slot>占位
  2. 使用组件时,<Mysialog></Mydialog>标签内部,传入结构替换slot

后备内容(默认值)

通过插槽完成了内容的定制,传什么显示什么,但是如果不传,则是空白,在<slot>标签内,放置内容,作为默认显示内容

image.png

具名插槽

需求:一个组件内有多处结构从外部标签传入,所以需要区分这几个标签分别放在哪个位置,需要定制名字进行区分

具名插槽语法:

  1. 多个slot使用name属性区分名字

  2. template配合v-slot:名字来分发对应标签

image.png

简化语法:

image.png

作用域插槽

定义slot插槽的同时是可以传值的,给插槽上可以绑定数据,将来使用组件时可以用 基本使用步骤:

  1. 给slot标签,以添加属性的方式传值
<slot :id='item.id' msg='测试文本'></slot>
  1. 所有添加的属性,都会被收集到一个对象中
{id:3,msg:'测试文本'} 
  1. 在template中,通过#插槽名="obj"接收,默认插槽名伪default
<MyRTable :list='list'>
<template #default="obj">
<button @click="del(obj.id)">删除</button>
</template>
</Mytable>

场景:封装表格组件

  1. 父传子,动态渲染表格内容
  2. 利用默认插槽,定制操作列
  3. 删除或查看都需要用到当前项的id,属于组件内部的数据,通过作用域插槽传值绑定,进而使用

父组件App

//父组件
<template>
  <div>
<MyTable :data="list"><!-- 父传子,绑定list数据动态渲染表格内容,在子组件中props中接收 -->
 
  <template #default="obj"> <!-- 在子组件中slot传过来的值,使用  #插槽名字接收 -->
    <button @click="del(obj.row.id)">删除</button><!--绑定点击事件,里面的值是来自从插槽接收到的值-->
  </template>
</MyTable>
    

     <MyTable :data="list2">
     <template #default="obj">
      <button @click="show(obj.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(obj)
      alert(`姓名:${row.name}  年龄:${row.age}`)
    }
  },
  components: {
    MyTable
  }
}
</script>

子组件Mytable

//子组件
<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:   ,name:  ,age :  },
              msg:测试项目
            }
           -->
          
        </td>
      </tr>
     
    </tbody>
  </table>
</template>

<script>
export default {
  props:{
    data:Array
  }

}
</script>

综合案例:商品列表

Mytag组件封装

mt-Tag组件的封装(表格结构的渲染)

  1. 双击显示输入框,输入自动框自动获取焦点

v-if v-else @dblclick 操作isapper 控制输入框的显示

自动聚焦: ①:nextTick(()=>{$refs})获取到dom,进行focus获取焦点②:封装v-focus全局指令 标签内使用v-focus命令即可

  1. 失去焦点,隐藏输入框

    @blur操作isappear即可

  2. 回显标签信息

回显的标签信息是父组件传递过来的 v-model实现功能(简化代码)v-model=>:value和@input 组件内部通过props接收,value设置给输入框

  1. 内容修改,回车-->修改标签信息

@keyup.enter,触发事件$emit('input',e.target.value)

Mytable组件封装

my-table 表格组件的封装(表格中的具体数据渲染)

  1. 数据不能写死,动态传递表格渲染的数据 props
  2. 结构不能写死 - 多处结构自定义 【具名插槽】

(1) 表头支持自定义(2) 主体支持自定义

案例源代码

My-Tag组件代码:

//My-Tag组件
<template>
    <div class="my-tag">
              <input
              v-if="isappear"
              ref="inp"
              v-focus
                class="input"
                type="text"
                placeholder="输入标签"
                @blur="isappear=false"
                @keyup.enter="handelenter"
              />
              <div class="text" v-else @dblclick="handleclick">{{value}}</div>
              <!--  1. v-if和v-else控制二者到底谁显示或隐藏
                    2. ref获取当前元素
                    3. v-focus是注册的自定义全局指令,表单一显示就自动获取焦点
                    4. @blur是失去焦点监听事件
                    5. @keyup是键盘按下回车键的监听事件
                    6. @dblclick是监听鼠标双击事件--鼠标双击之后展示表单且把value值回显在表单上-->
            </div>
</template>

<script>
export default {
    props:{
        value:String
    }
    ,
    data(){
        return{
            isappear:false //布尔值控制输入框与标签到底谁显示
        }
    },
    methods:{
        handleclick(){
            //双击后切换到显示状态(vue是异步dom更新)
            this.isappear=true
                //等dom更新完了再获取焦点
           /*  this.$nextTick(()=>{
                //立刻获取焦点
                this.$refs.inp.focus()
            }) */
        },
        handelenter(e){
            this.$emit('input',e.target.value)
            this.isappear=false
        }
    }

}
</script>

<style lang="less">
.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>

my-Table组件代码:

<template>
   <table class="my-table">
      <thead>
        <tr>
          <slot name="head"></slot><!--具名插槽-->
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in data" :key="item.id"><!--循环遍历data中的值,并将id绑定到key属性中-->
       <slot name="body" :item="item"></slot><!--通过具名插槽传值-->
     </tr>
      </tbody>
    </table>
</template>

<script>
export default {
    props:{
    //接收父组件传来的值
        data:{
            type:Array,
            required:true,
        }
    }

}
</script>

</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>

App组件代码:

//App组件
<template>
  <div class="table-case">
   <MyTable :data="goods"><!--父向子传值,子组件props接收-->
    <template #head>
      <th>编号</th>
          <th>图片</th>
          <th>名称</th>
          <th width="100px">标签</th>
    </template>
    <template #body="{item}">
     
          <td>{{item.id}}</td>
          <td><img :src="item.picture"/> </td>
          <td>{{ item.name }}</td>
          <td>
            <MyTag v-model="item.tag">
            </MyTag><!--双向绑定组件的值,子组件向当前父组件传值时直接使用input事件即可被监听到-->
           
          </td>
       
    </template>
  
  
  </MyTable>
  </div>
</template>

<script>
import MyTag from '@/components/MyTag.vue'
import MyTable from '@/components/MyTable.vue'
//mytag 标签组件的封装
//1. 创建组件--初始化
//2. 实现功能
// (1) 双击显示,并且自动聚焦
//      v-if v-else @dblclick 操作 isapper
//      自动聚焦:
//      1. $nextTick=>$refs 获取到dom,进行focus获取焦点
//      2. 封装v-focus 全局指令 标签内使用v-focus命令即可
// (2) 失去焦点,隐藏输入框
// (3) 回显标签信息
//      会显的标签信息是父组件传递过来的
// (4) 内容修改了,回车 =>修改标签信息

export default {
  components:{
    MyTag,
    MyTable
  },
  name: 'TableCase',
  data () {
    return {
      tempText:'茶壶',
      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;
  }
}

</style>