组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树。
一、四个核心组成
1、props 2、自定义事件 event 3、插槽及作用域插槽 slot 4、组件方法 method。
任何组件均离不开以上4点,我们在开发过程中,以这4点入手,封装我们想要的组件。
以element-ui table组件为例
Table Attributes 指的是 props
props data 显示的数据
Table Events 指的是 自定义事件
自定义事件 selection-change 当选择项发生变化时会触发该事件
Table Slot 插槽
append 插入至表格最后一行之后的内容,如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。若表格有合计行,该 slot 会位于合计行之上。
Table-column Scoped Slot 插槽及作用域插槽 slot
Table-column 是 el-table-column组件,有一个默认插槽 自定义列的内容,参数为 { row, column, $index }
我们通常这样使用
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
<el-button type="text" size="small">编辑</el-button>
</template>
</el-table-column>
slot-scope="scope" 父组件通过scope访问子组件作用域。
Table Methods 指的是 组件方法
组件方法 clearSelection 用于多选表格,清空用户的选择 。 组件方法是通过 添加ref 索引,获取组件实例后调用。this.$refs.组件ref标识.组件方法
以上是组件核心概念。
二、组件注册
1、组件名
组件名应该始终是多个单词的,根组件 App 除外
这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。
单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)
混用文件命名方式有的时候会导致大小写不敏感的文件系统的问题,这也是横线连接命名同样完全可取的原因
使用 kebab-case
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>
Vue.component('my-component-name', { /* ... */ })
使用 PascalCase
当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的
Vue.component('MyComponentName', { /* ... */ })
2、全局注册
以上方法都属于全局注册, 也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中, 比如
HTML
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
JS
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
new Vue({ el: '#app' })
在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用.
3、局部注册
如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的 components 属性实现局部注册, 这里不做详述。
三、父子组件通信
在vue组件通信中其中最常见通信方式就是父子组件之中的通信,而父子组件的设定方式在不同情况下又各有不同。最常见的就是父组件为控制组件子组件为视图组件。父组件传递数据给子组件使用,遇到业务逻辑操作时子组件触发父组件的自定义事件。无论哪种组织方式父子组件的通信方式都是大同小异
父组件到子组件通讯
父组件到子组件的通讯主要为:子组件接受使用父组件的数据,这里的数据包括属性和方法(String, Number, Boolean, Object, Array, Function)。vue提倡单项数据流,因此在通常情况下都是父组件传递数据给子组件使用,子组件触发父组件的事件,并传递给父组件所需要的参数
通过 props 传递数据 (推荐)
父子通讯中最常见的数据传递方式就是通过props传递数据,就好像方法的传参一样,父组件调用子组件并传入数据,子组件接受到父组件传递的数据进行验证使用
props 可以是数组或对象,用于接收来自父组件的数据。props 可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义校验和设置默认值
prop 的定义应该尽量详细,至少需要指定其类型
<!-- 父组件 -->
<template>
<div>
<my-child :parentMessage="parentMessage"></my-child>
</div>
</template>
<script>
import MyChild from '@components/common/MyChild'
export default {
components: {
MyChild
},
data() {
return {
parentMessage: "我是来自父组件的消息"
}
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<span>{{ parentMessage }}</span>
</div>
</template>
<script>
export default {
props: {
parentMessage: {
type: String,
default: '默认显示的信息'
// require: true // 必填
}
}
}
</script>
子组件到父组件通讯
子组件到父组件的通讯主要为父组件如何接受子组件之中的数据。这里的数据包括属性和方法(String, Number, Boolean, Object, Array, Function)
通过 $emit 传递父组件数据 (推荐)
与父组件到子组件通讯中的$on配套使用,可以向父组件中触发的方法传递参数供父组件使用
<!-- 父组件 -->
<template>
<div>
<my-child @childEvent="parentMethod"></my-child>
</div>
</template>
<script>
import MyChild from '@components/common/MyChild'
export default {
components: {
MyChild
},
data() {
return {
parentMessage: '我是来自父组件的消息'
}
},
methods: {
parentMethod({ name, age }) {
console.log(this.parentMessage, name, age)
}
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<h3>子组件</h3>
</div>
</template>
<script>
export default {
mounted() {
this.$emit('childEvent', { name: 'zhangsan', age: 10 })
}
}
</script>
不推荐的通信方式
this.children
this.$refs
四、兄弟组件通信
1、vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,它是响应式的,状态发生变化,组件会更新。
vuex实现兄弟组件通信非常简单,组件A引用vuex数据,组件B通过方法改变vuex数据,vuex状态是响应式的,数据放生变化,组件A会更新。
2、eventBus又称为事件总线
在vue中可以使用eventBus来作为沟通桥梁, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所有组件都可以通知其他组件。
初始化
首先需要创建一个事件总线并将其导出, 以便其他模块可以使用或者监听它。
我们在src/components/目录下新建文件bus.js。
import Vue from 'vue'
export default new Vue()
发送事件
假设你有两个兄弟组件: ComA和ComB,ComA发送消息给ComB。
ComA这样
<template>
<div>
<button @click="sendMsg">给组件B发送消息</button>
</div>
</template>
<script>
import bus from './bus'
export default {
name: 'comA',
data () {
return {
}
},
methods: {
sendMsg() {
bus.$emit('fromA', {
phone: 13800138000
})
}
}
}
</script>
很显然,ComA中使用bus.$emit(事件名,数据);向事件中心注册发送事件。
接收事件
ComB接受ComA发送过来的消息。
<template>
<div>
<p>{{fromA}}</p>
</div>
</template>
<script>
import bus from './bus'
export default {
name: 'comB',
data () {
return {
fromA: '',
}
},
mounted() {
bus.$on('fromA', param => {
this.fromA = param.phone;
})
}
}
</script>
于是,当ComA发送了一个手机号码phone给ComB时,comB就会接收并显示。
父组件
在父组件中调用ComA和ComB两个兄弟组件。
<template>
<div>
<comA></comA>
<comB></comB>
</div>
</template>
<script>
import ComA from './ComA.vue'
import ComB from './ComB.vue'
export default {
components: {
ComA,
ComB
},
}
</script>
五、多层级组件通讯
1、vuex
2、eventBus
3、provide / inject
provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
六、作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 <current-user> 组件:
<span>
<slot>{{ user.lastName }}</slot>
</span>
我们可能想换掉备用内容,用名而非姓来显示。如下:
<current-user>
{{ user.firstName }}
</current-user>
然而上述代码不会正常工作,因为只有 <current-user> 组件可以访问到 user 而我们提供的内容是在父级渲染的。
为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 <slot> 元素的一个 attribute 绑定上去:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。
七、组件的方法
组件方法,就是我们写组件时,在methods选项里边定义的一些方法,他通常是对数据的CURD。
element-ui我们常用的有
this.refs.[treeName].getCheckedKeys() tree 返回目前被选中的节点的 key 所组成的数组
八、vue 语法糖
1、v-model
v-model可以实现数据双向的绑定,自动为组件添加了props 名为 value 和 自定义事件 名为 input。
<input type="text" v-model="name">
实际上,上面的代码是下面代码的语法糖。
<input v-bind:value="name" v-on:input="name=$event.target.value"/>
自定义组件的 v-model
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
现在在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。
2、.sync 修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。
这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:
this.$emit('update:title', newTitle)
然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符:
<text-document v-bind:title.sync="doc.title"></text-document>
九、组件选项推荐顺序
<script>
export default {
el: '#app', // 只在由 new 创建的实例中遵守。
// 全局感知
name: 'name', // 组件name
parent: VueInstance, // 指定父实例
// 组件类型
functional: false, // 没有data 实例 没有上下文
// 模板修改器
delimiters: ['${', '}'], // 分隔符变成了 ES6 模板字符串的风格
comments: false, // 当设为 true 时,将会保留且渲染模板中的 HTML 注释。默认行为是舍弃它们。
// 模板依赖
components: {}, // 子组件
directives: {}, // 自定义指令
filters: {}, // 自定义过滤器
// 组合
extends: CompA, // 扩展另一个组件 和 mixins 类似
mixins: [tableEvents], // 混入选项对象, 混入实例对象可以像正常的实例对象一样
// 接口
inheritAttrs: true,
model: { // 自定义组件在使用 v-model 时定制 prop 和 event
prop: 'checked',
event: 'change'
},
propsData: { // 只用于 new 创建的实例中。 创建实例时传递 props。
},
// 本地状态
data: () => ({ // 本地状态
}),
computed: { // 计算属性
},
// 事件 生命周期钩子
watch: {
},
// 生命周期钩子
beforeCreate() {
},
created() {
},
beforeMount() {
},
mounted() {
},
beforeUpdate() {
},
updated() {
},
activated() {
},
deactivated() {
},
beforeDestroy() {
},
destroyed() {
},
// 非响应式的属性 (不依赖响应系统的实例属性)
methods() {
},
// 渲染 (组件输出的声明式描述)
template: '<div>demo</div>', // 渲染模板
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
},
renderError (h, err) { // 只在开发者环境下工作。
return h('pre', { style: { color: 'red' }}, err.stack)
}
}
</script>
十、组件样式推荐使用CSS 作用域
<style scoped>
</style>
网友评论