技术标签: 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">
就是在父子关系中,中间跨了很多个层级。
一个再复杂的组件,都是由三部分组成的: prop
、 event
、 slot
,它们构成了 Vue.js 组件的 API。
prop
定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时, props
最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props
的数组用法,这样的组件往往是不严谨的。
插槽 slot
,它可以分发组件的内容。和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
Something bad happened.
可能会渲染出这样的东西:
Error!Something bad happended.
幸好,Vue 自定义的 元素让这变得非常简单:
Vue.component('alert-box', {
template: `
Error!
`
})
如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!
两种写法:
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
会监听不到。
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
组件,那这种情况下,是暂时无法实现的,后面会讲解到方法。
一种无依赖的组件通信方法:Vue.js 内置的 provide
/ inject
接口。
provide
/ inject
是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
provide
和inject
主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
假设有两个组件: A.vue
和 B.vue
,B 是 A 的子组件:
// A.vue
export default {
provide: {
name: 'Aresn'
}
}
// B.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // Aresn
}
}
需要注意的是: provide
和 inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
只要一个组件使用了 provide
向下提供数据,那其下所有的子组件都可以通过 inject
来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,那这个组件中就不能再声明 这个数据了,因为它已经被父级占有。
provide
/ inject
API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:
父组件向子组件(支持跨级)传递数据;
子组件向父组件(支持跨级)传递数据。
这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,下面介绍一种在父子组件间通信的方法 dispatch
和 broadcast
。
如果父组件 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
方法,将具有以下功能:在子组件调用 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
里定义的 dispatch
和 broadcast
方法会被混合到组件里,自然就可以用 this.dispatch
和 this.broadcast
来使用。
这两个方法都接收了三个参数,第一个是组件的 name
值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。
可以看到,在 dispatch
里,通过 while
语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent
即为父组件实例),直到匹配到定义的 componentName
与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast
方法与之类似,只不过是向下遍历寻找。
来看一下具体的使用方法。有 A.vue
和 B.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 监听事件即可。
以上就是自行实现的 dispatch
和 broadcast
方法。
它适用于以下场景:
由一个组件,向上找到最近的指定组件;
由一个组件,向上找到所有的指定组件;
由一个组件,向下找到最近的指定组件;
由一个组件,向下找到所有指定的组件;
由一个组件,找到指定组件的兄弟组件。
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
是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。
有时候两个组件之间需要进行通信,但是它们彼此不是父子组件的关系。在一些简单场景,你可以使用一个空的 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 的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 详情可参考: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
免费福利
好文和朋友一起点个 在看~文章浏览阅读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..._达梦数据库导入导出
文章浏览阅读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
文章浏览阅读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
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读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...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读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++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读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怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf