Vue父子组件通信的6种方式,props、emits、defineExpose、provide、attrs...

3,772 阅读3分钟

一、引言

Vue采用组件化开发模式,将一个页面拆分成多个组件,每个组件都有自己的数据和方法。在实际开发中,经常需要对组件之间进行通信,以实现数据共享或事件传递的功能,本文将会介绍Vue中组件通信的6种方式:props、emits、ref/defineExpose、v-model、provide/inject、attrs。

二、props

props通过在子组件上绑定属性来实现父子组件之间的数据传递。父组件将数据作为prop传递给子组件,子组件在自己的作用域中可以访问和使用这些prop。

为节省篇幅,css代码就不放出来了,能说明通信的方式就行。

<!-- App.vue -->
<template>
  <h1>父组件</h1>
  <div class="input-group">
    <input type="text" v-model="val">
    <button @click="handle">发送数据</button>
  </div>
  <Child :msg="list"/>
</template>

<script setup>
    import { ref } from 'vue';
    import Child from './components/Child1-1.vue';
    const val = ref("");
    const list = ref(['666']);
    const handle = () => {
      list.value.push(val.value);
    }
</script>
<!-- Child1-1.vue -->
<template>
  <h1>子组件</h1>
  <ul class="list">
    <li class="list-item" 
     v-for="(item, index) in props.msg" :key="index">
      {{ item }}
    </li>
  </ul>
</template>

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

效果图:

QQ截图20230608105029.jpg

三、emits

emits通过在子组件中声明可触发的事件,使得子组件可以在适当的时机触发这些事件。父组件可以在子组件上监听这些事件,并在触发时执行相应的处理逻辑。这种方式实现了子组件向父组件的通信,子组件可以通过事件来向父组件传递信息或请求。

<!-- App2.vue -->
<template>
  <Child  @add="handleAdd"/>
  <h1>父组件</h1>
  <ul class="list">
    <li class="list-item" 
    v-for="(item, index) in list" :key="index">
      {{ item }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue';
import Child from './components/Child2-1.vue';
const list = ref(['默认测试数据'])
const handleAdd = (val) => {
  list.value.push(val);
}
</script>
<!-- Child2-1.vue -->
<template>
  <h1>子组件</h1>
  <div class="input-group">
    <input type="text" v-model="val">
    <button @click="handle">发送数据</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
const val = ref("");
const emits = defineEmits(['add'])  //发布事件
const handle = () => {
  emits('add', val.value)
}
</script>

Dingtalk_20230608124512.jpg

四、ref/defineExpose

ref/defineExpose通过使用ref将数据或方法包装,并使用defineExpose将其暴露给父组件。父组件可以通过在子组件上使用ref来访问这些暴露出来的数据或方法。这种方式使得父组件可以直接访问和操作子组件的内部状态。

childRef?.msg这里的问号是等childRef数据加载完毕后才去取数据,否则这里可能取不到值,这里需要考虑到父组件和子组件的生命周期。

<!-- App4.vue -->
<template>
  <Child ref="childRef"/>
  <h1>父组件</h1>
  <ul class="list">
    <li class="list-item" 
    v-for="(item, index) in childRef?.msg" :key="index">
      {{ item }}
    </li>
  </ul>
</template>

<script setup>
  import { ref } from 'vue';
  import Child from './components/Child4.vue';
  const childRef = ref("");
</script>
<!-- Child4.vue -->
<template>
  <h1>子组件</h1>
  <div class="input-group">
    <input type="text" v-model="val">
    <button @click="handle">添加</button>
  </div>
</template>

<script setup>
    import { ref } from 'vue';
    const val = ref('')
    const list = ref(['默认测试数据!'])
    const handle = () => {
      list.value.push(val.value);
    }
    defineExpose({msg:list})
</script>

五、v-model

v-model是通过将属性和事件绑定到同一个值来实现双向数据绑定。相当于父组件将自己的数据源交给子组件处理,父组件里不需要写什么代码,操作大都放到子组件里去写。

<!-- App3.vue -->
<template>
  <Child v-model:msg="list"/>
  <h1>父组件</h1>
  <ul class="list">
    <li class="list-item" 
    v-for="(item, index) in list" 
    :key="index">
      {{ item }}
    </li>
  </ul>
</template>

<script setup>
    import { ref } from 'vue';
    import Child from './components/Child3.vue';
    const list = ref(['默认测试数据!'])
</script>
<!-- Child3.vue -->
<template>
  <h1>子组件</h1>
  <div class="input-group">
    <input type="text" v-model="val">
    <button @click="handle">添加</button>
  </div>
</template>

<script setup>
  import { ref } from 'vue';
  const props = defineProps({
    msg:Array
  })
  const val = ref('')
  const emits = defineEmits(['update:msg']) 
  const handle = () => {
    const arr = props.msg;  
    arr.push(val.value);
    emits('update:msg', arr);
  }
</script>

六、provide/inject

provide/inject通过在祖先组件中使用provide提供数据或方法,并在后代组件中使用inject来注入这些数据或方法。不仅仅是子组件、孙子组件或曾孙组件等都可以直接使用inject接收到父组件传递的数据。

<!-- App5.vue -->
<template>
  <h1>父组件5</h1>
  <div class="input-group">
    <input type="text" v-model="val">
    <button @click="handle">添加</button>
  </div>
  <Child/>
</template>

<script setup>
  import { ref, provide } from 'vue';
  import Child from './components/Child5.vue';
  const val = ref('')
  const list = ref(['默认测试数据!'])
  const handle = () => {
    list.value.push(val.value);
  }
  provide("list", list.value);
</script>
<!-- Child5.vue -->
<template>
  <h1>子组件5</h1>
  <ul class="list">
    <li class="list-item" 
      v-for="(item, index) in testList" :key="index">
      {{ item }}
    </li>
  </ul>
</template>

<script setup>
  import { inject } from 'vue';
  const testList = inject("list");
</script>

七、attrs

attrs用于子组件接收父组件中不是通过props接收的属性。父组件给子组件传递属性,可以是自定义属性,这对于在子组件中将额外的prop传递给子元素或子组件比较有用。

<!-- App6.vue -->
<template>
  <h1>父组件 传送数据</h1>
  <Child :fruit="data1" :type="data2"/>
</template>

<script setup>
  import { ref } from 'vue';
  import Child from './components/Child6.vue';
  const data1 = ref("水果");
  const data2 = ref("哈密瓜");
</script>
<!-- Child6.vue -->
<template>
  <h1>子组件 接收数据</h1>
  <p>子组件通过props收到的是: {{props.fruit}}</p>
  <p>子组件通过attrs收到的是: {{attrs.type}}</p>
</template>

<script setup>
  import { useAttrs } from 'vue';
  const props = defineProps({
    fruit: {
      type: String,
      default: ""
    }
  })
  const attrs = useAttrs();
  console.log(attrs);
</script>

QQ截图20230609115553.jpg

八、最后的话

这里梳理的都是父子组件之间的通信,如果要兄弟组件之间传值,大家可以去用仓库(store),vuexpinia都不错!

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态。Pinia 是 Vue 的存储库,可以用来跨组件/页面共享状态。

能力一般,水平有限,本文可能存在纰漏或错误,如有问题欢迎大佬指正,感谢你阅读这篇文章,如果你觉得写得还行的话,不要忘记点赞、评论、收藏哦!祝生活愉快!