Vue 组件化开发

本文详细介绍了Vue.js中的组件化开发思想,包括组件化的概念、创建与注册组件的步骤、全局与局部组件的区别、父子组件间的数据传递以及如何通过props和事件进行通信。此外,还探讨了组件的插槽功能,包括默认插槽、具名插槽和作用域插槽的使用方法,以提高组件的可扩展性。

Vue 组件化开发

1. 什么是组件化

  • 人面对复杂问题的处理方式:
    • 任何一个人处理信息的逻辑能力都是有限的
    • 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。
    • 但是,我们人有一种天生的能力,就是将问题进行拆解。
    • 如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。
  • 组件化也是类似的思想:
    • 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
    • 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

2. Vue组件化思想

  • 组件化是Vue.js中的重要思想
    • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
    • 任何的应用都会被抽象成一颗组件树。
  • 组件化思想的应用:
    • 有了组件化的思想,我们在之后的开发中就要充分的利用它。
    • 尽可能的将页面拆分成一个个小的、可复用的组件。
    • 这样让我们的代码更加方便组织和管理,并且扩展性也更强。

3. 注册组件的基本步骤

  • 组件的使用分成三个步骤:

    • 创建组件构造器
    • 注册组件
    • 使用组件
  • 通过代码注册组件

    • 组件的基本使用
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <!-- 3. 使用组件 -->
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
        </div>
      </body>
      <script src="../../vue.js"></script>
      <script>
        // 1. 创建组件构造器
        const cpn = Vue.extend({
          template: `<h1>hello</h1>`,// 模板 页面结构
        });
        // 2. 注册组件 Vue.component(标签名, 组件构造器);
        Vue.component("cpn", cpn);
    
        const app = new Vue({
          el: "#app",
        });
      </script>
    </html>
    
    • 组件的基本使用(语法糖)
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <!-- 3. 使用组件 -->
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
        </div>
      </body>
      <script src="../../vue.js"></script>
      <script>
        Vue.component("cpn", {
          template: `<h1>hello</h1>`,
        });
    
        const app = new Vue({
          el: "#app",
        });
      </script>
    </html>
    

在这里插入图片描述

3.1 注册组件步骤解析
  • Vue.extend()

    • 调用Vue.extend()创建的是一个组件构造器。
    • 通常在创建组件构造器时,传入template代表我们自定义组件的模板。
    • 该模板就是在使用到组件的地方,要显示的HTML代码。
    • 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用上面我们会写到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
  • Vue.component()

    • 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称
    • 所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
  • 组件必须挂载在某个Vue实例下,否则它不会生效。

    • 代码实例:
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <!-- 3. 使用组件 -->
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
          <cpn></cpn>
        </div>
        <div id="title">
          <p>title</p>
          <cpn></cpn>
        </div>
      </body>
      <script src="../../vue.js"></script>
      <script>
        Vue.component("cpn", {
          template: `<h1>hello</h1>`,
        });
    
        const app = new Vue({
          el: "#app",
        });
      </script>
    </html>
    

在这里插入图片描述

4. 全局组件和局部组件

  • 当我们通过调用Vue.component()注册组件时,组件的注册是全局的

    • 这意味着该组件可以在任意Vue实例下使用。
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <!-- 单标签和双标签都可以 -->
          <p>app:</p>
          <my-cpn></my-cpn>
          <my-cpn />
        </div>
        <div id="title">
          <p>title:</p>
          <my-cpn></my-cpn>
          <my-cpn />
        </div>
      </body>
      <script src="../../vue.js"></script>
      <script>
        // 这种方式注册组件 是一个全局组件。 全局组件:可以在任意 Vue 实例下挂载的区域使用
        Vue.component("my-cpn", {
          template: `<div>
            <h>hello world</h1>
            </div>`, // 模板
        });
    
        const app = new Vue({
          el: "#app",
        });
        const title = new Vue({
          el: "#title",
        });
      </script>
    </html>
    

在这里插入图片描述

  • 如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <p>app:</p>
          <my-cpn></my-cpn>
        </div>
        <div id="title">
          <p>title:</p>
          <my-cpn></my-cpn>
        </div>
      </body>
      <script src="../../vue.js"></script>
      <script>
        // 创建组件构造器
        const cpn = Vue.extend({
          template: `<div>
            <h1>hello world</h1>
            </div>`,
        });
        const app = new Vue({
          el: "#app",
          components: {
            // 局部注册
            "my-cpn": cpn,
          },
        });
        const title = new Vue({
          el: "#title",
        });
      </script>
    </html>
    

在这里插入图片描述

  • 局部注册语法糖

    • 一般用法
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <p>app:</p>
          <my-cpn></my-cpn>
        </div>
      </body>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          components: {
            // 局部注册
            "my-cpn": {
              template: `
                <div>
                  <h1>hello world</h1>
                </div>`,
            },
          },
        });
      </script>
    </html>
    
    • script 用法
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <p>app:</p>
          <my-cpn></my-cpn>
        </div>
      </body>
      <!-- 这样可以方便直接写 html 代码 -->
      <script type="text/template" id="cpn">
        <div>
          <h1>hello world</h1>
        </div>
      </script>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          components: {
            // 局部注册
            "my-cpn": {
              template: cpn,
            },
          },
        });
      </script>
    </html>
    
    • template用法
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <p>app:</p>
          <my-cpn></my-cpn>
        </div>
      </body>
      <!-- 这样可以方便直接写 html 代码 -->
      <template id="cpn">
        <div>
          <h1>hello world</h1>
        </div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          components: {
            // 局部注册
            "my-cpn": {
              template: cpn,
            },
          },
        });
      </script>
    </html>
    

5. 父组件和子组件

  • 组件树:

    • 组件和组件之间存在层级关系
    • 而其中一种非常重要的关系就是父子组件的关系
  • 通过代码如何组成的这种层级关系:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <father></father>
        </div>
      </body>
      <template id="father">
        <div>
          <h3>father</h3>
          <children></children>
        </div>
      </template>
      <template id="children">
        <div>
          <h3>children</h3>
        </div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          components: {
            father: {
              template: father,
              components: {
                children: {
                  template: children,
                },
              },
            },
          },
        });
      </script>
    </html>
    

在这里插入图片描述

5.1 组件可以访问Vue实例数据吗?
  • 组件是一个单独功能模块的封装:
    • 这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
  • 组件中的数据是保存在哪里呢?顶层的Vue实例中吗?
    • 组件中能不能直接访问Vue实例中的data
      • 是不能访问的,而且即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变的非常臃肿。
    • 结论:Vue组件应该有自己保存数据的地方。
5.2 组件数据的存放
  • 组件自己的数据存放在哪里呢?

    • 组件对象也有一个data属性(也可以有methods等属性)
    • 只是这个data属性必须是一个函数
    • 而且这个函数返回一个对象,对象内部保存着数据
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <!-- 使用子组件 -->
          <cpn></cpn>
        </div>
      </body>
      <!-- 子组件 -->
      <template id="cpn">
        <div>
          <h1>子组件的message:{{message}}</h1>
        </div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        // 子组件中无法直接访问父组件的data的数据
        // 子组件可以拥有 data,并且data必须是一个函数,并且返回一个对象
        // 父组件(根组件)
        const app = new Vue({
          el: "#app",
          data: {
            message: "hello world",
          },
          components: {
            // 注册子组件
            cpn: {
              template: cpn,
              data() {
                return {
                  message: "hello world",
                };
              },
            },
          },
        });
      </script>
    </html>
    

在这里插入图片描述

5.3 为什么是一个函数呢?
  • 为什么data在组件中必须是一个函数呢?
    • 首先,如果不是一个函数,Vue直接就会报错。
    • 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响,而函数具有作用域,彼此不会相互干扰
5.4 父子组件的通信
  • 在上面,我们知道了子组件是不能引用父组件或者Vue实例的数据的。
  • 但是,在开发中,往往一些数据确实需要从上层传递到下层:
    • 比如在一个页面中,我们从服务器请求到了很多的数据。
    • 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
    • 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
  • 如何进行父子组件间的通信呢?Vue官方提到
    • 通过props向子组件传递数据
    • 通过事件向父组件发送消息
5.4.1 props基本用法
  • 在组件中,使用选项props来声明需要从父级接收到的数据。

  • props的值有两种方式:

    • 方式一:字符串数组,数组中的字符串就是传递时的名称。
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <!-- 使用子组件 -->
          <!-- 自定义属性 -->
          <!-- title 这里没有绑定 表示把title直接作为字符串传递 -->
          <cpn :message="message" title="title"></cpn>
        </div>
      </body>
      <!-- 子组件 -->
      <template id="cpn">
        <div>
          <h1>{{message}}</h1>
          <p>{{title}}</p>
        </div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          data: {
            message: "hello world",
          },
          components: {
            // 注册子组件
            cpn: {
              template: cpn,
              props: ["message", "title"],
            },
          },
        });
      </script>
    </html>
    

在这里插入图片描述

  • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <!-- 使用子组件 -->
      <!-- 自定义属性 在子组件 props 接收 -->
      <!-- title 这里没有绑定 表示把title直接作为字符串传递 -->
      <cpn :message="message" title="title"></cpn>
    </div>
  </body>
  <!-- 子组件 -->
  <template id="cpn">
    <div>
      <h1>{{message}}</h1>
      <p>{{title}}</p>
    </div>
  </template>
  <script src="../../vue.js"></script>
  <script>
    const app = new Vue({
      el: "#app",
      data: {
        message: "hello world",
      },
      components: {
        // 注册子组件
        cpn: {
          template: cpn,
          props: {
            message: String, // 类型
            title: {
              type: String, // 类型
              dafault: 100, // 默认值
              required: true, // true 必传 false 可以不传
            },
          },
        },
      },
    });
  </script>
</html>

在这里插入图片描述

5.4.2 props数据验证
  • 验证都支持哪些数据类型呢?
    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbo
5.5 子级向父级传递
  • props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。

  • 我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。

  • 什么时候需要自定义事件呢?

    • 当子组件需要向父组件传递数据时,就要用到自定义事件了。
    • 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
  • 自定义事件的流程:

    • 在子组件中,通过$emit()来触发事件。
    • 在父组件中,通过v-on来监听子组件事件。
  • 自定义事件代码

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <!-- 子组件向父组件传递数据 在子组件通过$emit提交事件类型,在父组件通过v-on绑定自定义事件类型 -->
        <div id="app">
          <h1>{{counter}}</h1>
          <cpn @increment="addCounter" @decrement="subCounter"></cpn>
        </div>
      </body>
      <template id="cpn">
        <div>
          <button @click="decrement">-</button>
          <button @click="increment">+</button>
        </div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          data: {
            counter: 0,
          },
          // 父组件的方法
          methods: {
            addCounter(total) {
              this.counter = total;
            },
            subCounter(total) {
              this.counter = total;
            },
          },
          components: {
            cpn: {
              template: cpn,
              data() {
                return {
                  total: 0,
                };
              },
              // 子组件的方法
              methods: {
                decrement() {
                  this.total--;
                  this.$emit("decrement", this.total);
                },
                increment() {
                  this.total++;
                  this.$emit("increment", this.total);
                },
              },
            },
          },
        });
      </script>
    </html>
    

在这里插入图片描述

5.6 父子组件的访问方式: $children
  • 有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。

    • 父组件访问子组件:使用$children$refs
    • 子组件访问父组件:使用$parent
  • 我们先来看下$children的访问

    • this.$children是一个数组类型,它包含所有子组件对象。
    • 我们这里通过一个遍历,取出所有子组件的message状态。
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <cpn1></cpn1>
          <cpn2></cpn2>
          <button @click="getChildren">获取子组件</button>
        </div>
      </body>
      <template id="cpn1">
        <div>cpn1</div>
      </template>
      <template id="cpn2">
        <div>cpn2</div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          components: {
            cpn1: {
              template: cpn1,
              data() {
                return {
                  msg: "cpn1",
                };
              },
              methods: {
                sayHi() {
                  console.log("cpn1:hi");
                },
              },
            },
            cpn2: {
              template: cpn2,
              data() {
                return {
                  msg: "cpn2",
                };
              },
              methods: {
                sayHi() {
                  console.log("cpn2:hi");
                },
              },
            },
          },
          methods: {
            getChildren() {
              // this.$children 数组
              // 访问子组件的data
              console.log(this.$children[0].msg);
              console.log(this.$children[1].msg);
              // 访问子组件的methods的方法
              this.$children[0].sayHi();
              this.$children[1].sayHi();
            },
          },
        });
      </script>
    </html>
    

在这里插入图片描述

5.7 父子组件的访问方式: $refs
  • $children的缺陷:

    • 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
    • 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变话。
    • 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
  • $refs的使用:

    • $refsref指令通常是一起使用的。
    • 首先,我们通过ref给某一个子组件绑定一个特定的ID。
    • 其次,通过this.$refs.ID 就可以访问到该组件了。
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <!-- 给子组件添加唯一标识 ref -->
          <cpn1 ref="cpn1"></cpn1>
          <cpn2 ref="cpn2"></cpn2>
          <button @click="getChildren">获取子组件</button>
        </div>
      </body>
      <template id="cpn1">
        <div>cpn1</div>
      </template>
      <template id="cpn2">
        <div>cpn2</div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          components: {
            cpn1: {
              template: cpn1,
              data() {
                return {
                  msg: "cpn1",
                };
              },
              methods: {
                sayHi() {
                  console.log("cpn1:hi");
                },
              },
            },
            cpn2: {
              template: cpn2,
              data() {
                return {
                  msg: "cpn2",
                };
              },
              methods: {
                sayHi() {
                  console.log("cpn2:hi");
                },
              },
            },
          },
          methods: {
            getChildren() {
              // 通过 $refs 访问子组件的data
              console.log(this.$refs.cpn1.msg);
              console.log(this.$refs.cpn2.msg);
              this.$refs.cpn1.sayHi();
              this.$refs.cpn2.sayHi();
            },
          },
        });
      </script>
    </html>
    

在这里插入图片描述

5.8 父子组件的访问方式: $parent
  • 如果我们想在子组件中直接访问父组件,可以通过$parent
  • 注意事项:
    • 尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做。
    • 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
    • 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
    • 另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
5.9 非父子组件通信
  • 刚才我们讨论的都是父子组件间的通信,那如果是非父子关系呢?
    • 非父子组件关系包括多个层级的组件,也包括兄弟组件的关系。
  • Vue1.x的时候,可以通过$dispatch$broadcast完成
    • $dispatch用于向上级派发事件
    • $broadcast用于向下级广播事件
    • 但是在Vue2.x都被取消了
  • Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。
    • 但是这种方案和直接使用Vuex的状态管理方案还是逊色很多。
    • 并且Vuex提供了更多好用的功能,所以这里我们暂且不讨论这种方案,后续我们专门学习Vuex的状态管理。

6. 插槽 slot

6.1 为什么使用slot
  • slot翻译为插槽:
    • 在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
    • 插槽的目的是让我们原来的设备具备更多的扩展性。
    • 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
  • 组件的插槽:
    • 组件的插槽也是为了让我们封装的组件更加具有扩展性。
    • 让使用者可以决定组件内部的一些内容到底展示什么。
  • 例子:移动网站中的导航栏。
    • 移动开发中,几乎每个页面都有导航栏。
    • 导航栏我们必然会封装成一个插件,比如nav-bar组件。
    • 一旦有了这个组件,我们就可以在多个页面中复用了。
6.1 如何封装这类组件呢?slot
  • 如何去封装这类的组件呢?
    • 它们也很多区别,但是也有很多共性。
    • 如果,我们每一个单独去封装一个组件,显然不合适:比如每个页面都返回,这部分内容我们就要重复去封装。
    • 但是,如果我们封装成一个,好像也不合理:有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。
  • 如何封装合适呢?抽取共性,保留不同。
    • 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
    • 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
    • 是搜索框,还是文字,还是菜单。由调用者自己来决定。
6.2 slot基本使用
  • 了解了为什么用slot,我们再来谈谈如何使用slot

    • 在子组件中,使用特殊的元素<slot>就可以为子组件开启一个插槽。
    • 该插槽插入什么内容取决于父组件如何使用。
  • 我们通过一个简单的例子,来给子组件定义一个插槽:

  • <slot>中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn>
        <button>按钮</button>
      </cpn>
      <cpn>
        <input type="text" />
      </cpn>
    </div>
  </body>
  <template id="cpn">
    <div>
      <slot></slot>
    </div>
  </template>
  <script src="../../vue.js"></script>
  <script>
    const app = new Vue({
      el: "#app",
      components: {
        cpn: {
          template: cpn,
        },
      },
    });
  </script>
</html>

在这里插入图片描述

6.3 具名插槽slot
  • 当子组件的功能复杂时,子组件的插槽可能并非是一个。

    • 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
    • 那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
    • 这个时候,我们就需要给插槽起一个名字
  • 如何使用具名插槽呢?

    • 非常简单,只要给slot元素一个name属性即可
    • <slot name='myslot'></slot>
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
          .nav-bar {
            display: flex;
          }
        </style>
      </head>
      <body>
        <div id="app">
          <cpn>
            <div slot="left"></div>
            <div slot="center"></div>
            <div slot="right"></div>
          </cpn>
          <cpn>
            <button slot="left"></button>
            <div slot="center"></div>
            <div slot="right"></div>
          </cpn>
        </div>
      </body>
      <template id="cpn">
        <div class="nav-bar">
          <slot name="left"></slot>
          <slot name="center"></slot>
          <slot name="right"></slot>
        </div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          components: {
            cpn: {
              template: cpn,
            },
          },
        });
      </script>
    </html>
    

在这里插入图片描述

6.4 作用域插槽:
6.4.1 作用域插槽:准备
  • 作用域插槽是slot一个比较难理解的点,而且官方文档说的又有点不清晰。
  • 这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会:
    • 父组件替换插槽的标签,但是数据由子组件来提供。
  • 我们先提一个需求:
    • 子组件中包括一组数据,比如:language: ["Java", "Vue", "React", "Angular"]
    • 需要在多个界面进行展示:
      • 某些界面是以水平方向一一展示的,
      • 某些界面是以列表形式展示的,
      • 某些界面直接展示一个数组
    • 内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
      • 利用slot作用域插槽就可以了
6.4.2 作用域插槽:使用
  • 在父组件使用我们的子组件时,从子组件中拿到数据:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          <cpn>
            <!-- 2. 在组件中使用 slot-scope 接收 -->
            <template slot-scope="data">
              <ul>
                <li v-for="(item,index) in data.language" :key="index">{{item}}</li>
              </ul>
            </template>
          </cpn>
          <cpn>
            <template slot-scope="data">
              <span v-for="(item,index) in data.language" :key="index">
                {{item}}-
              </span>
            </template>
          </cpn>
        </div>
      </body>
      <template id="cpn">
        <div>
          <!-- 1. 在 slot 绑定属性 -->
          <slot :language="language"></slot>
        </div>
      </template>
      <script src="../../vue.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          components: {
            cpn: {
              template: cpn,
              data() {
                return {
                  language: ["Java", "Vue", "React", "Angular"],
                };
              },
            },
          },
        });
      </script>
    </html>
    

在这里插入图片描述

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值