Vue-组件高级(下)

一、目标

  • 能够知道如何使用ref引用DOM和组件实例
  • 能够知道$nextTick的调用时机
  • 能够说出keep-alive元素的作用
  • 能够掌握插槽的基本用法
  • 能够知道如何自定义指令

二、目录

  • ref引用
  • 动态组件
  • 插槽
  • 自定义指令
  • Table案例

ref引用


1.什么是ref引用

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

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

 2.使用ref引用DOM元素

如果想要使用ref引用页面上的DOM元素,则可以按照如下的方式进行操作:

 3.使用ref引用组件实例

如果想要使用ref引用页面上的组件实例,则可以按照如下的方式进行操作:

<template>
  <div>
    <h1>这是App.vue根组件</h1>
    <hr />
    <button type="button" class="btn btn-primary" @click="reset">重置</button>
    <ref ref="conterRef"></ref>
  </div>
</template>
<script>
import ref from "./components/ref/ref.vue";
export default {
  components: {},
  name: "MyApp",
  data() {
    return {};
  },
  methods: {
    reset() {
      this.$refs.conterRef.rset();
    },
  },
  components: {
    ref,
  },
};
</script>
<template>
  <div>
    <h1>数字:{
   
   { data }}</h1>
    <button type="button" @click="add">+1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      data: 0,
    };
  },
  methods: {
    add() {
      this.data++;
    },
    rset() {
      this.data = 0;
    },
  },
};
</script>

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

通过布尔值inputVisible来控制组件中的文本框与按钮的按需切换。示例代码如下:

5.让文本框自动获得焦点

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

 在这里会输出undefined,原因是  DOM元素的更新是异步的,当执行到这行代码的时候,Dom还没有进行更新。

6.this.$nextTick(cd)方法

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

动态组件 


1.什么是动态组件

动态组件指的是动态切换组件的显示与隐藏。vue提供了一个内置的<component>组件,专门用来实现组件的动态渲染。

①<component>是组件的占位符

②通过is属性动态指定要渲染的组件名称

③<component is="要渲染的组件的名称"></component>

2.如何实现动态组件渲染

<template>
  <div>
    <h1>这是App.vue根组件</h1>
    <hr />
    <button type="button" class="btn btn-primary" @click="comName = 'ref'">
      组件一
    </button>
    <button type="button" class="btn btn-primary" @click="comName = 'reff'">
      组件二
    </button>
    <component :is="comName"></component>
  </div>
</template>
<script>
import ref from "./components/ref/ref.vue";
import reff from "./components/ref/reff.vue";

export default {
  components: {},
  name: "MyApp",
  data() {
    return {
      comName: "ref",
    };
  },
  methods: {
    change() {},
  },
  components: {
    ref,
    reff,
  },
};
</script>

 3.使用keep-alive保持状态

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用vue内置的<keep-alive>组件保持动态组件的状态。示例代码如下:

  在切换组件的时,切换组件的时候组件的数据会重置。将component组件用keep-alive包括,可以缓存组件中的数据,切换组件时可以保存数据,不会丢失。 

插槽

1.什么是插槽

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

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

2.体验插槽的基础用法

 如果子组件中有内容不确定可以用slot占位,在父组件中使用子组件是插入即可,会自动渲染到子组件预留的位置。

 2.1没有预留插槽的内容会被丢弃

如果在封装组件时没有预留任何<slot>插槽,则用户提供的任何自定义内容会被丢弃。示例代码如下:

 没用slot的,不会被渲染出来

 2.2后备内容(默认内容)

封装组件时,可以为预留的<slot>插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。示例代码如下:

3.具名插槽

如果在封装组件时需要预留多个插槽节点,则需要为每个<slot>插槽指定具体的name名称。这种带有具体名称的插槽叫做“具名插槽”。示例代码如下: 

 注意:没有指定name名称的插槽,会有隐含的名称叫做"default"。

3.1为具名插槽提供内容

在向具名插槽提供内容的时候,我们可以在一个<template>元素上使用v-slot指令,并以v-slot的参数的形式提供其名称。示例代码如下:

必须使用template包裹,才能用v-slot指定具体的插槽

3.2具名插槽的简写形式

跟v-on和v-bind一样,v-slot也有缩写,即把参数之前的所有内容(v-slot:)替换为字符 #。例如v-slot:header可以被重写为#header 

4.作用域插槽

通过插槽由子组件向父组件传递数据,子组件用:info=‘数据’,父组件v-slot:插槽名字=“接收数据”

在封装组件的过程中,可以为预留的<slot>插槽绑定props数据,这种带有props数据的<slot>叫做“作用域插槽”。示例代码如下:

 无论子组件传递几个数据,父组件只需要用一个数据接收即可,这个数据是以对象的形式包裹。

4.1解构作用域插槽的Prop

案例一:

案例二:

将prop向父组件传递的数据结构出来{数据1,数据2}

自定义指令

 1.什么是自定义指令

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

vue中的自定义指令分为两类,分别是:

  • 私有自定义指令
  • 全局自定义指令

2.声明私有自定义指令的语法

在每个vue组件中,可以在directives节点下声明私有自定义指令。示例代码如下:

 4.声明全局自定义指令的语法

全局共享的自定义指令需要通过“单页面应用程序的实例对象”进行声明,示例代码如下:

5.updated函数

mounted函数只在元素第一次插入DOM时被调用,当DOM更新时mounted函数不会被触发。updated函数会在每次DOM更新完成后被调用。示例代码如下:

 注意:在vue2的项目中使用自定义指令时,【mounted->bind】【updated->update

6.函数简写

如果mounted和updated函数中的逻辑完全相同,则可以简写成如下格式:

 7.指令的参数值

在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值,示例代码如下:

Table案例 


1.案例效果

 2.用到的知识点

  • 组件封装
  • 具名插槽
  • 作用域插槽
  • 自定义指令

3.实现步骤

①搭建项目的基本结构

②请求商品列表的数据

封装MyTable组件

④实现删除功能

实现添加标签的功能

 

 

 

 

 

 

 

 

 

 

 

 

 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://applet-base-api-t.itheima.net';
app.config.globalProperties.$http = axios

app.mount('#app')

App.vue

<template>
  <div>
    <h1>这是App根组件</h1>
    <MyTable :data="goodslist">
      <template #header>
        <th>#</th>
        <th>商品名称</th>
        <th>价格</th>
        <th>标签</th>
        <th>操作</th>
      </template>
      <template v-slot: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"
            @click="deleteGoods(row.id)"
          >
            删除
          </button>
        </td>
      </template>
    </MyTable>
  </div>
</template>

<script>
import MyTable from "./components/my-table/MyTable.vue";
export default {
  name: "MyApp",
  data() {
    return {
      //商品列表的数据
      goodslist: [],
    };
  },
  created() {
    //发起请求
    this.getGoodsList();
  },
  components: { MyTable },
  methods: {
    async getGoodsList() {
      const { data: res } = await this.$http.get("/api/goods");
      if (res.status !== 0) {
        return console.log("获取商品列表数据失败!");
      }
      this.goodslist = res.data;
    },
    //根据id删除商品
    deleteGoods(id) {
      this.goodslist = this.goodslist.filter((x) => x.id !== id);
    },
    onInputConfirm(row) {
      const val = row.inputValue;
      row.inputValue = "";
      row.inputVisible = false;
      if (!val || row.tags.indexOf(val) !== -1) {
        return;
      }
      row.tags.push(val);
    },
  },
  directives: {
    focus(el) {
      el.focus();
    },
  },
};
</script>

<style lang="less" scoped>
.form-ipt {
  width: 80px;
  display: inline;
}
</style>

MyTable.vue

<template>
  <table class="table table-bordered table-striped">
    <!-- 标题区域 -->
    <thead>
      <tr>
        <slot name="header"></slot>
      </tr>
    </thead>
    <!-- 内容主题区域 -->
    <tbody>
      <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,
      require: true,
      default: [],
    },
  },
  Date() {
    return {};
  },
};
</script>

<style lang="less" scoped>
</style>

总结


①能够知道如何使用ref引用DOM和组件实例

  • 通过ref属性指令引用的名称、使用this.$refs访问引用实例

②能够知道$nextTick的调用时机

  • 组件的DOM更新之后,才执行$nextTick中的回调

③能够说出keep-alive元素的作用

  • 保持动态组件的状态

④能够掌握插槽的基本用法

  • <slot>标签、具名插槽、作用域插槽、v-slot:简写为#

⑤能够知道如何自定义指令

  • 私有自定义指令、全局自定义指令

猜你喜欢

转载自blog.csdn.net/a_xia_o/article/details/131860061