组件的分类
- 通例页面组件,由 vue-router 发生的每一个页面,它本质上也是一个组件(.vue),主要承载当前页面的 HTML 构造,会包括数据猎取、数据整顿、数据可视化等通例营业。
- 功用性笼统组件,不包括营业,自力、详细功用的基本组件,比方日期选择器、弹窗正告等。这类组件作为项目标基本控件,会被大批运用,因而组件的 API 举行太高强度的笼统,可以经由过程差别设置完成差别的功用。
- 营业组件,它不像第二类自力组件只包括某个功用,而是在营业中被多个页面复用的,它与自力组件的区别是,营业组件只在当前项目中会用到,不具有通用性,而且会包括一些营业,比方数据要求;而自力组件不含营业,在任何项目中都可以运用,功用单一,比方一个具有数据校验功用的输入框。
组件的关联
父子组件
父子关联等于组件 A 在它的模板中运用了组件 B,那末组件 A 就是父组件,组件 B 就是子组件。
// 注册一个子组件
Vue.component('child', {
data: function(){
return {
text: '我是father的子组件!'
}
},
template: '<span>{{ text }}</span>'
})
// 注册一个父组件
Vue.component('father', {
template: '<div><child></child></div>' // 在模板中运用了child组件
})
兄弟组件
两个组件互不援用,则为兄弟组件。
Vue.component('brother1', {
template: '<div>我是老大</div>'
})
Vue.component('brother2', {
template: '<div>我是小弟</div>'
})
运用组件的时刻:
<div id="app">
<brother1></brother1>
<brother2></brother2>
</div>
跨级组件
就是在父子关联中,中间跨了许多个层级
组件的构成
一个再庞杂的组件,都是由三部份构成的:prop、event、slot,它们构成了 Vue.js 组件的 API。
属性 prop
prop 定义了这个组件有哪些可设置的属性,组件的中间功用也都是它来肯定的。写通用组件时,props 最好用对象的写法,如许可以针对每一个属性设置范例、默许值或自定义校验属性的值,这点在组件开辟中很主要,然则许多人却无视,直接运用 props 的数组用法,如许的组件往往是不严谨的。
插槽 slot
插槽 slot,它可以分发组件的内容。和 HTML 元素一样,我们常常须要向一个组件通报内容,像如许:
<alert-box>
Something bad happened.
</alert-box>
能够会衬着出如许的东西:
Error!Something bad happended.
幸亏,Vue 自定义的 <slot> 元素让这变得异常简朴:
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
如你所见,我们只需在须要的处所到场插槽就好了——就这么简朴!
自定义事宜 event
两种写法:
- 在组件内部自定义事宜event
<template>
<button @click="handleClick">
<slot></slot>
</button>
</template>
<script>
export default {
methods: {
handleClick (event) {
this.$emit('on-click', event);
}
}
}
</script>
经由过程 $emit,便可以触发自定义的事宜 on-click ,在父级经由过程 @on-click 来监听:
<i-button @on-click="handleClick"></i-button>
- 用事宜修饰符 .native直接在父级声明
所以上面的示例也可以如许写:
<i-button @click.native="handleClick"></i-button>
假如不写 .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');
}
}
}
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 弹窗
}
}
</script>
$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 的上下文特征很类似。
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。
$attrs和$listeners
假如父组件A下面有子组件B,组件B下面有组件C,这时候假如组件A想通报数据给组件C怎么办呢? Vue 2.4最先供应了$attrs和$listeners来处置惩罚这个题目,可以让组件A之间通报音讯给组件C。
Vue.component('C',{
template:`
<div>
<input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"> </div>
`,
methods:{
passCData(val){
//触发父组件A中的事宜
this.$emit('getCData',val)
}
}
})
Vue.component('B',{
data(){
return {
mymessage:this.message
}
},
template:`
<div>
<input type="text" v-model="mymessage" @input="passData(mymessage)">
<!-- C组件中能直接触发getCData的缘由在于 B组件挪用C组件时 运用 v-on 绑定了$listeners 属性 -->
<!-- 经由过程v-bind 绑定$attrs属性,C组件可以直接猎取到A组件中通报下来的props(除了B组件中props声明的) -->
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
`,
props:['message'],//获得父组件通报过来的数据
methods:{
passData(val){
//触发父组件中的事宜
this.$emit('getChildData',val)
}
}
})
Vue.component('A',{
template:`
<div>
<p>this is parent compoent!</p>
<B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B>
</div>
`,
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:`
<div>
<A></A>
</div>
`
})
派发与播送——自行完成 dispatch 和 broadcast 要领
要完成的 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 通讯:
<!-- A.vue -->
<template>
<button @click="handleClick">触发事宜</button>
</template>
<script>
import Emitter from '../mixins/emitter.js';
export default {
name: 'componentA',
mixins: [ Emitter ],
methods: {
handleClick () {
this.broadcast('componentB', 'on-message', 'Hello Vue.js');
}
}
}
</script>
// 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 要领。
找到恣意组件实例——findComponents 系列要领
它适用于以下场景:
- 由一个组件,向上找到近来的指定组件;
- 由一个组件,向上找到一切的指定组件;
- 由一个组件,向下找到近来的指定组件;
- 由一个组件,向下找到一切指定的组件;
- 由一个组件,找到指定组件的兄弟组件。
5 个差别的场景,对应 5 个差别的函数,完成道理也迥然差别。
向上找到近来的指定组件——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 中的数据和要领:
<!-- component-a.vue -->
<template>
<div>
组件 A
<component-b></component-b>
</div>
</template>
<script>
import componentB from './component-b.vue';
export default {
name: 'componentA',
components: { componentB },
data () {
return {
name: 'Aresn'
}
},
methods: {
sayHello () {
console.log('Hello, Vue.js');
}
}
}
</script>
<!-- component-b.vue -->
<template>
<div>
组件 B
</div>
</template>
<script>
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
}
}
}
</script>
向上找到一切的指定组件——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 };
向下找到近来的指定组件——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 };
向下找到一切指定的组件——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 };
找到指定组件的兄弟组件——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:`
<div>
<brother1></brother1>
<brother2></brother2>
</div>
`
})
// 在组件 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://github.com/iview/ivie…
参考 https://github.com/iview/ivie…