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。
当子组件只有单个根节点时,会继承到根节点上。
如何禁用呢?
// 禁用根元素继承非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>