在项目中,对于组件的划分,我们一般会划分为业务组件和功能组件,也可以称为视图组件和容器组件。在vue中也被称为逻辑组件和UI组件。组件划分明确,对于代码的可维护性和阅读性有一定的便利性。
通过阅读源码以及文章,我认为组件的设计需要考量以下几点:
可扩展性强
扩展性首先是我们要考虑的点,如果不能扩展,就代表着代码写死,失去了代码的灵活性
组件中方法函数的抽离,便于复用,适用程度高。
尽可能使用方法定义,避免使用template表达式,不便于复用
文档清楚详细
毕竟写的组件是给人用的,不完善的文档让别人如何使用,肯定不能手把手教别人怎么使用吧,所以一个组件详细的使用说明是必须的。
颗粒度合适,适度抽象
这个是一个经验的问题,如何衡量颗粒度是否合适,其实是一个度的问题,每个人有每个人的看法,但是尽量保证一个组件完成的功能是单一的,而不是多个功能的结合体。
功能尽可能单一,代码行数适中
这一点和上面颗粒度类似,以代码行数衡量也可以,一般的组件如果抽离合适的话,绝对不会超过1000行,如果代码太多,就说明组件划分不合理,抽离不完善,需要重新设计。
必要的时候需要ui的配合(设计不止于好看,更关乎好用。—乔布斯)
有的组件设计出来太丑,程序员的眼光和一个专业的设计师的眼光还是有一定差距的,所以如果可以的话可以请专业的设计师设计一下ui界面,在一定程度上可以吸引到别人。
2.12
组件化必须考虑到组件间的通信。 总结组件间通信的方式:
方法一: 通过props/ $emit
父组件通过props的方式向子组件传递数据,子组件中通过$emit,父组件中使用v-on
父组件向子组件传递数据比较简单:
举个例子:
// 父组件
<template>
<div id="app">
<users :users="users"></users>//前者自定义名称便于子组件调用,后者要传递数据名
</div>
</template>
<script>
import Users from "./components/Users"
export default {
name: 'App',
data(){
return{
users:["Henry","Bucky","Emily"]
}
},
components:{
"users":Users
}
}
//users子组件
<template>
<div class="hello">
<ul>
<li v-for="user in users">{{user}}</li>//遍历传递过来的值,然后呈现到页面
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props:{
users:{ //这个就是父组件中子标签自定义名字
type:Array,
required:true
}
}
}
</script>
子组件向父组件传递数据需要通过事件
举个例子:
// 子组件
<template>
<header>
<h1 @click="changeTitle">{{title}}</h1>//绑定一个点击事件
</header>
</template>
<script>
export default {
name: 'app-header',
data() {
return {
title:"Vue.js Demo"
}
},
methods:{
changeTitle() {
this.$emit("titleChanged","子向父组件传值");//自定义事件 传递值“子向父组件传值”
}
}
}
</script>
// 父组件
<template>
<div id="app">
<app-header v-on:titleChanged="updateTitle" ></app-header>//与子组件titleChanged自定义事件保持一致
// updateTitle($event)接受传递过来的文字
<h2>{{title}}</h2>
</div>
</template>
<script>
import Header from "./components/Header"
export default {
name: 'App',
data(){
return{
title:"传递的是一个值"
}
},
methods:{
updateTitle(e){ //声明这个函数
this.title = e;
}
},
components:{
"app-header":Header,
}
}
</script>
这种方法实际上是通过子组件来触发父组件的事件。所以要注意$emit中的参数对应的是父组件中的v-on: [参数]
方法二: $emit / $on (eventBus)
这种方法通过一个空的vue实例作为中央事件总线,用它来触发事件和监听事件,巧妙而轻量的实现了任何组件间的通信,包括父子、兄弟和跨级。
实现方式:
var Event = new Vue() Event.$emit(事件名,数据) Event.$on(事件名, data=>{})
举个例子:
假设兄弟组件有三个,分别是A、B、C组件,C组件如何获取A或者B组件的数据
<div id="itany">
<my-a></my-a>
<my-b></my-b>
<my-c></my-c>
</div>
<template id="a">
<div>
<h3>A组件:{{name}}</h3>
<button @click="send">将数据发送给C组件</button>
</div>
</template>
<template id="b">
<div>
<h3>B组件:{{age}}</h3>
<button @click="send">将数组发送给C组件</button>
</div>
</template>
<template id="c">
<div>
<h3>C组件:{{name}},{{age}}</h3>
</div>
</template>
<script>
var Event = new Vue();//定义一个空的Vue实例
var A = {
template: '#a',
data() {
return {
name: 'tom'
}
},
methods: {
send() {
Event.$emit('data-a', this.name);
}
}
}
var B = {
template: '#b',
data() {
return {
age: 20
}
},
methods: {
send() {
Event.$emit('data-b', this.age);
}
}
}
var C = {
template: '#c',
data() {
return {
name: '',
age: ""
}
},
mounted() {//在模板编译完成后执行
Event.$on('data-a',name => {
this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
})
Event.$on('data-b',age => {
this.age = age;
})
}
}
var vm = new Vue({
el: '#itany',
components: {
'my-a': A,
'my-b': B,
'my-c': C
}
});
</script>
$on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。
再举个组件化式的例子:
假设现在有两个组件: click,vue , show.vue。需要通过click.vue组件向show.vue组件传递数据。
首先给click.vue组件添加点击事件
//click.vue
<div class="click" @click="doClick($event)"></div>
想要在doClick()方法中实现对show组件的通信,需要一个中间通信件,新建一个js文件创建eventBus,暂命名为bus.js
// bus.js
import Vue from 'vue' export default new Vue()
然后在click.vue 和 show,vue组件中import bus.js
然后在doClick方法中,触发一个事件
methods: {
doClick(event) {
Bus.$emit('getTarget', event.target);
}
}
这里我们在click组件中每次点击,都会在bus中触发这个名为'getTarget'的事件,并将点击事件的event.target顺着事件传递出去。
接着,我们要在show组件中的created()钩子中调用bus监听这个事件,并接收参数:
created() {
Bus.$on('getTarget', target => {
console.log(target);
});
}
方法三: $attrs/$listeners
官方介绍:
vm.$attrs: 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
vm.$listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
举个跨级通信的例子:
// index.vue
<template>
<div>
<h2>===</h2>
<child-com1
:foo="foo"
:boo="boo"
:coo="coo"
:doo="doo"
title="hmk"
></child-com1>
</div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
components: { childCom1 },
data() {
return {
foo: "Javascript",
boo: "Html",
coo: "CSS",
doo: "Vue"
};
}
};
</script>
// childCom1.vue
<template class="border">
<div>
<p>foo: {{ foo }}</p>
<p>childCom1的$attrs: {{ $attrs }}</p>
<child-com2 v-bind="$attrs"></child-com2>
</div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
components: {
childCom2
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
props: {
foo: String // foo作为props属性绑定
},
created() {
console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "hmk" }
}
};
</script>
// childCom2.vue
<template>
<div class="border">
<p>boo: {{ boo }}</p>
<p>childCom2: {{ $attrs }}</p>
<child-com3 v-bind="$attrs"></child-com3>
</div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
components: {
childCom3
},
inheritAttrs: false,
props: {
boo: String
},
created() {
console.log(this.$attrs); // {"coo": "CSS", "doo": "Vue", "title": "hmk" }
}
};
</script>
// childCom3.vue
<template>
<div class="border">
<p>childCom3: {{ $attrs }}</p>
</div>
</template>
<script>
export default {
props: {
coo: String,
title: String
}
};
</script>
简单来说:$attrs与$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。
方法四:$parent / $children与 ref
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
这两种方法都可以得到组件实例,使用后可以直接调用组件的方法访问数组
举个例子:
// 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>
方法五: vuex
这也是用得最多的通信方式和数据存储方式
方法六: provide/ inject
这种方式官方推荐用来生产插件,所以不深入研究
总结:
常见使用场景可以分为三类:
父子通信:
父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;$attrs/$listeners
兄弟通信:
Bus;Vuex
跨级通信:
Bus;Vuex;$attrs/$listeners