Vue(三)--组件&组件通信

190 阅读3分钟

组件

组件是用来提高代码的复用性,可以分为局部组件和全局组件。在使用的时候可以认为他就是个自定义的标签,渲染的结果是template中的内容,Vue实例(new Vue)是根组件。

全局组件

注册全局组件: Vue.component("组件名",配置对象)

 <div id="app">
    <!-- 使用组件 -->
    <v-tab></v-tab>
    <v-tab></v-tab>
  </div>
  <div id="app2">
    <!-- 使用组件 -->
    <v-tab></v-tab>
    <v-tab></v-tab>
  </div>
  <template id="first-template">
    <!-- 此处必须得有一个元素节点包裹我们要复用的内容 -->
    <div>
      <h3>标题</h3>
      <p>文章的段落</p>
    </div>
  </template>
  <script>
    // 注册全局组件
    Vue.component("v-tab",
      {
        //配置组件,封装的html结构写在template属性中
        template: "#first-template"
      })
    //创建Vue实例1,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {},
      methods: {}
    });
    //创建Vue实例2,得到 ViewModel
    var vm = new Vue({
      el: '#app2',
    });
  </script>

image.png

从渲染的结果我们可以发现实例1和实例2都可以使用注册的这个全局组件

局部组件

部组件只能用在定义的组件中。组件中可以使用 data 和 methods ,不过data不能直接写一个对象,而是要写成一个函数,并返回一个新的对象(为什么呢?)。 -->

<body>
  <div id="app">
    <v-tab></v-tab>
    <!-- 以下内容并不会渲染 -->
    <z-tab></z-tab>
  </div>
  <div id="app2">
    <!-- 以下内容并不会渲染 -->
    <v-tab></v-tab>
    <z-tab></z-tab>
  </div>
  <!-- template标签是占位符,并不会渲染到页面上 -->
  <template id="feature-template">
    <!-- 根元素有且只能有一个 -->
    <div>
      <h1>hello,vue</h1>
      <p>{{msg}}</p>
      <button @click="sumbit">提交</button>
      <!-- VM实例中的子组件 -->
      <z-tab></z-tab>
    </div>
  </template>
  <script>
    //创建Vue实例1
    var vm = new Vue({
      el: '#app',
      data: {},
      //组件定义在这个属性中,这里定义的组件只能在当前的vm中使用。
      components: {
        // Vm实例中首组件
        "v-tab": {
          template: '#feature-template',
          data() {
            return {
              msg: "2022加油"
            }
          },
          methods: {
            sumbit() {
              alert("提交按钮")
            }
          },
          //VM实例中的子组件
          components: {
            "z-tab": {
              template: "<p>hello,2022</p>"
            }
          }
        }
      }
    });
    //创建Vue实例2
    var vm2 = new Vue({
      el: '#app2',
      data: {}
    });
  </script>
</body>

为什么组件中的data是一个函数?(重点)

组件是可复用的,为了防止组件中的数据彼此影响,每个组件的数据应该互相隔离,也就是说,每个组件应该都有自己的数据。设置成函数之后,在函数中返回一个数据对象。组件每复用一次,data数据就会被复制一次。

动态组件

我们可以通过v-if、v-show来动态的控制组件的显示和隐藏。利用component标签的is属性来控制组件的显示和隐藏。 格式: <component :is="显示的组件名(可以是data中的数据)"></component>

 <div id="app">
    <button @click="which='index'">首页</button>
    <button @click="which='car'">购物车</button>
    <button @click="which='user'">用户</button>
    <keep-alive>
      <component :is="which"></component>
    </keep-alive>
  </div>
  <template id="tmp">
    <div>这是购物车组件 <input type="text"></div>
  </template>
  <script>
    //创建Vue实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        which: "car"
      },
      methods: {},
      components: {
        "index": {
          template: '<div>这是首页组件</div>'
        },
        "car": {
          template: '#tmp'
        },
        "user": {
          template: '<div>这是用户组件</div>'
        }
      },
    });
  </script>

Tips

  • component实际上是在用v-if来动态渲染组件,切换组件时,不渲染的组件实际上是被销毁了。
  • 我们想要component标签来保存组件的状态,可以配合keep-alive来使用
格式:
        <keep-alive>
            <component :is="which"></component>
        </keep-alive>

生命周期

  • Vue组件从创建到销毁的一系列过程。
  • Vue的生命周期分为三大阶段:创建阶段、运行阶段、销毁阶段;每个阶段都有对应的函数,叫做钩子,又被称为钩子函数。
  • 创建阶段的钩子只会在创建的时候执行一次,之后就再也不会执行了

生命周期.png

数据传递

组件在注册的时候作用域是孤立的,如果A组件想要使用B组件的数据,我们必须通过一定的方法来实现。

父→子传递

实现步骤:

  1. 创建Vue实例,用定义局部组件的方式定义son组件,使两个组件为父子关系
  2. 在father使用时,在son中自定义一个属性来接收father的数据
  3. 在子组件注册中使用props,使得son拿到father的数据,这样son就可以使用了
<!-- vue是最大的根组件,其实我们这里的father是vue的子组件,son是father的子组件 -->
  <div id="app">
    <father></father>
  </div>
  <!-- 父组件模版内容 -->
  <template id="father">
    <div>
      父组件:<input type="text" v-model="msg">
      <hr>
      <!-- info是我们自定义的属性名加上数据绑定,就可以接收到父组件inp框输入的数据了 -->
      <son :info="msg"></son>
    </div>
  </template>
  <!-- 子组件模版内容 -->
  <template id="son">
    <div>
      <!-- 渲染接收到的数据info -->
      <p>子组件得到父组件的数据是:{{ info }}</p>
    </div>
  </template>
  <script>
  Vue.component('father', {
    //父组件
    template: '#father',
    data() {
      return {
        msg: "",
      }
    },
    //定义局部组件的方式定义子组件
    components: {
      //子组件
      son: {
        template: "#son",
        //props中的元素名必须和我们在父组件中自定义的属性名一致
        props: ["info"]
      }
    }
  });
  new Vue({
    el: "#app",
  })
</script>

下载.gif

子→父传递

原理:让子组件调用父组件的方法,通过方法传参的方式传递数据

实现步骤:

  1. 在father使用时,给son自定义一个事件A,这个事件的值为father的methods中的方法名A-m
  2. 在子组件的methods中定义一个名为B-m的方法,在methods中 通过 this.$emit("A-m",子组件的数据)来触发A-m方法,并通过参数的方式传递数据给father
 <div id="app">
    <father></father>
  </div>
  <!-- 父组件 -->
  <template id="father">
    <div>
     <p>父组件接收到的数据是:{{ msg }}</p>
      <hr>
      <!-- 自定义一个事件accept,当触发的时候就可以拿到传过来的数据 -->
      <son @accept="accept" :msg="msg"></son>
    </div>
  </template>
  <!-- 子组件 -->
  <template id="son">
    <div>
      子组件:<input type="text" v-model="msg" placeholder="请输入你要发送的内容">
      <!-- 点击按钮,发送子组件的数据 -->
      <button @click="sendOut">发送数据</button>
    </div>
  </template>
   Vue.component('father', {
    template: '#father',
    data() {
      return {
        msg: "",
      }
    },
    methods: {
      accept(value) {
        this.msg = value;
      }
    },
    components: {
      son: {
        template: "#son",
        data() {
          return {
            msg: this.msg
          }
        },
        methods: {
          sendOut() {
            // 通过this.$emit来触发accept事件,并将数据以参数(this.msg)的形式传给father
            this.$emit('accept', this.msg);
          },
        },
      }
    }
  });
  new Vue({
    el: "#app",
  })
</script>

下载.gif

非父子传递

本质上是通过事件触发一个非本身的自定义的事件处理函数,然后通过传参的方式实现通信。过程需要一个媒介也就是空的Vue实例对象,需要调用$on$emit方法来实现监听和传递数据。

实现步骤:

  • 第一步,创建一个空的中央处理组件bus,空的实例
  • 第二步,A组件使用中定义一个事件,并在A组件中的methods中使用这个事件(这里我们用的事件类型是click名字是sendtoB)
  • 第三步,A组件 mounted钩子函数中使用bus.$on("Aincident",传递数据的变量=>{....代码块....})来监听一个自定义类型的事件函数
  • 第四步,B组件的mehonds中使用:bus.$emit("Aincident", this.Binfo)来触发Aincident事件处理函数。
  • 第五步,当我们在使用了B的methods中的方法时,就会将Aincident激活,并将this.Binfo(B要给A的消息)传递出去,同时A的Aincident事件激活,并对A发来的消息处理。
  • 第六步,以上第二至第四步再配置一下B组件就OK了。 例子:
 <!-- 要求是:A和B两个朋友间的通信 -->
  <!-- 实例Vm -->
  <div id="app">
    <!-- A组件 -->
    <Ampp></Ampp>
    <hr>
    <!-- B组件 -->
    <Bmpp></Bmpp>
  </div>
  <!-- 使用A组件 -->
  <template id="Amodel">
    <div>
      <h1>A</h1>
      <h3>A要发送给B的消息内容:{{Ainfo}}</h3>
      <input type="text" v-model="Ainfo" placeholder="请输入消息">
      <button @click="sendtoB">发送</button>
      <h2>接收到的消息:{{remsgB}}</h2>
    </div>
  </template>
  <!-- 使用B组件 -->
  <template id="Bmodel">
    <div>
      <h1>B</h1>
      <h3>B要发送给A的消息内容:{{Binfo}}</h3>
      <input type="text" v-model="Binfo" placeholder="请输入消息">
      <button @click="sendtoA">发送</button>
      <h2>接收到的消息:{{remsgA}}</h2>
    </div>
  </template>
  <script>
    let bus = new Vue();
    // 创建一个vue实例
    var Vm = new Vue({
      el: "#app",
      data: {},
      methods: {},
      components: {
        // A组件
        Ampp: {
          template: '#Amodel',
          // 存储组件数据
          data() {
            return {
              // 发送给B的消息内容
              Ainfo: '',
              // 接收储存B消息的变量
              remsgB: ""
            }
          },
          methods: {
            sendtoB() {
              // console.log("要给B发消息了");
              bus.$emit("Bincident", this.Ainfo)
            }
          },
          mounted() {
            bus.$on("Aincident", value => {
              this.remsgB = value;
            })
          },
        },
        //  B组件
        Bmpp: {
          template: '#Bmodel',
          //  存储数据
          data() {
            return {
              // 发送给A的消息内容
              Binfo: '',
              // 接收储存A消息的变量
              remsgA: ""
            }
          },
          methods: {
            sendtoA() {
              // console.log("要给A发消息了");
              bus.$emit("Aincident", this.Binfo)
            }
          },
          mounted() {
            bus.$on("Bincident", value => {
              this.remsgA = value;
            })
          },
        }
      },
    })
  </script>

下载.gif

单向数据流

上面的例子:父子通信的时候我们发现,数据有父→子 之后,子无法改变数据内容,如果改变就会报错,这就是单向数据流的问题。那我们应该怎么解决呢? 这里我们可以使用变量来接收父组件传来的数据,然后在子组件中使用watch监听的方法来监听值的变化。