从0学习Vue3(4)

147 阅读7分钟

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>

但如果到处都需要这个功能的话,每次都这样写非常麻烦,这个时候我们可以通过自定义指令来简化操作。

先复习一下指令:

  1. v-on:绑定事件 @
  2. v-bind:绑定属性 :
  3. v-text:绑定文本
  4. v-for:遍历
  5. 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文件 不用管它都是依赖。那我们来一个一个看文件都放些什么

image.png

  • 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 组件化开发

我们看过很多学校的什么选课系统之类的,都有很类似的布局例如这样:

image.png

那这里可以说成是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>

这样就完成了!~

小结:组件化开发开分的是页面,模块化开发是拆分功能。