Vue组件与生命周期

目标:
掌握.vue单文件组件的基本用法
组件的props(自定义属性):数组写法、对象写法
解决多个组件间的样式冲突 – scoped
/deep/样式穿透应用场景:修改第三方组件库组件的默认样式

掌握组件生命周期的执行顺序和应用场景:创建阶段、运行阶段、销毁阶段
vue 中常用的生命周期函数/生命周期钩子:created、mounted

能够掌握组件通讯的三种方式:父-> 子(自定义属性)、子-> 父(自定义事件)、兄弟组件(EventBus)

[toc]

vue组件

  1. 什么是组件化开发
    组件化开发指的是∶根据封装的思想,把页面上可重用的UI结构封装为组件,从而方便项目的开发和维护。

  2. vue中的组件化开发
    vue是一个支持组件化开发的前端框架。
    vue中规定:组件的后缀名是.vue
    vue中,root就是根,而这个根就是el所控制的那个区域。在main.js文件里,vue实例中通过 render 函数指定组件渲染到HTML页面中,替换了el区域。即:render函数中,渲染的是哪个.vue组件,那么这个组件就叫做“根组件”

  3. vue组件的三个组成部分

    • template -> 组件的模板结构
    • script -> 组件的JavaScript行为
    • style -> 组件的样式

    注意:每个组件中必须包含template模板结构,而script行为和style样式是可选的组成部分

简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<!-- 组件的模版结构中,应该包含一个根元素,这个最外层的div不能有兄弟元素 -->
<div class="text-box">
<h3 >这是用户自定义的Test.vue -- {{ username }}</h3>
<button @click="changeName">修改用户名</button>
</div>
</template>

<script>
export default {// 默认导出,固定写法!
// 注意:.vue组件中的data不能像之前一样,不能指向对象,必须是一个函数
data(){// data数据源
return{// 这个return出去的对象中,可以定义数据
username: 'admin',
}
},
methods:{
changeName(){
console.log(this);// 在组件中,this就表示当前组件的实例对象
this.username = 'aaa'
}
}
};
</script>

<style lang="less">
.text-box{
background-color: pink;
h3 {
color: red;
}
}
</style>
  1. 组件之间的父子关系
    组件之间的父子关系、兄弟关系,是在使用时候才会产生的
    image

使用组件的三个步骤

image
注意:在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
2
3
4
5
6
7
8
export default {
//组件的自定义属性,允许使用者指定初始值
props:['自定义属性A','自定义属性B','其它自定义属性...'],
//组件的私有数据
data() {
return { }
}
}
  • 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
2
3
4
5
6
7
8
9
10
export default {
props: {
// 自定义属性:{ /* 配置选项 */ }
init: {
//用default属性定义属性的默认值
// 如果外界使用Count组件,没有传递默认值,则默认值生效
default: 0
}
}
}

props的 type 值类型

在声明自定义属性时,可以通过type来定义属性的值类型。示例代码如下:

1
2
3
4
5
6
7
8
9
10
export default {
props: {
init: {
default: 0,
// 用type属性定义属性的值类型
// init的值类型必须是Number数字
type:Number
}
}
}

还可以通过数组形式,为当前属性定义多个可能的类型:type: [Number, String]
如果是带有默认值的对象类型,对象或数组默认值必须从一个工厂函数获取:

1
2
3
4
5
6
7
8
// 封面的信息对象
cover: {
type: Object,
// 默认值是一个空对象
default: function () {
return {}
}
}

props的 required 必填项

加上required必填项之后,哪怕有默认值,不传也会报错

1
2
3
4
5
6
7
8
9
export default {
props: {
init: {
default: 0,
type:Number,
require: true,//必填项校验
}
}
}

props属性的命名规定

Vue里面有一个规定,如果某一个属性在定义的时候,里面包含了一个大写的字符(是一个小驼峰),在进行绑定的时候,可以直接写成原名字,不过建议改成连字符格式,因为这样看起来会更舒服一些
示例:

1
2
3
4
5
6
// 子组件中
props:[cmtCount]
// 父组件中
<xxx :cmtCount="..."></xxx>
// 或
<xxx :cmt-count="..."></xxx> // 建议

组件间的样式冲突 - 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文件放到首页里面去,浏览器就会解析和执行这里面的代码,把组件都给渲染到页面上

组件的生命周期

  1. 生命周期 & 生命周期函数
    生命周期(Life Cycle)是指一个组件从创建->运行->销毁的整个阶段,强调的是一个时间段

    生命周期函数:是由vue框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行
    注意:生命周期强调的是时间段生命周期函数强调的是时间点

  2. 组件生命周期函数(生命周期钩子)的分类
    在生命周期里面,它会按顺序依次执行这些函数。
    img

  3. 生命周期图示
    可以官方给出的“生命周期图示”进一步理解组件生命周期执行过程
    中文详解图示

  • 执行次数
    创建阶段:1次
    运行阶段:0-N次
    销毁阶段:1次

  • 最重要的生命周期钩子

    1. created
      data、props、methods都是可用状态
      常用它发起ajax请求来拿数据,转存到data中,供template模板渲染时候使用。
    2. mounted
      第一次把DOM元素结构渲染好,最早去操作DOM
    3. updated
      可以操作最新的更新过后的DOM

    其他的很少会用到,记不住也没关系

组件之间的通讯(数据共享)

  1. 组件之间的关系
    最常见的:兄弟关系、父子关系
    离的比较远的,也可以认为是一种变态的兄弟关系

  2. 父向子传值自定义属性
    再次注意: props是只读的!
    image

  3. 子向父传值自定义事件
    第一步是绑定了一个事件,这是个处理函数。$emit就代表触发这个事件,谁触发这个事件,谁传一个值过来。
    子向父传值:在子组件调用$emit,父组件在子组件标签上用@绑定(监听)这个自定义事件
    绑定事件和触发事件要在同一个实例上!!有绑定有触发
    image

    vue在内部要想触发click事件,它也是用this或vm

    1
    <button @click='show'>按钮</button>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    method:{
    show(e){}
    }
    vm.$emit('click',{//事件对象e
    ...
    clientX:"",
    clientY:"",
    target:dom元素
    })

  4. 兄弟组件之间的数据共享EventBus
    vue2.x中,兄弟组件之间数据共享的方案是EventBus

    • EventBus使用步骤
    1. 创建eventBus.js模块,并向外共享一个Vue的实例对象
    2. 在数据发送方,调用bus.$emit('事件名称',要发送的数据)方法触发自定义事件
    3. 在数据接收方,调用bus.$on('事件名称',事件处理函数)方法注册一个自定义事件
      数据在事件处理函数的形参中拿到。

    依旧是发送方触发事件,发送数据,接收方绑定事件监听,接受数据。
    这里的$on就相当于jQuery的on绑定事件
    为什么这里要用到一个共享的实例EventBus ?因为绑定事件和触发事件要在同一个实例上!!有绑定有触发

image

注意:虽然可以使用EventBus来在父子组件之间传值,但是这不是Vue官方推荐的做法。props和自定义事件更适合处理父子组件之间的通信,因为它们更直观和易于理解。 Event Bus 通常更适用于非父子关系的组件之间的通信,例如兄弟组件之间。

拓展

vscode插件 Path Autocomplete - @路径提示

下载插件之后,要在自己配置项里面添加如下配置,添加在头部就行
设置 – setting.json

1
2
3
4
5
6
// 导入文件时是否携带文件的扩展名
"path-autocomplete.extensionOnImport":true,
// 配置 @ 的路径提示
"path-autocomplete.pathMappings":{
"@":"${folder}/src"
},

注意:只有vscode打开的文件夹根目录是项目根目录时才起作用!!

总觉得这个功能vscode也集成进去了?一开始没装的时候,好像就触发了,也懒得验证了hh

Auto Close Tag插件

输入<xx>标签前半部分,会自动补全后半部分
不过新版vscode好像已经集成了这个功能,没有安装也是这样,装了没啥变化

方法简写形式

如果方法只有一行,可以简写到行内

1
2
<p>count的值是:{{count}}</p>
<button @click="count += 1"></button>

编码规范

先指令,后绑定,最后绑事件
这是一个子组件的绑定示例:

1
2
3
4
5
6
7
8
<Goods
v-for="item in list"
:key="item.id"
:id="item.id"
...
:state="item.goods_state"
@state_change="getNewState"
></Goods>