目录
(一) 组件化 概述
在一个页面中存在大量的处理逻辑, 如果全部放在一起, 处理起来就会变得非常复杂, 且不利于后续维护以及扩展
组件化思想: 将页面拆分成一个个小的功能块, 每个功能块完成属于自己这部分独立的功能, 那么之后整个页面的管路和维护就变得非常容易
组件化是Vue.js中的重要思想
- 它提供一种抽象, 可以发出一个个独立可复用的小组件来构造整个应用
- 任何应用都会被抽象成一颗组件树. (图摘自Vue官方文档)
Vue实例与组件的关系
- 一个Vue项目是由一个根实例和多个组件组成
- 组件是可复用的 Vue 实例, 因此组件与Vue实例内部极其相似, 拥有 data、methods、computed、生命周期函数等等选项
- 组件中的data选项必须是一个函数, 且返回一个对象(内部存储数据). 组件是可复用的, 如果data选项是一个对象的话, 多个组件使用同一份data数据, 造成数据交叉错误.
(二) 组件注册
组件的使用分成三个步骤:
- 创建组件构造器
- 注册组件
- 使用组件
1. 注册全局组件
全局组件可以在多个Vue实例中使用
<div id="app">
<!-- 3.在 app Vue实例中使用自定义组件 -->
<my-component></my-component>
</div>
<script>
// 1.创建组件构造器对象
const componentConstructor = Vue.extend({
// template: 自定义组件模板, 最终会替换挂载的元素(组件名). 挂载元素的内容都将被忽略,除非模板的内容有分发插槽
template: `
<div>
<h3>我是标题</h3>
<p>我是内容: 哈哈哈哈</p>
<h3>我是页尾</h3>
</div>
`
})
// 2.注册全局组件 Vue.component(组件标签名称, 组件构造器)
Vue.component('my-component', componentConstructor);
const app = new Vue({
el: '#app'
})
</script>
2. 注册局部组件
局部组件:在Vue实例的components选项中注册组件, 组件只能在此Vue实例中使用
const app = new Vue({
el: '#app',
// components 选项: 注册局部组件, 组件名称: 组件构造器
components: {
myComponent: componentConstructor
}
})
3. 注册组件简写
注册组件的语法糖
// 注册全局组件 Vue.component('组件名', { 传入Vue.extend()的对象 })
Vue.component('global-component', {
template: `
<div>全局组件</div>
`
})
// 注册局部组件
const app = new Vue({
el: '#app',
components: {
partComponent: {
template: `<div>局部组件</div>`
}
}
})
注册组件的模板抽离, 使用template标签
<div id="app">
<parent-component></parent-component>
</div>
<template id="part">
<div>局部组件</div>
</template>
const partComponet = {
template: '#part' // css选择器 或 HTMLElement
}
const app = new Vue({
el: '#app',
components: {
partComponet
}
})
(三) 父子组件
1. 注册父子组件
在组件实例中通过components选项 注册子组件, 且在template选项中使用子组件
<div id="app">
<parent-component></parent-component>
</div>
<template id="child">
<p>子组件内容</p>
</template>
<template id="parent">
<div>
<h3>父组件标题</h3>
<!-- 父组件中使用子组件 -->
<child-component></child-component>
<h3>父组件页尾</h3>
</div>
</template>
// 创建子组件对象
const childComponent = {
template: '#child'
}
// 创建父组件对象
const parentComponent = {
// 父组件中局部注册子组件
components: {
childComponent
},
template: '#parent'
}
const app = new Vue({
el: '#app',
// Vue实例(根组件)中局部注册父组件
components: {
parentComponent
}
})
注意:
2. 父子组件的通信
在Vue开发中, 往往一些数据需要从父组件传递到子组件, 或者 数据从子组件传递到父组件.Vue官方分别对这两种情况提供传递方式:
- 通过props向子组件传递数据
- 通过自定义事件向父组件发送消息
Props: 父组件向子组件传递数据
- 父组件通过 v-bind:变量名 = “变量” 传递给子组件
- 变量名: 自定义变量名, v-bind 不支持驼峰标识, 需要做转换: v-bind:myInfo --> v-bind:my-info
- 变量: 父组件data函数返回的对象
- 子组件通过props 选项接受父组件传来的变量, props选项的类型为:
- Array数组: props: [‘自定义变量名’, …]
- Object对象:
props: { 变量名: { type: Array, // 对父组件传递的变量进行类型校验(String, Number, Boolean, Array, Object, Data) default() { // 对父组件传递的变量初始化默认值, 当变量类型为 Object/Array时, default属性是一个函数 return [] }, required: true // boolean, 是否必须传递 }, ... }
将父组件中movies数据传递给子组件显示
<!-- Vue实例挂彩目标 -->
<div id="app">
<parent-component></parent-component>
</div>
<!-- 子组件template模板抽离 -->
<template id="child">
<ul>
<li v-for="item in movies" :key="item">{{item}}</li>
</ul>
</template>
<!-- 父组件template模板抽离 -->
<template id="parent">
<!-- 父组件通过 v-bind:变量名 = "变量" 传递给 子组件-->
<child-component :movies="movies"></child-component>
</template>
// 创建子组件对象
const childComponent = {
template: '#child',
// 子组件通过props 选项接受父组件传来的变量
props: {
movies: {
type: Array,
default() {
return []
},
required: true
}
}
}
// 创建父组件对象
const parentComponent = {
template: '#parent',
components: {
childComponent
},
data() {
return {
movies: ['海贼王', '火影忍者', '魁拔', '大鱼海棠']
}
}
}
const app = new Vue({
el: '#app',
components: {
parentComponent
}
})
单向数据流:
所有的porp都使得器父子之间形成了一个单向下行绑定: 父组件prop的更新会向下流动到子组件中(实时更新). 但反之不行, 这样做的目的是防止子组件意外变更父组件的状态, 从而导致数据流向难以理解.
单向下行绑定, 意味着我们不应该在子组件内部改变prop(控制台会发出警告), 如果非得改变porp的值, 那么可以将它定义为本地data数据 或 computed计算属性
$emit event: 子组件通过向父组件发送自定义事件来传递数据
- 子组件通过 $emit(‘自定义事件名称’, …data) 向父组件发送自定事件, 同时传递数据
- 父组件通过 v-on:自定义事件名称 来监听子组件事件
父组件打印输出子组件点击分类对象
<!-- Vue实例挂彩目标 -->
<div id="app">
<parent-compent></parent-compent>
</div>
<!-- 子组件template模板抽离 -->
<template id="child">
<ul>
<li v-for="item in categories" :key="item.id">
<a href="javascript:void(0)" @click="handleClick(item)">{{item.id}}.{{item.name}}</a>
</li>
</ul>
</template>
<!-- 父组件template模板抽离 -->
<template id="parent">
<!-- 父组件通过 v-on:自定义事件名称 来监听子组件事件 -->
<child-component @on-click="handleClick"></child-component>
</template>
// 创建子组件对象
const childComponent = {
template: '#child',
data() {
return {
categories: [
{id: 1, name: '热门推荐'},
{id: 2, name: '手机数码'},
{id: 3, name: '家用家电'},
{id: 4, name: '电脑办公'},
]
}
},
methods: {
handleClick(item) {
// 子组件发送自定义事件 on-click, 且传递数据 分类对象
this.$emit('on-click', item)
}
}
}
// 创建父组件对象
const parentCompent = {
template: '#parent',
components: {
childComponent
},
methods: {
handleClick(item) {
console.log(JSON.stringify(item))
}
}
}
const app = new Vue({
el: '#app',
components: {
parentComponent
}
})
3. 父子组件的访问方式
- 父组件访问子组件: 使用$children 或 $refs
- 子组件访问父组件: 使用$parent
$children: this.$children 获取到是一个数组对象, 它包含当前组件所有的子组件对象
<template id="parent">
<div>
<!-- 子组件复用三次 -->
<child-component></child-component>
<child-component></child-component>
<child-component></child-component>
</div>
</template>
const parentCompent = {
template: '#parent',
components: {
childComponent
},
mounted() {
console.log(this.$children) // 长度为3的子组件数组对象
console.log(this.$children[0].message) // 输出第一个子组件的message对象
this.$children[0].showInfo() // 调用第一个子组件的showInfo方法
}
}
$refs: 需要在子组件上指定ref属性, 然后通过this.$refs.ref 获取到指定的子组件对象
<template id="parent">
<div>
<!-- 子组件添加ref属性 -->
<child-component ref="childComponent"></child-component>
</div>
</template>
const parentCompent = {
template: '#parent',
components: {
childComponent
},
mounted() {
// 通过this.$refs.ref 获取到指定的子组件对象
console.log(this.$refs.childComponent)
this.$refs.childComponent.showInfo()
console.log(this.$refs.childComponent.message)
}
}
$parent: this.$parent 获取到父组件对象
const childComponent = {
template: '#child',
mounted() {
console.log(this.$parent) // this.$parent 获取到父组件对象
this.$parent.showInfo() // 调用父组件的showInfo方法
console.log(this.$parent.message) // 打印输入父组件的message对象
}
}
$root: this.$root 获取到根Vue实例
(三) Slot 插槽
1. 插槽的基本使用
Slot: Vue实现了一套内容分发的API, 将<slot>元素作为承接分发内容的出口. 组件的插槽也是为了让我们封装的组件更加具有扩展性, 可以让使用者在组件内部自定义展示内容
<div id="app">
<child-component>
<button>按钮</button>
</child-component><hr>
<child-component>
<a href="#">www.baidu.com</a>
</child-component><hr>
<!-- 组件内没元素: 使用插槽默认值 -->
<child-component></child-component>
</div>
<template id="child">
<div>
<h3>组件标题</h3>
<p>组件内容</p>
<slot>插槽具有默认值</slot>
</div>
</template>
const childComponent = {
template: '#child',
}
const app = new Vue({
el: '#app',
components: {
childComponent
}
})
2. 具名插槽
组件内可以拥有多个插槽内容, 使用 name属性区分, 使用 slot属性指定替换的插槽
<div id="app">
<child-component>
<!-- 添加slot属性, 指定替换的插槽 -->
<span slot="center"><input type="text" placeholder="搜索"></span>
</child-component>
</div>
<template id="child">
<div>
<!-- 添加name属性, 区分插槽 -->
<slot name="left">左边默认插槽</slot>
<slot name="center">中间默认插槽</slot>
<slot name="right">右边默认插槽</slot>
</div>
</template>
3. 作用域插槽
编译作用域: 父组件模板的所有东西都会在父级作用域内编译, 子组件模板的所有东西都会在子级作用域内编译
作用域插槽: 父组件替换插槽的标签, 但是内容由子组件来提供. 更通俗的来讲: 父组件使用子组件插槽时, 需要用到子组件的数据
- 子组件定义插槽传递数据: v-bind:自定义变量名 = “子级作用域的变量”
- 父组件使用<template>标签接受数据: v-slot:插槽名称(默认default) = “插槽属性名称”, 通过 插槽属性名称.自定义变量名 访问数据
父组件通过不同的标签显示子组件的movies
<h3>默认插槽: ui标签展示movies</h3>
<child-component>
</child-component><hr>
<h3>自定义插槽: ui + a标签展示movies</h3>
<child-component>
<!-- <template>标签 + v-slot:插槽名称(默认default) = "插槽属性名称" -->
<template v-slot:default="slotPorp" >
<!-- 通过 插槽属性名称.自定义变量名 访问数据 -->
<li v-for="item in slotPorp.data"><a href="#">{{item}}</a></li>
</template>
</child-component><hr>
<template id="child">
<div>
<!-- v-bind:自定义变量名 = "子级作用域的变量" -->
<slot :data="movies" name="center">
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
</slot>
</div>
</template>
const childComponent = {
template: '#child',
data() {
return {
movies: ['海贼王', '火影忍者', '魁拔', '大鱼海棠']
}
}
}
const app = new Vue({
el: '#app',
components: {
childComponent
}
})