组件传值
父向子传值
props传递
父组件传递数据给子组件,子组件通过 props
接收。
vue
<template>
<HelloWorld :message="message" />
<button @click="message = 'girl'">change</button>
</template>
<script setup lang="ts">
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
const message = ref("tom");
</script>
vue
<template>
<div>{{ message }}</div>
</template>
<script setup lang="ts">
const { message } = defineProps({
message: {
type: String,
required: true,
default: "unknown"
}
});
</script>
$attrs传递
子组件通过 v-bind="$attrs"
获取父组件传递的静态参数。
vue
<template>
<HelloWorld title="标题" content="内容" />
</template>
<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
</script>
vue
<template>
<div v-bind="$attrs"></div>
</template>
<script setup lang="ts">
import { useAttrs } from "vue";
const { title, content }: any = useAttrs();
console.log(title); // 标题
console.log(content); // 内容
</script>
provide+inject
当父子组件层级较深时,可以使用 provide
和 inject
进行依赖注入。
缺点
数据来源不明确,组件嵌套层级过深时,难以维护;
只能用于嵌套的祖孙组件之间传值,不能用于兄弟组件(原因:父子组件具有相同的上下文关系链,而兄弟组件没有);
provider 传递 ref 或 reactive 时,子组件接收到的也是响应式的,如果某个子组件更改了数据,可能导致全局数据都发生变化,解决方案:
vue<script setup lang="ts"> import { provide, ref, readonly } from "vue"; const message = ref<string>("你好"); // 使用 readonly 关键字包裹,变为只读的响应式对象,子组件无法更新 provide("message", readonly(message)); </script>
vue<script setup> import { provide, ref } from 'vue' const message = ref<string>('你好') function updateMessage() { location.value = 'hello' } // provider 提供时,传递专门用于更新 message 的方法,让子组件去调用 provide('message', { location, updateMessage }) </script>
:::
vue
<template>
<HelloWorld />
</template>
<script setup lang="ts">
import { provide, ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
const message = ref<string>("你好");
provide("message", message);
</script>
vue
<template>
<div>{{ msg }}</div>
</template>
<script setup lang="ts">
import { inject } from "vue";
// 第二个参数可以指定 inject 注入时的默认值
const msg = inject("message", "这是默认值");
</script>
在某些场景下,inject 注入的默认值可能需要通过一个函数动态计算,此时第二个参数可以是一个 getter 函数,且第三个参数为 true,表示默认值应该被当作是一个工厂函数。
ts
const msg = inject('message', () => method(), true);
子向父传值
emit传递
vue
<template>
<div>{{ receivedMsg }}</div>
<HelloWorld @change="handleChange" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
const receivedMsg = ref<string>("Hello, world");
function handleChange(message: string) {
receivedMsg.value = message;
}
</script>
vue
<template>
<button @click="updateMessage('你好,世界')">change</button>
</template>
<script setup lang="ts">
const emit = defineEmits(["change"]);
const updateMessage = (message: string) => {
emit("change", message);
};
</script>
defineExpose暴漏
vue
<template>
<div>{{ message }}</div>
<HelloWorld ref="RefInstance" />
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
const message = ref<string | undefined>("");
// 通过 InstanceType 获取组件的实例,显式设置类型
const RefInstance = ref<InstanceType<typeof HelloWorld>>();
onMounted(() => {
message.value = RefInstance.value?.message;
});
</script>
vue
<script setup lang="ts">
import { ref } from "vue";
const message = ref("子组件的数据");
// 子组件向外暴漏属性
defineExpose({ message });
</script>
父调用子的方法
defineExpose暴漏
defineExpose
编译器宏用来显式指定在 <script setup>
组件中 要暴露出去的属性或方法:
vue
<template>
<HelloWorld ref="RefInstance" />
<button @click="handleClick">Increment</button>
</template>
<script setup lang="ts">
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
// 通过 InstanceType 获取组件的实例,显式设置类型
const RefInstance = ref<InstanceType<typeof HelloWorld>>();
function handleClick() {
RefInstance.value?.increment(200);
}
</script>
vue
<template>
<div>{{ count }}</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const count = ref(555);
const increment = (data: number) => (count.value = data);
// 向外暴漏 increment 方法
defineExpose({ increment });
</script>
子调用父的方法
props传递
vue
<template>
<div>{{ number }}</div>
<HelloWorld :method="print" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
const number = ref<number>(0);
function print(data: number) {
number.value = data;
}
</script>
vue
<template>
<button type="button" @click="handleClick">handleClick</button>
</template>
<script setup lang="ts">
const { method } = defineProps<{ method: Function }>();
// 直接传递参数,父组件可以接收参数
function handleClick() {
method && method(200);
}
</script>
emit传递
vue
<template>
<div>{{ number }}</div>
<HelloWorld @method="print" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
const number = ref<number>(0);
function print(data: number) {
number.value = data;
}
</script>
vue
<template>
<button type="button" @click="handleClick">handleClick</button>
</template>
<script setup lang="ts">
const emit = defineEmits(["method"]);
function handleClick() {
emit("method", 200);
}
</script>
任意组件传值
eventBus
EventBus原理
EventBus的核心原理就是 发布/订阅模式,组件订阅的事件会被存储到 事件列表 中,当事件发布时,所有订阅该事件的回调都会被触发。
使用第三方库 mitt 进行数据传递,首先进行安装:
shell
pnpm i mitt
创建 utils/mitt.ts 文件,全局导出 eventBus 对象:
ts
import mitt from "mitt";
export const eventBus = mitt();
使用 mitt 进行全局任意组件之间传值:
vue
<template>
<HelloWorld />
<button @click="handleClick">sendMessage</button>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import { eventBus } from "./utils/mitt";
import HelloWorld from "./components/HelloWorld.vue";
const message = reactive({
name: "王一博",
age: 24
});
const handleClick = () => {
eventBus.emit("message", message);
};
</script>
vue
<template>
<div>{{ name }} -- {{ age }}</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import { eventBus } from "../utils/mitt";
const name = ref<string>("陈伟霆");
const age = ref<number>(18);
onMounted(() => {
eventBus.on("message", handleEvent);
});
onUnmounted(() => {
eventBus.off("message", handleEvent);
});
function handleEvent(event) {
name.value = event.name;
age.value = event.age;
}
</script>
手写EventBus
javascript
class EventBus {
constructor() {
// 事件列表
this.events = {}
}
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = []
}
this.events[eventName].push(callback)
}
emit(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback(data))
}
}
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
cb => cb !== callback
)
}
}
}
const eventBus = new EventBus()
export default eventBus