com组件的ref有时需要有时不需要?_vue 组件通信看这篇就够了(12种通信方式)
点击上方“前端小苑”,选择“置顶公众号”
精品技术文章,热门资讯第一时间送达
vue 组件间的通信是 vue 开发中很基础也十分重要的部分,作为使用 vue 的开发者每天都在使用。同时,vue 通信也是面试中非常高频的问题,有很多面试题,都是围绕通信展开。
本文会介绍常见的通信方式,并分析每种方式的使用场景和注意点。
vue中提倡单向数据流,这是为了保证数据流向的简洁性,使程序更易于理解。但对于一些边界情况,vue也提供了隐性的通信方式,这些通信方式会打破单向数据流的原则,应该谨慎使用。
下面我们将组件通信分为父子组件通信 和 非父子组件通信进行分析。
父子组件通信
prop 和 events
prop
和 events
最基础也最常用,这里不提供示例。
通过 prop
向下传递,通过事件向上传递是一个 vue 项目最理想的通信状态。
使用时有两点需要注意:
第一,不应该在一个子组件内部改变
prop
,这样会破坏单向的数据绑定,导致数据流难以理解。如果有这样的需要,可以通过data
属性接收或使用computed
属性进行转换。第二,如果
props
传递的是引用类型(对象或者数组),在子组件中改变这个对象或数组,父组件的状态会也会做相应的更新,利用这一点就能够实现父子组件数据的“双向绑定”,虽然这样实现能够节省代码,但会牺牲数据流向的简洁性,令人难以理解,最好不要这样去做。想要实现父子组件的数据“双向绑定”,可以使用v-model
或.sync
。
v-model 指令
v-model
是用来在表单控件或者组件上创建双向绑定的,他的本质是 v-bind
和 v-on
的语法糖,在一个组件上使用 v-model
,默认会为组件绑定名为 value
的 prop
和名为 input
的事件。
当我们组件中的某一个 prop
需要实现上面所说的”双向绑定“时,v-model
就能大显身手了。有了它,就不需要自己手动在组件上绑定监听当前实例上的自定义事件,会使代码更简洁。
下面以一个 input 组件实现的核心代码,介绍下 v-model
的应用。
<template> <base-input v-model="input">base-input>template><script> export default { data() { return { input: '' } }, }script>
<template> <input type="text" :value="currentValue" @input="handleInput">template><script> export default { data() { return { currentValue: this.value === undefined || this.value === null ? '' } }, props: { value: [String, Number], }, methods: { handleInput(event) { const value = event.target.value; this.$emit('input', value); }, },}script>
有时,在某些特定的控件中名为 value
的属性会有特殊的含义,这时可以通过 model
选项来回避这种冲突。
.sync 修饰符
.sync
修饰符在 vue 1.x 的版本中就已经提供,1.x 版本中,当子组件改变了一个带有 .sync
的 prop
的值时,会将这个值同步到父组件中的值。这样使用起来十分方便,但问题也十分明显,这样破坏了单向数据流,当应用复杂时,debug 的成本会非常高。
于是乎,在vue 2.0中移除了 .sync
。但是在实际的应用中,.sync
是有它的应用场景的,所以在 vue 2.3 版本中,又迎来了全新的 .sync
。
新的 .sync
修饰符所实现的已经不再是真正的双向绑定,它的本质和 v-model
类似,只是一种缩写。
document v-bind:title="doc.title" v-on:update:title="doc.title = $event">text-document>
上面的代码,使用 .sync
就可以写成
document v-bind:title.sync="doc.title">text-document>
这样,在子组件中,就可以通过下面代码来实现对这个 prop
重新赋值的意图了。
this.$emit('update:title', newTitle)
v-model 和 .sync 对比
.sync
从功能上看和v-model
十分相似,都是为了实现数据的“双向绑定”,本质上,也都不是真正的双向绑定,而是语法糖。相比较之下,
.sync
更加灵活,它可以给多个prop
使用,而v-model
在一个组件中只能有一个。从语义上来看,
v-model
绑定的值是指这个组件的绑定值,比如 input 组件,select 组件,日期时间选择组件,颜色选择器组件,这些组件所绑定的值使用v-model
比较合适。其他情况,没有这种语义,个人认为使用.sync
更好。
ref
ref 特性可以为子组件赋予一个 ID 引用,通过这个 ID 引用可以直接访问这个子组件的实例。当父组件中需要主动获取子组件中的数据或者方法时,可以使用 $ref
来获取。
<template> <base-input ref="baseInput">base-input>template><script> export default { methods: { focusInput: function () { this.$refs.usernameInput.focus() } }}script>
<template> <input ref="input">template><script> export default { methods: { focus: function () { this.$refs.input.focus() } }}script>
使用 ref 时,有两点需要注意
$refs
是作为渲染结果被创建的,所以在初始渲染的时候它还不存在,此时无法无法访问。
$refs
不是响应式的,只能拿到获取它的那一刻子组件实例的状态,所以要避免在模板和计算属性中使用它。
$parent 和 $children
$parent
属性可以用来从一个子组件访问父组件的实例,$children
属性 可以获取当前实例的直接子组件。
看起来使用 $parent
比使用prop传值更加简单灵活,可以随时获取父组件的数据或方法,又不像使用 prop
那样需要提前定义好。但使用 $parent
会导致父组件数据变更后,很难去定位这个变更是从哪里发起的,所以在绝大多数情况下,不推荐使用。
在有些场景下,两个组件之间可能是父子关系,也可能是更多层嵌套的祖孙关系,这时就可以使用 $parent
。
下面是 element ui 中的组件 el-radio-group 和 组件 el-radio 使用示例:
<template> <el-radio-group v-model="radio1"> <el-radio :label="3">备选项el-radio> <component-1> <el-radio :label="3">备选项el-radio> component-1> el-radio-group>template><script> export default { data () { return { radio2: 3 }; } }script>
在 el-radio-group 和 组件 el-radio 通信中, 组件 el-radio 的 value 值需要和 el-radio-group的 v-model
的值进行“绑定”,我们就可以在 el-radio 内借助 $parent
来访问到 el-radio-group 的实例,来获取到 el-radio-group 中 v-model
绑定的值。
下面是获取 el-radio 组件中获取 el-radio-group 实例的源码:
// el-radio组件 let parent = this.$parent; while (parent) { if (parent.$options.componentName !== 'ElRadioGroup') { parent = parent.$parent; } else { this._radioGroup = parent; // this._radioGroup 为组件 el-radio-group 的实例 } }
非父子组件通信
$attrs 和 $listeners
当要和一个嵌套很深的组件进行通信时,如果使用 prop
和 events
就会显的十分繁琐,中间的组件只起到了一个中转站的作用,像下面这样:
<parent-component :message="message">我是父组件parent-component> <child-component :message="message">我是子组件child-component> <grand-child-component :message="message">我是孙子组件grand-child-component>
当要传递的数据很多时,就需要在中间的每个组件都重复写很多遍,反过来从后代组件向祖先组件使用 events 传递也会有同样的问题。使用 $attrs
和 $listeners
就可以简化这样的写法。
$attrs
会包含父组件中没有被 prop
接收的所有属性(不包含class 和 style 属性),可以通过 v-bind="$attrs"
直接将这些属性传入内部组件。
$listeners
会包含所有父组件中的 v-on
事件监听器 (不包含 .native
修饰器的) ,可以通过 v-on="$listeners"
传入内部组件。
下面以父组件和孙子组件的通信为例介绍它们的使用:
<template> <child :name="name" :message="message" @sayHello="sayHello">child>template><script>export default { inheritAttrs: false, data() { return { name: '通信', message: 'Hi', } }, methods: { sayHello(mes) { console.log('mes', mes) // => "hello" }, },}script>
<template> <grandchild v-bind="$attrs" v-on="$listeners">grandchild>template><script>export default { data() { return {} }, props: { name, },}script>
<template>template><script>export default { created() { this.$emit('sayHello', 'hello') },}script>
provide 和 inject
provide
和 inject
需要在一起使用,它可以使一个祖先组件向其所有子孙后代注入一个依赖,可以指定想要提供给后代组件的数据/方法,不论组件层次有多深,都能够使用。
<script>export default { provide: { author: 'yushihu', }, data() {},}script>
<script>export default { inject: ['author'], created() { console.log('author', this.author) // => yushihu },}script>
provide
和 inject
绑定不是响应的,它被设计是为组件库和高阶组件服务的,平常业务中的代码不建议使用。
dispatch 和 broadcast
vue 在2.0版本就已经移除了 $dispatch
和 $broadcast
,因为这种基于组件树结构的事件流方式会在组件结构扩展的过程中会变得越来越难维护。但在某些不使用 vuex 的情况下,仍然有使用它们的场景。所以 element ui 和 iview 等开源组件库中对 broadcast
和 dispatch
方法进行了重写,并通过 mixin 的方式植入到每个组件中。
实现 dispatch
和 broadcast
主要利用我们上面已经说过的 $parent
和 $children
。
//element ui 中重写 broadcast 的源码function broadcast(componentName, eventName, params) { this.$children.forEach(child => { var name = child.$options.componentName; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } });}
broadcast
方法的作用是向后代组件传值,它会遍历所有的后代组件,如果后代组件的 componentName
与当前的组件名一致,则触发 $emit
事件,将数据 params
传给它。
//element ui 中重写 dispatch 的源码 dispatch(componentName, eventName, params) { var parent = this.$parent || this.$root; var name = parent.$options.componentName; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.componentName; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }
dispatch
的作用是向祖先组件传值,它会一直寻找父组件,直到找到组件名和当前传入的组件名一致的祖先组件,就会触发其身上的 $emit
事件,将数据传给它。这个寻找对应的父组件的过程和文章前面讲解 $parent
的例子类似。
eventBus
对于比较小型的项目,没有必要引入 vuex 的情况下,可以使用 eventBus
。相比我们上面说的所有通信方式,eventBus
可以实现任意两个组件间的通信。
它的实现思想也很好理解,在要相互通信的两个组件中,都引入同一个新的vue实例,然后在两个组件中通过分别调用这个实例的事件触发和监听来实现通信。
//eventBus.jsimport Vue from 'vue';export default new Vue();
<script>import Bus from 'eventBus.js';export default { methods: { sayHello() { Bus.$emit('sayHello', 'hello'); } }}script>
<script>import Bus from 'eventBus.js';export default { created() { Bus.$on('sayHello', target => { console.log(target); // => 'hello' }); }}script>
通过 $root 访问根实例
通过 $root
,任何组件都可以获取当前组件树的根 Vue 实例,通过维护根实例上的 data
,就可以实现组件间的数据共享。
//main.js 根实例new Vue({ el: '#app', store, router, // 根实例的 data 属性,维护通用的数据 data: function () { return { author: '' } }, components: { App }, template: '',});
<script>export default { created() { this.$root.author = '于是乎' }}script>
<template> <div><span>本文作者span>{{ $root.author }}div>template>
通过这种方式,虽然可以实现通信,但在应用的任何部分,任何时间发生的任何数据变化,都不会留下变更的记录,这对于稍复杂的应用来说,调试是致命的,不建议在实际应用中使用。
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。对一个中大型单页应用来说是不二之选。
使用 Vuex 并不代表就要把所有的状态放入 Vuex 管理,这样做会让代码变的冗长,无法直观的看出要做什么。对于严格属于组件私有的状态还是应该在组件内部管理更好。
自己实现简单的 Store 模式
对于小型的项目,通信十分简单,这时使用 Vuex 反而会显得冗余和繁琐,这种情况最好不要使用 Vuex,可以自己在项目中实现简单的 Store。
//store.jsvar store = { debug: true, state: { author: 'yushihu!' }, setAuthorAction (newValue) { if (this.debug) console.log('setAuthorAction triggered with', newValue) this.state.author = newValue }, deleteAuthorAction () { if (this.debug) console.log('deleteAuthorAction triggered') this.state.author = '' }}
和 Vuex 一样,store 中 state
的改变都由 store 内部的 action
来触发,并且能够通过 log
保留触发的痕迹。这种方式十分适合在不需要使用 Vuex 的小项目中应用。
与 $root
访问根实例的方法相比,这种集中式状态管理的方式能够在调试过程中,通过 log
记录来确定当前变化是如何触发的,更容易定位问题。
欢迎关注我的公众号「前端小苑」,我会定期在上面更新原创文章。
更多相关文章
vue3.0抢先看(附尤雨溪vue分享ppt)
深入vue - 源码目录及构建过程分析
深入vue -- Virtual Dom是如何被创建的
点击“
点一下你会更好看耶
com组件的ref有时需要有时不需要?_vue 组件通信看这篇就够了(12种通信方式)相关推荐
- angularjs1访问子组件_vue 组件通信看这篇就够了(12种通信方式)
vue 组件间的通信是 vue 开发中很基础也十分重要的部分,作为使用 vue 的开发者每天都在使用.同时,vue 通信也是面试中非常高频的问题,有很多面试题,都是围绕通信展开. 本文会介绍常见的通信 ...
- com组件的ref有时需要有时不需要?_Vue3组件通信总结
前言 我们知道vue3的Composition Api是它几个最大亮点之一,所以下文都是在setup中演示代码的实现.后面会以开发几个简单form组件为例子来演示. 基本操作 这里先简单开发一个VIn ...
- 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 ...
- Vue的基本组件4(ref)
ref 简介:ref是refence的缩写,就是引用的意思,所以它的功能就是一种连接,各个模块的作用,这么说比较抽象,直接看代码. 学习代码: <!DOCTYPE html> <ht ...
最新文章
- Python引起的混乱解决之道——感悟
- 【leetcode】
- Unable to Open User Login File
- 活照片 android,活照片app安卓
- 2017年秋招美团Java程序员开发,看我如何拿到offer
- bzoj2938: [Poi2000]病毒
- redhat 6.5 yum不能使用之改进
- Liunx服务器部署系列
- Python使用标准库subprocess调用外部程序
- 2020年,为什么你该学PHP?!!
- Android设计模式--之命令模式
- acm路上的一些感想
- 程序员跳槽时,如何高效地准备面试?
- 怎么让小白理解intel处理器(CPU)的分类
- 腾讯Bugly,简单实用的崩溃日志收集
- java项目----教务管理系统_基于Java的教务管理系统
- GNS3实现VPLS实验
- 我的世界服务器如何修改权限设置,我的世界设置成员权限 | 手游网游页游攻略大全...
- “奈雪们”的瓶装茶饮,是门好生意吗?
- 黑苹果与白苹果的区别及其安装方式
热门文章
- LOGO设计没有灵感?5种方法来寻找标志设计的灵感和想法
- 直观简洁,轻易吸睛!促销海报模板
- python sqlite3 增删改查(最基本的增删改查)
- cudaMemset的调用方式
- Linux环境使用命名空间编写一个简单的容器应用程序:namespace,container,cgroups
- 【原译】汇编编程之:Hello World!详解- 好文!!!
- 【HTML+CSS网页设计与布局 从入门到精通】第6章-标题h1,h1字体格式的设置方式
- VUE3(setup响应式函数系统API)
- scipy.signal.find_peaks(峰值检测)
- servlet请求的执行过程_Springmvc执行流程