Skip to content

组件传值

父向子传值

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

当父子组件层级较深时,可以使用 provideinject 进行依赖注入。

缺点

  1. 数据来源不明确,组件嵌套层级过深时,难以维护;

  2. 只能用于嵌套的祖孙组件之间传值,不能用于兄弟组件(原因:父子组件具有相同的上下文关系链,而兄弟组件没有);

  3. 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

Released under the MIT License.