Vue3 数据通信

3 篇文章 0 订阅

一、基本概念

数据在 vue 中是单向流动的,有利于管理数据状态和变化。
而在日常组件开发中,难以避免组件之间的数据通信。组件通信可以分为不同的场景,例如父子组件通信、兄弟组件通信、跨层级组件通信等。
Vue3 提供了多种方式进行组件间的通信:

  • props: 自上而下传递数据
  • emit: 自下而上报事件
  • provide/inject: 跨层级传递数据
  • v-model: 双向绑定
  • expose/ref: 暴露组件实例的方法和数据
  • pinia/vuex: 状态管理库
  • eventBus/eitt: 全局事件总线

二、props/emit

props 用于父组件向子组件传递数据,子组件通过定义 props 来接收数据。数据流动是单向的,子组件只能接收数据,不能修改。
$emit 用于子组件向父组件发送事件通知,父组件可以根据这些事件执行相应的逻辑或更新状态。

<!-- 父组件  -->
<template>
  <ChildComponent :message="parentMessage" @childClicked="handleChildClick" />
</template>

<script setup>
import ChildComponent from './childComponent.vue'
import { ref } from 'vue'

const parentMessage = ref('Hello from Parent Component!')
const handleChildClick = (val) => {
  parentMessage.value = val
}
</script>
-------------------------------------------------------------------------------------------
<!-- 子组件 -->
<template>
    <p>parentMessage: {{ props.message }}</p>
    <button @click="notifyParent">Click Me</button>
  </template>
  
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
    message: String
})
const emit = defineEmits(['childClicked'])
const notifyParent = (val) => {
    val = 'Hello from Child Component!'
    emit('childClicked', val)
}
</script>

子组件接收的变量要与父组件冒号后的变量一致,父组件 @ 后面的事件名要与子组件 emit 的事件名一致

三、provide/inject

依赖注入解决的问题是:有多层级嵌套的组件,形成了一棵巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦。
在这里插入图片描述
provide 和 inject 就可以帮助我们解决这一问题。 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
在这里插入图片描述


<!-- 祖先组件 -->
<template>
    <DescendantComponent />
</template>
  
<script setup>
import { provide } from 'vue'
import DescendantComponent from './descendantComponent.vue'

const ancestorValue = 'I provided from Ancestor Component!'
provide('sharedValue', ancestorValue)
</script>
--------------------------------------------------------------------------------------------
<!-- 后代组件 -->
<template>
    <grandchildComponent />
</template>

<script setup>
import GrandchildComponent from './grandchildComponent.vue'
</script>
--------------------------------------------------------------------------------------------
<!-- 孙组件 -->
<template>
    <p>祖先数据:{{ grandchildValue }}</p>
</template>

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

const grandchildValue = inject('sharedValue', 'default value')
</script>

inject 的第一个参数要和 provide 第一个参数一致,它第二个参数是可选的,即在没有匹配到 key 时使用的默认值。第二个参数也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。在这种情况下,必须将 true 作为第三个参数传入,表明这个函数将作为工厂函数使用,而非值本身。

四、v-model

v-model 用于在父子组件之间实现双向数据绑定,Vue3 支持多个 v-model 绑定。使用 v-model 的时候,在子组件中一定要使用update:XXX开头的,绑定的变量就是父组件v-model:后的变量
应用场景: 用于在组件之间进行双向数据绑定,如表单输入组件、计数器等。

<!-- 父组件  -->
<template>
  <childComponent v-model:count="parentCount" />
  <p>Parent Count: {{ parentCount }}</p>
</template>

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

const parentCount = ref(0)
</script>
-------------------------------------------------------------------------------------------
<!-- 子组件 -->
<template>
    <button @click="increaseCount">Increase Count</button>
  </template>

<script setup>
import { defineEmits, defineProps } from 'vue'
const props = defineProps(['count'])
const emit = defineEmits(['update:count'])
const increaseCount = () => {
    emit('update:count', props.count + 1)
}
</script>

五、expose/ref

expose 和 ref 可以让子组件暴露某些方法或属性给父组件或其他组件访问,expose 用于定义子组件暴露的 API,ref 用于在父组件中引用子组件的实例并调用暴露的方法。

<!-- 父组件 -->
<template>
  <button @click="callChildMethod">Call Child Method</button>
  <ChildComponent ref="childRef" />
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './childComponent.vue'

const childRef = ref(null)
const callChildMethod = () => {
  childRef.value?.doSomething()
}
</script>
--------------------------------------------------------------------------------------------
<!-- 子组件 -->
<template>
    <button @click="doSomething">Do Something</button>
</template>

<script setup>
import { defineExpose } from 'vue'
const doSomething = () => {
    alert('Doing something')
}
defineExpose({ doSomething })
</script>

六、vuex

vuex 是 vue 的官方状态管理库,用于集中管理应用的状态,并提供机制来更新状态,适用于大型应用中多个组件共享状态的场景。
应用场景: 适用于需要管理全局状态的应用,如用户信息、购物车内容等。

// store.js
import { createStore } from 'vuex'
const store = createStore({
  state() {
    return {
      count: 0
    }
  },
  mutations: {
    add(state) {
      state.count++
    },
    sub(state) {
        state.count--
    }
  },
  actions: {
    add({ commit }) {
      commit('add')
    },
    sub({ commit }) {
      commit('sub')
    }
  },
  getters: {
    getCount(state) {
      return state.count
    }
  }
})
export default store
--------------------------------------------------------------------------------------------
<!-- 组件 add -->
<template>
    <div>
      <p>Count: {{ count }}</p>
      <button @click="add">+1</button>
    </div>
</template>

<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'

const store = useStore()
const count = computed(() => store.getters.getCount)
const add = () => store.dispatch('add')
</script>
--------------------------------------------------------------------------------------------
<!-- 组件 sub -->
<template>
    <div>
      <p>Count: {{ count }}</p>
      <button @click="sub">-1</button>
    </div>
</template>

<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'

const store = useStore()
const count = computed(() => store.getters.getCount)
const sub = () => store.dispatch('sub')
</script>

七、pinia

pinia 是 Vue3 的官方状态管理库,作为 vuex 的替代品,提供了一个更简洁的状态管理方案,支持 TypeScript,易于使用和扩展。

// countStore.js
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count', {
  state: () => ({
    count: 0
  }),
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    }
  }
})
-------------------------------------------------------------------------------------------
<!-- 组件 add -->
<template>
  <div>当前count: {{ counter.count }}</div>
  <button @click="counter.increment">+1</button>
</template>

<script setup>
import { useCountStore } from '@/stores/countStore'
const counter = useCountStore()
</script>
-------------------------------------------------------------------------------------------
<!-- 组件 sub -->
<template>
  <div>当前count: {{ counter.count }}</div>
  <button @click="counter.decrement">-1</button>
</template>

<script setup>
import { useCountStore } from '@/stores/countStore'
const counter = useCountStore()
</script>

八、eventBus

eventBus 是一种简单的事件发布/订阅模式,事件总线允许组件之间通过发布/订阅模式传递事件,但在大型应用中可能导致事件流动复杂,推荐使用其他状态管理工具。
在 Vue3 中,推荐使用 mitt 作为事件总线的实现,它提供了一个简单的 API 来处理事件总线。
应用场景: 适用于小型应用或临时的组件间通信需求。

// eventBus.js
import mitt from 'mitt'
export const emitter = mitt()
--------------------------------------------------------------------------------------------
<!-- 组件 A -->
<template>
  <button @click="sendMessage">Send Message to B</button>
</template>

<script setup>
import { emitter } from '../../eventBus'
const sendMessage = () => {
  emitter.emit('message', 'Hello from Component A!')
}
</script>
--------------------------------------------------------------------------------------------
<!-- 组件 B -->
<template>
  <p>当前message: {{ message }}</p>
</template>

<script setup>
import { ref } from 'vue'
import { emitter } from '../../eventBus'

const message = ref('默认信息')
emitter.on('message', (msg) => {
  message.value = msg
})
</script>

发布信息方用 emit,接受信息方用 on

  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值