vue组件常用的11种通信方式详解

前言

vue中到底有哪些通信方式呢?让我们一起看看吧!
vue组件间的通信方式有如下:props、自定义事件、全局事件总线、pubsub、vuex、ref和$parent、slot、provide和inject、pinia、v-model组件通信详解

背景

在Vue项目开发过程中,无论是使用Vue2还是Vue3,组件间传值都是一个非常重要的概念。这是因为在一个大型的项目中,我们通常会将页面拆分成多个组件,每个组件负责不同的功能和展示。在这种情况下,组件之间需要相互协作,共享数据和状态,以便实现整个应用的功能。因此,掌握组件间的通信技巧对于提高开发效率和代码质量至关重要。掌握组件间通信是Vue项目开发的核心技能之一。在实际开发过程中,我们需要根据不同场景选择合适的通信方式,以提高代码的可维护性和可扩展性。同时,了解这些通信方式的原理和实现方式,也有助于我们在面试过程中更好地展示自己的技术实力。
那我们就一起看看组件间的通信都有哪些

组件间的通信方式

组件间的通信方式有如下方式:

  1. 父子组件通信:这是最常见的组件间通信方式。父组件可以通过props属性向子组件传递数据,子组件可以通过自定义事件向父组件传递数据。这种方式适用于父子关系明确的组件之间的通信。
  2. 兄弟组件通信:如果两个组件没有直接的父子关系,但它们需要共享一些数据,可以使用Vue的事件总线来实现。事件总线是一个全局的Vue实例,任何组件都可以将其作为事件触发器或事件监听器。通过这种方式,可以实现任意两个组件之间的通信。
  3. 跨层级组件通信:当两个组件之间存在多层嵌套关系时,可以使用provide和inject来实现跨层级的组件通信。provide可以在当前组件的上下文中注入一个值,这个值可以在其所有子孙组件中通过inject访问。这种方式适用于跨越多层级组件的数据传递。
  4. Vuex:Vuex是一个专为Vue.js应用程序设计的状态管理库。它采用集中式存储管理应用的所有组件的状态,使得各个组件之间的状态变化能够更加清晰和可控。通过使用Vuex,我们可以实现更复杂的组件间通信和状态管理。
  5. Provide/Inject:这是Vue2中提供的跨层级组件通信的方式,类似于Vue3中的provide/inject。通过在父组件中使用provide提供数据,然后在子组件中使用inject注入数据,可以实现跨层级的组件通信。
  • props:父子组件通信,子父组件通信以及兄弟组件通信;
  • 自定义事件:父子组件通信,子组件向父组件传值;
  • 全局事件总线$bus:任意组件间通信;
  • pubsub:发布与订阅方式实现任意组件间通信;
  • vuex:全局集中式状态管理器,任意组件间通信;
  • ref和$parent:通过获取子组件实例,可以拿到子组件数据和方法;
  • slot:插槽方式实现父子组件通信;
  • provide和inject:父子组件通信,跨层级组件通信;
  • pinia: 和vuex相同,全局集中式状态管理,实现任意组件间通信;
  • v-model:父子组件间通信;
  • useAttrs【VUE3适用】类似props,父子组件通信;

组件通信实现方式实例

1、props

  • 可以在父子组件间通信,子组件内数据是只读的,不能修改父组件传递过来的数据!!!,需要修改只能父组件自己修改;

(1). vue2选项API方式

<template>
  <div>
    <h2>父组件</h2>
    <ChildComponent :message="message" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      message: 'Hello World!'
    }
  }
}
</script>
<template>
 <div>
   <h3>子组件</h3>
   <p>{{ message }}</p>
 </div>
</template>

<script>
export default {
 props: {
   message: {
     type: String,
     required: true
   }
 }
}
</script>

(2). vue3组合API方式

<template>
    <div>
        <h1>props通信实例</h1>
        <div class="container">
            <h2>我是父组件,传给子组件的值:{{ message }} </h2>
            <child :message="message" />
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import child from './child.vue';
let message = ref('Hello props')
</script>
<style scoped>
.container {
    width: 100%;
    height: 300px;
    background: greenyellow;
}
</style>
<template>
    <div class="son">
        <h2>我是子组件,拿到父组件传值:{{ props.message }}</h2>
        <!-- props可以省略 -->
        <p>{{ message }}==={{ props.message }}</p>
        <button @click="editParentMessage">修改父组件值</button>
    </div>
</template>
<script setup lang="ts">
const props = defineProps(['message'])
const editParentMessage = () => {
    // 提示警告:[Vue warn] Set operation on key "message" failed: target is readonly. Proxy(Object) {message: 'Hello props'}
    // 数据是只读的,不可修改
    props.message = 'Hello World!'
}
</script>
<style scope>
.son {
    width: 300px;
    height: 200px;
    background: skyblue;
}
</style>

2、自定义事件

事件包含原生DOM事件和自定义事件;
DOM事件:click,onmouseenter,onmouseleave…
自定义事件:自定义名称的事件;
(1). vue2选项API方式

<template>
  <div>
    <button @click="showMessageFromChild">点击子组件显示消息</button>
    <child-component @showMessage="showMessageFromChild"></child-component>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  methods: {
    showMessageFromChild(message) {
      console.log(message);
    }
  }
};
</script>

<template>
  <div>
    <button @click="showMessage">显示消息</button>
  </div>
</template>

<script>
export default {
  methods: {
    showMessage() {
      this.$emit('showMessage', 'Hello, Vue!');
    }
  }
};
</script>

(2). vue3组合API方式

<template>
    <div>
        <h1>自定义通信实例</h1>
        <div class="parent">
            <h2>我是父组件,接收子组件传来的值 {{ message }}</h2>
            <child @my-event-name="getParams" />
        </div>
    </div>
</template>
<script setup lang="ts">
import child from './child.vue';
import { ref } from 'vue'
let message = ref()
const getParams = (param1: any, param2: any) => {
    message.value = param1 + ' ' + param2
}
</script>
<style scoped>
.parent {
    width: 100%;
    height: 350px;
    background: goldenrod;
}
</style>
<template>
    <div class="son">
        <h2>我是子组件,我向父组件传值</h2>
        <button @click="handle">点我传值</button>
    </div>
</template>
<script setup lang="ts">
let $emit = defineEmits(['myEventName'])
const handle = () => {
    $emit('myEventName', 'hello', 'world')
}
</script>

<style scoped>
.son {
    width: 300px;
    height: 300px;
    background: pink;
}
</style>

3、全局事件总线$bus

  • $bus来创建一个事件总线。事件总线可以用来在不同的组件之间进行通信。全局方式的事件总线$bus,你可以在Vue原型链上挂载一个Vue实例,并将其作为事件总线使用。同时呢也可以局部组件去使用事件总线。

(1)、vue2选项API方式
首先,在你的Vue实例的入口文件main.js中添加如下代码:

import Vue from 'vue';
Vue.prototype.$bus = new Vue();

这样,我们将一个新的Vue实例挂载在Vue的原型链上,并将其命名为$bus.
然后就可以在任何组件中使用$emit来触发一个事件,并使用$on来监听这个事件:

// 组件A
methods: {
  handleClick() {
    this.$bus.$emit('busEvent', 'Hello, from Component A!');
  }
}

// 组件B
mounted() {
  this.$bus.$on('busEvent', (data) => {
    console.log(data); // 输出:Hello, from Component A!
  });
}

同样,记得在组件被销毁时使用$off来移除事件监听器,以防止内存泄漏。

// 组件B
beforeDestroy() {
  this.$bus.$off('busEvent');
}

现在,你可以在任何组件中使用$bus进行全局的组件间通信了。当组件A中的handleClick方法被调用时,组件B中的事件监听器会接收到并执行相应的逻辑。

(2). vue3组合API方式
注意:vue3中的vue构造函数没有了,导致没有了vm,vue原型对象也没有了,所以在vue3中使用类似于vue2中的$bus事件总线,就得要用到一个插件mitt

  1. 安装依赖插件

npm ,yarn ,pnpm,cnpm方式都行,习惯用哪个用哪个

npm install mitt --save
yarn add mitt
pnpm install mitt --save
cnpm install mitt --save
  1. 创建bus文件夹和index.ts文件,index文件引入插件并暴露出去;
    在这里插入图片描述
/**
 * index.ts
 * 1、引入mitt插件,
 * 2、执行mitt方法
 * 3、暴露方法执行结果$bus
 */
import mitt from 'mitt'
const $bus = mitt()
export default $bus
  1. 从父组件传值,在子组件中接收;
  2. 从子组件传值,父组件接收以及兄弟组件接收
<template>
    <div>
        <h1>全局事件总线$bus通信实例</h1>
        <div class="parent">
            <h2>我是父组件</h2>
            <button @click="passValue">点我给子组件1传值</button>
            <p>接收到子组件1传递来的值:{{ val }}</p>
            <div class="son-wrap">
                <child1 />
                <child2 />
            </div>
        </div>
    </div>
</template>
<script setup lang="ts">
import child1 from './child1.vue'
import child2 from './child2.vue'
import { ref, onMounted } from 'vue'
// 引入$bus
import $bus from '../../bus'
let val: any = ref('等待子组件1传值')
onMounted(() => {
    $bus.on('parentBrother', (params) => {
        val.value = params
    })
})
const passValue = () => {
    // 向子组件1传递值
    $bus.emit('child1Value', 10000)
}
</script>
<style scoped>
.parent {
    width: 100%;
    height: 500px;
    background-color: aqua;
}

.son-wrap {
    display: flex;
    align-items: center;
}
</style>
<template>
    <div class="son">
        <h2>我是子组件1</h2>
        <p>接收到父组件传来的值:{{ value }}</p>
        <button @click="brotherParentPassValue">点我给父组件和兄弟组件传值</button>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
// 引入$bus
import $bus from '../../bus'
let value = ref()
onMounted(() => {
    // 接收父组件传值
    $bus.on('child1Value', (params) => {
        console.log(params)
        value.value = params
    })
})

const brotherParentPassValue = () => {
    $bus.emit('parentBrother', '我是child1传递过来的值')
}

</script>
<style scoped lang="scss">
.son {
    width: 300px;
    height: 300px;
    background-color: bisque;
}
</style>

<template>
    <div class="son">
        <h2>我是子组件2</h2>
        <p>接收到子组件1传递来的值:{{ val }}</p>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
// 引入$bus
import $bus from '../../bus'
let val: any = ref('等待子组件1传值')
onMounted(() => {
    $bus.on('parentBrother', (params) => {
        val.value = params
    })
})
</script>
<style scoped lang="scss">
.son {
    margin-left: 20px;
    width: 300px;
    height: 300px;
    background-color: skyblue;
}
</style>

4、pubsub

消息订阅与发布和全局事件总线一样,也是一种组件间通信的方式,但pubsub需要安装一个插件才能使用。

(1)、vue2选项API方式

  • 安装
    npm ,yarn ,pnpm,cnpm方式都行,习惯用哪个用哪个
npm install pubsub-js --save
yarn add pubsub-js
pnpm install pubsub-js --save
cnpm install pubsub-js --save
  • 全局安装,在main.js里面全局引入
import PubSub from 'pubsub-js'
Vue.prototype.$PubSub = PubSub
  • 如此我们就可以像使用全局事件总线$bus一样去使用$PubSub了
// 组件A
methods: {
  handleClick() {
  	// 发布消息
    this.$PubSub.publish('msgName', '你要发布的消息');
  }
}

// 组件B
mounted() {
// 订阅组件A发布的消息
  this.$PubSub.subscribe('msgName', (data) => {
    console.log(data); // 输出:你要发布的消息
  });
}

同样,记得在订阅组件B被销毁时使用unsubcribe,clearAllSubscriptions来取消消息订阅,以防止内存泄漏。

// 组件B
beforeDestroy() {
  // 取消某一个订阅
  this.$PubSub.unsubcribe('msgName');
  // 清除所有订阅
  this.$PubSub.clearAllSubscriptions();
}

(2)、vue3组合API方式

  • 安装
    npm ,yarn ,pnpm,cnpm方式都行,习惯用哪个用哪个
npm install pubsub-js --save
yarn add pubsub-js
pnpm install pubsub-js --save
cnpm install pubsub-js --save
<!-- 父组件 -->
<template>
    <div class="">
        <h1>pubsub通信实例</h1>
        <div class="container">
            <h2>父组件</h2>
            <button @click="passValue">点我给子组件1传值:({{ message }})</button>
            <p>接收到子组件1传递来的值:{{ value }}</p>
            <div class="content">
                <child1 />
                <child2 />
            </div>
        </div>
    </div>
</template>
<script setup lang="ts">
import child1 from './child1.vue'
import child2 from './child2.vue'
import { ref, reactive, onMounted } from "vue"
let value = ref()
let message = ref('Hello pubsub-js')
// 引入pubsub
import pubsub from 'pubsub-js'
const passValue = () => {
	// 发布消息
    pubsub.publish('fatherMsg', message)
}
onMounted(() => {
	// 订阅消息
    pubsub.subscribe('parentBrother', (msgName: any, msg: string) => {
        console.log(msgName)
        value.value = msg
    })
})
</script>
<style scoped lang="scss">
.container {
    width: 100%;
    height: 500px;
    background-color: rebeccapurple;

    .content {
        display: flex;
    }
}
</style>

<!-- 子组件1 -->
<template>
    <div class="son">
        <h2>我是子组件1</h2>
        <p>订阅到父组件传来的消息:{{ value }}</p>
        <button @click="brotherParentPassValue">点我给父组件和兄弟组件发送消息</button>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
// 引入pubsub
import pubsub from 'pubsub-js'
let value = ref()
onMounted(() => {
    // 订阅父组件消息
    /**
     * subscribe回调函数有两个参数
     * msgName: 消息名称
     * msg: 消息具体信息,传递过来的值
     */
    pubsub.subscribe('fatherMsg', (msgName: any, msg: string) => {
        console.log(msgName)
        value.value = msg
    })
})

const brotherParentPassValue = () => {
	// 发布消息
    pubsub.publish('parentBrother', '我是child1组件发送过来的消息')
}

</script>
<style scoped lang="scss">
.son {
    width: 300px;
    height: 300px;
    background-color: bisque;
}
</style>

<!-- 子组件2 -->
<template>
    <div class="son">
        <h2>我是子组件2</h2>
        <p>接收到子组件1传递来的值:{{ val }}</p>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
// 引入pubsub
import pubsub from 'pubsub-js'
let val: any = ref('等待子组件1发布消息')
onMounted(() => {
	// 订阅消息
    pubsub.subscribe('parentBrother', (msgName: any, msg: string) => {
        val.value = msg
    })
})
</script>
<style scoped lang="scss">
.son {
    margin-left: 20px;
    width: 300px;
    height: 300px;
    background-color: skyblue;
}
</style>

5、vuex

Vuex是Vue.js的官方状态管理工具,用于管理应用程序中的状态(数据),vue2使用,在vue3中,我们是通过pinia依赖插件实现全局状态管理。
vuex中一共有五个状态 State Getter Mutation Action Module ;

  • State : 提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data;
  • Getter : 类似于vue中的computed,进行缓存,对于Store中的数据进行加工处理形成新的数据
  • Mutation : 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
  • Action : 通常用于异步操作,比如请求后台接口
  • Module : 大型项目开发时,数据量大,全都放到store就会显得很臃肿,为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter;这样可以清晰管理我们项目中数据。

Vuex的实例使用步骤:
1、安装Vuex:在项目目录下执行以下命令安装Vuex:

npm install vuex --save

2、创建Vuex store:在项目的src目录下创建一个store目录,并在该目录下创建一个index.js文件,作为Vuex的store配置文件。

// store/index.js 文件内容
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 在state对象中定义应用程序的所有状态(数据)
const state = {
  count: 0
}

// 定义mutations(用于修改状态的方法)
const mutations = {
  increment(state) {
    state.count++
  },
  decrement(state) {
    state.count--
  }
}
// 创建Vuex store实例
export default new Vuex.Store({
  state,
  mutations
})

3、在Vue实例中使用store:在main.js文件中,引入store并使用store选项来注入store实例。

// main.js
import Vue from 'vue'
import store from './store'
import HelloWorld from './components/HelloWorld.vue'
new Vue({
  store,
  render: h => h(HelloWorld)
}).$mount('#app')

4、在Vue组件中使用Vuex:在需要使用Vuex的组件中,引入Vuex,并使用mapState和mapMutations辅助函数来映射状态和方法。

// HelloWorld.vue
<template>
  <div>
    <p>当前计数:{{ count }}</p>
    <p>当前计数:{{ $store.state.count }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">减少</button>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'

export default {
  computed: {
    // 使用mapState辅助函数来映射state中的count属性
    ...mapState(['count'])
  },
  methods: {
    // 使用mapMutations辅助函数来映射mutations中的increment和decrement方法
    ...mapMutations(['increment', 'decrement'])
  }
}
</script>

6、ref和$parent

  • 在Vue实例中,ref是一个用来在模板或者代码中引用子组件或HTML元素的属性。通过在子组件或HTML元素上添加ref属性,我们可以通过this.$refs来引用这个子组件或HTML元素。
  • $parent是一个指向当前组件的父组件的指针。通过this.$parent可以访问父组件的属性和方法。

(1)、vue选项API方式

<template>
  <div>
    <child-component ref="child"></child-component>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  mounted() {
    // 通过this.$refs来引用子组件
    this.$refs.child.doSomething();
  }
};
</script>

在子组件当中可以调用父组件方法和访问父组件属性(组件中data里面的值)

<template>
  <div>
  	<p>子组件</p>
    <p>获取父组件的属性: {{ $parent.parentProp }}</p>
    <button @click="$parent.parentMethod">调用父组件的方法</button>
  </div>
</template>

(2)、vue3组合API方式

<template>
    <div class="">
        <h2>父组件</h2>
        <div class="content">
            <p>父组件数据:{{ msg }}</p>
            <button @click="handle">点击修改子组件数据</button>
            <button @click="handle1">点击调用子组件方法</button>
            <child ref="childRef" />
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
import child from "./child.vue";
let childRef = ref()
let msg = 'hello'
const handle = () => {
    childRef.value.msg = 'Hi'
}
const handle1 = () => {
    childRef.value.childFunction()
}
const parentFunction = () => {
    alert('$parent调用父组件方法,被调用')
}
onMounted(() => {
    childRef.value.msg = msg
})
/**
 * 组件内数据默认是被关闭的,
 * 需要使用defineExpose对外暴露,
 * 子组件才能访问子组件内的值以及修改
 */
defineExpose({
    msg,
    parentFunction
})
</script>
<style scoped lang="scss">
.content {
    width: 100%;
    height: 300px;
    background: green;
}
</style>

<template>
    <div class="son">
        <h2>子组件</h2>
        <p>拿到父组件数据:{{ msg }}</p>
        <button @click="handle($parent)">点我获取父组件数据</button>
        <button @click="handle1($parent)">点击调用父组件方法</button>
        <p>$parent获取父组件的值:{{ getParent }}</p>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue"
let msg = ref()
let getParent = ref()

const childFunction = () => {
    alert('子组件内方法被调用了')
}

const handle = ($parent: any) => {
    getParent.value = $parent.msg
}
const handle1 = ($parent: any) => {
    $parent.parentFunction()
}
onMounted(() => {
})
/**
 * 组件内数据默认是被关闭的,
 * 需要使用defineExpose对外暴露,
 * 父组件才能访问子组件内的值以及修改
 */
defineExpose({
    msg,
    childFunction
})
</script>
<style scoped lang="scss">
.son {
    width: 50%;
    height: 200px;
    background: skyblue;
}
</style>

7、slot

  • 插槽分为:默认插槽,具名插槽(带有名字的插槽),作用域插槽
  • v-slot指令可以简化为#
  • 作用域插槽:可以传递数据的插槽,子组件可以将数据回传给父组件,父组件可以决定回传的数据,数据是以何种结构或外观在子组件内展示。
  • 作用域插槽可以和具名插槽一起使用,但是不能嵌套使用;
  • 作用域插槽不能和默认插槽同时使用;
<template>
    <div>
        <h1>slot通信实例</h1>
        <div class="parent">
            <h2>父组件</h2>
            <child :todos="todos">
                <!-- 默认插槽结构 -->
                <!-- <div>默认插槽结构插入</div> -->
                <!-- 具名插槽a结构插入 -->
                <template v-slot:a>
                    <div>具名插槽a结构插入</div>
                </template>
                <!-- 具名插槽a结构插入 v-slot指令可以简化为# -->
                <template #b>
                    <div style="color: red;">具名插槽b结构插入</div>
                </template>
                <template v-slot="{ $item, $index }">
                    <span>{{ $item.name + $item.price }}元/kg -- {{ $index }}</span>
                </template>
            </child>
        </div>
    </div>
</template>
<script setup lang="ts">
import child from './child.vue'
import { ref } from 'vue'
let todos = ref([
    { name: '苹果', price: 10 },
    { name: '香蕉', price: 8 },
    { name: '芒果', price: 20 },
    { name: '梨子', price: 14 },
    { name: '大菠萝', price: 12 }
])
</script>
<style scoped lang="scss">
.parent {
    width: 70%;
    min-height: 400px;
    background: yellowgreen;
    padding: 10px 0;
}
</style>
<template>
    <div class="son">
        <h2>我是子组件</h2>
        <div class="top">
            <!-- <p>默认插槽</p>
            <slot></slot>
            <p>默认插槽</p> -->
            <p>具名插槽a</p>
            <slot name="a"></slot>
            <p>具名插槽a</p>
            <p>具名插槽b</p>
            <slot name="b"></slot>
            <p>具名插槽b</p>
        </div>
        <div>
            <!-- 作用域插槽 -->
            <ul>
                <li v-for="(item, index) in todos" :key="index">
                    <slot :$item="item" :$index="index"></slot>
                </li>
            </ul>
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue"
defineProps(['todos'])
</script>
<style scoped lang="scss">
.son {
    background: yellow;

    .top {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
}
</style>

8、provide和inject

  • provide:提供数据,inject:接收注入数据;
  • 可实现隔辈传值,父组件-子组件-孙子组件
  • provide有两个参数:第一个参数是提供数据的key,第二个参数是提供的数据值
  • 后代可以修改提供的数据
<template>
    <div class="">
        <h1>provide、inject通信实例</h1>
        <div class="content">
            <h2>父组件</h2>
            <p>提供给后代的数据:{{ msg }}</p>
            <child />
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, provide } from "vue"
import child from './child.vue'
let msg: any = ref('hello world')
provide('msgName', msg)
</script>
<style scoped lang="scss">
.content {
    background: skyblue;
}
</style>

<template>
    <div class="son">
        <h2>我是子组件</h2>
        <p>接收祖先提供的数据:{{ msg }}</p>
        <button @click="updateData">点我修改数据</button>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, inject } from "vue"
let msg: any = inject('msgName')
const updateData = () => {
    msg.value = '你好世界'
}
</script>
<style scoped lang="scss">
.son {
    height: 400px;
    background: red;
}
</style>

9、pinia

  • 需要安装pinia依赖插件才能使用
  • 两种API方式写法:选项式API和组合式API
    – 选项式API核心: state,actions,getters
    – 组合式API核心:ref,reactive 方式定义存储变量,自定义函数修改数据变量,计算属性使用compute
  • 可以实现任意组件间通信

1、安装pinia

pnpm install pinia --save

2、src下创建stores文件夹,stores文件夹下穿件index.ts文件和modules文件夹,modules文件夹创建我们数据管理摸文件,如:info.ts文件,todo.ts文件.
在这里插入图片描述

// stores/index.ts
// 创建入口仓库
import { createPinia } from 'pinia'
const store = createPinia()
export default store
// stores/modules/info.ts
// 选项式API写法
import { defineStore } from "pinia";
const userInfoStore = defineStore('info', {
    state: () => {
        return {
            count: 100,
            list: [1, 2, 3, 4, 5]
        }
    },
    actions: {
        updateCount() {
            this.count += 100
        }
    },
    getters: {
        total() {
            let num: any = this.list.reduce((prev, next) => {
                return prev + next
            }, 0)
            return num
        }
    }
})
export default userInfoStore
// stores/modules/todo.ts
// 组合式API写法
import { defineStore } from "pinia";
import { ref, reactive, computed } from 'vue'
const useTodoStore = defineStore('todo', () => {
    let msg = reactive([{ id: 1, name: '吃饭' }, { id: 2, name: '睡觉' }, { id: 3, name: '打豆豆' }])
    let arr = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

    // 计算属性
    const total = computed(() => {
        let num = arr.value.reduce((prev, next) => {
            return prev + next
        }, 0)

        return num
    })
    // 修改更新msg数据
    const updateTodoMsg = () => {
        msg[1].name = '睡觉就睡觉'
        msg.push({ id: 4, name: '看电影' })
    }
    return {
        msg,
        updateTodoMsg,
        total
    }
})

export default useTodoStore

3、入口main.ts内引入store

// 引入store
import store from './stores'
app.use(store)

4、父子组件内使用

<template>
    <div class="">
        <h1>pinia通信实例</h1>
        <div class="content">
            <h2>父组件</h2>
            <p>组合API的store数据:{{ todoStore.msg }}</p>
            <p>组合API的store数据计算值为:{{ todoStore.total }}</p>
            <div>
                <button @click="todoStore.updateTodoMsg">点我修改组合式store数据</button>
            </div>
            <div class="com">
                <child />
                <child2 />
            </div>
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue"
import child from './child1.vue'
import child2 from './child2.vue'
// 引入组合式API方式的store
import useTodoStore from '../../stores/modules/todo'

let todoStore = useTodoStore()

</script>
<style scoped lang="scss">
.com {
    display: flex;
}
</style>

<template>
    <div class="son">
        <h2>子组件1</h2>
        <button @click="handle">点击修改store数据</button>
        <p>store数据: {{ infoStore.count }}</p>
        <p>getters计算的值是:{{ infoStore.total }}</p>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue"
import useInfoStore from '../../stores/modules/info'
let infoStore = useInfoStore()
const handle = () => {
    // 方式一:直接修改
    // infoStore.count = 10000
    // 方式二:利用$patch修改数据
    // infoStore.$patch({
    //     count: 99999
    // })
    // 方式三:调用actions内部修改方法修改
    infoStore.updateCount()
}
</script>
<style scoped lang="scss">
.son {
    width: 200px;
    height: 200px;
    background: skyblue;
}
</style>

10、v-model

  • v-model除了在表单上收集数据,实现数据双向绑定之外,还可以实现父子组件之间的通信,让父子组件数据同步;
  • 可以在同一组件上绑定多个v-model
  • 在组件上使用
    – 第一:相当于给子组件传递props[modelValue]
    – 第二:相当于给子组件绑定自定义事件update:modelValue
<template>
    <div class="">
        <h1>v-modal通信实例</h1>
        <div class="content">
            <input type="text" v-model="inputVal">
            <p>input的数据:{{ inputVal }}</p>
            <p>num1:{{ num1 }}</p>
            <p>num2:{{ num2 }}</p>
            <h2>父组件</h2>
            <child v-model:num1="num1" v-model:num2="num2"></child>
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue"
import child from './child.vue'
let inputVal = ref('')
let num1: any = ref(1)
let num2: any = ref(10)
</script>
<style scoped lang="scss">
.content {
    width: 100%;
    background: green;
}
</style>
<template>
    <div class="son">
        <h2>子组件</h2>
        <button @click="handleNum1">num1:{{ num1 }}</button>
        <button @click="handleNum2">num2:{{ num2 }}</button>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue"
let props = defineProps(['num1', 'num2'])
let $emit = defineEmits(['update:num1', 'update:num2'])
const handleNum1 = () => {
    $emit('update:num1', props.num1 + 1)
}
const handleNum2 = () => {
    $emit('update:num2', props.num2 + 10)
}
</script>
<style scoped lang="scss">
.son {
    width: 600px;
    height: 200px;
    background: gold;
}
</style>

11、useAttrs

vue3组合API方式

  • 类似props,都可以获取到父组件传的属性值和方法
  • 注意:props不能和userAttrs同时获取同一个属性的值,若同时获取,props方式优先拿到值
<template>
  <div class="">
    <h1>useAttrs通信实例</h1>
    <div class="parent">
      <h2>父组件有这么一个值:{{ hello }}</h2>
      <!-- 将值传给子组件 -->
      <child :hello="hello" />
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue"
import child from './child.vue'
let hello = ref('hello world')
</script>
<style scoped lang="sass">

</style>

<template>
    <div class="son">
        <h2>子组件</h2>
        <p>接收到父组件的值:{{ attrs.hello }}</p>
    </div>
</template>
<script setup lang="ts">
import { ref, reactive, useAttrs } from "vue"
let attrs = useAttrs()

</script>
<style scoped lang="scss">
.son {
    width: 300px;
    height: 200px;
    background-color: yellow;
}
</style>

以上便是vue组件间通信方式了

学习参考: 2-12集
尚硅谷Vue项目实战硅谷甄选,vue3项目+TypeScript前端项目一套通关

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜又爱编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值