一、Vue组件
1.1 什么是组件化开发
组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护。
1.2 Vue中的组件化开发
Vue 是一个支持组件化开发的前端框架。
vue 中规定:组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 Vue 的组件
1.3 Vue 组件的三个组成部分
每个 .vue 组件都由 3 部分构成,分别是:
- template -> 组件的模板结构
- script -> 组件的 JavaScript 行为
- style -> 组件的样式
其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。
Tips:在vscode中安装Vetur和Vue 3 Snippets插件,在新的组件页面输入“<”时就能选择自动生成Vue结构

1.3.1 template
vue 规定:每个组件对应的模板结构,需要定义到 <template> 节点中。

注意:
- template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的DOM 元素。
- template 中只能包含唯一的根节点(el所控制的区域)。
1.3.2 script
vue 规定:开发者可以在 <script> 节点中封装组件的 JavaScript 业务逻辑。

注意:
- .vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。
data() {
return {
username: 'admin'
}
}
1.3.3 style
vue 规定:开发者可以在<style>节点中编写样式,用来美化当前组件的 UI 结构。

若需要style 中支持 less 语法
在<style>标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式:

1.4 组件之间的父子关系
1.4.1 使用组件的三个步骤

示例:
<template>
<div class="app-container">
<h1>App 根组件</h1>
<hr />
<div class="box">
<!-- 以标签的形式使用注册好的组件 -->
<!-- 渲染 Left 组件和 Right 组件 -->
<Left></Left>
<Right></Right>
</div>
</div>
</template>
<script>
// 1、导入需要使用的.vue组件
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
// 2、注册组件
components:{
Left,
Right
}
}
</script>
<style lang="less">
.app-container {
padding: 1px 20px 20px;
background-color: #efefef;
}
.box {
display: flex;
}
</style>
注意:路径中的@,Vue默认配置完成,可以使用 @ 作为 src 跟目录
想要导入文件时,有路径提示,先安装Path Autocomplete插件
配置方法:在vscode中的 settings.json 里添加以下代码:
"path-autocomplete.extensionOnImport": true,
"path-autocomplete.pathMappings": {
"@":"${folder}/src"
}
Tips:打开settings.json:
点击vscode左下角齿轮设置,在设置界面点击右上角“ 打开设置(json)”

1.4.2 通过 components 注册的是私有子组件
例如:在组件 A 的 components 节点下,注册了组件 F。 则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
为什么 F 不能用在组件 C 中?
因为C组件中没有注册F;
怎样才能在组件 C 中使用 F?
在C中的components节点注册F。
1.4.3 注册全局组件
如果某个组件频繁被用到,重复注册会很麻烦,可以注册全局组件解决,只需注册一次,除了自己其他每个组件都可以直接使用。
在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。示例代码如下:
// 导入需要被全局注册的那个组件
import Count from '@/components/Count.vue'
//参数1:字符串格式,表示组件的“注册名称”
//参数2:需要被全局注册的那个组件
Vue.component('MyCount', Count)
1.5 组件的 props
props 是组件的自定义属性,允许在使用自定义属性时为当前组件指定初始值。在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性! 它的语法格式如下:
export default {
//组件的自定义属性
//props 里面的属性,也能通过 this访问到
props:['自定义属性A','自定义属性B','其他自定义属性'...],
//组件的私有数据
data(){
return {
}
}
}
然后就能在使用这个组件的组件里使用自定义过的属性,props中的属性可以直接再模板结构中被使用。
举个栗子:
//Count.vue 全局组件名为MyCount
<p>count的值是:{{ init }}</p>
...
<scrpit>
export default {
...
props: ['init']
...
}
</script>
//Left.vue
<MyCount init="9"></MyCount>
//使用的是v-bind属性绑定,此处的9是数值,字符串应该再加一层引号
//Right.vue
<MyCount :init="6"></MyCount>
//使用的是v-bind属性绑定,此处的6是数值,字符串应该再加一层引号
区别:Count.vue是被封装的组件,Left.vue和Right.vue是该被封装组件的使用者。
1.5.1 props 是只读的
vue 规定:组件中封装的自定义属性是只读的,不能直接修改 props 的值。否则会直接报错。
解决办法:如果需要修改props自定义属性的值,可以将其转存到data中,因为data中的数据是可读可写的。
<!-- Count.vue -->
<p>count的值是:{{ count }}</p>
<button @click="count+=1">+1</button>
...
<script>
data() {
return {
count: this.init
}
}
...
</script>
1.5.2 props 的 default 默认值
在声明自定义属性时,可以通过 default 来定义属性的默认值。示例代码如下:
export default {
props: {
init: {
//用default定义默认值
default: 0
}
},
实际开发中更多是这样的写法,而不是上述数组的写法。
1.5.3 props 的 type 值类型
在声明自定义属性时,可以通过 type 来定义属性的值类型。示例代码如下:
export default {
props: {
init: {
default: 0,
// init 的值类型必须是 Number 数字
//若值的类型不符的时候终端则会报错
type: Number
}
},
1.5.4 props 的 required 必填项
在声明自定义属性时,可以通过 required 选项,将属性设置为必填项,强制用户必须传递属性的值。示例代码如下:
export default {
props: {
init: {
default: 0,
// init 的值类型必须是 Number 数字
type: Number,
// 必填项校验
required: true
}
},
小结:在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!
1.6 组件之间的样式冲突问题
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
导致组件之间样式冲突的根本原因是:
- 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的;
- 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素。
1.6.1 思考:如何解决组件样式冲突的问题
为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:

需要同一组件加相同的属性,不同的组件加的属性不一样。
1.6.2 style 节点的 scoped 属性
为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:

(在底层Vue会为该组件的标签加上如1.6.1所描述的属性data-v-xxxx一串数字)
1.6.3 /deep/ 样式穿透
如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用 /deep/ 深度选择器。

使用场景:当使用第三方组件库的时候,如果有修改第三方组件库默认样式的需求,需要用到/deep/
注意:/deep/ 是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep() 替代 /deep/。
小补充:
- 实际上浏览器并不认识.vue文件,需要通过Vue模板编译器编译成js文件再渲染到浏览器
- 被调用的子组件相当于一个构造函数,是Vue组件的实例对象,以标签形式使用组件相当于new一个构造函数的过程
二、组件的生命周期
2.1 生命周期 & 生命周期函数
生命周期(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点。
2.2 组件生命周期函数的分类

生命周期内的生命周期函数
- 组件创建之前->组件创建好(内存中)->组件渲染之前->组件渲染到浏览器页面上
- 组件更新前->组件更新后
- 组件销毁前->组件销毁后
2.3 生命周期图示
参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程:Vue 实例 — Vue.js

- created阶段经常调用 methods 中的方法,请求服务器的数据,并把请求到的数据,转存到 data 中,供 template 模板渲染的时候使用
- mounted阶段可以用于操作DOM元素
- updated阶段完成了$el中的数据与data中的数据的同步
三、组件之间的数据共享
3.1 组件之间的关系
在项目开发中,组件之间的最常见的关系分为如下两种:
① 父子关系 ② 兄弟关系
3.2 父子组件之间的数据共享
父子组件之间的数据共享又分为:
① 父 -> 子共享数据 ② 子 -> 父共享数据
3.3 父组件向子组件共享数据
父组件向子组件共享数据需要使用自定义属性props。示例代码如下:

即在子组件中props中声明属性,父组件中被调用的子组件绑定属性并传值。
3.4 子组件向父组件共享数据
子组件向父组件共享数据使用自定义事件。示例代码如下:

当$emit被触发的时候,自定义事件被触发,对应的处理函数被触发。左边this.count是实参,传递给右边val形参。
3.5 兄弟组件之间的数据共享
在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus。

EventBus 的使用步骤 :
- 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象;
- 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件;
- 在数据接收方,调用 bus.$on('事件名称', 事件处理函数) 方法注册一个自定义事件。
四、ref引用
4.1 什么是 ref 引用
ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。
<template>
<div class="right-container">
<h3>MyRef 组件</h3>
<button @click="getRef">获取 $refs 引用 </button>
</div>
</template>
<script>
export default {
methods: {
getRef() {
console.log(this) //打印的是vue的实例对象
}
}
}
</script>

默认情况下, 组件的 $refs 指向一个空对象。
前面带$的对象是Vue的内置成员。
4.2 使用 ref 引用 DOM 元素
如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:
<template>
<div class="right-container">
<!-- 使用 ref 属性,为对应的 DOM 添加引用名称 -->
<h3 ref="myh3">MyRef 组件</h3>
<button @click="getRef">获取 $refs 引用 </button>
</div>
</template>
<script>
export default {
methods: {
getRef() {
console.log(this)
//通过this.$refs.引用的名称可以获取到 DOM 元素的引用
console.log(this.$refs.myh3)
this.$refs.myh3.style.color = 'red'
}
}
}
</script>
即想要拿到DOM元素可以给元素加一个ref属性(属性值不能冲突),通过this.$refs.属性名可以拿到。
4.3 使用 ref 引用组件实例
如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:

即在父组件中引用子组件时给子组件添加ref属性,再通过this.$refs.属性值调用子组件中的方法。
即ref能够获得组件的引用,从而能够调用子组件的方法,从而可以操作子组件中的数据。
这是在父组件中直接去调子组件中的方法最常用的方式。
4.4 控制文本框和按钮的按需切换
写个小例子,前期准备:通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。示例代码如下:

4.5 让文本框自动获得焦点
当文本框展示出来之后,如果希望它立即获得焦点。
方法:为其添加 ref 引用,并调用原生 DOM 对象的 .focus() 方法即可。示例代码如下:

此处会出现报错:TypeError: Cennot read property' focus' of undefined

原用:当数据更新时,页面还没有渲染DOM结构。即当 this.inputVisible=true 执行完,页面上还是button而不是文本框,立即执行DOM操作拿不到。所以拿DOM元素的操作应该放在页面更新完之后进行。
对此Vue提供了this.$nextTick(cb)方法
4.6 this.$nextTick(cb) 方法
组件的 $nextTick(cb) 方法,会把 cb (callback)回调推迟到下一个 DOM 更新周期之后执行。
通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。

所以当需要将操作滞后至DOM更新后,可以使用该方法。
498





