携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
前言
大家好🥰!!之前我们一起讲完了Vue的作者历史、提供的一些指令、MVVM模型、Vue自带的一些方法、还有Vue的响应式原理,这章大家就一起来了解一下Vue的核心特点:组件化模式🚩
生命周期函数
首先在讲Vue组件之前,大家要了解一下Vue给我们提供的一些在关键时刻帮我们调用的一些特殊名称的函数,我们称之为生命周期函数
Vue一共提供了三个阶段的生命周期函数,分别是:挂载流程、更新流程、销毁流程,我们这里借用一下小破站天禹老师的一张注释图,如下图所示:
如上图所示,这就是Vue提供的生命周期,一共提供了8个生命周期函数,给大家区分一下,下面的vm指的是Vue实例
挂载流程
beforeCreate()
无法通过vm访问到data中的数据和methods中的方法created()
可以通过vm访问到data中的数据和methods中配置的方法beforeMount()
页面上显示未经Vue编译的DOM 结构 所有对DOM 的操作,最终都不奏效mounted()
初始化后挂载完毕后调用mounted 至此初始化过程结束 调用定时器、网络请求等
更新流程
beforeUpdata()
数据已更新,但页面还未更新,数据尚未同步Updated()
数据与页面都已更新,数据保持同步
销毁流程
deforeDstroy()
所有的操作还能进行,都处于可用状态 但是对数据的修改更新都没有效果了Destroyed()
完全销毁vm
PS:当销毁了VM实例,页面上面的数据还会存在 相当于管理员死了,但是他整理的资料还在。
一般不在beforeDestroy操作数据,因为即便操作数据,也不会触发更新流程
总结
大家一看八个生命周期咋记得住啊,这么多,但是实际上常用到的没有多少,比如我经常用到的只有mounted
和beforeDestroy
。
mounted
用来做 发送ajax请求、启动定时器、绑定自定义事件等(初始化操作)
beforeDestroy
用来做 清除定时器,解绑自定义事件等(收尾操作)
但是大家要注意销毁后借助Vue开发者工具看不到任何信息,但是里面可能还有残留数据。VM销毁后自定义事件会失效,但是原生的DOM事件依然有效,比如click依然会有效。
非单文件组件
🚩首先说到组件化,不得不提到模块化。组件(Component)
和模块(Module)
是一对容易混淆的名词,也常常被用来相互替换。实则二者差异很大。从设计上来看,组件强调复用,模块强调职责(内聚、分离),或者说组件是达到可复用要求的模块。
组件和模块
模块
模块。它的核心意义是分离职责,属于代码级模块化的产出。本身是一组具有一定内聚性代码的组合,职责明确。对外的接口可以是松散的,也可以是集中的。
它就是一个向外提供特定功能的js程序,一般就是一个js文件。可能大家会问为啥要用模块呢,首先因为js文件很复杂而且很多,按照普通开发,往往一个网站或者网页会有很多个js文件,而当我们使用模块之后,能够复用js、简化js的编写,提高js运行效率
而模块化:当应用中的js都以模块来编写的,那这个应用就是一个模块化的应用
组件
组件。它的核心意义在于复用,相对模块,对于依赖性有更高的要求。
Vue官方文档的介绍就是实现应用中局部功能代码和资源的集合。而使用组件的原因,首先能够复用编码、简化项目编码,提高运行效率。
我们常常说,组件就是一块砖,哪里需要哪里搬
而组件化:当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
组件的步骤
使用组件一共分为三大步骤
- 定义组件
Vue.extend(options)
- 注册组件
局部靠 component 配置项 全局靠Vue.component('组件名',组件)
- 使用组件
<组件名></组件名> 标签形式
<div id="app">
<!-- 第三步 页面上使用组件,直接以html标签的形式引入到页面中-->
<Student></Student>
</div>
<script>
// 第一步 使用Vue.extend来创建全局组件
const Stu = Vue.extend({
data() {
return {
name:'萌新'
}
},
// 通过template模板的属性来展示组件要显示的html
template: '<h1>学生姓名:{{name}}</h1>'
})
// 第二步 使用Vue.component('组件名称',创建出来的组件对象)
Vue.component('Student',Stu)
//创建Vue实例
const vm = new Vue({
el: '#app'
})
</script>
Data
-
组件里面的data为什么要写成函数形式?
-
因为data如果是对象的话重复组件中如果一些数据相同的话,一个数据改变其他组件中的相同数据也会发送改变,而是函数的话,因为函数作用域的关系不会影响其他组件中数据
-
执行方法会在栈中开辟一块空间,方法执行完成后销毁释放内存。基本数据类型值不会被改变,引用数据类型值会被改变
-
-
组件本质是一个名为
VueComponent的构造函数
,且不是程序员定义的,是Vue.extend
生成的 -
当我们注册组件时,Vue解析时会帮我们创建school组件的实例对象。即Vue帮我们执行的:
new VueComponent(options)
-
每次调用
Vue.extend
,返回的都是一个全新的VueComponent
Vue实例和组件的区别
📢📢:组件是由VueComponent构建出来的,而vm是由Vue构建出来的;Vue实例可以定义el,组件不可以;组件中的data必须是函数,而Vue实例中的data可以是对象也可以是函数
原型关系
说到原型不晓得大家是不是都忘记了,先写段代码带大家回忆一下
//定义一个构造函数
function Demo(){
this.a = 1;
this.b = 2;
};
//创建一个Demo的实例对象
const d = new Demo();
console.log(Demo.prototype); //显示原型属性
console.log(d.__proto__); //隐式原型属性
//显示原型属性的指向等于隐式原型属性的指向
console.log(Demo.prototype === d.__proto__); //true
//程序员通过显示原型属性操作原型对象,追加一个x属性,值为99
Demo.prototype.x = 99;
console.log(d.__proto__.x);
根据上面的代码大家应该对原型有点简单的印象了吧。
首先显示原型属性只有函数才会拥有,而实例对象只有隐式原型属性;实例的隐式对象永远指向自己缔造者的显示原型对象。
而在Vue中,VueComponent.prototype.proto === Vue.prototype 意思就是 VueComponent的prototype的__proto__就是Vue的原型对象
下面借用一张天禹老师关于Vue与VueComponent原型的关系图:
大家可以会想为啥要有这一步操作呢,为啥要搞这个关系呢? 实际上Vue这里之所以有这个关系是为了让组件实例对象可以访问到Vue原型上的属性、方法
单文件组件
认识Vue CLI
上面呢讲的是一些比较理论和需要简单记忆的东西,现在来带大家真正的了解一下组件开发是什么样子的,而说到组件化开发,那这里就需要谈到了一个Vue官方提供给我们的官方工具:Vue CLI 我们叫做脚手架
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,它呢是一个专门为单页面应用快速搭建繁杂的脚手架,它可以轻松的创建新的应用程序,而且可用于自动生成vue和webpack的项目模板。当我们后期真正开发项目的时候就可以使用到这款工具,这里带大家了解一下
它是基于webpack
构建,并且给我们设置了默认配置,当然也可以通过内部文件进行修改配置,而且可以安装插件进行扩展。官方文档的一句话是这么说的:它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题
当然这里就不详细讲解怎么安装Vue CLI了,下期出一个专门安装和使用Vue CLI的文章,大家可以先参考官方文档 Vue CLI
文件目录
当我们通过脚手架创建出来一个项目时,他会自带很多文件,这里给大家简单的介绍一下这些文件分别是干嘛的
脚手架文件分析
|—— node_modules 项目依赖包
|—— public 公共资源目录
| |—— favicon.ico 页签图标
| |—— index.html 主页面
|—— src 项目源文件目录
| |—— assets 存放静态资源
| | |—— logo.png
| |—— component 存放组件
| | |—— HelloWorld.vue
| |—— App.vue 汇总所有组件
| |—— main.js 入口文件
|—— .gitignore git版本管制忽略的配置
|—— babel.config.js babel的配置文件
|—— package.json 应用包配置文件
|—— README.md 应用描述文件
|—— package-lock.json 包版本控制文件
以上就是通过脚手架创建的一个普通项目目录结构,一般情况下大家不要随便去动里面的核心文件,当然也能进行修改和删除的,这个根据各自的需求进行操作。
目录介绍完带大家了解一下里面的一些的文件的内容
render函数
首先来看一下我们的入口文件 main.js
如下图所示 🔰
这是main.js
里面的内容,相信大家对于这个里面的大部分内容应该都比较熟悉,但是这里出现了一个我们没有看见过的函数render
,大家来思考思考这个是有啥作用呢?为啥会出现一个这样的函数呢?
首先大家应该知道我们的Vue实例是通过template
模板,来替换挂载的元素。按照道理来说我们应该这样写:
// 导入Vue
import Vue from 'vue'
// 导入App.vue 先不导入App.vue 我们直接定义一个div标签 看看效果
// import App from './App.vue'
// 关闭提示信息
Vue.config.productionTip = false
// 创建Vue实例
new Vue({
// render: h => h(App),
template:`<div><h2>Vue,你好啊,我是萌新</h2></div>`
}).$mount('#app')
这个大家应该特别熟悉吧,但是当我们运行的时候发现页面空白,控制器上面报了以下一个错误🔰
给大家简单的翻译一下是什么意思:你正在使用一个运行版本的Vue,并且这个模板解析器是没有成功获取到的。要么你把需要编译的模板交给render函数或者使用编译器存在的版本
。
翻译成人话就是:兄弟你正在使用一个运行时版本的Vue,你没有模板解析器。想要解决这个问题,要么通过这个render函数来编译模板,要么换一个带有模板编译器的Vue
。
首先我们可以使用 render
函数来编译模板
new Vue({
el:"#app",
//将App组件放入容器
//render: h => h(App)
render(createElement){
//创建元素
//第一个参数:标签 第二个参数:内容 必须要返回值
return createElement('h1','你好啊')
}
//简写
render:createElement => createElement('h1','你好啊')
})
为啥使用阉割版Vue
那运行时版本的Vue又是啥意思呢?简单来说就是一个阉割版的Vue,它相比完整版的Vue被阉割了一部分,而这一部分正是模板解析器!!!
大家应该知道我们通过模块化去引入文件的时候,每次都会具体到那个目录下面的那个文件,而在引入第三库时,我们只引入到文件夹但是并没有引入到指定的文件下面,而就因为这个导致是残缺版的Vue,当你指定为完整的Vue.js文件时,就可以使用完整版的Vue了
那如何给大家证明它是阉割版的Vue呢?
首先大家可以通过按住Ctrl键
点击 import Vue from 'vue' 被框选的Vue,进入Vue的依赖文件夹然后点开Vue的package.json
文件,我们可以看到很多内容,而我们只需要关注module这句话,这句话什么意思呢?当我们通过ES6模块化引入Vue时,就会根据这个里面的内容来指定我们引入的是那个文件,而这个文件就是一个残缺版的Vue
说到这里大家就要晓得Vue实际上是包含了二个部分:核心功能(生命周期、处理事件等)、模板解析器(解析版本工具)
晓得这两部分又对我们有啥帮助呢?和搞阉割版的文件有啥关系? 是这样的,我们可以仔细看一下上面文件,大家可以发现阉割版的Vue.js
文件比完整版的少了三分之一,这就很重要了。首先我们在开发阶段可能确实会使用到这个模板解析器,但是项目总有完成的时候吧,当我们项目完成,交付给webpack帮忙打包时,实际上整个使用webpack会帮我们去把 .vue
文件转换成 .html .css .js
文件,这个时候就不需要模板解析器,Vue团队为了帮减少大小而推出阉割版的Vue文件。
可能大家觉得中100KB这么小,我差这100KB嘛😡,一个高清图片就要好几百KB,谁在乎这几KB文件啊?个人看法:简单来说,一个100kb确实不大,但是当项目真正上线了,不可能就只有一个人在使用,会有很多人,这个时候坏处就出来了,而且js文件加载就比较缓慢,每次进来都要加载,能优化性能为啥不优化呢?图片我们还有懒加载优化图片性能对吧
关于不同版本的Vue
这里简单讲一下关于文件夹里面不同版本的Vue的区别
-
Vue.js
与Vue.rentime.xxx.js
的区别Vue.js
是完整版的Vue,包含:核心功能 + 模板解析器Vue.runtime.xxx.js
是运行搬的Vue,只包含:核心功能(没有模板解析器)
-
因为
Vue.runtime.xxx.js
没有模板解析器,所以不能使用template
配置项,需要使用render
函数接收到的createElement
函数去指定具体内容
ref属性
ref
相当于id
,给元素添加ref
然后通过this.$refs.名称
获取到该元素- 也可以给组件添加
ref
,然后获取到该组件的实例对象 - 如果应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<MengXin ref="sch"/>
</div>
</template>
<script>
//引入MengXin组件
import MengXin from './components/MengXin'
export default {
name:'App',
components:{MengXin},
data() {
return {
msg:'您好!我是萌新'
}
},
methods: {
showDOM(){
console.log(this.$refs.title) //真实DOM元素
console.log(this.$refs.btn) //真实DOM元素
console.log(this.$refs.sch) //MengXin组件的实例对象(vc)
}
},
}
</script>
props配置项
- 父组件向子组件传值,在子组件标签上面添加传递的属性
- PS:如果确定传过去的就是你定义的对象 不让js隐式解析 那就使用动态传值
- 在子组件通过props接收 🔰
//父组件
<MengXin name="萌新" sex="男" :age="20"/>
//子组件
export default{
//三种方法
props:['name','age','sex'] //简单声明接收
props:{
//指定类型 但是Vue还是会接收 但是会甩出一个报错
name:String,
age:Number,
sex:String
}
props:{
//指定类型和默认值、是否必须传
name:{
type:String, //类型
required:true //name是必要的
},
age:{
type:Number,
default:99 //默认值
},
age:{
type:String,
required:true //age是必要的
}
}
}
接收的
props
不能进行修改,通过在data
里面定义一个参数,然后通过this.xx
接收,然后修改data
的值 !!!!!props的优先级比data高
自定义事件
- 通过父组件给子组件传递函数类型的props
//父组件
<MengXin :getMengXinName="getMengXinName"/>
export default{
methods:{
getMengXinName(name){
console.log("App收到了姓名:",name)
}
}
}
//子组件
<button @click="sendMengXinName">把姓名给App</button>
export default{
data(){
return{
name:'萌新'
}
},
props:['getMengXinName'],
methods:{
sendMengXinName(){
this.getMengXinName(this.name)
}
}
}
- 通过父组件给子组件绑定一个自定义事件
$emit
//父组件
<School v-on:MengXinName="getMengXinName"/>
export default{
methods:{
getMengXinName(name){
console.log("App收到了姓名:",name)
}
}
}
//子组件
<button @click="sendMengXinName">把姓名给App</button>
export default{
methods:{
sendMengXinName(){
//触发MengXin组件实例身上的MengXinName事件
this.$emit('MengXinName',this.name)
}
}
}
当然可以绑定自定义事件,也能解绑自定义上面,上面还给大家讲到可以在beforeDestroy
生命周期解绑所有的自定义事件,现在这个问题就可以给大家解决掉了,首先解绑自定义事件一共有三个方法,大家需要那个就使用那个
this.$off('自定义事件名称')
解绑一个自定义事件this.$off(['自定义事件名称1','自定义事件名称2'])
解绑多个自定义事件this.$off()
解绑所有的自定义事件
//子组件
//只能点击一次
<button @click.once="sendMengXinName">把姓名给App</button>
<button @click="cancelVue">注销Vue</button>
export default{
beforeDestroy:{
//解绑所有的自定义事件
this.$off()
},
methods:{
sendMengXinName(){
//触发MengXin组件实例身上的MengXinName事件
this.$emit('MengXinName',this.name)
},
cancelVue(){
this.$destroy() //销毁了当前Vue实例,销毁后所有Vue实例的自定义事件全都不奏效
}
}
}
总结:
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
-
绑定自定义事件:
-
第一种方式,在父组件中:
<Demo @atguigu="test"/>
或<Demo v-on:atguigu="test"/>
-
第二种方式,在父组件中:
<Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('MengXin',this.name) }
-
若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。
-
-
触发自定义事件:
this.$emit('MengXin',数据)
-
解绑自定义事件
this.$off('MengXin')
-
组件上也可以绑定原生DOM事件,需要使用
native
修饰符。 -
注意:通过
this.$refs.xxx.$on('MengXin',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
个人总结
😭😭真的好累啊,兄弟们都好卷啊,真的卷不过,完全不是一个级别的,凌晨三点起来上个厕所打开小破站看了一眼,还有十几个卷王,都不用睡觉的嘛,卷不过卷不过,兄弟们你们加油,我要搬砖去了!😢😢
回顾正题,虽然是复习回顾,但是越回忆越温故但是感觉越来越模糊,有点迷茫,但是又不晓得该怎么办,还有一篇关于Vuex和router的文章,Vue就差不多算回顾结束了。接下来调整心态,准备去学习node,兄弟们一起加油🚩🥰
可能写的不是很好,有些地方也没有写的很清楚,但是这个是根据我个人学习的一个进度写的文章,如果有地方没有写好,欢迎大家在评论区提出,我会积极改进。兄弟们冲🦆冲🦆冲🦆
里面很多片段是根据在小破站学习天禹老师的视频总结的笔记,如果大家感觉眼熟,不要见怪,视频链接我就放这里啦。点击