vue ref是在组件里唯一吗_Vue.js 组件通信精髓归纳-程序员宅基地

技术标签: vue新开页面传递事件  vue 获取父组件里的ref  vue ref是在组件里唯一吗  

(给前端大学加星标,提升前端技能.)

作者:张炳

https://segmentfault.com/a/1190000018241972

组件的分类
常规页面组件

vue-router 产生的每个页面,它本质上也是一个组件( .vue),主要承载当前页面的 HTML 结构,会包含数据获取、数据整理、数据可视化等常规业务。

功能性抽象组件

不包含业务,独立、具体功能的基础组件,比如日期选择器、弹窗警告等。这类组件作为项目的基础控件,会被大量使用,因此组件的 API 进行过高强度的抽象,可以通过不同配置实现不同的功能。

业务组件

它不像第二类独立组件只包含某个功能,而是在业务中被多个页面复用的,它与独立组件的区别是,业务组件只在当前项目中会用到,不具有通用性,而且会包含一些业务,比如数据请求;而独立组件不含业务,在任何项目中都可以使用,功能单一,比如一个具有数据校验功能的输入框。

组件的关系
父子组件

父子关系即是组件 A 在它的模板中使用了组件 B,那么组件 A 就是父组件,组件 B 就是子组件。

// 注册一个子组件

Vue.component('child', {

data: function(){

return {

text: '我是father的子组件!'

}

},

template: '{ { text }}'

})

// 注册一个父组件

Vue.component('father', {

template: ' // 在模板中使用了child组件

})

兄弟组件

两个组件互不引用,则为兄弟组件。

Vue.component('brother1', {

template: '

我是大哥
'

})

Vue.component('brother2', {

template: '

我是小弟
'

})

使用组件的时候:

id="app">
跨级组件

就是在父子关系中,中间跨了很多个层级。

组件的构成

一个再复杂的组件,都是由三部分组成的: propeventslot,它们构成了 Vue.js 组件的 API。

属性 prop

prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时, props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。

插槽 slot

插槽 slot,它可以分发组件的内容。和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:

Something bad happened.

可能会渲染出这样的东西:

Error!Something bad happended.

幸好,Vue 自定义的 元素让这变得非常简单:

Vue.component('alert-box', {

template: `

Error!

`

})

如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!

自定义事件 event

两种写法:

1、在组件内部自定义事件 event

@click="handleClick">

export default {

methods: {

handleClick (event) {

this.$emit('on-click', event);

}

}

}

通过 $emit,就可以触发自定义的事件 on-click ,在父级通过 @on-click 来监听:

@on-click="handleClick">

2、用事件修饰符 .native 直接在父级声明

所以上面的示例也可以这样写:

@click.native="handleClick">

如果不写 .native 修饰符,那上面的 @click 就是自定义事件 click,而非原生事件 click,但我们在组件内只触发了 on-click 事件,而不是 click,所以直接写 @click 会监听不到。

组件的通信
ref和$parent和$children

Vue.js 内置的通信手段一般有两种:

  • ref:给元素或组件注册引用信息;

  • $parent / $children:访问父 / 子实例。

ref 来访问组件(部分代码省略):

// component-a

export default {

data () {

return {

title: 'Vue.js'

}

},

methods: {

sayHello () {

window.alert('Hello');

}

}

}

ref="comA">

export default {

mounted () {

const comA = this.$refs.comA;

console.log(comA.title); // Vue.js

comA.sayHello(); // 弹窗

}

}

$parent$children 类似,也是基于当前上下文访问父组件或全部子组件的。

这两种方法的弊端是,无法在跨级或兄弟间通信,比如下面的结构:

// parent.vue

<component-a>component-a>

<component-b>component-b>

<component-b>component-b>

我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,是暂时无法实现的,后面会讲解到方法。

provide / inject

一种无依赖的组件通信方法:Vue.js 内置的 provide / inject 接口。

provide / inject 是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。provideinject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

假设有两个组件: A.vueB.vue,B 是 A 的子组件:

// A.vue

export default {

provide: {

name: 'Aresn'

}

}

// B.vue

export default {

inject: ['name'],

mounted () {

console.log(this.name); // Aresn

}

}

需要注意的是: provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

只要一个组件使用了 provide 向下提供数据,那其下所有的子组件都可以通过 inject 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,那这个组件中就不能再声明 这个数据了,因为它已经被父级占有。

provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:

  • 父组件向子组件(支持跨级)传递数据;

  • 子组件向父组件(支持跨级)传递数据。

这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,下面介绍一种在父子组件间通信的方法 dispatchbroadcast

$attrs和$listeners

如果父组件 A 下面有子组件 B,组件 B 下面有组件 C,这时如果组件 A 想传递数据给组件C怎么办呢? Vue 2.4 开始提供了 $attrs$listeners 来解决这个问题,能够让组件 A 之间传递消息给组件 C。

Vue.component('C',{

template:`

`,

methods:{

passCData(val){

//触发父组件A中的事件

this.$emit('getCData',val)

}

}

})

Vue.component('B',{

data(){

return {

mymessage:this.message

}

},

template:`

`,

props:['message'],//得到父组件传递过来的数据

methods:{

passData(val){

//触发父组件中的事件

this.$emit('getChildData',val)

}

}

})

Vue.component('A',{

template:`

this is parent compoent!

`,

data(){

return {

message:'hello',

messagec:'hello c' //传递给c组件的数据

}

},

methods:{

getChildData(val){

console.log('这是来自B组件的数据')

},

//执行C子组件触发的事件

getCData(val){

console.log("这是来自C组件的数据:"+val)

}

}

})

var app=new Vue({

el:'#app',

template:`

`

})

派发与广播——自行实现 dispatch 和 broadcast 方法

要实现的 dispatchbroadcast 方法,将具有以下功能:在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件;相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。

// 部分代码省略

import Emitter from '../mixins/emitter.js'

export default {

mixins: [ Emitter ],

methods: {

handleDispatch () {

this.dispatch(); // ①

},

handleBroadcast () {

this.broadcast(); // ②

}

}

}

//emitter.js 的代码:

function broadcast(componentName, eventName, params) {

this.$children.forEach(child => {

const name = child.$options.name;

if (name === componentName) {

child.$emit.apply(child, [eventName].concat(params));

} else {

broadcast.apply(child, [componentName, eventName].concat([params]));

}

});

}

export default {

methods: {

dispatch(componentName, eventName, params) {

let parent = this.$parent || this.$root;

let name = parent.$options.name;

while (parent && (!name || name !== componentName)) {

parent = parent.$parent;

if (parent) {

name = parent.$options.name;

}

}

if (parent) {

parent.$emit.apply(parent, [eventName].concat(params));

}

},

broadcast(componentName, eventName, params) {

broadcast.call(this, componentName, eventName, params);

}

}

};

因为是用作 mixins 导入,所以在 methods 里定义的 dispatchbroadcast 方法会被混合到组件里,自然就可以用 this.dispatchthis.broadcast 来使用。

这两个方法都接收了三个参数,第一个是组件的 name 值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。

可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。

来看一下具体的使用方法。有 A.vueB.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:

@click="handleClick">触发事件

import Emitter from '../mixins/emitter.js';

export default {

name: 'componentA',

mixins: [ Emitter ],

methods: {

handleClick () {

this.broadcast('componentB', 'on-message', 'Hello Vue.js');

}

}

}

// B.vue

export default {

name: 'componentB',

created () {

this.$on('on-message', this.showMessage);

},

methods: {

showMessage (text) {

window.alert(text);

}

}

}

同理,如果是 B 向 A 通信,在 B 中调用 dispatch 方法,在 A 中使用 $on 监听事件即可。

以上就是自行实现的 dispatchbroadcast 方法。

找到任意组件实例——findComponents 系列方法

它适用于以下场景:

  1. 由一个组件,向上找到最近的指定组件;

  2. 由一个组件,向上找到所有的指定组件;

  3. 由一个组件,向下找到最近的指定组件;

  4. 由一个组件,向下找到所有指定的组件;

  5. 由一个组件,找到指定组件的兄弟组件。

5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。

1、向上找到最近的指定组件——findComponentUpward

// 由一个组件,向上找到最近的指定组件

function findComponentUpward (context, componentName) {

let parent = context.$parent;

let name = parent.$options.name;

while (parent && (!name || [componentName].indexOf(name) < 0)) {

parent = parent.$parent;

if (parent) name = parent.$options.name;

}

return parent;

}

export { findComponentUpward };

比如下面的示例,有组件 A 和组件 B,A 是 B 的父组件,在 B 中获取和调用 A 中的数据和方法:

组件 A

import componentB from './component-b.vue';

export default {

name: 'componentA',

components: { componentB },

data () {

return {

name: 'Aresn'

}

},

methods: {

sayHello () {

console.log('Hello, Vue.js');

}

}

}

组件 B

import { findComponentUpward } from '../utils/assist.js';

export default {

name: 'componentB',

mounted () {

const comA = findComponentUpward(this, 'componentA');

if (comA) {

console.log(comA.name); // Aresn

comA.sayHello(); // Hello, Vue.js

}

}

}

2、向上找到所有的指定组件——findComponentsUpward

// 由一个组件,向上找到所有的指定组件

function findComponentsUpward (context, componentName) {

let parents = [];

const parent = context.$parent;

if (parent) {

if (parent.$options.name === componentName) parents.push(parent);

return parents.concat(findComponentsUpward(parent, componentName));

} else {

return [];

}

}

export { findComponentsUpward };

3、向下找到最近的指定组件——findComponentDownward

// 由一个组件,向下找到最近的指定组件

function findComponentDownward (context, componentName) {

const childrens = context.$children;

let children = null;

if (childrens.length) {

for (const child of childrens) {

const name = child.$options.name;

if (name === componentName) {

children = child;

break;

} else {

children = findComponentDownward(child, componentName);

if (children) break;

}

}

}

return children;

}

export { findComponentDownward };

4、向下找到所有指定的组件——findComponentsDownward

// 由一个组件,向下找到所有指定的组件

function findComponentsDownward (context, componentName) {

return context.$children.reduce((components, child) => {

if (child.$options.name === componentName) components.push(child);

const foundChilds = findComponentsDownward(child, componentName);

return components.concat(foundChilds);

}, []);

}

export { findComponentsDownward };

5、找到指定组件的兄弟组件——findBrothersComponents

// 由一个组件,找到指定组件的兄弟组件

function findBrothersComponents (context, componentName, exceptMe = true) {

let res = context.$parent.$children.filter(item => {

return item.$options.name === componentName;

});

let index = res.findIndex(item => item._uid === context._uid);

if (exceptMe) res.splice(index, 1);

return res;

}

export { findBrothersComponents };

相比其它 4 个函数, findBrothersComponents 多了一个参数 exceptMe,是否把本身除外,默认是 true。寻找兄弟组件的方法,是先获取 context.$parent.$children,也就是父组件的全部子组件,这里面当前包含了本身,所有也会有第三个参数 exceptMe。Vue.js 在渲染组件时,都会给每个组件加一个内置的属性 _uid,这个 _uid 是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。

Event Bus

有时候两个组件之间需要进行通信,但是它们彼此不是父子组件的关系。在一些简单场景,你可以使用一个空的 Vue 实例作为一个事件总线中心(central event bus):

//中央事件总线

var bus=new Vue();

var app=new Vue({

el:'#app',

template:`

`

})

// 在组件 brother1 的 methods 方法中触发事件

bus.$emit('say-hello', 'world')

// 在组件 brother2 的 created 钩子函数中监听事件

bus.$on('say-hello', function (arg) {

console.log('hello ' + arg); // hello world

})

vuex处理组件之间的数据交互

如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex 的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 详情可参考:https://vuex.vuejs.org/zh-cn/。

参考
  • vue组件之间8种组件通信方式总结(https://blog.csdn.net/zhoulu001/article/details/79548350)

  • https://github.com/iview/iview/blob/2.0/src/mixins/emitter.js

  • https://github.com/iview/iview/blob/2.0/src/utils/assist.js

免费福利

f9fcd4993a54588ddc9594ee4fdc967b.png

好文和朋友一起点个 在看~
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39562340/article/details/109981404

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签