Vue3的10种组件通信方式总结

1. Props

父组件传值给子组件(简称:父传子)
Props 文档

父组件

// Parent.vue

<template>
  <!-- 使用子组件 -->
  <Child :msg="message" />
</template>

<script setup>
import Child from './components/Child.vue' // 引入子组件

let message = 'hello'
</script>

子组件

// Child.vue
<template>
  <div>
    {{ msg }}
  </div>
</template>

<script setup>
const props = defineProps({
  msg: {
    type: String,
    default: ''
  }
})
// 在 js 里需要使用 props.xxx 的方式使用。在 html 中使用不需要 props
console.log(props.msg) 
</script>
  • <script setup> 中必须使用 defineProps API 来声明 props,它具备完整的推断并>* 且在 <script setup> 中是直接可用的。
  • <script setup> 中,defineProps `不需要另外引入。
  • props 其实还能做很多事情,比如:设置默认值 default ,类型验证 type ,要求必传 required ,自定义验证函数 validator 等等。

2. emits

子组件通知父组件触发一个事件,并且可以传值给父组件。(简称:子传父)
emits 文档

父组件

// Parent.vue
<template>
  <div>父组件:{{ message }}</div>
  <!-- 自定义 changeMsg 事件 -->
  <Child @changeMsg="changeMessage" />
</template>

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

let message = ref('hello')

// 更改 message 的值,data是从子组件传过来的
function changeMessage(data) {
  message.value = data
}
</script>

子组件

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

<script setup>

// 注册一个自定义事件名,向上传递时告诉父组件要触发的事件。
const emit = defineEmits(['changeMsg'])

function handleClick() {
  // 参数1:事件名
  // 参数2:传给父组件的值
  emit('changeMsg', '扶苏')
}
</script>
  • props 一样,在 <script setup> 中必须使用 defineEmits API 来声明 emits,它具备完整的推断并且在 <script setup> 中是直接可用的。更多细节请看 文档。
  • <script setup> 中,defineEmits 不需要另外引入。

3. ref 与 $parent

ref 可以获取元素的 DOM 或者获取子组件实例的 VC。既然可以在父组件内部通过 ref 获取子组件实例 VC,那么子组件内部的方法与响应式数据父组件也是可以使用的。

父组件

<template>
  <div class="box">
    <h1>我是父组件:ref parent</h1>
    <h2>父组件拥有财产:{{ money }}</h2>
    <button @click="handler">向子组件1拿100元</button>
    <div class="container">
      <Children1 ref="children1"></Children1>
      <Children2 ref="children2"></Children2>
    </div>
  </div>
</template>

<script lang="ts" setup>
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
import Children1 from "./Children1.vue";
import Children2 from "./Children2.vue";

import { ref } from "vue";

let money = ref(10000);

// 获取子组件的实例 变量名就是ref绑定的名字,切记!
let children1 = ref();
let children2 = ref();
console.log(children1, children2);

//父组件内部按钮点击回调
const handler = () => {
  money.value += 100;
  children1.value.money -= 100;
};

defineExpose({
  money,
});
</script>

<style scoped>
.box {
  width: 1000px;
  height: 500px;
  background: skyblue;
}

.container {
  display: flex;
  justify-content: space-between;
}
</style>

但是需要注意,如果想让父组件获取子组件的数据或者方法需要通过 defineExpose 对外暴露,因为 Vue3 中组件内部的数据对外“关闭的”,外部不能访问。

子组件

<template>
  <div class="children1">
    <h2>我是子组件1</h2>
    <h3>子组件1拥有财产:{{ money }}</h3>
  </div>
</template>

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

let money = ref(9999);

defineExpose({
  money,
});
</script>

<style scoped>
.children1 {
  width: 300px;
  height: 250px;
  background-color: yellow;
}
</style>

$parent 可以获取某一个组件的父组件实例 VC,因此可以使用父组件内部的数据与方法。必须子组件内部拥有一个按钮点击时候获取父组件实例,当然父组件的数据与方法需要通过 defineExpose 方法对外暴露。

<template>
  <div class="children2">
    <h2>我是子组件2</h2>
    <h3>子组件2拥有财产:{{ money }}</h3>
    <button @click="handler($parent)">向父组件拿300元</button>
  </div>
</template>

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

let money = ref(1000);

const handler = ($parent) => {
  money.value += 300;
  console.log($parent);
  $parent.money -= 300;
};
</script>

<style scoped>
.children2 {
  width: 300px;
  height: 250px;
  background-color: yellowgreen;
}
</style>

4. useAttrs

Vue3 中可以利用 useAttrs 方法获取组件的属性与事件(包含:原生 DOM 事件或者自定义事件),该函数功能类似于 Vue2 框架中 attrs 属性与 $listeners 方法。

比如:在父组件内部使用一个子组件 HintButton

<template>
  <div class="box">
    <h1>我是父组件:attrs</h1>
    <el-button type="primary" size="large" :icon="Edit"></el-button>
    <!-- 自定义组件 -->
    <!-- 实现将光标放在按钮上,会有文字提示 -->
    <HintButton
      type="primary"
      size="large"
      :icon="Edit"
      @click="handler"
      @xxx="handler"
      :title="title"
    ></HintButton>
  </div>
</template>

<script lang="ts" setup>
import { Edit } from "@element-plus/icons-vue";
import { ref } from "vue";
import HintButton from "./HintButton.vue";

const handler = () => {
  alert(12306);
};

let title = ref("编辑");
</script>
<style scoped>
.box {
  width: 1000px;
  height: 500px;
  background: skyblue;
}
</style>

子组件内部可以通过 useAttrs 方法获取组件属性与事件。它类似于 props,可以接受父组件传递过来的属性与属性值。需要注意如果 defineProps 接受了某一个属性, useAttrs 方法返回的对象身上就没有相应属性与属性值。

<template>
  <div :title="title">
    <el-button v-bind:="$attrs"></el-button>
  </div>
</template>

<script lang="ts" setup>
//引入useAttrs方法:获取组件标签身上属性与事件
import { useAttrs } from "vue";
//此方法执行会返回一个对象
let $attrs = useAttrs();

// 万一用 props 接受 title
let props = defineProps(["title"]);
// props 与 useAttrs 方法都可以获取父组件传递过来的属性与属性值
//但是 props 接收了 useAttrs 方法就获取不到了
console.log($attrs);
</script>
<style scoped></style>

5. v-model

5.1. 单值的情况

组件上的 v-model 使用 modelValue 作为 propupdate:modelValue 作为事件。
父组件

// Parent.vue
<template>
  <Child v-model="message" />
</template>

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

const message = ref('雷猴')
</script>

子组件

// Child.vue
<template>
  <div @click="handleClick">{{modelValue}}</div>
</template>

<script setup>
import { ref } from 'vue'

// 接收
const props = defineProps([
  'modelValue' // 接收父组件使用 v-model 传进来的值,必须用 modelValue 这个名字来接收
])
const emit = defineEmits(['update:modelValue']) // 必须用 update:modelValue 这个名字来通知父组件修改值
function handleClick() {
  // 参数1:通知父组件修改值的方法名
  // 参数2:要修改的值
  emit('update:modelValue', '喷射河马')
}
</script>

5.2. 多个 v-model 绑定

父组件

// Parent.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>

子组件

// Child.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'])
function changeMsg1() {
  emit('update:msg1', '鲨鱼辣椒')
}
function changeMsg2() {
  emit('update:msg2', '蝎子莱莱')
}
</script>

5.3. v-model 修饰符

父组件

// Parent.vue
<template>
  <Child v-model.uppercase="message" />
</template>

<script setup>
import { ref } from 'vue'
import Child from './components/Child.vue'
const message = ref('hello')
</script>

子组件

// Child.vue
<template>
  <div>{{modelValue}}</div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
const props = defineProps([
  'modelValue',
  'modelModifiers'
])

const emit = defineEmits(['update:modelValue'])
onMounted(() => {
  // 判断有没有 uppercase 修饰符,有的话就执行 toUpperCase() 方法
  if (props.modelModifiers.uppercase) {
    emit('update:modelValue', props.modelValue.toUpperCase())
  }
})
</script>

6. 插槽 slot

插槽可以理解为传一段 HTML 片段给子组件。子组件将 元素作为承载分发内容的出口。
本文打算讲讲日常用得比较多的3种插槽:默认插槽、具名插槽、作用域插槽。

6.1. 默认插槽

父组件

// Parent.vue
<template>
  <Child>
    <div>雷猴啊</div>
  </Child>
</template>

子组件

// Child.vue
<template>
  <div>
    <slot></slot>
  </div>
</template>

6.2. 具名插槽

具名插槽 就是在 默认插槽 的基础上进行分类,可以理解为对号入座。
父组件

// Parent.vue
<template>
  <Child>
    <template v-slot:monkey>
      <div>雷猴啊</div>
    </template>
    <!-- 简写 -->
    <template #monkey>
      <div>雷猴啊</div>
    </template>
    <button>鲨鱼辣椒</button>
  </Child>
</template>

子组件

// Child.vue
<template>
  <div>
    <!-- 默认插槽 -->
    <slot></slot>
    <!-- 具名插槽 -->
    <slot name="monkey"></slot>
  </div>
</template>
  • 父组件需要使用 <template> 标签,并在标签上使用v-solt: + 名称
  • 简写:#名称
  • 子组件需要在 标签里用 name= 名称 对应接收。
  • 这就是 对号入座。
  • 最后需要注意的是,插槽内容的排版顺序,是 以子组件里的排版为准。
  • 上面这个例子就是这样,你可以仔细观察子组件传入顺序和子组件的排版顺序。

6.3. 作用域插槽

父组件

// Parent.vue
<template>
  <!-- v-slot="{scope}" 获取子组件传上来的数据 -->
  <!-- :list="list" 把list传给子组件 -->
  <Child v-slot="{scope}" :list="list">
    <div>
      <div>名字:{{ scope.name }}</div>
      <div>职业:{{ scope.occupation }}</div>
      <hr>
    </div>
  </Child>
</template>

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

const list = ref([
  { name: '雷猴', occupation: '打雷'},
  { name: '鲨鱼辣椒', occupation: '游泳'},
  { name: '蟑螂恶霸', occupation: '扫地'},
])
</script>

子组件

// Child.vue
<template>
  <div>
    <!-- 用 :scope="item" 返回每一项 -->
    <slot v-for="item in list" :scope="item" />
  </div>
</template>

<script setup>
const props = defineProps({
  list: {
    type: Array,
    default: () => []
  }
})
</script>

7. provide / inject

  • 遇到多层传值时,使用 propsemit 的方式会显得比较笨拙。这时就可以用 provide inject 了。

  • provide 是在父组件里使用的,可以往下传值。

  • inject 是在子(后代)组件里使用的,可以网上取值。

  • 无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。
    在这里插入图片描述

父组件

// Parent.vue

<template>
  <Child></Child>
</template>

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

const name = ref('猛虎下山')
const msg = ref('雷猴')

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

provide('msg', msg)

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

子组件

// Child.vue
<template>
  <div>
    <div>msg: {{ msg }}</div>
    <div>name: {{name}}</div>
    <button @click="handleClick">修改</button>
  </div>
</template>

<script setup>
import { inject } from 'vue'
const name = inject('name', 'hello') // 看看有没有值,没值的话就适用默认值(这里默认值是hello)
const msg = inject('msg')
const changeName = inject('changeName')

function handleClick() {
  // 这样写不合适,因为vue里推荐使用单向数据流,当父级使用readonly后,这行代码是不会生效的。没使用之前才会生效。
  // name.value = '雷猴'

  // 正确的方式
  changeName('虎躯一震')

  // 因为 msg 没被 readonly 过,所以可以直接修改值
  msg.value = '世界'
}
</script>
  • provide 可以配合 readonly 一起使用,详情可以看上面例子和注释。

  • provide 和 inject 其实主要是用在深层关系中传值,上面的例子只有父子2层,只是为了举例说明 我懒。

8. getCurrentInstance

getcurrentinstance 是 vue 提供的一个方法,支持访问内部组件实例。

getCurrentInstance 只暴露给高阶使用场景,典型的比如在库中。强烈反对在应用的代码中使用 getCurrentInstance。请不要把它当作在组合式 API 中获取 this 的替代方案来使用。

说白了,这个方法 适合在开发组件库的情况下使用,不适合日常业务开发中使用。

getCurrentInstance 只能在 setup 或生命周期钩子中调用。

<script setup>
import { getCurrentInstance } from 'vue'
// proxy 相当于
const { proxy } = getCurrentInstance()
</script>
  • proxy 是一个被包装过的对象,它提供了对当前组件内属性和方法的访问。
  • 你可以使用 proxy 来访问当前组件的属性和方法,而无需显式引用当前组件的实例对象。这使代码更加简洁和容易理解。

9. mitt.js

mitt.js 提供了更多的方法。
比如:

  • on:添加事件
  • emit:执行事件
  • off:移除事件
  • clear:清除所有事件

mitt.js 不是专门给 Vue 服务的,但 Vue 可以利用 mitt.js 做跨组件通信。

9.1. 安装

npm i mitt

9.2. 使用

Bus.js

// Bus.js
import mitt from 'mitt'
export default mitt()

Parent.vue

// Parent.vue

<template>
  <div>
    Mitt
    <Child />
  </div>
</template>

<script setup>
import Child from './Child.vue'
import Bus from './Bus.js'

Bus.on('sayHello', () => console.log('雷猴啊'))
</script>

Child.vue

// Child.vue

<template>
  <div>
    Child:<button @click="handleClick">打声招呼</button>
  </div>
</template>

<script setup>
import Bus from './Bus.js'

function handleClick() {
  Bus.emit('sayHello')
}
</script>

10. Pinia

Pinia安装使用和持久化和模块化

11. Vuex(不推荐)

Vuex官网

  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值