Vue3 组件通信方式小结

也是零零散散用 vue3 来搞一些前端的页面, 每次在组件通信, 主要是传数据这块总是忘记, 大多无非父传子, 子传父等情况, 这里再来做一个小结.

父传子 Props

最常见的就是父组件给子组件传递数据, 不论是传字符串也好, 还是数组, 对象, 函数等, 都可以通过 属性传值 的方式, 子组件通过 defineProps 对应接收即可.

App.vue

<script setup>
import Son from './components/Son.vue'

// 1. 定义父组件要通过 arr 属性传递给子组件 Son.vue 的数据
const arr = [1, 2, { name: 'youge', age: 18 }]
</script>

<template>
  <!-- 子组件 -->
  <Son :arr="arr" @refresh="handleRefresh"></Son>
</template>

Child.vue

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

defineProps({
  arr: Array,
})

</script>

<template>
  <div>
    子组件: 
    <br /> <br />
    父组件传来的数据: <br />
    {{ arr }}
  </div>
</template>
子组件:

父组件传来的数据:
[ 1, 2, { "name": "youge", "age": 18 } ]

总之父传子, 通过属性传递方式, 啥都能传哈, 子组件通过defineProps 都能接收处理.

父传孙 provide / inject

常规父子组件通信的流程是通过 props 父 -> 子 -> 孙 -> 曾孙 ... 这样逐级传递.

但通过 provide -> inject 这种依赖注入的方式, 可以一步透传数据, 不论嵌套多深, 有点厉害.

App.vue

通过 provide(key, value) 开始向任何后台派发数据啦.

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

// 要传递给孙组件的数据
const data = [
  { name: 'youge', age: 20 },
  { name: 'yaya',  age: 18 }
]

// 直接穿透中间层, 哪里想引用直接进行 inject
provide('data', data)

</script>

<template>
  <div class="con">
    父组件
  </div>

  <!-- 调用子组件 -->
  <Son />

</template>

<style scoped>
 .con {
  margin: 100px;
  width: 300px;
  height: 200px;
  border: solid 1px red;
 }
</style>

Son.vue

对于祖父辈的 provide(key, value) 中间商也是可以通过 inject(key) 获取到数据, 当然后面的也可享受恩泽.

<template>
  <div class="con">
    <p>子组件:</p>
    {{ data }}
  </div>
  <!-- 调用子组件, 父组件的孙组件 -->
  <GrandSon />
</template>


<script setup>
import GrandSon from './grandson.vue'
import { inject } from "vue"

const data = inject('data')

</script>


<style scoped>
.con {
  margin: 100px;
  width: 300px;
  height: 200px;
  border: solid 1px blue;
}
</style>

GrandSon.vue

通过 inject(key) 来接收

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

// 接收曾祖父传递过来的数据
const data = inject('data')

</script>

<template>
  <div class="con">
    <p>我是孙组件, 接收爷组件传递过来的数据: </p>
    {{ data }}
  </div>
</template>

<style scoped>
.con {
  margin: 100px;
  height: 200px;
  width: 500px;
  border: 1px solid green;
}
</style>

子传父 Emit

而使用最多的场景是将页面拆分为很多小组件, 子组件, 数据在子组件交互然后渲染或者传递给父组件, 这种情况就用 $emit 来实现.

先又子组件派发, 传递, 然后父组件对应接收.

Child.vue

<script setup>
const data = [
  { name: 'huoya', age: 18 },
  { name: 'cj'   , age: 20 }
]

// 01: 都是要进行先定义出事件名称
const emit = defineEmits(['refresh_01', 'refresh_02'])

// 02: 推荐函数写法可以更灵活控制 emit 的触发时间
const handleClick = () => {
  emit('refresh_02', data)
}

</script>

<template>
  <div class="con">
    <!-- 行内写法 -->
    <button @click="$emit('refresh_01', data)">标签写法: 向父组件传数据</button>
    <!-- 函数写法 -->
    <button @click="handleClick">函数写法: 向父组件传数据</button>
  </div>
</template>

App.vue

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

const sonData_01 = ref(null)
const sonData_02 = ref(null)

// 处理子组件传递过来的数据
const handleRefresh_01 = (e) => {
  sonData_01.value = e
}

const handleRefresh_02 = (e) => {
  sonData_02.value = e
}
</script>

<template>
  <div class="con">
    父组件:
    <br /><br>
    {{ sonData_01 }}
      
    <br /><br>
    {{ sonData_02 }}
  </div>


  <!-- 调用子组件 -->
  <Son @refresh_01="handleRefresh_01" />

  <Son @refresh_02="handleRefresh_02" />


</template>

小结子传父通过 emit 事件实现的 4 步走:

  • 01: 在子组件先定义事件, const emit = defineEmits(['A', 'B'])
  • 02: 在子组件调用事件, 向父组件传递数据 const handleClick = () => { emit('A', data }
  • 03: 在父组件通过 @A="handleA" 来监听子组件传过来的事件 A
  • 04: 在父组件定义一个方法 handleA 来接收子组件通过 事件A 传递过来的数据

子传父 DefineExpos / Ref

还有种常用的方法是, 子组件通过 defineExpose 向外暴露一个方法,

然后父组件通过 ref 调用子组件的方法, 顺带将数据带过来.

Child.vue

  • defineExpose 暴露
<script setup>
import { ref, reactive } from 'vue'

// 模拟接口数据
const data = reactive([
  { name: 'huoya', age: 18 },
  { name: 'cj'   , age: 20 }
]
)

function getData (syncData) {
  data.value = syncData
  return data
}

// 将获取数据的方法向外暴露
defineExpose({
  getData
})

</script>

App.vue

  • ref 绑定
<script setup>
import { ref, nextTick } from 'vue'
import Son from './components/Son.vue'

const sonRef = ref(null)
const sonData = ref(null)


// nextTick(() => {
//   sonData.value = sonRef.value.getData()
// })

const getData = () => sonData.value = sonRef.value.getData()


</script>

<template>
  <!-- 父组件 -->
  <div class="con">
    父组件:
    <br /><br>
    <p>子组件数据: </p> <br>
    {{ sonData }}
  </div>

  <!-- 调用子组件 -->
  <div>
    <button @click="getData">获取子组件数据</button>
    <Son ref="sonRef" />
  </div>

</template>

双向绑定 v-model

v-model 可以在组件上使用以实现双向绑定, 自然也是包含子传父的场景啦.

从 Vue 3.4 开始,推荐的实现方式是使用 defineModel()宏:

Child.vue

定义 defineModel

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

// 模拟接口数据
const data = reactive([
    { name: 'huoya', age: 18 },
    { name: 'cj', age: 20 }
  ]
)
// 通过 defineModel() 返回的是一个 ref
const modelData = defineModel()

function getData() {
  modelData.value = data
}

</script>

<template>
  <div class="con">
    <p>子组件</p>
    <button @click="getData">向父组件传数据</button>
  </div>
</template>

<style scoped>
 .con {
   margin: 100px;
   width: 300px;
   height: 200px;
   border: solid 1px blue;
 }
</style>

App.vue

绑定 v-model

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

const sonData = ref(null)

</script>

<template>
  <div class="con">
    父组件:
    <br /><br>
    <p>子组件数据: </p> <br>
    {{ sonData }}
  </div>

  <!-- 调用子组件 -->
  <div>
    <Son v-model="sonData" />
  </div>

</template>

<style scoped>
 .con {
  margin: 100px;
  width: 300px;
  height: 200px;
  border: solid 1px red;
 }
</style>


defineModel() 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:

  • 它的 .value 和父组件的 v-model 的值同步;
  • 当它被子组件变更了,会触发父组件绑定的值一起更新

它的底层机制是:

  • 一个名为 modelValue 的属性, 本地 ref 的值与其同步
  • 一个名为 update:modelValue 的事件, 当本地 ref 的值发生变化时触发

在用 vue 3.4 之前的版本, 通常需要自己手动来完成方法和事件的编写.

Child.vue

手动定义 defineProps()defineEmits()

<template>
  <div class="con">
    <p>子组件</p>
    <button @click="getData">向父组件传数据</button>
  </div>
</template>


<script setup>

// 1. 属性值必须要是 modelValue
const props = defineProps({
  modelValue: [String, Array]
})

// 2. 事件名称必须是 update:modelvalue
const emit = defineEmits(['update:modelValue'])


// 模拟接口数据
const data = [
  { name: 'huoya', age: 18 },
  { name: 'cj', age: 20 }
]


const getData = () => {
  emit("update:modelValue", data)
}

</script>


<style scoped>
.con {
  margin: 100px;
  width: 300px;
  height: 200px;
  border: solid 1px blue;
}
</style> -->

App.vue

<script setup>
import { ref, nextTick } from 'vue'
import Son from './components/Son.vue'

const sonData = ref(null)

</script>

<template>
  <!-- 父组件 -->
  <div class="con">
    父组件:
    <br /><br>
    <p>子组件数据: </p> <br>
    {{ sonData }}
  </div>

  <!-- 调用子组件 -->
  <div>
    <Son v-model="sonData" />
  </div>

</template>

<style scoped>
 .con {
  margin: 100px;
  width: 300px;
  height: 200px;
  border: solid 1px red;
 }
</style>

常用的这几个方式就基本足够用了.

posted @ 2024-03-17 16:06  致于数据科学家的小陈  阅读(179)  评论(0编辑  收藏  举报