1.ref 引用

ref 用来辅助开发者在不依赖 jQuery 的情况下,获取DOM元素或组件的引用。

1.1 每个vue的组件实例上,都包含一个$refs对象,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的$refs指向一个空对象。

1.2 使用ref引用页面上的DOM元素和引用页面上的组件实例:

<template>
  <div>
    <!-- 1、使用ref属性,为对应的DOM添加引用名称 -->
    App 根组件
    <button type="button" class="btn btn-primary" @click="getRefs">获取 $refs 引用</button>
    <!-- 3、使用ref属性,为组件添加引用名称 -->
    <my-counter ref="counterRef"></my-counter>
  </div>
</template>
 
<script>
// 导入组件
import MyCounter from './Counter.vue'
 
export default {
  name: 'MyApp',
  methods: {
    getRefs() {
      // this代表当前组件的实例对象,this.$refs默认指向空对象
      // 通过this.$refs.引用的名称 可以获取到DOM元素的引用
      // console.log(this.$refs)
      // 2、操作DOM元素,把文本颜色变成红色
      // this.$refs.myh11.style.color = 'red'
 
      // 4、拿到组件的引用,调用组件上的reset()方法
      // console.log(this.$refs.counterRef)
      this.$refs.counterRef.reset()
    },
  },
  // 注册组件
  components: {
    MyCounter,
  },
}
</script>
<template>
  <div class="counter-container">
    <h3>Counter 组件 --- {{ count }}</h3>
    <hr />
    <button type="button" class="btn btn-info" @click="count += 1">+1</button>
  </div>
</template>
 
<script>
export default {
  name: 'MyCounter',
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    reset() {
      this.count = 0
    },
  },
}
</script>
 
<style lang="less" scoped>
.counter-container {
  margin: 20px;
  padding: 20px;
  border: 1px solid #efefef;
  border-radius: 4px;
  box-shadow: 0px 1px 10px #efefef;
}
</style>

1.3 控制文本框和按钮的按需切换

  • 通过布尔值 inputVisible 来控制组件中文本框与按钮的按需切换。

  • 当文本框展示出来之后,如果希望它立即获得焦点,可以为其添加 ref 引用,并调用原生 DOM 对象的 .focus() 方法即可。

  • 在vue中DOM元素的更新是异步进行更新的,同步执行 this.$refs.ipt.focus() 时DOM还没有完成更新,此时还没有文本框,所以拿不到最新的DOM,会报错。需要将 this.$refs.ipt.focus() 这行代码进行延迟,延迟到DOM更新完成再执行,此时使用 this.$nextTick(cb)

  • 组件的 $nextTick(cb) 方法: 把cb回调推迟到下一个DOM更新周期之后执行。(即,等组件的DOM异步地重新渲染完成后,再执行cb回调函数,从而保证cb回调函数可以操作到最新的DOM元素。)

<template>
  <div>
    App 根组件
    <hr />
    <input type="text" class="form-control" v-if="inputVisible" ref="ipt" />
    <button type="button" class="btn btn-primary" v-else @click="showInput">展示 input 输入框</button>
  </div>
</template>
 
<script>
export default {
  name: 'MyApp',
  data() {
    return {
      inputVisible: false,
    }
  },
  methods: {
    showInput() {
      // 要展示文本框
      this.inputVisible = true
      // 延迟到DOM重新渲染完成后,再执行回调函数
      this.$nextTick(() => {
        //console.log(this.$refs.ipt)
        // 获取到文本框的DOM引用对象,然后调用 focus() 方法使其自动获得焦点
        this.$refs.ipt.focus()
      })
    },
  },
}
</script>
 
<style lang="less" scoped>
input.form-control {
  width: 280px;
  display: inline;
  vertical-align: bottom;
}
</style>

2.动态组件

动态组件是指动态切换组件的显示与隐藏。

2.1 vue提供了一个内置的<component>组件,专门用来实现组件的动态渲染。

  • <component> 是组件的占位符。
  • 通过 is 属性动态指定要渲染的组件名称。
  • <component is="要渲染的组件名称"></component>

2.2 如何实现动态组件渲染:

  • 使用keep-alive保持组件状态:默认情况下,切换动态组件时无法保持组件的状态,此时可以使用vue内置的<keep-alive>组件保持动态组件的状态,从而使其不会被销毁。
<template>
  <div>
    App 根组件
    <button type="button" class="btn btn-primary" @click="comName = 'MyHome'">首页</button>
    <button type="button" class="btn btn-info ml-2" @click="comName = 'MyMovie'">电影</button>
    <hr />
    <!-- 保持动态组件状态 -->
    <keep-alive>
      <!-- 动态组件 -->
      <component :is="comName"></component>
    </keep-alive>
  </div>
</template>
 
<script>
// 导入组件
import MyHome from './Home.vue'
import MyMovie from './Movie.vue'
 
export default {
  name: 'MyApp',
  data() {
    return {
      comName: 'MyHome'
    }
  },
  components: {
    MyHome,
    MyMovie,
  },
}
</script>

3.插槽

插槽是组件封装期间,为用户预留的内容占位符。

3.1 插槽的定义

  • 插槽(Slot)是vue为组件封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。

3.2 插槽的基础用法

  • 在封装组件时,可以通过<slot>元素定义插槽,从而为用户预留内容占位符。
  • 如果在封装组件时没有预留任何<slot>插槽,则用户提供的任何自定义内容都被会丢弃。
  • 封装组件时,可以为预留的<slot>插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。
<template>
  <div class="com-container">
    <h3>MyCom 组件 --- 插槽的基础用法</h3>
    <hr />
    <p>这是第一个 p 标签</p>
    <slot>这里是后备内容</slot>
    <p>这是最后一个 p 标签</p>
  </div>
</template>
 
<script>
export default {
  name: 'MyCom',
}
</script>
<template>
  <div>
    App 根组件
    <hr />
    <my-com>
        这里是用户自定义插槽内容
    </my-com>
  </div>
</template>
 
<script>
import MyCom from './MyCom.vue'
 
export default {
  name: 'MyApp',
  components: {
    MyCom,
  },
}
</script>

3.3 具名插槽

  • 如果在封装组件时需要预留多个插槽节点,则需要为每个<slot>插槽指定具体的name名称,这种带有具体名称的插槽叫做“具名插槽”。
  • 没有指定name名称的插槽,会有隐含的名称叫做default。
  • 向具名插槽提供内容时,我们可以在一个<template>元素上使用v-slot指定,并以v-slot的参数的形式提供其名称。v-slot: 可以简写为 # 。【注意:外层的<template>是不能省略的!!!】
<template>
  <div>
    <!-- 我们希望把页头放到这里 -->
    <header>
      <slot name="header"></slot>
    </header>
    <!-- 我们希望把主要内容放到这里 -->
    <main>
      <slot></slot>
    </main>
    <!-- 我们希望把页脚放到这里 -->
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>
 
<script>
export default {
  name: 'MyArticle',
}
</script>
<template>
  <div>
    App 根组件
    <hr />
    <my-article>
      <template v-slot:header>
        滕王阁序
      </template>
      <template #default>
        <p>豫章故郡,洪都新府。</p>
        <p>星分翼轸,地接衡庐</p>
        <p>襟三江而带五湖,控蛮荆而引瓯越。</p>
      </template>
      <template #footer>
        <p>落款:王勃</p>
      </template>
    </my-article>
  </div>
</template>
 
<script>
import MyArticle from './MyArticle.vue'
 
export default {
  name: 'MyApp',
  components: {
    MyArticle,
  },
}
</script>

3.4 作用域插槽

  • 在封装组件的过程中,可以为预留的<slot>插槽绑定props数据,这种带有props数据的<slot>叫做“作用域插槽”。
  • 使用场景:在封装组件的时候,如果不确定组件的DOM渲染成什么样子,同时用户需要获取数据,这时可以使用作用域插槽的形式把数据传递给使用者。
<template>
  <div>
    <h3>这是 TEST 组件</h3>
    <!-- 默认插槽,默认名称为default -->
    <!-- 为插槽动态地绑定数据 -->
    <slot :info="infomation" :msg="message"></slot>
  </div>
</template>
 
<script>
export default {
  name: 'MyTest',
  data() {
    return {
      infomation: {
        phone: '138xxxx6666',
        address: '中国北京',
      },
      message: 'abc'
    }
  },
}
</script>
<template>
  <table class="table table-bordered table-striped table-dark table-hover">
    <!-- 表头区域 -->
    <thead>
      <tr>
        <th>Id</th>
        <th>Name</th>
        <th>State</th>
      </tr>
    </thead>
    <!-- 表格主体区域 -->
    <tbody>
      <!-- 循环渲染表格数据 -->
      <tr v-for="item in list" :key="item.id">
        <slot :user="item"></slot>
      </tr>
    </tbody>
  </table>
</template>
 
<script>
export default {
  name: 'MyTable',
  data() {
    return {
      // 列表的数据
      list: [
        { id: 1, name: '张三', state: true },
        { id: 2, name: '李四', state: false },
        { id: 3, name: '赵六', state: false },
      ],
    }
  },
}
</script>
<template>
  <div>
    App 根组件
    <hr />
    <!-- 使用自定义组件 MyTest -->
    <my-test>
      <!-- 接收插槽传过来的数据,用{ }解构赋值 -->
      <template #default="{ msg, info }">
        <p>{{ msg }}</p>
        <p>{{ info.address }}</p>
      </template>
    </my-test>
    <hr />
    <!-- 使用自定义组件 MyTable -->
    <my-table>
      <template #default="{ user }">
        <td>{{ user.id }}</td>
        <td>{{ user.name }}</td>
        <td>
          <input type="checkbox" :checked="user.state" />
        </td>
      </template>
    </my-table>
  </div>
</template>
 
<script>
import MyTest from './MyTest.vue'
import MyTable from './MyTable.vue'
 
export default {
  name: 'MyApp',
  components: {
    MyTest,
    MyTable,
  },
}
</script>

解构赋值 - JavaScript | MDN

4 自定义指令

vue官方提供了v-for、v-model、v-if等常用的内置指令。除此之外还允许开发者自定义指令。

4.1 私有自定义指令

  • 自定义指令在使用的时候必须以v-前缀开头,但是在声明自定义指令时不需要v-前缀。
  • 在每个vue组件中,可以在directives节点下声明私有自定义指令。

4.2 全局自定义指令

  • 全局共享的自定义指令需要通过 “单页面应用程序的实例对象” 进行声明。

4.3 mounted 函数和 updated 函数

  • mounted 函数只在元素第一次插入DOM时被调用,当DOM更新时 mounted 函数不会被触发。
  • updated 函数会在每次DOM更新完成后被调用。
  • 注意:在vue2.0项目中使用自定义指令时,mounted要改为bind,updated要改为update。
  • 注意:如果mounted和updated函数中的逻辑完全相同,可以进行函数式的简写。

4.4 指令的参数值

  • 在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值。
<template>
  <div class="home-container">
    <h3 v-color="'red'">MyHome 组件 --- {{ count }}</h3>
    <hr />
    <!-- 自定义指令 v-focus -->
    <!-- 在使用自定义指令 v-color 时,可以通过=绑定指令的值 -->
    <input type="text" class="form-control" v-focus v-color="'cyan'" />
    <button type="button" class="btn btn-primary" @click="count += 1">+1</button>
  </div>
</template>
 
<script>
export default {
  name: 'MyHome',
  data() {
    return {
      count: 0,
    }
  },
  directives: {
    // 自定义一个私有指令 v-focus
    // focus: {
    //   mounted(el) { // 当被绑定的元素被渲染到DOM中时,自动触发mounted函数
    //     el.focus()  // 让被绑定的元素自动获得焦点
    //   },
    // },
  },
}
</script>
// main.js
const app = createApp(App)
 
// 声明全局自定义指令
/* app.directive('focus', {
  mounted(el) {
    console.log('mounted')
    el.focus()
  },
  updated(el) {
    console.log('updated')
    el.focus()
  },
}) */
// 如果mounted和updated函数中的逻辑完全相同,可以简写为如下格式
app.directive('focus', (el) => {
  el.focus()
})
 
app.directive('color', (el, binding) => {
  // binding.value 就是通过=为指令绑定的值
  el.style.color = binding.value
})
 
app.mount('#app')

5 Table案例

  • main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import './assets/css/bootstrap.css'
import axios from 'axios'
 
const app=createApp(App)
 
axios.defaults.baseURL='https://www.escook.cn'
app.config.globalProperties.$http=axios
 
app.mount('#app')
  • index.css
:root{
  font-size: 12px;
}
 
body{
  padding: 8px;
}
  • App.vue
<template>
  <div>
    App根组件
    <hr/>
    <!-- 通过属性绑定的形式为表格指定data数据源 -->
    <my-table :data="goodslist">
      <!-- 表格的标题 -->
      <template #header>
        <th>#</th>
        <th>商品名称</th>
        <th>价格</th>
        <th>标签</th>
        <th>操作</th>
      </template>
      <!-- 表格每行的单元格 -->
      <template #body="{row,index}">
        <td>{{index+1}}</td>
        <td>{{row.goods_name}}</td>
        <td>¥{{row.goods_price}}</td>
        <td>
          <!-- 添加标签文本框 -->
          <input
            type="text"
            class="form-control form-control-sm form-ipt"
            v-if="row.inputVisible"
            v-focus
            v-model.trim="row.inputValue"
            @blur="onInputConfirm(row)"
            @keyup.enter="onInputConfirm(row)"
            @keyup.esc="row.inputValue=''"
          />
          <!-- 添加标签按钮 -->
          <button type="button" class="btn btn-primary btn-sm" v-else @click="row.inputVisible=true">+Tag</button>
          <!-- 循环渲染标签信息 -->
          <span class="badge badge-warning ml-2" v-for="item in row.tags" :key="item">{{ item }}</span>
        </td>
        <td>
          <button type="button" class="btn btn-danger btn-sm" @click="onRemove(row.id)">删除</button>
        </td>
      </template>
    </my-table>
  </div>
</template>
 
<script>
import MyTable from './components/my-table/MyTable.vue'
 
export default {
  name: 'MyApp',
  data(){
    return{
      //商品列表数据
      goodslist:[]
    }
  },
  created(){
    this.getGoodsList()
  },
  methods:{
    //初始化商品列表的数据
    async getGoodsList(){
      const {data:res}=await this.$http.get('/api/goods')
      if(res.status!==0) return console.log('获取商品列表失败!')
      this.goodslist=res.data
    },
    //根据id删除商品信息
    onRemove(id){
      this.goodslist=this.goodslist.filter(x=>x.id!==id)
    },
    onInputConfirm(row){
      // 1、把用户在文本框中输入的值,预先转存到常量val中
      const val=row.inputValue
      // 2、清空文本框的值
      row.inputValue=''
      // 3、隐藏文本框
      row.inputVisible=false
      // 4、判断val的值是否为空,若为空不添加;判断val值是否存在于tags数组中,防止重复添加
      if(!val||row.tags.indexOf(val)!==-1)return
      // 5、将用户输入的内容,作为新标签push到当前行的tags数组中
      row.tags.push(val)
    },
  },
  directives:{
    focus(el){
      el.focus()
    }
  },
  components: {
    MyTable
  }
}
</script>
 
<style lang="less" scoped>
.form-ipt {
  width: 80px;
  display: inline;
}
</style>
  • /components/my-table/MyTable.vue
<template>
  <table class="table table-bordered table-striped">
    <!-- 表格:标题区域 -->
    <thead>
      <tr>
        <!-- 为了提高组件复用性,把表格标题区域预留为具名插槽,方便使用者自定义表格标题 -->
        <slot name="header"></slot>
      </tr>
    </thead>
    <!-- 表格:内容主体区域 -->
    <tbody>
      <!-- 使用v-for命令,循环渲染表格的数据行 -->
      <tr v-for="(item,index) in data" :key="item.id">
          <!-- 为了提高组件复用性,把表格数据行区域预留为作用域插槽,方便使用者自定义表格内容 -->
          <slot name="body" :row="item" :index="index"></slot>
      </tr>
    </tbody>
  </table>
</template>
 
<script>
export default {
  name: 'MyTable',
  props:{
      //表格的数据源
      data:{
          type:Array,
          required:true,
          default:[],
      },
  },
}
</script>
 
<style lang="less" scoped>
 
</style>

参考: https://blog.csdn.net/SongD1114/article/details/124041382

https://blog.csdn.net/Gik99/article/details/129349041

https://blog.csdn.net/Gik99/article/details/129343661