3.5 组件的生命周期钩子
什么是生命周期呢,我们把Vue的代码简便地写出来看一眼:
<div id='app'>
<h1>{{msg}}</h1>
</div>
<script>
Vue.createApp({
data(){
msg:'hello world'
}
}).mount("#app")
</script>
这个代码呢 分为两部分,上面div部分为 html 代码,下面的script部分为JS代码。我们做任何事情都有个先后顺序,生命周期就是这么个意思,我们要展示这个 msg,第一步首先它会先创建create,第二步是挂载mount到html中。
这两个步骤我们肉眼当然看不到,它不会给我们看到。在创建之前呢,createApp里面我们写的所有东西,都不存在。只有创建好之后才能拿到数据。创建完了之后它挂载到html上才能最终在页面中被我们看到。
Vue呢有生命周期函数 ,也叫做钩子函数。这里举例子:
Vue.createApp({
data(){
return {
msg:'hello world'
}
},
beforeCreate(){ //beforeCreate(),顾名思义:在create创建之前
console.log(this.msg)
}
}).mount('#app')
这里我们会得到一个undefined,因为创建之前,肯定是没有一切数据的,所有是读不到msg的。如果把beforeCreate换成created就能够读到msg了。
那么除了这个还有一些,这些都是页面一刷新、一加载的时候 自动会执行的事情,而不需要点击按钮之类的触发:
beforeCreate(){} //create之前
created(){} //create完成后
beforeMount(){} //create之后,挂载之前
mounted(){} //挂载完成后
那mounted和beforeMount有什么区别呢,mount是挂载的意思,如果还未挂载的话,那么在我们根组件里面应该拿不到html元素 也就是标签。可以实践一下:
<div id="app">
<h1 ref="title">{{msg}}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
data(){
return {
msg:'hello world'
}
},
beforeMount(){
console.log(this.$refs.title)
},
mounted(){
console.log(this.$refs.title)
}
}).mount('#app')
</script>
那我们在控制台会看到一个undefined还有一个h1标签,这么说明了beforeMount()是拿不到html元素的,而mounted()可以拿到。
那么它的作用是很广泛的例如在这个期间可以判断登录之类的,可以写一个函数 放到你想要的位置即可。
总览一下Vue3组件中的生命周期函数:
beforeCreate(){
console.log('实例刚刚被创建')
},
created(){
console.log('实例已经创建完成')
},
beforeMount(){
console.log('模板编译之前')
},
mounted(){
console.log('模板编译完成')
},
beforeUpdate(){
console.log('数据更新之前')
},
updated(){
console.log('数据更新完成')
},
activated(){
console.log('keep-alive缓存的组件激活时调用')
},
deactivated(){
console.log('keep-alive缓存的组件停用时调用')
},
beforeUnmount(){
console.log('实例销毁之前')
},
unmounted(){
console.log('实例销毁完成')
},
3.6 自定义指令
为了更好理解嘞,这里说一个场景,场景是我们手机点击一个验证码验证按钮,跳转到一个页面 然后输入框自动选中并且跳出了键盘给我们输入,这个效果呢 归功于 输入框自动获取焦点。用原生JS这么实现:
<div id="app">
<input type='text' />
</div>
<script>
const input = document.querySelector("input");
input.focus();
</script>
那么用Vue的话,我们可以利用上一个点学的生命周期函数实现:
<div id="app">
<input ref='inp' type='text' />
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
mounted(){ //挂载完毕后才能拿到dom
this.$refs.inp.focus()
}
}).mount('#app')
</script>
但如果到处都需要这个功能的话,每次都这样写非常麻烦,这个时候我们可以通过自定义指令来简化操作。
先复习一下指令:
- v-on:绑定事件 @
- v-bind:绑定属性 :
- v-text:绑定文本
- v-for:遍历
- v-if:条件判断
下面我们来自定义指令:
<div id="app">
<input v-focus ref='inp' type='text' />
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({})
app.directive("focus",{ //给指令起名字,这里叫focus,使用的时候写到标签里面的
mounted(el){ //同样是mounted,但它的参数el 表示的是被写进的标签的dom节点
el.focus()
}
})
app.mount("#app")
</script>
好~这样就可以实现自定义指令啦,那我们之前在学习组件的时候,一开始是用app.component( )来注册组件,后来又在createApp内部的components属性注册组件。这里的directive当然也有对应属性:
Vue.createApp({
directives:{
focus:(el) => el.focus() //这里用箭头函数,非常简洁
}
}).mount("#app")
3.7 单文件组件
这边呢先介绍一个东西,Vite。
Vite是vue的作者尤雨溪在开发vue3.0的时候开发的一个web开发构建工具。由于其原生ES模块导入方式,可以实现闪电般的冷服务器启动。
Vite的特点:
- 快速的冷启动
- 即使的模块热更新
- 真正的按需编译
原理:利用ES6的import会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间
那么我们这里来创建一个vue项目,建一个文件夹在底下创:npm init vite-app vue3_demo
这样就生成了一个项目。项目中有package.json,我们只需要npm install就可以安装依赖了,会生成一个node_modules文件 不用管它都是依赖。那我们来一个一个看文件都放些什么
- public:放静态文件的目录
- src:vue项目写代码的位置 src下面细分
- assets:也是放静态文件的目录,等下解释有什么区别跟public
- components:看名字就知道,放组件的
- main.js:点开会发现很眼熟,之前用html写vue的时候,用的createApp等等在这里,通过mount()挂载到html页面上,html页面在下面 index.html
- index.html:里面引入了上面的main.js文件,
<script type="module" src="/src/main.js"></script> - index.css:创建项目自带的css样式,我们把它删掉,免得影响我们后面自己写样式。
- App.vue:单文件组件
ok终于归到主题,单文件组件就是一个 .vue文件。也就是说以后写的代码都写在单文件组件里,不写到html文件里。那我们点开App.vue看一下,我写一些备注助于理解:
<template>
<!-- template:模板(html结构),之前写组件的时候也用到了template吧 -->
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3.0 + Vite" />
</template>
<script>
<!-- js代码依旧写到script标签里 -->
import HelloWorld from './components/HelloWorld.vue' //引入components文件夹中的组件HelloWorld
export default { //export default 默认导出的意思
name: 'App',
components: { // 这里用到子组件注册的方式,说明现在这个App.vue是一个根组件
HelloWorld // HelloWorld是一个子组件,它位置在components文件夹下
}
}
</script>
<!-- 如果要写样式的话,可以下面加style标签 -->
<!-- 里面的scoped是作用域的意思,加上scoped的话表示只有在这个组件能用,不加的话其他的也能用 -->
<style scoped>
</style>
那我们启动看一下:npm run dev,它会给我们地址打开即可。里面这些东西是它本身写好的。可以去组件里面修修改改看看效果变化。可以试着这样子实现之前的购物车案例。
3.8 组件化开发与模块化开发的基本概念
3.8.1 组件化开发
我们看过很多学校的什么选课系统之类的,都有很类似的布局例如这样:
那这里可以说成是header、nav、content三个组件。那我们现在就去创建三个组件,vue的程序组件写在components里。 (组件名不能和已有的html标签名一样) :
<!-- MyHeader.vue -->
<template>
<span>Logo</span>
<span>用户信息</span>
</template>
<!-- MyNav.vue -->
<template>
<ul>
<li>掘金</li>
<li>bilibili</li>
<li>百度</li>
</ul>
</template>
<!-- MyContent.vue -->
<template>
<h1>Hello MyContent</h1>
<h2>主要内容</h2>
</template>
接着我们来把它们搬到根组件中,我就简单还原一下上面画的图的那个结构,它属于是先分成上下结构,然后下面那部分呢再分为左右结构。
<!-- App.vue 第一步:先分上下结构 -->
<template>
<div class="header"></div>
<div class="container"></div>
</template>
<!-- App.vue 第二部:再把下面分为左右结构 -->
<template>
<div class="header"></div>
<div class="container">
<div class="nav"></div>
<div class="content"></div>
</div>
</template>
接下来给它们写一点样式,随便写点:
<style scoped>
.header{
height: 80px;
border: 1px solid red;
}
.container{
display: flex;
}
.container .nav{
flex: 1;
height: 600px;
border: 1px solid blue;
}
.container .content{
flex:5;
height: 600px;
border: 1px solid green;
}
</style>
把子组件都引进根组件,把它们放到相应位置,总的代码是这样:
<template>
<div class="header">
<MyHeader></MyHeader>
</div>
<div class="container">
<div class="nav">
<MyNav></MyNav>
</div>
<div class="content">
<MyContent></MyContent>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue'
import MyNav from './components/MyNav.vue'
import MyContent from './components/MyContent.vue'
export default {
name: 'App',
components: {
MyHeader,
MyNav,
MyContent
}
}
</script>
<style scoped>
.header{
height: 80px;
border: 1px solid red;
}
.container{
display: flex;
}
.container .nav{
flex: 1;
height: 600px;
border: 1px solid blue;
}
.container .content{
flex:5;
height: 600px;
border: 1px solid green;
}
</style>
那么以上呢,就是组件化开发。这样做的好处呢,如果说我们看到页面哪里坏掉了,诶可以很快地去定位到bug的位置。
3.8.2 模块化开发
从上面的学习中可以意识到,组件化开发呢是把页面拆分开,那么模块化开发其实是拆分功能(函数) 。比如说:上面的例子中,我在MyNav.vue组件中,放了一个列表有三个值,掘金、bilibili、百度。那我想实现点击对应的字能跳转到对应页面,我想实现这个方法,并且可能这个方法在别的组件也会用到,所以我必须模块化开发来让所有组件都能引入这个方法 而不是只写在一个组件中。我们这样做:
在src目录下建一个utils文件夹(工具文件夹),里面就用来放我们所有的工具函数。在里面我就写一个jump()函数:
function jump(url){ //传一个参数进来,就是对应网站的地址
window.location.href = url
}
export default jump //一定记得export default,这是模块化语法,不然在组件内import找不到
export import 这些属于模块化语法,如果在组件中想要import拿到一些东西,那么这些东西必须有export导出才能被拿到。
然后到MyNav.vue中引用一下:
<template>
<ul>
<li @click="jump('https://juejin.cn/')">掘金</li>
<li @click="jump('https://www.bilibili.com/')">bilibili</li>
<li @click="jump('https://www.baidu.com/')">百度</li>
</ul>
</template>
<script>
import jump from '../utils/jump.js'
export default {
methods:{ //函数的写法跟原来一样,把引入的jump赋值给这里methods中的jump,上面就可以使用了
jump:jump
}
}
</script>
这样就完成了!~
小结:组件化开发开分的是页面,模块化开发是拆分功能。