Vue组件与生命周期
目标:
掌握.vue单文件组件的基本用法
组件的props(自定义属性):数组写法、对象写法
解决多个组件间的样式冲突 – scoped
/deep/样式穿透应用场景:修改第三方组件库组件的默认样式
掌握组件生命周期的执行顺序和应用场景:创建阶段、运行阶段、销毁阶段
vue 中常用的生命周期函数/生命周期钩子:created、mounted
能够掌握组件通讯的三种方式:父-> 子(自定义属性)、子-> 父(自定义事件)、兄弟组件(EventBus)
[toc]
vue组件
什么是组件化开发
组件化开发指的是∶根据封装的思想,把页面上可重用的UI结构封装为组件,从而方便项目的开发和维护。vue中的组件化开发
vue是一个支持组件化开发的前端框架。
vue中规定:组件的后缀名是.vue
vue中,root就是根,而这个根就是el所控制的那个区域。在main.js文件里,vue实例中通过 render 函数指定组件渲染到HTML页面中,替换了el区域。即:render函数中,渲染的是哪个.vue组件,那么这个组件就叫做“根组件”vue组件的三个组成部分
- template -> 组件的模板结构
- script -> 组件的JavaScript行为
- style -> 组件的样式
注意:每个组件中必须包含template模板结构,而script行为和style样式是可选的组成部分
简单示例:
1 | <template> |
- 组件之间的父子关系
组件之间的父子关系、兄弟关系,是在使用时候才会产生的
使用组件的三个步骤
注意:在components节点中,注册组件是一个对象的写法,前面是定义的名称,后面是要注册的组件。在对象里面,键名和值如果一样,可以省略值只写键名。如’Left’:Left可以简写成Left
通过components注册的是私有子组件
例如:
在组件A的components节点下,注册了组件F。
则组件F只能用在组件A中;不能被用在组件C中。注册全局组件
如果某个组件,使用非常频繁,好多组件中都要用到它,那么每次都要导入注册并使用,就很麻烦。所以可以注册全局组件在vue项目的 main.js入口文件中,通过Vue.component()方法,可以注册全局组件。示例代码如下:
1
2
3
4
5
6// 导入需要全局注册的组件
import Count from '@/components/Count.vue'
// 参数1:字符串格式,表示组件的“注册名称”
// 参数2:需要被全局注册的那个组件
Vue.component('MyCount',Count)注意:在自己的组件中,不能使用自己!
组件的props
props是组件的自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!
它的语法格式如下:
1 | export default { |
props中的数据,可以直接在模板结构中被使用。使用示例:
1
2
3
4
5
6
7
8
9// Count.vue组件中
//...
<p>{{init}}</p>
//...
export default {props:['init'],..}
// Left.vue组件中,使用注册了的Count组件
// 指定初始值,但是这里传入的是一个字符串,并不是一个真正的数值
<Count init="6"></Count>结合v-bind使用自定义属性
1
2// 这样就传入的是数值6了
<Count :init="6"></Count>官网文档->即便
9
是静态的,我们仍然需要v-bind
来告诉 Vue,这是一个 JavaScript 表达式而不是一个字符串props对子组件是只读的 (父组件->子组件 单向数据传递)
Vue规定:组件中封装的自定义属性是只读的,程序员不能直接修改props的值。否则会直接报错。
注意:props是对定义该属性的子组件只读,在父组件中,你可以通过v-bind绑定props的值,并且这个值是可以更改的。当父组件中的值改变时,Vue会自动将新值传递给子组件,并且子组件会根据新的值重新渲染。这种方式实际上是通过props属性进行了单向数据流,确保数据流动的可预测性和可维护性。
如果子组件需要修改传递过来的值,通常应该通过触发事件并将修改传递给父组件来实现。
props可以被作为初始值使用的,把它传给data的属性进行初始赋值使用:1
2
3
4
5
6
7
8
9
10
11// Count.vue组件中
//...
<p>{{count}}</p>
<button @click="count+=1">+1</button>
//...
export default {
props:['init'],
data:{ count:this.init, }
}
<Count :init="6"></Count>
props的default默认值
在声明自定义属性时,可以通过default来定义属性的默认值。示例代码如下:
1 | export default { |
props的 type 值类型
在声明自定义属性时,可以通过type来定义属性的值类型。示例代码如下:
1 | export default { |
还可以通过数组形式,为当前属性定义多个可能的类型:type: [Number, String]
如果是带有默认值的对象类型,对象或数组默认值必须从一个工厂函数获取:
1 | // 封面的信息对象 |
props的 required 必填项
加上required必填项之后,哪怕有默认值,不传也会报错
1 | export default { |
props属性的命名规定
Vue里面有一个规定,如果某一个属性在定义的时候,里面包含了一个大写的字符(是一个小驼峰),在进行绑定的时候,可以直接写成原名字,不过建议改成连字符格式,因为这样看起来会更舒服一些
示例:
1 | // 子组件中 |
组件间的样式冲突 - scoped
默认情况下,写在.vue组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
导致组件之间样式冲突的根本原因是:
1. 单页面应用程序中,所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的
2. 每个组件中的样式,都会影响整个index.html页面中的DOM元素
注意:单页面应用程序中,HTML页面是只有一个的,我们的所有组件都是在这个HTML中生效的
解决方案 – 核心原理:css属性选择器
给当前页面标签都加一个固定的自定义属性,不同的组件之间的自定义属性不一样vue简化:scoped属性
只要给当前组件的style加上scoped的属性,vue底层在生成组件的时候,会自动给每个标签自动生成一个data-v-xxx
/deep/样式穿透
作用:在父组件中改造子组件里的样式
应用场景:当使用第三方组件库的时候,如果有修改组件默认样式的需求,需要用到/deep/
scoped有一个缺陷,场景:
Left 和 Right 都注册使用了 Count组件
Left加了scoped属性,而Right没加
如果想在Left中的style里修改Count里面的样式,会发现不起作用,而如果在Right中修改,会发现Left和Right里面的Count组件样式都变了。原因:
在Left中修改,比如h5{..},因为加了scoped,所以会变成h5[data-v-xx]{..},而count中的标签并没有data-v-xx属性,所以不会生效。
而在没有scoped属性的Right中修改,则产生的就是样式冲突解决方案:/deep/样式穿透
/deep/ h5{ … }
在选择器前面加个前缀+空格,在浏览器可以查看到,这个h5标签被加上了一个[data-v-xx] 的前缀
加上/deep/
之后就不再是类名[data-v-xxx]
的 选择特定属性的选择器 了,而是变成了[data-v-xxx] 类名
的 后代选择器 。
Vue运行原理
在把app渲染到页面的过程中,不是把页面的模板结构直接丢到页面上,而是有个编译的过程。
浏览器能够直接解析HTML页面,但是不识别vue页面。
在package.json
里面,devDependencies
里面有个vue-template-complier
包,就是vue模板编译器,作用就是把.vue文件编译成js交给浏览器解析运行。
在.vue中写的任何代码,要渲染到浏览器中,都依赖于这个包的解析与转换。
我们上面在页面上渲染了2次Count.vue组件,我们在页面上看到的两个Count组件,就是两个组件的实例。
实例,就是相当于new一个构造函数得到一个实例。我们编写的Count.vue,可以理解为它就是一个构造函数,new这个构造函数就会得到一个Count实例。
我们没有直接new,而是通过标签形式去用这个组件,这个用的过程可以理解为一个new的过程
所以说,这个组件,在定义的时候,它只是一个模板结构,当用标签的形式去使用的时候,才是在创建一个它的实例。
如果在components下声明一个组件,不去用它不会创建组件实例。
整个项目如何跑起来的:
webpack从main.js(入口文件)开始打包,发现main里面用到了app,于是把app创建一个实例出来。然后app里面又用到了Left和Right,然后这两个里面又用到了Count,就这样一直通过一个树的形式,把整个树都解析转换,最后编译成纯js代码。然后把js文件放到首页里面去,浏览器就会解析和执行这里面的代码,把组件都给渲染到页面上
组件的生命周期
生命周期 & 生命周期函数
生命周期(Life Cycle)是指一个组件从创建->运行->销毁的整个阶段,强调的是一个时间段。生命周期函数:是由vue框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点。组件生命周期函数(生命周期钩子)的分类
在生命周期里面,它会按顺序依次执行这些函数。生命周期图示
可以官方给出的“生命周期图示”,进一步理解组件生命周期执行过程。
中文详解图示
执行次数
创建阶段:1次
运行阶段:0-N次
销毁阶段:1次最重要的生命周期钩子
- created
data、props、methods都是可用状态
常用它发起ajax请求来拿数据,转存到data中,供template模板渲染时候使用。 - mounted
第一次把DOM元素结构渲染好,最早去操作DOM - updated
可以操作最新的更新过后的DOM
其他的很少会用到,记不住也没关系
- created
组件之间的通讯(数据共享)
组件之间的关系
最常见的:兄弟关系、父子关系
离的比较远的,也可以认为是一种变态的兄弟关系父向子传值 – 自定义属性
再次注意: props是只读的!子向父传值 – 自定义事件
第一步是绑定了一个事件,这是个处理函数。$emit
就代表触发这个事件,谁触发这个事件,谁传一个值过来。
子向父传值:在子组件调用$emit,父组件在子组件标签上用@绑定(监听)这个自定义事件
绑定事件和触发事件要在同一个实例上!!有绑定有触发。vue在内部要想触发click事件,它也是用this或vm
1
<button @click='show'>按钮</button>
1
2
3
4
5
6
7
8
9method:{
show(e){}
}
vm.$emit('click',{//事件对象e
...
clientX:"",
clientY:"",
target:dom元素
})兄弟组件之间的数据共享 – EventBus
在vue2.x中,兄弟组件之间数据共享的方案是EventBus。- EventBus使用步骤
- 创建
eventBus.js
模块,并向外共享一个Vue的实例对象 - 在数据发送方,调用
bus.$emit('事件名称',要发送的数据)
方法触发自定义事件 - 在数据接收方,调用
bus.$on('事件名称',事件处理函数)
方法注册一个自定义事件
数据在事件处理函数的形参中拿到。
依旧是发送方触发事件,发送数据,接收方绑定事件监听,接受数据。
这里的$on
就相当于jQuery的on绑定事件
为什么这里要用到一个共享的实例EventBus ?因为绑定事件和触发事件要在同一个实例上!!有绑定有触发。
注意:虽然可以使用EventBus来在父子组件之间传值,但是这不是Vue官方推荐的做法。props和自定义事件更适合处理父子组件之间的通信,因为它们更直观和易于理解。 Event Bus 通常更适用于非父子关系的组件之间的通信,例如兄弟组件之间。
拓展
vscode插件 Path Autocomplete - @路径提示
下载插件之后,要在自己配置项里面添加如下配置,添加在头部就行
设置 – setting.json
1 | // 导入文件时是否携带文件的扩展名 |
注意:只有vscode打开的文件夹根目录是项目根目录时才起作用!!
总觉得这个功能vscode也集成进去了?一开始没装的时候,好像就触发了,也懒得验证了hh
Auto Close Tag插件
输入<xx>
标签前半部分,会自动补全后半部分
不过新版vscode好像已经集成了这个功能,没有安装也是这样,装了没啥变化
方法简写形式
如果方法只有一行,可以简写到行内:
1 | <p>count的值是:{{count}}</p> |
编码规范
先指令,后绑定,最后绑事件
这是一个子组件的绑定示例:
1 | <Goods |