生命周期
Vue会在一些特殊时刻去调用一些特殊的函数
这些要经过的特殊时刻就是生命周期,要调用的特殊函数就是生命周期函数(生命周期钩子,生命周期回调函数)
为什么叫钩子:vm在某个步骤上已经写了执行该名称的函数,但函数内容未定义,得程序员来定义。即生命周期函数的名字不可更改,但函数的具体内容需要程序员根据业务需求编写
Vue实例的生命周期:
1、初始化显示
beforeCreate()
=> 初始化:生命周期、事件,但数据代理还未开始create()
=> 初始化:数据监测、数据代理beforeMount()
=> 模板解析完毕,虚拟DOM建立,但还未挂载mounted()
=> 挂载完毕,虚拟DOM转为真实DOM
2、更新状态: this.xxx = value
beforeUpdate()
=> 数据已更新,但页面还未更新,新旧虚拟dom还未对比updated()
=> 数据和页面都是新的
3、销毁 vue 实例: vm.$destroy()
触发下面两个钩子
beforeDestroy()
=> 此时vm上所有东西都是可用的,但不再解析模板更新页面destroyed()
=> vm彻底被销毁
还有三个钩子不在图中,和路由相关
常用钩子:
- mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
- beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
关于销毁Vue实例:
- 在大多数场景中不应该调用 vm.$destroy() 。最好使用 v-if 和 v-for 指令以数据驱动的方式控制子组件的生命周期。
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生D0M事件依然有效
- 在beforeDestroy中做好善后工作
组件
组件:应用中局部功能代码和资源的集合
在 Vue 中,组件是可复用的 Vue实例
组件体现了封装的思想,组件也可以嵌套封装
组件化:编写一套组件,在所需的页面引入组件,当应用中的功能都是多组件的方式来编写的, 这个应用就是一个组件化的应用
为什么要组件化:传统的三件套可以只能做到拆分css、js模块化,而html结构不复用,且文件依赖关系(N-1-N)复杂,组件化后,页面的每个部分都对应一个组件,组件中的小功能部分也能对应更多组件,组件逐层嵌套,最终由一个Vue实例管理,依赖关系清晰,通过引入组件构建页面,代码复用率高,后续维护也能针对性找到组件进行操作
非单文件组件
非单文件组件:一个文件中包含n个组件,实际开发几乎不用这种写法
使用组件流程:
- 定义组件:
Vue.extend()
- 配置项中不能写el属性,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
- 组件中的data必须用函数式,避免组件被复用时,数据存在引用关系。
- 使用 template 配置组件模板结构,必须有一个根元素包裹1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const school = Vue.extend({
// 模板,template中的模板必须有一个根元素包裹
template: `
<div>
<h3>{{name}}</h3>
<h3>{{city}}</h3>
</div>
`,
// 组件中的data必须用函数式
// 因为对象是引用类型数据,函数式返回一个新对象才能保证多次引用组件的data互不影响
data() {
return {
name: '五道口',
city: '京海'
}
}
}) - 注册组件:
-Vue.component()
全局注册
- 局部注册,在vm配置项的 components 属性中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 全局注册组件,多个vue实例都能使用该组件
Vue.component('school', school)
// 组件由vm管理
new Vue({
el: '#root',
data: {
msg: '信息'
},
// 局部注册组件
components: {
// 组件名
student
}
}) - 使用组件:在容器中写组件标签
案例:
1 | <div id="root"> |
注意事项
关于组件名:
- 组件名只有一个单词时,引用组建时写小写
<student>
或大写<Student>
Vue只会去找小写的组件名,即组件名只有一个单词时需使用小写 - 组件名由多个单词组成时,有两种写法:横杠写法、双驼峰写法(只能在脚手架环境中使用)
1
2
3
4
5
6
7
8
9
10
11<!-- 横杠写法 -->
<my-school></my-school>
<script>
Vue.component('my-school', MySchool)
</script>
<!-- 双驼峰写法(只能在脚手架环境中使用) -->
<MySchool></MySchool>
<script>
Vue.component('MySchool', MySchool)o[]
</script> - 不能使用html已有标签名作为组件名
- 在定义组件时添加 name 配置,无论注册使用时是什么名,在开发者工具中显示的都是该 name(首字母同样会自动显示大写)
1
2
3
4
5
6
7
8
9const school = Vue.extend({
// 设置 name
name: 'MySchool',
template: ``,
data() {
return {}
}
})
在脚手架环境下,可以使用组件时可以写双标签 <school></school>
或自闭合标签 <school/>
,不用使用脚手架时,<school/>
会导致后续组件不能渲染。
定义组件可以简写:
注册组件时底层有判断,若是一个对象,Vue会帮我们调用Vue.extend,并将对象作为配置项传入
1 | // 简写,实际上也调用了Vue.extend |
组件嵌套
组件嵌套:一个组件也会用到其它多个组件,组件关系逐层嵌套,形成父子组件关系
使用方式:在父组件中 components 配置项注册其子组件,组件在哪注册就要在哪使用
开发中的技巧:定义一个名为 app 的组件,用于管理页面中最上层的所有组件,而 vm 本身只管理 app 组件
1 | <div id="root"></div> |
VueComponent构造函数
每个组件的本质都是一个 VueComponent()
构造函数,它由 Vue.extend()
生成并返回
每次调用 Vue.extend()
,返回的都是一个全新的 VueComponent()
构造函数
打印app组件:
1 | const app = Vue.extend({ |
1 | ƒ VueComponent(options) { |
既然是构造函数,使用它就需要 new 创建实例
只需要在合适的位置写 <school></school>
,Vue 在解析模板时会自动去创建对应的VueComponent构造函数的实例
即Vue会在解析模板时自动执行:new VueComponent(options)
1、this的指向:
(1) Vue.extend(options)
组件配置中,this 指向 VueComponent 实例对象
(2) new Vue(options)
配置中,this 指向 vm 实例对象
2、Vue对组件的关系设计:
- 每种组件定义时都需要调用
Vue.extend()
,它返回一个新的VueComponent()
构造函数,即不同种类的组件,其对应的 VueComponent 构造函数不同,以此来区分组件的种类 - Vue在解析模板时,会对每个组件标签都调用一次对应的构造函数,返回的实例都是互不影响的,以此来区分同种组件的不同实例(同种组件在出现在页面不同位置,展示不同内容)
vm与vc
VueComponent()
的实例对象,简称vc(也可称之为:组件实例对象)Vue()
的实例对象,简称vm。
对vm和vc的理解:
1、vm 和 vc 长得差不多(属性和方法都一样的),因为 Vue(options)
和 VueComponent(options)
两个构造函数,在源码中都是调用了其原型上的 _init(options)
2、vm 或 vc 的 $children
属性以数组形式保存着 vm 或 vc 所管理的组件或子组件的实例对象
3、组件是可复用的 Vue 实例(vm),所以它们与 new Vue()
接收相同的配置选项 options,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
4、vc 由 vm 管理,vc 没有 el 配置,只有 vm 能通过 el 配置来指定为谁服务
重要内置关系
VueComponent.prototype.__proto__ = Vue.prototype
复习原型与原型链:JavaScript基础笔记(3)—原型与原型链
$mount、$watch 等属性和方法都在 Vue.prototype 上,也就是 Vue 的原型上
作用:这个重要的内置关系将组件的原型与Vue的原型通过 __proto__ 相连,使 VueComponent 继承 Vue原型,让组件实例顺着原型链也能使用$mount、$watch 等属性和方法,
目的:让组件实例对象(vc)可以访问到Vue原型上的属性、方法
单文件组件
单文件组件:一个文件中仅包含一个组件,文件后缀为 .vue
浏览器并不认识.vue文件,需要通过 webpack 或脚手架,将其加工为JS文件
文件名推荐规则:单个单词大写,多个单词双驼峰
.vue文件中代码分为三个标签:
<template>
=> 组件的结构<script>
=> 组件交互相关代码,包含组件所用的数据、方法<style>
=> 组件的样式
主要的文件:需放入脚手架中才能运行
1、xxx.vue 各种组件
2、App.vue 汇总所有组件
3、main.js 入口文件,创建Vue实例,注册App组件并应用
4、index.html 页面
1 | <template> |
必须有 App.vue 汇总所有组件
1 | <template> |
main.js 入口文件,创建Vue实例,注册App组件并应用
1 | import App from "./App.vue" // 引入App组件 |
index.html 页面
1 |
|
脚手架Vue-cli
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
Vue 有多种脚手架,Vue-cli 是其中一种
起步
安装:该全局模块会暴露一个全局命令vue
1 | npm install -g @vue/cli |
创建一个项目
1 | vue create <项目名> |
选择项目的vue版本,babel用于ES6转ES5,eslint语法检查
1 | Vue CLI v5.0.8 |
等待安装完毕,运行项目
1 | npm run serve |
项目文件结构
默认 HelloWord 项目结构:
1 | ├─ public |
文件解释:
1、根目录中的一些配置文件:
- babel.config.js babel控制文件,脚手架已写好,无需再动
- package.json 定义了当前项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等)
- package-lock.json 锁定所有模块的版本号,包括主模块和所有依赖子模块
- .gitignore git忽略文件配置
- jsconfig.json 指定根文件和JavaScript语言服务提供的功能选项,明确项目的文件范围
- vue.config.js 一个可选的脚手架配置文件,进行vue项目本地运行和构建相关配置,若根目录中存在这个文件,它会被 @vue/cli-service 自动加载
2、src文件夹:有些熟悉的文件,main.js入口文件、app.vue组件等
- assets文件夹:存放静态资源
- components:存放组件,除了App组件
1 | // 整个项目的入口文件 |
3、public文件夹:页面的根目录,存放了index.html和ico图标
应该知道:main.js之所以是入口文件,而且它能找到public中的index.html去绑定容器,是脚手架配置、规定的
render函数
在 main.js 中,render: h => h(App)
作用是将App组件放入容器中
若使用之前的模板写法,控制台会报错
1 | import App from "./App.vue" // 引入App组件 |
报错内容: [vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
意思是:正在使用Vue的仅运行时版本(runtime模式),其中模板编译器不可用。将模板预编译为render函数,或者使用包含编译器的内部版本。
问题原因:
Vue包含两个部分,核心部分与模板解析器,核心部分是生命周期与各种属性与函数,模板解析器用于解析模板,当通过cdn引入来使用Vue时,应该将两者都引入,但如果使用脚手架、webpack将.vue文件都翻译为.js文件,且模板都已经解析为html,模板解析器在生产环境就没有用了
因为Vue中不是所有部分都在生产环境中用到,且模板解析器占Vue体积的三分之一,所以Vue除了完整版,还有许多精简版,带模板解析器的称为 compiler(模板)模式,反之称为 runtime(运行时)模式
(1) vue.js 是完整版Vue,包含:核心功能+模板解析器
(2) vue.runtime.xxx.js 是运行版Vue,只包含核心功能,没有模板解析器
通过 import Vue from 'vue'
引入的就是不带模板解析器的精简版,vue模块的package.json的main/model字段默认为runtime模式,指向了”dist/vue.runtime.common.js”。
解决办法一:引入完整版Vue,不推荐
1 | import Vue from 'vue/dist/vue.js' |
解决办法二:使用渲染函数 render()
代替模板写法
render 配置项是一个函数,传入 createElement 参数也是一个函数
createElement()
可以创建具体的元素,可以传入一个组件或是 ('标签名','标签内容')
的形式 ,返回创建好的元素
Vue会调用 render()
并接收其返回值,将 createElement()
的返回值(创建好的元素)返回即可
1 | // 完整写法 |
注意:在开发环境中有 vue-cli 自带有 vue-template-compiler 插件,能解析 .vue 文件中的 <template></template>
模板,但写在配置对象 template 属性中的模板没有插件能解析,所以要使用 render()
以后一般也只有在创建vm时配置项里会用到render
自定义脚手架配置
脚手架默认使用main.js作为入口文件等默认配置都在一个文件中,该文件被隐藏了起来
使用命令导出、查看脚手架配置文件的拷贝
1 | vue inspect > output.js |
在vue-cli配置参考项中查看可配置项
在 vue.config.js 文件中进行自定义配置
1 | // vue.config.js |
ref标签属性
替代 id 属性给元素或子组件注册引用信息(打标识)
给元素标签或组件标签添加 ref 属性,就能在组件实例对象的 $refs 上获取dom元素或子组件实例对象
1 | <!-- 打标识 --> |
1 | <template> |
props配置项
子组件的 props 配置项接收父组件通过组件标签传入的数据
作用:实现父组件与子组件的通信
通过标签属性给子组件传数据:
注意:
- 普通属性传的是字符串,要传入其它数据类型的属性,需要 v-bind 绑定属性,传入表达式的值
- 传入引用数据类型时,传的是引用,浅拷贝
1 | <Student name="chuckle" sex="男" :age="18"></Student> |
子组件接收数据,三种写法
1 | // 简单接收,不对数据类型进行限制 |
props 接收到的数据会代理到 vc 身上,但不允许修改只能读取(直接修改也有效果,但控制台会发出警告),所以也不能使用 v-model 绑定
若需修改,应先将数据拷贝到data中,用一个新属性接收
1 | data() { |
mixins混入
多个相似组件中通常会有重复的配置项,可以将这些配置项提取成一个混入对象,给这些组件引入复用(混入)
将相同的配置项提取至 mixin.js
1 | export const mixin1 = { |
组件通过 mixins 配置项引入 mixin.js 中的配置
Vue会将这些外部的配置混入组件的同名配置中(混合合并),配置中重复的属性保留组件中的
mixins: []
局部混入
1 | // 引入混入对象文件 |
Vue.mixin()
全局混入
1 | import {mixin1, mixin2} from '../mixin.js'; |
插件
Vue中插件本质是一个对象,对象中必须有一个 install()
方法
在 plugins.js 或其它名字的独立js文件中定义插件
1 | export default { |
在 main.js 中导入并使用 Vue.use()
应用插件
1 | // 导入插件 |
插件对象的 install()
方法默认第一个参数接收 Vue构造函数 作为参数,在插件中可以进行各种全局配置以及在原型上添加属性和方法
1 | import {mixin1, mixin2} from './mixin.js'; |
自定义参数:Vue.use()
使用插件时也可以传入其它参数,会被 install()
方法接收,第一个参数仍然是 Vue构造函数
1 | // 传入三个自定义参数,三个数字 |
scoped样式
多个组件有多个 style 标签,所有组件样式都会汇总到一个 CSS 文件中
当组件中有相同类名时,样式就会冲突,可以给组件的 style 标签加上 scoped 属性解决冲突
作用:让样式仅在当前组件中生效,防止样式冲突
原理:当style标签加上 scoped 属性后,Vue会给该组件所有标签元素都加上一个随机的 data-v 属性,css选择器也会自动选择 data-v
1 | <div data-v-22321ebb="" class="demo"> |
1 | .demo[data-v-22321ebb] { |
注意:一般在 App.vue 的 style 标签中写全局共用样式,所以 App 不适合加 scoped
更多: style 标签的 lang 属性可以指定 CSS 预处理语言,常用 less,使用 less 前需要安装 less-loader 用于解析 less
1 | <style lang="less" scoped> |