com组件的ref有时需要有时不需要?_Vue3组件通信总结
前言
我们知道vue3
的Composition Api
是它几个最大亮点之一,所以下文都是在setup
中演示代码的实现。后面会以开发几个简单form组件
为例子来演示。
基本操作
这里先简单开发一个VInput
的输入框组件。组件就像一个函数,主要就是处理输入和输出。Vue3
在setup
函数上提供了两个参数,一个props
,一个是context
下面的emit
方法,分别来处理输入和输出。
props
现在VInput
就是子组件,我需要它能够接受父级传递一个值,让它可以帮我做后续的逻辑处理在返回给父级。所以,这里需要最基本的一些父子通信方式v-bind
,props
。
父级组件中
// 通过v-bind将数据想子组件传递:value="valueRef" />
const valueRef = ref('')复制代码
VInput中
:value="value" type="text" />
复制代码
emit
当我们在组件中接受参数,进行一些逻辑处理后,我们就需要将处理好的值,向外部进行一个返回,外部同时需要实现一个事件函数去接受。此时我就可以使用emit
方法
假设我们希望VInput
组件返回给外部的是一个限制长度的字符串。此时外部就需要实现一个对应的事件函数去接收这个值,然后VInput
内部通emit
执行事件,将内部的处理好的值当做参数返回出去。
VInput
:value="value" type="text" @input="onInput" ref="inputRef" />
复制代码
父级组件
// 通过v-on向子组件传递一个函数,用户接受返回值:value="valueRef" :maxLength="10" @onInput="onInput" />
复制代码
对于这种input
的组件的使用,我猜大家肯定都不想在父级组件这么麻烦的去接收和改变一个值,所以vue
是提供了v-model
来更快捷的实现输入和输出。
v-model
通过Vue3
的文档可以发现,这个指令的用法发生了一定的变化。在之前,我们要想实现一个自定义的非表单组件的双向绑定,需要通过xxxx.sync
的这种语法来实现,如今这个指令已经被废除了,而是统一使用v-model
这个指令。
父级组件
新的v-model
还可以支持多个数据的双向绑定。
v-model:value="valueRef" v-model:keyword="keywordRef" />复制代码
自定义的非表单组件
click="clickHandle">click
export default defineComponent({ name: 'VBtn', props: { value: String, keyword: String },setup(props, { emit }) {// 省略其他代码
// 用户点击按钮const clickHandle = (e: any) => {// 省略其他代码
// 修改对应的props的数据 emit('update:value', value) emit('update:keyword', value + '123') }
return {// ... } }})
复制代码
以上就是在Vue3
中一些基本通信方式的API的介绍。在Vue3
中一般都是采用Composition Api
的形式开发,所以你会发现开发的时候不能在采用this.$xxx
的方式去调用实例上的某个函数或者是属性。那些this.$parent
,this.$children
,this.$on
,this.$emit
等等都不能在使用了。
那在Vue3
中如何解决组件间那些通信的呢?咱们从简单到复杂的场景,一个个来分析。
先来看一下,开发的三个form
组件,组合起来的实际的用法是怎么样的:
ref="validateFormRef1" :model="state" :rules="rules">label="用户名" prop="keyword">placeholder="请输入"requiredv-model:modelValue="state.keyword" />label="密码" prop="password">placeholder="请输入"requiredtype="password"v-model:modelValue="state.password" />class="btn btn-primary" @click="submit(0)">提交复制代码
所有组件的功能,是模仿Element UI
去实现的。
父传子
父组件向子组件传递一个数据,可以用这两种方式:
v-bind
refs获取子组件内部某个函数,直接调用传参(这里简称refs方式)
refs方式
关于v-bind
咱们就不细说了,在基本操作章节已经讲过其对应的使用方式了。这小节主要在中讲Vue3
如何通过ref
获取子组件实例并调用其身上的函数来对子组件进行传值。
子组件
// 渲染从父级接受到的值
Son: {{ valueRef }}
复制代码
父组件
sonRef
click="sendValue">send// 这里ref接受的字符串,要setup返回的ref类型的变量同名ref="sonRef" />
复制代码
这里可以看一下流程图: 其实这种方式跟Vue2
中使用this.$refs
,this.$children
的方式很相似,都是通过拿到子组件实例,直接调用子组件身上的函数。方法千篇一律,不过在Vue3
中没有了this
这个黑盒。
这里我们可以在控制台看一下这个sonRef.value
是一个怎样的东西。
可以发现,通过ref
获取到的子组件实例上面可以拿到setup
返回的所有变量和方法,同时还可以拿到其他的一些内部属性。我们可以看一下官方文档Vue 组合式 API的描述。
在 Virtual DOM patch 算法中,如果一个 VNode 的 ref 对应一个渲染上下文中的 ref,则该 VNode 对应的元素或组件实例将被分配给该 ref。这是在 Virtual DOM 的 mount / patch 过程中执行的,因此模板 ref 仅在渲染初始化后才能访问。
ref方式总结
优点:
父组件可以获取快速向确定存在的子组件传递数据
传递的参数不受限制,传递方式比较灵活
缺点:
ref获取的子组件必须确定存在的(不确定存在的情况:如插槽上子组件,
v-if
控制的子组件)子组件还需要实现接受参数的方法
父传更深的后代
一般往深度层级传递值,有这两种方式:
provide / inject
vuex
provide / inject
一看到“深”这个字,大家肯定第一想到的就Vue2
中的provide / inject
选项。没错,这套逻辑在vue3
中同样适用,这两个选项变成了两个方法。
provide
允许我们向当前组件的所有后代组件,传递一份数据,所有后代组件能够通过inject
这个方法来决定是否接受这份数据。
大致的示意图如下:
实际应用场景
主要应用的场景有两中,一种深度传递一个参数或者一个函数的时候,另一种是给插槽上不确定性的组件传参的时候。
重点说一下给插槽上的组件传参。先实现一个最外层的ValidateForm
组件,它主要负责接受一整个表单数据和整个表单数据的校验规则。其内部提供了一个插槽,用于放置一些不确定性的组件。还有一个ValidateFormItem
组件可以接受一个字段名,通过这字段名准确知道需要校验哪个字段(tips:功能其实和element-ui
类似)。
组件化开发,需要将参数和功能进行解耦,所以我们这样来设计:
ValidateForm
:model
,rules
,只管接受整份表单的数据和校验规则ValidateFormItem
:prop
,只管接受字段名,只需知道自己需要验证哪一个字段
ref="validateFormRef" :model="formData" :rules="rules">label="用户名" prop="keyword">label="密码" prop="password">复制代码
如果ValidateFormItem
组件需要通过prop
去效验某个字段,那它就需要拿到那份表单的数据,通过formData[prop]
去取到那个字段的值,那这份formData
从哪里来呢?首先不可能每写一个ValidateFormItem
组件都传递一份。因为,实际开发中我们并不能确定在ValidateForm
下要写多少个ValidateFormItem
组件,如果每写一个都手动传递一份表单的数据,这些写起来就会多了很多冗余的代码而且也很麻烦。所以,就由ValidateForm
这个组件独立接受并分发下来。
ValidateForm
所以我们需要ValidateForm
来向下分发数据。
复制代码
ValidateFormItem
ValidateFormItem
接受上面传递的数据。
复制代码
provide / inject总结
在这篇文章Vue组件通信方式及其应用场景总结中,大佬对其的优缺点已经总结很好了。这里提一下它的缺点,就是不能解决兄弟组件的通信。
vuex
vuex
一直以来是vue
生态中一个解决不同层级组件数据共享的优质方案。不仅是在父传子中可以适用,在子传父,或者祖先传后代,后代传祖先,兄弟组件间都是一个非常好的方案。因为它是一个集中状态管理模式。其本质实现也是响应式的。这里只简单提一下Vue3
中是如何使用的。
创建一个store
import { createStore } from 'vuex'
export enum Mutarions { SET_COUNT = 'SET_COUNT'}
export default createStore({ state: { count: 231 }, getters: { count: state => state.count }, mutations: { [Mutarions.SET_COUNT]: (state, num: number) => (state.count = num) }})复制代码
父组件
father
ref="sonRef" />
复制代码
子组件
Son: {{ count }}
复制代码
子传父
子级向父级传递数据,可以有这三种方式:
v-on
refs方式
事件中心
refs方式
通过ref
的方式向父级传递一个数据是同样适用的。具体思路:子组件内部实现一个函数,该函数可以返回一个值。父级组件通过ref
取到子组件实例后调用该方法,得到需要的返回值。
这里来看一下实际的应用场景,我们希望ValidateForm
组件去验证下面所有的表单项,然后通过一个函数将组件内部的一个验证状态返回出去。
父组件
ref="validateFormRef" :model="formData" :rules="rules">label="用户名" prop="keyword">label="密码" prop="password">
复制代码
ValidateForm
复制代码
这里来看一下大致的流程图:
通过该种方法还可以拿到子组件内部的数据,这就跟闭包函数一样的道理。
事件中心
这种通信方式为什么拿到这里来讲呢?因为我觉接下的实际案例用上事件中心这种方式会非常的恰当。在上一个小节中,我们留下来一个坑,那就是ValidateForm
组件要去验证整个表单是否通过,就必须想办法让每个ValidateFormItem
将内部的校验结果返回给它。
首先会遇到两个问题
ValidateForm
下面的组件是通过插槽去挂载的,所以无法通过ref
的方式去拿到每个子表单项的实例,所以就没办法拿到每个ValidateFormItem
的验证状态了。上面的章节中有一个图片,展示了通过
ref
拿到的组件实例。可以发现,你可以找到$parent
属性,但是没有$children
属性。这就很尴尬了,我们没办法像Vue2
一样在ValidateForm
中通过$children
拿到每个子组件的实例。
解决思路
既然没有办法拿到插槽上的组件实例,那咱们就绕开它,通过一个事件中心的方式来解决。思路是这样的:
在
ValidateForm
实例初始化的时候,去创建一个事件中心Emitter
实例,它可以注册一个事件,当这个事件被执行时可以接受一个函数,并存在一个队列中。将这个
Emitter
通过provide
传递给后代,保证这个事件中心在不同的ValidateForm
组件中都是独立的。换句话说,就是如果写了多个ValidateForm
,他们的事件中心不会相互干扰。在
ValidateFormItem
中使用inject
接收自己所在表单域的Emitter
,在挂载的时候,执行Emitter
上的事件,将自己的内部的validate
函数,传递发送给ValidateForm
,并由其将方法缓存在队列中。ValidateForm
执行校验的时候,就可以执行队列中的所有校验函数,并得出校验结果。
具体代码实现:
先来实现一个Emitter
事件中心的类
import { EmitterHandles } from '@/type/utils'
export class Emitter {// 存放事件函数private events: EmitterHandles = {}
// 用于注册事件on(eventName: string, eventHandle: Function) {this.events[eventName] = eventHandle }
// 删除事件off(eventName: string) {if (this.events[eventName]) {delete this.events[eventName] } }
// 触发事件emit(eventName: string, ...rest: any[]) {if (this.events[eventName]) {this.events[eventName](...rest) } }}复制代码
当事件中心实现好了,这里来完善一下ValidateForm
的代码
复制代码
ok,现在实现了validateForm
的逻辑,我们再来写一下validateFormItem
的逻辑
class="form-group">v-if="label" class=" col-form-label">{{ label }}v-if="error.isError" class="invalid-feedback">
{{ error.errorMessage }}
复制代码
为了更详细的理解上面的过程,这里来画一个示意图:
注册事件,分发事件中心
执行事件,发送验证函数
整个过程的总结就是,顶层组件创建和分发事件中心,并注册事件监听函数。后代组件执行该事件然后发送信息,顶层组件回收信息。
Tips
这里再提一点,在使用Emitter
这个事件中心的时候,是在ValidateForm
的setup
中去创建并且去下发的,并不是使用一个全局的事件中心。就像大佬的这篇文章Vue组件通信方式及其应用场景总结中总结到的,事件总线的形式是有一个致命缺点的,如果一个页面上有多个公共组件,我们只要向其中的一个传递数据,但是每个公共组件都绑定了数据接受的方法,那就会出现混乱的情况。但是,我们的事件总线不是一个全局的,而是单个作用域里面的一个事件中心。
因为事件中心是在当前组件内部创建,并使用provide
向下发布的,这样就只有当前组件的后代才能使用这个事件中心。所以,就算一个面上写了多个ValidateForm
,他们的校验都是独立的。
ref="validateFormRef1" :model="formData1" :rules="rules">label="用户名" prop="keyword">label="密码" prop="password">ref="validateFormRef2" :model="formData2" :rules="rules">label="用户名" prop="keyword">label="密码" prop="password">复制代码
示意图:
事件中心总结
优点:
可以解决
Vue3
不能使用this.$children
的问题可以灵活使用,不受组件层级的限制
这种通信方式不受框架的限制
缺点:
需要控制好事件中心的作用范围
需要控制好事件名的规范
事件中心进阶
因为在Vue3
的Composition API
中,vue
的功能api更加的颗粒化。我们可以对事件中心进行一个自定义需求的改造。
可以通过引入reactive, ref
帮助我们的事件中心内部维护一个响应式的数据,可以实现当事件中心进行一定通信行为时,去更新对应的视图。还可以引入computed
实现计算属性的功能。
import { reactive, ref, computed } from 'vue'
export class Emitter {// 响应式的数据中心private state = reactive({})private events: EmitterHandles = ref({})
// 记录当前事件中心 事件的数量private eventLength = computed(() => Object.keys(events.value).length)
// 省略部分代码}
复制代码
加入watch,watchEffect
实现数据监听做出一定逻辑行为的功能。我认为Composition API
和React Hooks Api
都是非常强大,因为它们允许我们将功能函数当成积木一样去任意组装成我们希望得到的应用程序。
深层后代向顶层通信,兄弟通信
我觉得其实其他的场景,其通信方式基本都差不多了,所谓千篇一律。后代向祖先传值,或者兄弟组件传值,都可以使用vuex
或者是事件中心
的方式。兄弟层级,或者相邻层级的,就可以使用ref
,$parent
等方式。
com组件的ref有时需要有时不需要?_Vue3组件通信总结相关推荐
- com组件的ref有时需要有时不需要?_vue 组件通信看这篇就够了(12种通信方式)
点击上方"前端小苑",选择"置顶公众号" 精品技术文章,热门资讯第一时间送达 vue 组件间的通信是 vue 开发中很基础也十分重要的部分,作为使用 vue 的 ...
- vue3 setup中父组件通过Ref调用子组件的方法
在setup()钩子函数中调用 父组件 <template><div>我是父组件<children ref="childrenRef" />&l ...
- React---基础2(List/Key、表单(ref、event)、状态提升(共享组件)、组件占位符)
七.列表 和 键 列表(List), 键(Key) 回顾一下在javascript中如何转换列表:在数组中使用map()函数对numbers数组中的每个元素依次执操作 const number ...
- vue3 如何给动态渲染的组件添加ref
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.问题示例 二.尝试解决方案 三.最终解决方案 四.vue3官网解决方案 前言 提示:这里可以添加本文要记录的大概内 ...
- React基础(2)—— React函数式组件使用ref
React函数式组件使用ref ref ref的作用 ref用于获取DOM元素或子组件实例. useRef useRef作用 useRef用于返回一个可变的ref对象.这个refduix的curren ...
- vue 父组件调用子组件方法ref
一.ref被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的$refs对象上 vue中如果父组件想调用子组件的方法,可以在子组件中加上ref,然后通过this.$refs.ref.metho ...
- 支付宝小程序组件传参,父组件调用子组件方法ref
组件传参 子组件中 //js中声明 Component({props: {title: "标题", // 支付宝小程序props变量传参不能传对象,设置类型,只能传字符串onCon ...
- 【Day13】说一下 Vue 组件的通信方式都有哪些?(父子组件,兄弟组件,多级嵌套组件等等)
说一下 Vue 组件的通信方式都有哪些?(父子组件,兄弟组件,多级嵌套组件等等) 一.父组件向子组件传值 二.子组件向父组件传值 三.兄弟组件传值 四.跨组件 一.父组件向子组件传值 1.1 prop ...
- vue父子组件之间的传值,及互相调用父子组件之间的方法
场景:父子组件之间的传值方法,以及调用他们的内部的方法 *** 父组件给子组件传值是通过属性绑定的方法 *** 子组件给父组件传值是通过绑定对应的方法将自身的值传递给父 ...
最新文章
- OpenCV+python:圆检测
- 深度探索C++ 对象模型(6)-Data member的存取
- 用LINQ来对文章列表进行操作
- Python实现根据图片进行着色的词云
- socket编程TCP通信
- 奇妙的安全旅行之DES算法(一)
- boost 获取时间
- 【kafka】Group coordinator xx is unavailable or invalid, will attempt rediscovery
- QOS之NBAR 下
- mysql 使用service mysqld start 提示未识别服务 进入/etc/rc.d/init.d 下面未发现有mysqld解决方法
- 将标签重新定义为4个空格
- 斗地主发牌编程PHP,php模拟实现斗地主发牌
- matlab图像取样和量化,一文看懂数字图像的取样和量化
- Java实现 蓝桥杯 算法提高 矩阵翻转
- 微信小程序--动态时间实现
- [HCTF 2018]admin 1
- 解决VS CODE官网下载速度慢的问题
- scala case语句中的中置表示法
- Java如何连接Redis?
- hive on spark 线上问题排查案例分享
热门文章
- Creating a custom ComboBox item renderer in Flex
- Linux集群和自动化维1.5.1 服务器物理硬件的优化
- 四十一、Android Notification通知详解
- 业务服务管理究竟为何可望而不可及
- 智能实验室-全能优化(Guardio) 4.0.0.700 新春贺岁版
- 回调函数 相当于线程_Java中的回调机制,这篇给你整的明明白白的
- python爬取百度百科表格_第一个python爬虫(python3爬取百度百科1000个页面)
- 【PP】生产发货仓位决定
- 【BOM精讲】BOM 入门基本常识
- SAP修改登录桌面背景图片