Vue3中组件数据通信方式总结
作者:书源
如果在面试中,面试官问你Vue组件之间有哪些数据通信方式,你会怎么回复?
要回答这个问题,我们先来了解一下组件之间数据通信有哪些场景。
如图所示,组件之间数据通信有父子组件之间通信,跨级组件之间通信以及兄弟组件之间通信三种场景,那这三种场景分别有哪些通信手段呢?
父子组件通信
相信大家对这个场景都很熟悉,我这里简单总结下这个场景下的通信方案。
props
在 Vue.js 中实现“父子组件”单向数据通信,一般都是通过 Props 来进行的。
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,这避免了子组件意外修改父组件状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
每次父组件更新后,所有的子组件中 props 都会被更新到最新值,这意味着我们不应该在子组件中去更改一个 prop。如果做了,Vue 会在控制台上向你抛出警告:
const props = defineProps(['foo']) // ❌ 警告!prop 是只读的! props.foo = 'bar'
如果我们在需求中需要更改 prop怎么办?答案是子组件可以抛出一个事件$emit
来通知父组件做出改变。
$emit
在子组件中可以直接使用 $emit 方法触发自定义事件,父组件可以通过 v-on (缩写为 @) 来监听事件,然后在监听的回调函数中做出数据的改变。示例:
<!-- 子组件触发事件 --> <button @click="$emit('someEvent')">click me</button> <!-- 父组件监听 --> <MyComponent @some-event="callback" />
通过$emit,子组件可以通过事件参数向父组件传递数据。
不过这种获取子组件数据的方式比较被动,需要等待子组件事件的触发,但一些特殊场合下父组件需要主动获取子组件数据,这时又该如何处理呢?
ref
ref指的是模版引用,它可以直接访问DOM元素,也可以被用在一个子组件上,获取组件实例,进而操作对应元素或者获取数据。
示例,对 DOM 的直接操作:
<script setup> import { ref, onMounted } from 'vue' // 声明一个 ref 来存放该元素的引用 // 必须和模板里的 ref 同名 const input = ref(null) onMounted(() => { // 用来从父级组件聚焦输入框 input.value.focus() }) </script> <template> <input ref="input" /> </template>
示例,对子组件的直接操作:
<script setup> import { ref, onMounted } from 'vue' import Child from './Child.vue' const child = ref(null) onMounted(() => { // child.value 是 <Child /> 组件的实例 }) </script> <template> <Child ref="child" /> </template>
其它的父子组件通信方式诸如$parent等等,大家请自行理解啦,接下来我们来看下跨级关系的组件如何通信。
跨级组件通信
这个场景下,适合用 provide/inject 进行处理,它允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深。祖先组件中通过 provider 来提供变量,子孙组件中通过 inject 来注入变量,不过它的使用场景,主要是子组件获取上级组件的状态。
我们来看个简单的使用案例:
<script setup> import { ref, provide } from 'vue' // 提供静态值 provide('foo', 'bar') // 提供响应式的值 const count = ref(0) provide('count', count) </script> <script setup> import { inject } from 'vue' // 注入值的默认方式 const foo = inject('foo') // 注入响应式的值 const count = inject('count') </script>
需要说明的是 provide/inject 在父子组件通信中虽然也OK,但更多的场景是子孙组件。
兄弟组件通信
看到这个场景,有没有朋友想到我们可以借助与父组件通信,来完成兄弟组件通信?纵然不看代码,也能想到这种方案十分繁杂。
借用响应式数据文件
于是乎,我们可以考虑换个方案,把通信数据都放在一个响应式数据的文件里,所有通信需要的组件都直接引用这个文件里的数据,然后直接在各自组件间进行读数据或写数据。如果有组件里的模板视图使用到这个公共响应式数据,数据被其它组件修改,那也会同时触发模板视图的更新。这么多文字看着很抽象,我们来看下示例:
store.js,存放响应式数据的文件:
import { reactive } from "vue"; export const store = reactive({ text: "这里是公共响应式数据文件", });
B.vue:
<template> <div> <div>B 组件</div> <div>store:{{ store.text }}</div> </div> </template> <script setup> import { store } from "./store"; </script>
C.vue:
<template> <div> <div>C 组件</div> <div>store:{{ store.text }}</div> </div> </template> <script setup> import { store } from "./store"; </script>
父组件:
<template> <div @click="handle">change</div> <B></B> <C></C> </template> <script setup> import B from "./components/B.vue"; import C from "./components/C.vue"; import { store } from "./components/store"; function handle() { store.text = "改变数据文件"; } </script>
B
组件和C
组件通过响应式数据文件里的store进行通信,当我们在父组件里改了store.text数据,子组件的视图也会自动更新。
不过聪明的你肯定也发现了,虽然这样兄弟组件可以互相通信,但是呢,一旦公共响应式数据变得庞大,所有引用组件都可以自由修改数据,那么数据流向管理将会变得很混乱,不好管理和维护。
既然我们遇到了这样的问题,别人同样会遇到,于是乎Vue官方为我们提供了Pinia
,一个数据管理的 JavaScript 库。
Pinia
这里我想借鉴一下Pinia官网对Pinia的描述,更详细的描述可以参考官网。
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的 export const state = reactive({})
来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:
Devtools 支持
- 追踪 actions、mutations 的时间线
- 在组件中展示它们所用到的 Store
- 让调试更容易的 Time travel
热更新
- 不必重载页面即可修改 Store
- 开发时可保持当前的 State
插件:可通过插件扩展 Pinia 功能
为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
支持服务端渲染
总结
今天我们一起了解了Vue组件通信的3个场景:
父子组件:父组件可以通过props
向子组件传递数据,子组件可以通过**$emit
** 方法触发父组件接收数据,父组件还可以通过模版引用主动获取子组件数据。
跨级组件:一般通过provider/inject
传递数据,其实也可以Pina
进行通信
兄弟组件:简单的场景我们可以借助于响应式数据文件来完成通信,复杂的场景可以使用Vue官网提供的Pinia
库来管理数据。
到此这篇关于Vue3中组件数据通信方式总结的文章就介绍到这了,更多相关Vue3组件数据通信内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!