Vue3复习:组件与组件的通信

115 阅读2分钟

1.组件

1.1. 全局组件

全局组件是在应用程序一开始就会全局注册完成,那么也就意味着在任何组件都可以使用全局组件,无需引入、注册。

  <body>
    <div id="app"></div>
    <template id="myApp">
      <div>嘿嘿</div>
      <!-- 全局组件,在哪里都可以直接使用,无需引入注册 -->
      <components-a></components-a>
    </template>

    <template id="myApp1">
      <div>{{msg}}</div>
    </template>

    <script src="vue.js"></script>
    <script>
      const app = Vue.createApp({
        template: "#myApp",
      });

      // 注册全局组件
      app.component("components-a", {
        template: "#myApp1",
        data() {
          return {
            msg: "我是全局组件",
          };
        },
      });
      app.mount("#app");
    </script>
  </body>

1.2. 局部组件

局部组件需要引入、注册后才能正常使用

  <body>
    <div id="app"></div>
    <template id="myApp">
      <div>嘿嘿</div>
      <!-- 局部组件 只能在引入、注册后才能被使用 -->
      <cpn1></cpn1>
    </template>

    <template id="myApp1">
      <div>cpn1</div>
    </template>

    <script src="vue.js"></script>
    <script>
      const cpn1 = {
        template: `#myApp1`,
      };
      const app = Vue.createApp({
        template: "#myApp",
        // 注册局部组件
        components: {
          cpn1,
        },
      });

      app.mount("#app");
    </script>
  </body>

2.组件通信——父传子

(1)父组件向子组件传值,主要通过props来完成组件之间的通信。即在子组件的props中声明一些属性,父组件给这些属性赋值,子组件通过属性的名称获取到对应的值。

(2) Props有两种常见的用法:

 方式一:字符串数组,数组中的字符串就是attribute的名称;

 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等;(常用)

父组件

<template>
  <div>
    我是父组件
    {{ obj.name }}
  </div>
  <button @click="totalAdd">+++</button>
  <!-- 给子组件传递方法,传递参数, -->
  <child
    :totalAdd="totalAdd"
    :total="total"
    :obj="obj"
    v-model:obj="obj"
  ></child>
  <!-- 自定义组件v-model没有命名,那子组件就要用modelValue接收 -->
</template>

<script>
import { ref } from "vue";
import child from "@/components/child.vue";

export default {
  name: "App",
  components: {
    child,
  },
  setup() {
    const total = ref(0);
    const obj = ref({
      name: "coderY",
      age: 18,
    });
    const totalAdd = () => {
      total.value++;
    };
    return {
      total,
      totalAdd,
      obj,
    };
  },
};
</script>

<style></style>


子组件

<template>
  <div class="child">
    <hr />
    我是子组件
    <!-- 使用父组件传递过来的数据 -->
    {{ total }}
    <!-- 调用父组件中的方法 -->
    <button @click="childtotalAdd">+++</button>
    <!-- 自定义组件使用 v-model -->
    <input type="text" v-model="copyObj.name" />
  </div>
</template>

<script>
import { ref, watch } from "vue";
export default {
  props: {
    totalAdd: {
      type: Function,
      default: () => {},
    },
    // 父组件传递过来的数据
    total: {
      type: Number,
      default: 0,
    },
    // 父组件v-model
    obj: {
      type: Object,
      default: () => ({}),
    },
  },
  emits: ["totalAdd", "update:obj"],
  setup(props, { emit }) {
    // 拷贝一份,如果是深层次的,就要使用深拷贝
    const copyObj = ref({ ...props.obj });
    watch(
      copyObj,
      (newValue) => {
        // 因为v-model命名了, 所以我们这里使用update:obj发射事件
        emit("update:obj", newValue);
      },
      {
        deep: true,
      }
    );

    // 子组件调用父组件的方法
    const childtotalAdd = () => {
      props.totalAdd();
    };

    return {
      childtotalAdd,
      copyObj,
    };
  },
};
</script>

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

(3)非props属性

当我们传递给一个组件某个属性的时候,该属性并没有定义相对应的props或emits时,就称之为非props的Attribute

当子组件只有单个根节点时,会继承到根节点上。

image.png

如何禁用呢?

 // 禁用根元素继承非prop和emit的属性
 inheritAttrs: false,

3.组件通信——子传父

(1). 背景

 子组件有一些内容想要传递给父组件的时候,再比如,当子组件有一些事件发生的时候,在组件中发生了点击,父组件需要根据子组件传递的信息切换内容;  

(2). 实现思路

 A. 我们需要在子组件中定义好在某些情况下触发的事件名称;先使用 emits属性声明对外暴露的方法 → 再使用$emit对声明的方法对外传递。

PS:在Vue2.x中,可以不用emits事先声明,但Vue3.x中需要,否则会报警告。

 B. 在父组件中以v-on的方式(简写@)传入要监听的事件名称,并且绑定到对应的方法中;

 C. 在子组件中发生某个事件的时候,根据事件名称触发对应的事件;

子组件

<template>
 <div class="child">
   <hr />
   我是子组件{{ childValue }}
   <button @click="handlerValue">+++</button>
 </div>
</template>

<script>
import { ref } from "vue";
export default {
 // 可以是数组或者是对象,数组比较简单,这里使用对象
 emits: {
   sendValue(childValue) {
     // 不符合的话,会有警告提示
     if (childValue >= 5) {
       return false;
     } else {
       return true;
     }
   },
 },
 setup(props, { emit }) {
   const childValue = ref(1);
   const handlerValue = () => {
     emit("sendValue", childValue.value++);
   };
   return {
     childValue,
     handlerValue,
   };
 },
};
</script>

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

父组件

<template>
 <div>我是父组件</div>

 <child @sendValue="sendValue"></child>
</template>

<script>
import child from "@/components/child.vue";

export default {
 name: "App",
 components: {
   child,
 },
 setup() {
   const sendValue = (childValue) => {
     console.log(childValue);
   };
   return {
     sendValue,
   };
 },
};
</script>

<style></style>

4.组件通信——爷传孙

Vue3中使用provide和inject,更多的是用于多层级组件的通信,感觉用的比较少。

provide和inject为了保证数据的响应性,一般传递ref对象;而且传递的数据要符合单向数据流原则,即传递的数据只允许子组件调用不允许子组件修改,所有通常再用readonly包裹一下

父组件

<template>
  <div>我是父组件</div>
  {{ dataObj.name }}
  {{ dataObj.age }}
  <button @click="handlerName">修改,看看孙子是不是响应式</button>
  <child></child>
</template>

<script>
import { ref, provide, readonly } from "vue";
import child from "@/components/child.vue";

export default {
  name: "App",
  components: {
    child,
  },
  setup() {
    const dataObj = ref({
      name: "coderY",
      age: 18,
    });
    // 第一个参数是key 第二个参数是value,为了符合单向数据流,我们设置为readonly
    provide("dataObj", readonly(dataObj.value));
    // 经过测试,孙子组件的数据也是响应式的
    const handlerName = () => {
      dataObj.value.name = "test";
    };

    return {
      dataObj,
      handlerName,
    };
  },
};
</script>

<style></style>

儿子组件

<template>
  <div class="child">
    <hr />
    我是儿子
    <child2 />
  </div>
</template>

<script>

import child2 from "@/components/child2.vue";
export default {
  components: {
    child2,
  },
  setup() {},
};
</script>

<style scoped></style>

孙子组件

<template>
  <div class="child2">
    <hr />
    我是孙子
    {{ dataObj.name }}
    {{ dataObj.age }}
  </div>
</template>

<script>
import { inject } from "vue";
export default {
  setup() {
    // inject() can only be used inside setup() or functional components.
    // inject 只能放在setup运行的生命周期里,我感觉project也一样,具体没测试。
    const dataObj = inject("dataObj");
    return {
      dataObj,
    };
  },
};
</script>

<style scoped></style>

5.组件通信———兄弟组件、任意组件

在Vue2.x中,兄弟组件或者任意两个组件之间的传值可以使用 $emit发送 和$on接收 来实现,但在Vue3从实例中移除了 $on$off$once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库:mitt 库

5.1 安装,导出

【npm install mitt -S】

    import mitt from 'mitt';
    const emitter = mitt();
    export default emitter;

5.2 通过mitt库中emit方法发送,通过on方法接收,这两个方法通过key来建立联系。

父组件

<template>
  <div>我是父组件</div>
  {{ dataObj.name }}
  {{ dataObj.age }}
  <button @click="sendData">发射</button>
  <child></child>
</template>

<script>
import { ref } from "vue";
import child from "@/components/child.vue";
import emitter from "@/utils/eventbus.js";

export default {
  name: "App",
  components: {
    child,
  },
  setup() {
    const dataObj = ref({
      name: "coderY",
      age: 18,
    });
    const sendData = () => {
       // 发射
      emitter.emit("dataObj", dataObj.value);
    };

    return {
      dataObj,
      sendData,
    };
  },
};
</script>

<style></style>


孙子组件

<template>
  <div class="child2">
    <hr />
    我是孙子
  </div>
</template>

<script>
import emitter from "@/utils/eventbus";
import { onUnmounted } from "vue";
export default {
  setup() {
    // 监听父亲发射的数据
    emitter.on("dataObj", (data) => {
      console.log(data);
    });
    //用完记得销毁
    onUnmounted(() => {
      emitter.off("dataObj");
    });

    return {};
  },
};
</script>

<style scoped></style>