跳至主要內容

组件通信方式

黄曦2023年9月1日大约 6 分钟组件通信vue

组件通信方式

方式一: provide 和 inject

用于多层传值,provide 在父组件中使用,inject 在后代中使用。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖
提供者。

<template>
  <!-- 我是父组件 -->
  <Child></Child>
</template>

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

// 数据
const name = ref("小明同学");
const msg = ref("真好喝!");

// provide 方法
// 使用 readonly 可以让子组件无法直接修改,需要调用 provide 往下传的方法来修改
provide("name", readonly(name));
provide("msg", msg);

provide("changeName", (value) => {
  name.value = value;
});
</script>

```vue
<template>
  <div>
    <!-- 我是孩子组件 -->
    <div>msg: {{ msg }}</div>
    <div>name: {{ name }}</div>
    <button @click="handleClick">修改</button>
  </div>
</template>

<script setup>
import { inject } from "vue";

// 从 inject 中取值
const msg = inject("msg", "默认值");
const name = inject("name", "默认值");
const changeName = inject("changeName");

// 自身的方法
const handleClick = () => {
  changeName("小爱同学");
  msg.value = "来了!";
};
</script>
```

## 方式二: Props

> 只能实现父传子,优点是简单,用的概况大。

```vue
<template>
  <!-- 我是父组件 -->
  <Son :list="DataList"></Son>
</template>

<script setup>
import Son from "./components/Son.vue";
import { ref } from "vue";

let DataList = ref([]);
</script>
```

```vue
<template>
  <!-- 我是子组件 -->
  <div v-for="item in DataList"></div>
</template>

<script setup>
const props = defineProps({
  DataList: {
    type: Array,
    default: [],
    required: true,
  },
});
// 在 JS 中使用,是使用 props.DataList
// 在 template 中使用,直接使用即可
</script>
```

## 方式三: emits

> 子组件通知父组件触发一个事件,并且可以传值给父组件。

```vue
<template>
  <!-- 我是父组件 -->
  <div>{{ msg }}</div>

  <!-- 自定义事件 changeMsg -->
  <Child @changeMsg="changeMessage"></Child>
</template>

<script setup>
import Child from "./components/Child.vue";
import { ref } from "vue";

let message = ref("小爱同学");

// 此时的 data 是由子组件传递过来的值
const changeMessage = (data) => {
  message.value = data;
};
</script>
```

```vue
<template>
  <div>子组件:<button @click="handleClick">子组件的按钮</button></div>
</template>

<script setup>
// 可以是有多个自定义的事件,此处填上自定义事件名
const emit = defineEmits(["changeMsg", "多个自定义的事件"]);

// 选择调用的时机
const handleClick = () => {
  // 参数1:事件名
  // 参数2:传给父组件的值
  emit("changeMsg", "参数2:传给父组件的值");
};
</script>
```

## 方式四: ref 和 expose

> 子组件通过 expose 来暴露自身的方法和数据。
> 父组件通过给子组件打上 ref,来获取子组件并调用其方法或访问数据。

```vue
<template>
  <div>父组件拿到子组件数据: {{ msg }}</div>
  <button @click="callChildFn">父组件调用子组件方法:</button>

  <Child ref="Son"></Child>
</template>

<script setup>
import { ref, onMounted } from "vue";
import Child from "./components/Child.vue";

// 通过模板 ref 绑定子组件,名字就是绑定那个 ref 绑定的值
const Son = ref(null);
const msg = ref("");
// 加载完成后,将子组件的值赋给父组件,需要保证子组件有那个方法和属性
onMounted(() => {
  msg.value = Son.value.message;
});

const callChildFn = () => {
  // 子组件的方法,直接调用
  Son.value.changeMessage("小爱同学");
  // 此时子组件的值已经被改变了,重新赋给父组件
  msg.value = Son.value.message;
};
</script>
```

```vue
<template>
  <div>子组件:{{ message }}</div>
</template>

<script setup>
import { ref } from "vue";

const message = ref("小明同学");
const changeMessage = (data) => {
  message.value = data;
};

// 此处父组件仍然不能够拿到子组件的方法和属性,需要对外暴露
defineExpose({
  message,
  changeMessage,
});
</script>
```

## 方式五: 非 Prop 的 Attribute(属性,特征)

> 没使用 prop 或 emits 定义的 attribute,可以通过 $attrs 来访问,但是不如 props,可以控制类型,所以不推荐使用。
> 分为单根组件和多根组件两种情况。

```vue
<template>
  <!-- 父组件 -->
  <Child msg="小明同学" message="小爱同学"></Child>
</template>
```

```vue
<template>
  <!-- 子组件:打开控制台看看 -->
</template>
```

`传递的信息会被直接挂载到 "<div>" 上。`
`若子组件的根组件不是一个时,需要通过"$attrs"的方式去绑定。`

```
vue
<template>
  <div :message="$attrs.msg">只绑定某个属性</div>
  <div v-bind="$attrs">全绑定</div>
</template>
```

## 方式六: v-model

> v-model 是 Vue 的一个语法糖,双向数据流。应该常用于 (element) 的组件值绑定。

```vue
<template>
  父组件
  <Child v-model="message" />
</template>

<script setup>
import { ref } from "vue";
import Child from "./components/Child.vue";

const message = ref("雷猴");
</script>
```

```vue
<template>
  子组件
  <div @click="handleClick">{{ modelValue }}</div>
</template>

<script setup>
import { ref } from "vue";

// 接收父子件的 v-model 值
const props = defineProps([
  "modelValue", // 接收父组件使用 v-model 传进来的值,必须用 modelValue 这个名字来接收
]);

const emit = defineEmits(["update:modelValue"]); // 必须用 update:modelValue 这个名字来通知父组件修改值

function handleClick() {
  // 参数1:通知父组件修改值的方法名
  // 参数2:要修改的值
  emit("update:modelValue", "我是子组件传递给父组件的值");
}
</script>
```

> 绑定多个值(v-model)

```vue
<template>
  父组件
  <Child v-model:msg1="message1" v-model:msg2="message2" />
</template>

<script setup>
import { ref } from "vue";
import Child from "./components/Child.vue";

const message1 = ref("雷猴");
const message2 = ref("雷猴");
</script>
```

```vue
<template>
  子组件
  <div><button @click="changeMsg1">修改msg1</button> {{ msg1 }}</div>

  <div><button @click="changeMsg2">修改msg2</button> {{ msg2 }}</div>
</template>

<script setup>
import { ref } from "vue";
const props = defineProps({
  msg1: string,
  msg2: string,
});

const emit = defineEmits(["update:msg1", "update:msg2"]);

const changeMsg1 = () => {
  emit("update:msg1", "传数据改父组件message1");
};

const changeMsg2 = () => {
  emit("update:msg2", "传数据改父组件message2");
};
</script>
```

## 方式七: Bus 总线

> 弄一个独立的工具出来专门控制数据。
> 新建一个 Bus.js,用来控制数据和注册事件的,构建一个 BUS 类。

- eventList 是必须项,用来存放事件列表的。
- constructor 里除了 eventList 外,其他都是自定义数据,公共数据就是存在这里的。
- $on 方法用来注册事件。
- $emit 方法可以调用 $on 里的事件。
- $off 方法可以注销 eventList 里的事件。

```js
import { ref } from "vue";
class Bus {
  constructor() {
    // 初始化事件函数
    this.eventList = {};
    // 自定义值
    this.msg = ref("欣小萌");
  }

  // 订阅
  $on(name, fn) {
    this.eventList[name] = this.eventList[name] || [];
    this.eventList[name].push(fn);
  }

  // 发布
  $emit(name, data) {
    if (this.eventList[name]) {
      this.eventList[name].forEach((fn) => {
        fn(data);
      });
    }
  }

  // 取消订阅
  $off(name) {
    if (this.eventList[name]) {
      delete this.eventList[name];
    }
  }
}

export default new Bus();
```

```vue
<template>
  <div>需要使用 BUS 的组件01</div>
  <div>{{ msg }}</div>
  <Child></Child>
</template>

<script setup>
import Bus from "./Bus.js";
import Child from "./components/Child.vue";

// 直接去拿 Bus 中定义的数据
const msg = ref(Bus.msg);

// 定义自己的数据
const message = ref("hello");

// 用监听的写法 给 BUS 类 加方法 改变需要使用 BUS 的组件01的值
Bus.$on("changeMsg", (data) => {
  message.value = data;
});
</script>
```

```vue
<template>
  <div>
    <div>需要使用 BUS 的组件02</div>
    <button @click="handleBusEmit">触发 Bus.$emit</button>
    <button @click="changeBusMsg">修改总线里的 msg</button>
  </div>
</template>

<script setup>
import Bus from "../Bus.js";

// 触发事件总线中 changeMsg 的方法。后面是传入的值
const handleBusEmit = () => {
  Bus.$emit("changeMsg", "小明同学");
};

// 直接在其他组件中修改 BUS 中的值 (不推荐直接使用)
const changeBusMsg = () => {
  Bus.msg.value = "在子组件里修改了总线的值";
};
</script>
```

## 方式八: vuex

> Vue 官方推荐的全局状态管理方案

希望这有助于您将文档内容转换为漂亮的 Markdown 格式!如果您需要更多帮助或有其他问题,请随时提问。