Vue从入门到入土 - 进阶篇

77 阅读10分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

前言

大家好🥰!!之前我们一起讲完了Vue的作者历史、提供的一些指令、MVVM模型、Vue自带的一些方法、还有Vue的响应式原理,这章大家就一起来了解一下Vue的核心特点:组件化模式🚩

生命周期函数

首先在讲Vue组件之前,大家要了解一下Vue给我们提供的一些在关键时刻帮我们调用的一些特殊名称的函数,我们称之为生命周期函数

Vue一共提供了三个阶段的生命周期函数,分别是:挂载流程更新流程销毁流程,我们这里借用一下小破站天禹老师的一张注释图,如下图所示:

生命周期.png

如上图所示,这就是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操作数据,因为即便操作数据,也不会触发更新流程

总结

大家一看八个生命周期咋记得住啊,这么多,但是实际上常用到的没有多少,比如我经常用到的只有mountedbeforeDestroy

mounted 用来做 发送ajax请求、启动定时器、绑定自定义事件等(初始化操作) beforeDestroy用来做 清除定时器,解绑自定义事件等(收尾操作)

但是大家要注意销毁后借助Vue开发者工具看不到任何信息,但是里面可能还有残留数据。VM销毁后自定义事件会失效,但是原生的DOM事件依然有效,比如click依然会有效。

非单文件组件

🚩首先说到组件化,不得不提到模块化。组件(Component)模块(Module)是一对容易混淆的名词,也常常被用来相互替换。实则二者差异很大。从设计上来看,组件强调复用,模块强调职责(内聚、分离),或者说组件是达到可复用要求的模块。

组件和模块

模块

模块。它的核心意义是分离职责,属于代码级模块化的产出。本身是一组具有一定内聚性代码的组合,职责明确。对外的接口可以是松散的,也可以是集中的。

它就是一个向外提供特定功能的js程序,一般就是一个js文件。可能大家会问为啥要用模块呢,首先因为js文件很复杂而且很多,按照普通开发,往往一个网站或者网页会有很多个js文件,而当我们使用模块之后,能够复用js、简化js的编写,提高js运行效率

cc38c08ccf6e6e79c6b88ae56f56fb92.png

而模块化:当应用中的js都以模块来编写的,那这个应用就是一个模块化的应用

组件

组件。它的核心意义在于复用,相对模块,对于依赖性有更高的要求。

Vue官方文档的介绍就是实现应用中局部功能代码和资源的集合。而使用组件的原因,首先能够复用编码、简化项目编码,提高运行效率

我们常常说,组件就是一块砖,哪里需要哪里搬

9f3531f2fd0ee1a7f7cf98d8c279655d.png

而组件化:当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用

组件的步骤

使用组件一共分为三大步骤

  • 定义组件 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为什么要写成函数形式?

    1. 因为data如果是对象的话重复组件中如果一些数据相同的话,一个数据改变其他组件中的相同数据也会发送改变,而是函数的话,因为函数作用域的关系不会影响其他组件中数据

    2. 执行方法会在栈中开辟一块空间,方法执行完成后销毁释放内存。基本数据类型值不会被改变,引用数据类型值会被改变

  • 组件本质是一个名为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原型的关系图:

3ca95bb75b2ff6c60eac78063db63f50.png

大家可以会想为啥要有这一步操作呢,为啥要搞这个关系呢? 实际上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 如下图所示 🔰

image.png

这是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')

这个大家应该特别熟悉吧,但是当我们运行的时候发现页面空白,控制器上面报了以下一个错误🔰

image.png

给大家简单的翻译一下是什么意思:你正在使用一个运行版本的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

image.png

image.png

说到这里大家就要晓得Vue实际上是包含了二个部分:核心功能(生命周期、处理事件等)模板解析器(解析版本工具)

晓得这两部分又对我们有啥帮助呢?和搞阉割版的文件有啥关系? 是这样的,我们可以仔细看一下上面文件,大家可以发现阉割版的Vue.js文件比完整版的少了三分之一,这就很重要了。首先我们在开发阶段可能确实会使用到这个模板解析器,但是项目总有完成的时候吧,当我们项目完成,交付给webpack帮忙打包时,实际上整个使用webpack会帮我们去把 .vue 文件转换成 .html .css .js文件,这个时候就不需要模板解析器,Vue团队为了帮减少大小而推出阉割版的Vue文件

可能大家觉得中100KB这么小,我差这100KB嘛😡,一个高清图片就要好几百KB,谁在乎这几KB文件啊?个人看法:简单来说,一个100kb确实不大,但是当项目真正上线了,不可能就只有一个人在使用,会有很多人,这个时候坏处就出来了,而且js文件加载就比较缓慢,每次进来都要加载,能优化性能为啥不优化呢?图片我们还有懒加载优化图片性能对吧

关于不同版本的Vue

这里简单讲一下关于文件夹里面不同版本的Vue的区别

  1. Vue.jsVue.rentime.xxx.js 的区别

    • Vue.js 是完整版的Vue,包含:核心功能 + 模板解析器
    • Vue.runtime.xxx.js 是运行搬的Vue,只包含:核心功能(没有模板解析器)
  2. 因为 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实例的自定义事件全都不奏效
      }  
  }
}

总结:

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('MengXin',this.name)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('MengXin',数据)

  5. 解绑自定义事件this.$off('MengXin')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('MengXin',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

个人总结

😭😭真的好累啊,兄弟们都好卷啊,真的卷不过,完全不是一个级别的,凌晨三点起来上个厕所打开小破站看了一眼,还有十几个卷王,都不用睡觉的嘛,卷不过卷不过,兄弟们你们加油,我要搬砖去了!😢😢

回顾正题,虽然是复习回顾,但是越回忆越温故但是感觉越来越模糊,有点迷茫,但是又不晓得该怎么办,还有一篇关于Vuex和router的文章,Vue就差不多算回顾结束了。接下来调整心态,准备去学习node,兄弟们一起加油🚩🥰

可能写的不是很好,有些地方也没有写的很清楚,但是这个是根据我个人学习的一个进度写的文章,如果有地方没有写好,欢迎大家在评论区提出,我会积极改进。兄弟们冲🦆冲🦆冲🦆

里面很多片段是根据在小破站学习天禹老师的视频总结的笔记,如果大家感觉眼熟,不要见怪,视频链接我就放这里啦。点击