基础知识
el与data的两种写法
el的写法
第一种写法:直接声明el
1
2
3const vm=new Vue({
el:'#root'
})第二种写法:使用$mount进行声明
1
2
3
4const vm=new Vue({
})
vm.$mount('#root');
data的写法
第一种写法:对象式
1
2
3
4const vm=new Vue({
el:'#root',
data:{}
})第二种写法:函数式
1
2
3
4
5
6
7
8const vm=new Vue({
el:'#root',
data(){
return {
}
}
})PS:data不能写成箭头函数式 【data:()=>{}】,因为箭头函数this没有内置对象,向外找的时候,会造成引用对象变为window
MVVM
- M:模型(Model)【对应data中的数据】
- V:视图(View)【模板】
- VM:视图模型(ViewModel)【Vue实时对象】
数据代理
Object.defineProperty
1 | Object.defineProperty(要给哪个对象添加,添加的字段名,添加的配置项) |
PS:添加的数据不可以被枚举(遍历)
理解数据代理
**数据代理:**通过一个对象代理对另一个对象中属性的操作(读/写)
**基本原理:**通过Object.defineProperty()把data对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个getter/setter。在getter/setter内部去操作(读/写)data中对应的属性
事件处理
事件的基本使用
- 使用v-on:xxx或者@xxx绑定事件,其中xxx是事件名;【绑定的事件可以写一些简单的表达式】
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数,不需要箭头函数!否则this就不是vm了
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象;
- @click=”demo”和@click=”demo($event)效果一致,但后者可以传参”【@click=”demo(number)会替换掉event对象,但是可以写成demo(number,$event),就不会丢失event对象】
事件修饰符
==常用的按键别名==
- **prevent:**阻止默认事件(常用)
- **stop:**阻止事件冒泡(常用)
- **once:**事件只触发一次(常用)
- **capture:**使用事件的捕获模式
- **self:**只有
event.target
是当前操作的元素时才能触发事件 - **passive:**事件的默认行为立即执行,无需等待事件回调执行完毕
键盘事件
- ==常用的键盘别名==
- 回车===>enter
- 删除===>delete【捕获删除和退格键】
- 退出===>esc
- 空格===>space
- 换行===>tab【特殊,必须配合keydown去使用】
- 上===>up
- 下===>down
- 左===>left
- 右===>right
- Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
- 系统修饰符(用法特殊):==ctrl、alt、shift、meta==
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才能触发
- 配合keydown使用:正常触发事件
- 也可以使用keyCode去指定具体的按键(不推荐)
- Vue.config.keyCodes.自定义键名=键码,可以去定制按键别名
计算属性与监视属性
计算属性(computed)
基本用法
- **定义:**由已知属性计算出来的全新属性叫做计算属性
- **原理:**底层借助了Object.defineproperty方法提供的getter和setter
- get什么时候调用?
- 初次读取xxx时
- 所依赖的数据发生变化时
- set什么时候调用?
- 当xxx会被修改成另一个值的时候【比如this.xxx=”李四”】,如果确定不会被修改,set可以不写
- **优势:**与methods实现相比,内部有缓存机智(复用),效率更高,测试方便
- 备注:
- 计算属性最终会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生变化
1 | computed:{ |
计算属性的简写
如果不会用到set方法,可以使用简写
1 | coputed:{ |
监视属性(watch)
基本用法
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视!!
- 监视的两种写法
- new Vue时传入watch配置
- 通过vm.$watch监视
1 | watch: { |
深度监视
- Vue中的watch默认不监测内部值得改变(一层)
- 使用
deep:true
可以监测对象内部值得改变(多层)
PS:
- Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
- 使用watch时根据数据的具体结构,决定是否采用深度监视
1 | watch: { |
监视属性的简写
当配置项里面只有handler的时候,可以使用简写
1 | watch: { |
coputed和watch之间的区别
computed能完成的功能,watch都可以完成
watch能完成的功能,computed不一定能完成。例如:watch可以进行异步操作
两个重要的小原则:
- 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
- 所有不被Vue所管理的函数(定时器的回调函数,ajax的回调函数等),最好都写成箭头函数。这样this的指向才是vm或组件实例对象
常见渲染
条件渲染
1.v-if与v-show使用场景:
如果需要高频率刷新页面,则优先使用v-show。
2.v-if、v-else-if与v-else可用作条件渲染
1 | <body> |
注意点:
v-if与v-else-if中间不能有间断,否则会报错(v-if必须作为开头)
1
2
3
4//此种情况会报错
<div v-if="n < 2">Angular</div>
<div>@@@</div>
<div v-else-if="n == 2">React</div>template标签只能与v-if配合使用,不能与v-show配合使用
使用v-if的时候,元素不一定能获取到,但是使用v-show的时候一定能获取到
列表渲染
基本列表
v-for指令
- 用于展示列表数据
- 语法:v-for=”(item,index) in xxx” :key=”yyy”
- 可遍历:数组,对象,字符串,指定次数
基础案例
人员列表案例(遍历数组)
效果图如下:
直接上代码:
1 | <body> |
v-for可以用于对数组进行遍历
车列表案例(遍历对象)
效果图如下:
直接上代码:
1 | <body> |
注意value和key遍历的位置顺序,其中value放在前,key放在后面
遍历字符串案例(用的少)
效果图如下:
直接上代码:
1 | <body> |
遍历指定次数案例(用的少)
效果图如下:
直接上代码:
1 | <body> |
属性讲解
1.key
key属性属于vue中较特殊的属性,相当于让每一个li标签都有了一个唯一的标识,相当于身份证。每当进行遍历操作生成同样结构的数据时,需要给这些同样结构的数据绑定特殊的标识
2.item
遍历出的每组数据
3.index
索引值
key的原理
虚拟Dom对比算法
以index作为key
代码如下:
1 | <body> |
以id作为key
代码如下:
1 | <body> |
总结
- 如果进行过破坏顺序操作,使用index作为key会出现错乱
- 对比算法对比的是虚拟Dom,不是真实Dom
- 以index作为key,效率较低(因为对比不成功的情况下,会需要生成多个新的节点)
- key中没有给定值的时候,默认使用索引作为key(所以不写key的时候,也出现了第一种案例【以index作为key】中出现错乱的情形)
面试题
react vue中的key有什么作用(key的内部原理)
虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异对比,对比规则如下:
旧虚拟DOM中找到了与新虚拟DOM中相同的key:
(1)若虚拟DOM中内容没变,直接使用之前的真实DOM!
(2)若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到页面。
用index作为key可能会引发的问题:
若对数据进行:逆序添加,逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新==》界面效果没问题,但效率低
如果结构中还包含输入类的DOM:
会残生错误DOM更新===》界面有问题
开发中如何选择key?
- 最好使用每条数据的唯一表示作为key,例如id,手机号,学号等唯一值。
- 如果不存在对数据进行逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
指令
内置指令
自定义指令
函数式
举例
如果想自定义一个指令,点击按钮,使得值放大10倍
1 |
|
注意:
自定义指令函数什么时候会被调用?
- 指令与元素成功绑定时(一上来时)
- 指令所在的模板被重新解析时(例如data中的name被进行修改后,该函数又被调用了)
对象式
举例
定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
1 |
|
注意:
需要指令元素被插入页面(即获得父元素时),才推荐使用对象式
Vue监视数据的原理
对象
Vue监视数据的改变靠得就是其中的getter,setter
1 |
|
这时候点击按钮是会渲染的,因为对象中匹配了getter,setter方法
PS:问一个问题【vm._ data.student===vm.student,此时vm._ data=vm不?】
答案是不等于,_data做了数据加工
数组
Vue没有直接为数组的索引匹配setter和getter方法,这时就会出现一个问题
1 |
|
这时候点击按钮,就无法渲染修改马冬梅的信息(Vue没有直接为数组的索引匹配setter和getter方法,需要使用数组的方法,例如【push,pop,shife,unshift,splice,soft.reverse】等方法去修改)
PS:
- Vue将被侦听的数组的变更==方法==进行了包裹,所以它们特将触发视图更新
- 通过==vm.$set(vm.personsinfo,0,{ id: “001”, name: “杰克吗”, age: “30”, sex: “2” })==也可以修改,但是使用的不多
总结
Vue监视数据的原理:
Vue会监视data中==所有层次==的数据
如何监测对象中的数据?
通过setter实现监听,且要在new Vue时传入要监测的数据
- 对象中==后追加==的属性,Vue默认==不做==响应式处理
- 如需给后续的属性做响应式,请使用如下API:
- Vue.set(target,propertyName/index,value)
- vm.$set(target,propertyName/index,value)
如何监测数组中的数据?
通过包括数组更新元素的方法实现,本质就是做了两件事
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
在Vue修改数组的某个元素一定要用如下方法
- 使用这些API:**push()、pop()、shift()、unshift()、splice()、sort()、reverse()**、
- Vue.set()或vm.$set() 例如:==vm.$set(vm.personsinfo,0,{ id: “001”, name: “杰克吗”, age: “30”, sex: “2” })==
**特别注意:**Vue.set()和vm.$set()不能给vm或vm的根数据对象添加属性!!!例如vm.$set(vm,”sex”,”男”)会报错
生命周期
案例:将“欢迎学习Vue”的实现透明度变为0后,又变为1,形成动画效果
1 |
|
注意:从这个案例中我们可以看到“生命周期”的作用:即在某一个时刻会自动执行的代码
定义:
- 又名:生命周期函数、生命周期函数、生命周期钩子
- 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的this指向是vm或者组件实例对象
包括:
- beforeCreate ==将要创建==
- created ==创建完毕==
- beforeMount:已经解析完了模板,但是还没来及往页面上放 ==将要挂载==
- mounted:Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted 【重要】 ==挂载完毕==
- **beforeUpdate ** ==将要更新==
- **updated ** ==更新完毕==
- beforeDestroy:此时vm中的data、methods、指令等等都处于可用状态。一般在这时候:关闭定时器、取消订阅消息、解绑自定义事件等==收尾操作== 【重要】 ==将要销毁==
- **destroyed ** ==销毁完毕==
总结:
- 常用的生命周期钩子:
- mounted:发送ajax请求,启动定时器、绑定自定义事件、订阅消息等【初始化操作】
- beforeDestroy:清除定时器、解除自定义事件、取消订阅消息等【收尾工作】
- 关于销毁Vue实例
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会在beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了
Vue组件化编程
传统方式编写应用
缺点:
- 依赖关系混乱不好维护
- 代码复用率不高
组件化方式编程
定义:实现应用中==局部==功能==代码==(css/html/js)和==资源==(mp3/mp4/ttf/zip/image)的==集合==(html/css/js/image……)
配置组件的时候需要用到:const XXX=Vue.extend({配置项});
注意:
- 在创建组件的时候,data必须使用==函数式==,否则会报错(究其根本,是因为组件复用时,对象式是引用对象,地址指向同一块区域,容易一个数值变,全部变)
- 由Vue管理的函数,一定不要写箭头函数,一旦写箭头函数,this就不再是Vue实例了
组件化编程的步骤:
- 创建组件
1 | //创建学校组件 |
- 注册组件(示例:局部注册)
1 | const vm = new Vue({ |
- 编写组件标签(组件标签名字除首字母外不能为大写,否则浏览器会报错【html】)
1 | <div id="root"> |
总结:
如何定义一个组件?
使用Vue.enxtend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但是区别如下:
el不要写,为什么? —–因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务那个容器
data必须写成函数式,为什么? —-避免组件被服用时,数据存在引用关系
**备注:**使用template可以配置组件结构
如何注册组件
- 局部注册:靠new Vue时候传入components选项
- 全局注册:靠Vue.component(‘组件名’,组件)
编写组件标签
几个注意点:
关于组件名:
- 一个单词组成:
- 第一种写法(首字母小写):school
- 第二种写法(首字母大写):School
- 多个单词组成
- 第一种写法(keyab-case命名):my-school
- 第二种写法(CamelCase命名):MySchool(需要脚手架支持)
- 备注:
- 组件名尽可能回避HTML中已有的元素名称,例如:h1,h2都不行
- 可以使用name配置项执行组件在开发者工具中呈现的名字
- 一个单词组成:
关于组件标签:
第一种写法:
第二种写法:
备注:不使用脚手架时,
会导致后续组件不能渲染
一个简写方式
const school=Vue.extend(options)可简写为: const school=options
非单文件组件(了解)
定义:一个文件中包含有n个组件
局部注册:
1 |
|
全局注册:
1 |
|
单文件组件(重要)
定义:一个文件中只包含有1个组件
MySchool.vue(组件)
1 | <template> |
App.vue(组件中“一人(vm)之上,万人(所有的组件)之下”的存在)
1 | <template> |
main.js
1 | import App from './App.vue' |
index.html(入口文件)
1 |
|
VueComponent(重要)
关于VueComponent:
- school组件本质是一个名为==VueComponent的构造函数==,且不是程序员定义的,时Vue.extend生成的。
- 我们只需要写
<school/>
或者<school></school>
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的是:==new VueComponent(options)== - **特别注意:每次调用Vue.extend,返回的都是一个全新的**VueComponent!!!
- 关于this的指向
- 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是==【VueComponet实例对象】==
- new Vue(options)配置中:data函数、methodsmethods中的函数、watch中的函数、computed中的函数,他们的this均是==【Vue实例对象】==
- VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm
一个重要的内置关系
1 | <script type="text/javascript"> |
PS:需要复习原型知识(对象上有_ proto_ ,属性上有prototype,指向的都是缔造者的原型对象)
一个重要的内置关系:VueComponent.prototype._ proto_ === Vue.prototype(VueComponet的原型对象的原型对象等于Vue的原型对象)
为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法
使用Vue脚手架
Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
初始化脚手架
具体步骤
第一步(仅第一次执行):全局安装@vue/cli
1
npm install -g @/vue/cli
第二步:**切换到你要创建项目的目录**,然后使用命令创建项目
1
vue create XXXXX
这是我开始定义好的环境【Vue3 node-sass,babel,router,vuex,eslint】
启动项目
1
npm run serve
备注:
1 | 1. 如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry |
render函数
Vue-cli引入的是残缺版Vue,缺少模板解析器,故main.js中使用的了render函数
1 | let vm = new Vue({ |
PS:为啥要使用残缺版Vue呢?完整版Vue比残缺版也就大个几百K?
因为例如以装修—铺瓷砖举个例子:
第一种:
买瓷砖(Vue核心)+买工人(模板解析器)====》铺好的瓷砖+工人(引入完整Vue)
第二种:
买瓷砖(Vue核心)+雇工人(模板解析器)====》铺好的瓷砖(使用render函数)
综上来看,使用render函数更容易避免资源浪费
总结
- 关于不同版本的Vue:
- vue.js是完整版的Vue。包含:核心功能+模板解析器
- vue.runtime.xxx.js是运行版的Vue。只包含:核心功能,没有模板解析器
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElment函数去指定具体内容
ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式
- 打标识:
或
- 获取:this.$refs.xxx
- 打标识:
props配置项
数组型
1 | props:["name","age","sex"] |
**优点:**简单方便
**优点:**不能添加默认值
数组型
1 | //接收的同时对数据类型进行限制 |
PS:
- 如果要想修改外部传进来的属性值,要在data中先定义一个属性来接收
- key,ref等官方已经声明的属性,不能作为传递的属性值去传递与接收
总结
功能:让组件接收外部传过来的数据
传递数据
<Demo name="xxx"/>
接收数据:
- 只接收
1
props:['name']
- 限制类型
1
2
3props:{
name:string
}- 限制类型、限制必要性、指定默认值
1
2
3
4
5
6
7
8
9
10props:{
name:{
type:String, //要传的name类型
required:true //是否必传
},
age:{
type:Number, //要传的age类型
default:99 //不传的话默认值是XX
},
}
备注:props是只读的,Vue底层会监测你对props的修改。如果进行了修改,就会发出警告,若业务确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
mixin混入
功能:可以把多个组件共用的配置项提取成一个混入对象
使用方式:
- 第一步定义混合,例如:{data(){…},methods:{…}}
1 | export const minxin={ |
- 使用混入,例如:
- 全局混入:Vue.mixin(xxx)
- 局部混入:minxins:[‘xxx’]
插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是参见使用者传递的数据
定义插件:
1 | 对象.install=function(Vue,options){ |
使用插件:==Vue.use(xxxxxxxx)==
浏览器本地存储
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
浏览器端通过Window.sessionStorage和Window.localStorage属性来实现
相关API:
xxxxxStorage.setItem
该方法接受一个键和值作为参数,会把键值添加到存储中
xxxxxStorage.getItem
该方法接受一个键名作为参数,返回键名对应的值
xxxxxStorage.removeItem
该方法接受一个键名作为参数,并把该键名与值从存储中删除
xxxxxStorage.clear
该方法会清除存储中所有数据
备注:
- SessionStorage存储的内容会随着浏览器窗口关闭而关闭
- LocalStorage存储的内容,需要手动清除才会消失
xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是nullJSON.parse(null)
的结果依然是null
localStorage
1 |
|
sessionStorage
1 |
|
自定义事件
如果存在子组件School与子组件Student,当点击按钮后,如何将对应子组件的某个属性显示到父组件中?

链接:https://pan.baidu.com/s/15Mbtq94coWd-QAccgDKUeg
提取码:6e19
PS:子给父传递数据常用的有两种方法,一个是通过$emit, 另一个则是使用props配置项
绑定
$emit
第一种方法
绑定自定义事件时,只要$emit绑定的父组件方法无误,即可将参数传递过去
第二种方法
1 | <template> |
但是要注意【vue3中去除了$on,$off 和 $once 实例方法】,因此在vue3中使用会报以下错误
==555,找这错误找了好久……T^T==
props配置项
使用props配置项时,需要父组件给子组件预留一个位置【属性/方法】,然后在子组件中props接收后,通过这个方法去传递
解绑
在哪个组件中绑定的自定义事件,就在那个组件中进行解绑
解绑一个自定义事件
1
2
3unbind(){
this.$off('chrishly')
}解绑多个自定义事件
1
2
3unbind(){
this.$off(['chrishly','chrishly2'])
}解绑所有的自定义事件
1
2
3unbind(){
this.$off()
}
销毁
- 销毁vc组件(如student组件)的的实例,销毁后所有student组件实例的自定义事件所有自定义事件都不奏效
- 销毁vm,则子组件所有自定义事件都被销毁(但不包括自定义事件)
自定义事件的注意点
要注意this的指向【特别是回调函数的】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//请问此时的this指向是不是问题
mounted () {
console.log(this)
this.$refs.student.$on('chrishly', function(name){
this.schoolname=name;
})
}
//答案是肯定的,this的指向是student组件的实例对象,要怎样改才是正确的呢?
//答案如下
mounted () {
console.log(this)
this.$refs.student.$on('chrishly', name=>{
this.schoolname=name;
})
}
//或者如下
mounted () {
console.log(this)
this.$refs.student.$on('chrishly', this.demo(name))
}
methods:{
demo(name){
this.schoolname=name;
}
}vc组件实例对象绑定自定义方法时,回调函数中的this指向的时vc组件实例对象,而不是vm,为啥改为箭头函数后就好了呢?因为箭头函数没有自己的this,于是它往上翻去找this,找到的是mounted中的,vue给了一个承诺,就是你标准的在vue中定义生命周期函数,生命周期函数中的this指向的就是vm。同理第二种改法也是因为methods中普通函数的指向也是vm
组件也可以绑定原生DOM事件,需要使用
native
修饰符
总结
- 一种组件间通信的方式,使用于**子组件===》父组件**
- 使用场景:==A==是==父组件==,==B==是==子组件==,B想给A传数据,那么就需要在A中给B绑定自定义事件【**事件的回调在A中**】
- 绑定与解绑详见上面两小节
- 使用
this.$refs.xxx.$on(自定义事件, 回调)
绑定自定义事件是回调**要么配置在methods中,要么使用箭头函数**,否则this的指向会有问题
全局事件总线(GlobalEventBus)
**全局事件总线:**任意组件间通信
一种组件间通信的方式,适用于**任意组件间通信。**
安装全局事件总线【main.js】
1
2
3
4
5
6
7new Vue({
...
beforeCreate() {
Vue.prototype.$bus=this;
}
...
}).$mount('#app')使用事件总线
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的**回调在A组件本身**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<template>
<div class="school">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
export default {
name: 'Student',
props: ['getSchoolName'],
data () {
return {
name: '嘀嘀嘀大学',
address: '北京'
}
},
methods: {
},
mounted(){
this.$bus.$on('hello',(data)=>{
console.log("我是School组件,我接收到了数据",data)
})
},
beforeDestroy() {
this.$bus.$off('hello')
}
}
</script>
<style scoped>
.school {
background-color: skyblue;
}
</style>提供数据【
this.$bus.$emit("xxx",数据)
】1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32<template>
<div class="student">
<h2>学生名称:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
name: 'Student',
data () {
return {
name: '张三',
sex: '男'
}
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>
<style scoped>
.student {
background-color: pink;
}
</style>
消息订阅与发布
类似于**报纸订阅**
报纸订阅:
- 订阅报纸:地址
- 邮递员送报纸:报纸
消息订阅:
- 订阅消息:消息名(手机号,邮箱号)
- 发布消息:消息的内容
借助的第三方库:PubSub等
使用步骤:
安装第三方库,以PubSub为例
1
npm i pubsub-js
引入第三方库
1
import pubsub from 'pubsub-js'
使用第三方库
订阅消息【注意回调函数中的this指向不是vm】
1
pubsub.subscribe(消息名,回调函数【消息名,数据】)
发布消息
1
this.pubId=pubsub.publish(消息名,数据)
取消订阅
1
pubsub.unsubscribe(this.pubId)
最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去**取消订阅**
Vue动画
作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名
图示:
写法:
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- 元素进入的样式:
使用
<transition>
包裹要过度的元素,并配置name属性1
2
3
4
5<transition name="hello">
<h1 v-show="isShow">
你好啊!
</h1>
</transition>备注:若有多个元素需要过去。则需要使用
<transition-group>
,且每个元素都要指定key
值
插槽
- 作用:让父组件可以向子组件指定位置插入html结构,也是一种通信方式,适用于:**父组件===》子组件**
- 分类:**默认插槽、具名插槽、作用域插槽**
默认插槽
1 | 父组件中: |
具名插槽
1 | 父组件中: |
作用域插槽
理解:**数据在组件的自身,但根据数据生成的结果需要组件的使用者来决定**。(game数据在Category组件中,但使用数据所遍历出来的结构由App组件来决定)
具体编码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34父组件中:
<Category>
<template scope="scopeData">
<!--生成的是ul列表-->
<ul>
<li v-for="(item,index) in scopeData.games" :key="index">{{item}}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="scopeData">
<!--生成的是h4标题-->
<h4 v-for="(item,index) in scopeData.games" :key="index">
{{item}}
</h4>
</template>
</Category>
子组件中:
<template>
<div>
<!--定义插槽-->
<slot :games="games">插槽默认内容</slot>
</div>
</template>
<script>
export default {
name: "Category",
data(){
return {
games: ['GTA1', 'GTA2', 'GTA3', 'GTA4']
}
}
}
</script>
Vuex
理解Vuex
Vuex是什么?
- 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间的通信的方式,且适用于任意组件间通信【教室里一个教师给很多学生讲课】
- Github地址:https://github.com/vuejs/vuex
什么时候使用Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
Vuex原理图
VueComponets:顾客
Actions:服务员
Mutations:厨师
State:菜品
VueComponets和服务员说他想要一份蛋炒饭,服务员和厨师说有位顾客需要一份蛋炒饭,厨师加工菜品后,将菜品给了顾客VueComponets【在已知需要处理的数据时,其实是可以越过Actions,直接与Mutations交流的,Actions的作用主要是预防有未知数据需要向后端发送请求时,承担起向后端发送请求,获取数据的一个桥梁作用】
图中缺少了一个管理者
store
,store
统筹管理State、Action、Mutations
搭建Vuex环境
搭建步骤:
npm i vuex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2. 创建文件:`src/store/index.js`
```js
//该文件用于创建vuex中最核心的store
import Vue from "vue";
//引入vuex
import Vuex from 'vuex';
//引用vuex插件
Vue.use(Vuex)
//准备actions,用于相应组件中的动作
const actions={}
//准备mutations,用于操作数据(state)
const mutations={}
//准备state,用于存储数据
const state={}
//创建store并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})在
main.js
中创建vm时传入store
配置项1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import Vue from 'vue'
import App from './App.vue'
import store from '../store'
Vue.config.productionTip = false
/*const Demo=Vue.extend({});
const d=new Demo();
Vue.prototype.x=d;*/
new Vue({
render: h => h(App),
store
}).$mount('#app')
求和案例
count.vue
1 | <template> |
index.js
1 | //该文件用于创建vuex中最核心的store |
getters配置项
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工
在
index.js
中追加getters
配置【在index.js中添加,类似于计算属性】1
2
3
4
5const getters ={
xxx(state){
return ...
}
}组件中读取数据:
$store.getters.xxx
四个map方法的使用
mapState方法:用于映射
state
中的属性为计算属性1
2
3
4
5
6
7
8import {mapState} from 'vuex'
...
computed:{
//借助mapState生成计算属性(对象写法)
...mapState({xxx:'xxx',xxxx:'xxxx'})
//借助mapState生成计算属性(数组写法)【需要映射名与index.js中state保持一致】
...mapState(['xxx','xxxx'])
}mapGetters方法:用于映射
getters
中的属性为计算属性1
2
3
4
5
6
7
8import {mapGetters} from 'vuex'
...
computed:{
//借助mapGetters生成计算属性(对象写法)
...mapGetters({xxx:'xxx',xxxx:'xxxx'})
//借助mapGetters生成计算属性(数组写法)【需要映射名与index.js中getters中保持一致】
...mapGetters(['xxx','xxxx'])
}mapActions方法:用于帮助生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数1
2
3
4
5
6
7
8
9
10
11
12
13
14<template>
<button @click="increment(params)">+</button>
</template>
<script>
import {mapActions} from 'vuex'
...
methods:{
//借助mapActions生成(对象写法)
...mapActions({increment:'xxx',xxxx:'xxxx'})
//借助mapActions生成(数组写法)【需要映射名与index.js中actions中保持一致】
...mapActions(['xxx','xxxx'])
}
</script>mapMutations方法:用于帮助生成与
mutations
对话的方法,即:包含$store.commit(xxx)
的函数1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<button @click="increment(params)">+</button>
</template>
<script>
import {mapMutations} from 'vuex'
...
methods:{
//借助mapMutations生成(对象写法)
...mapMutations({increment:'xxx',xxxx:'xxxx'})
//借助mapMutations生成(数组写法)【需要映射名与index.js中mutations中保持一致】
...mapMutations(['xxx','xxxx'])
}
</script>
模块化+命名空间
目的:让代码更好维护,让多种数据分类更加明确
修改
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const countAbout = {
namespaced:true, //开启命名空间
state:{
x:1
},
mutations:{...},
actions:{...},
getters:{
xxx(state){
return ...
}
}
}
const personAbout = {
namespaced:true, //开启命名空间
state:{...},
mutations:{...},
actions:{...},
}
const store = new Vuex.Store({
modules:{
contentAbout,
personAbout
}
})开启命名空间后,组件中读取state数据:
1
2
3
4//方法一:自己直接读取
this.$store.state.personAbout.list
//方法二:借助mapState读取
...mapState('countAbout',['xxx','xxxx'])开启命名空间后,组件中读取getters数据:
1
2
3
4//方法一:自己直接读取
this.$store.getters('personAbout/firstPersonName')
//方法二:借助mapGetters读取
...mapGetters('countAbout',['xxx'])开启命名空间后,组件中调用dispatch
1
2
3
4//方法一:自己直接读取
this.$store.dispatch('personAbout/addPerson',person)
//方法二:借助mapActions读取
...mapActions('countAbout'{increment:'xxx',xxxx:'xxxx'})开启命名空间后,组件中调用commit
1
2
3
4//方法一:自己直接读取
this.$store.commit('personAbout/ADD_PERSON',person)
//方法二:借助mapActions读取
...mapActions('countAbout'{increment:'xxx',xxxx:'xxxx'})
路由
理解路由
- 理解:一个路由(route)就是一组映射关系(key,value),多个路由需要路由器(route)进行管理。
- 前端路由:key是路径,value是组件
- 几个注意点:
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息 - 整个应用只有一个router,可以通过组件的
$router
属性获取到
- 路由组件通常存放在
路由的基本使用
安装vue-router,命令:
npm i vue-router
应用插件:
Vue.use(VueRouter)
编写router配置项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//引入VueRouter
import VueRouter from 'vue-router'
//引入组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
router:[
{
path:'/about',
component:About
}
{
path:'/home',
component:Home
}
]
})
export default router实现切换(active-class可配置高亮样式)
1
<router-link active-class="active" to="/about">About</router-link>
指定展示位置
1
<router-view></router-view>
嵌套(多级)路由
配置路由规则,使用children配置项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//引入VueRouter
import VueRouter from 'vue-router'
//引入组件
import About from '../components/About'
import Home from '../components/Home'
import News from '../components/News'
import Messages from '../components/Messages'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
router:[
{
path:'/about',
component:About
}
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
path:'messages',
component:Messages
}
]
}
]
})
export default router跳转(要写完整链接)
1
<router-link active-class="active" to="/home/news">About</router-link>
路由传参
query参数
传递参数
字符串写法
1
2
3
4
5
6<ul>
<li v-for="item in messageList">
<router-link :to="`/home/message/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>
</li>
</ul>对象写法
1
2
3
4
5
6
7
8
9
10
11
12
13<ul>
<li v-for="item in messageList">
<router-link :to="{
path:'/home/message/detail',
query:{
id:item.id,
title:item.title
}
}">
{{item.title}}
</router-link>
</li>
</ul>
接收参数
1
2$route.query.id
$route.query.title
params参数
配置路由,生命接收params参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
path:'messages',
component:Messages,
children:[{
name:"getDetail"
path:'detail/:id/:title',
component:Details
}]
}
]
}传递参数
字符串写法
1
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
对象写法
1
2
3
4
5
6
7<router-link :to="{
name:"getDetail"
params:{
id:666,
title:"你好"
}
}">跳转</router-link>
**特别注意:**路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置
接收参数
1
2$route.params.id
$route.params.title
命名路由
可以简化路由的跳转
如何使用
给路由命名:
1
2
3
4
5
6
7
8
9
10
11
12
{
path:'/home',
component:Home,
children:[
{
name:'haha'
path:'news',
component:News
}
]
}简化跳转
1
2
3
4<!--简化前,需要写完整路径-->
<router-link to="/home/news">跳转</router-link>
<!--简化前后,直接通过名字跳转-->
<router-link :to="{name:"haha"}">跳转</router-link>
路由的props配置项
谁接收数据,就在谁里面绑定props配置
作用:让路由组件更方便的收到参数
举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
name:"getDetail"
path:'detail/:id/:title',
component:Details,
//第一种写法,props为对象,该对象中所有的key-value的组合最后都会通过props传给Detail组件
//props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
//props:true
//第三种写法,props时函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}
route-link的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别是
push
和replace
,push
是追加历史记录,replace
是替换当前记录,路由跳转时候默认是push
- 如何开启
replace
模式:<router-link replace><router-link>
编程式路由导航
作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活具体编码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18this.$router.push({
name:'getDetail',
params:{
id:xxx,
title
}
})
this.$router.replace({
name:'getDetail',
params:{
id:xxx,
title:xxx
}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go(number) //可前进也可后退
缓存路由组件
作用:让不展示的路由由组件保持挂载,不被销毁
具体编码:
1
2
3
4<!--组件名-->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
两个新的生命周期钩子【路由独有】
actived
和deactived
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态
- 具体名字:
actived
路由组件被激活时触发deactived
路由组件失活时触发
路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫、独享守卫、组件内守卫
全局守卫
1 | //全局前置守卫【初始化执行,每次路由切换前执行】 |
独享守卫
1 | { |
组件内守卫
1 | //进入守卫,通过路由规则,进入组件时被调用 |
history模式与hash模式
- 对于一个url来说,什么是hash值? ===》#及其后面的内容就是hash值
- hash值不会包含在HTTP请求中,即:hash值不会带给服务器
- hash模式
- 地址中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app校验严重,则地址会被标记为不合法
- 兼容性较好
- history模式
- 地址干净美观
- 兼容行和hash模式相比较差
- 应用部署上线时需要后端人员支持,解决刷新页面服务器404的问题
Vue3
常用Composition API
setup
- 理解:Vue3.0中一个新的配置项,值为一个函数
- set是所有**Composition API(组合API)**”表演的舞台“。
- 组件中所用到的:数据、方法等等,均要配置在setup中
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点关注)
- 若返回一个渲染函数,则可以自定义渲染内容(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methods、computed…)中**可以访问到**setup中的属性、方法
- 但在setup中**不能访问到**Vue2.x配置(data、methods、computed…)
- 如果有重名,setup优先
- setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性
- 尽量不要与Vue2.x配置混用
ref函数
- 作用:定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的**引用对象(reference对象,简称ref对象)**
- JS中操作数据:
xxx.value
- 模板中读取数据:不需要
.value
,直接<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型,也可以是对象类型
- 基本数据类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的 - 对象类型的数据:内部求助了Vue3中的一个新函数—-
reactive
函数
reactive函数
- 作用:定义一个**对象类型**的响应式数据(基本类型别用它,用
ref函数
) - 语法:
const 代理对象=reactive(被代理对象)
接收一个对象(或数组),返回一个**代理器对象(proxy的实例对象,简称proxy对象)** - reactive定义的响应式数据是”深层次的“
- 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
reactive对比ref
- 从定义数据角度对比:
- ref用来定义:**基本类型数据**
- reactive用来定义:**对象(或数组)类型数据**
- 备注:ref也可以用来定义**对象(或数组)类型数据,它内部会自动通过
reactive
转为代理对象**
- 从原理角度对比
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持) - reactive通过使用**proxy来实现响应式(数据劫持),并通过Reflect操作源对象**内部的数据
- ref通过
- 从使用角度来看:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取**不需要**.value
- reactive定义的数据:操作数据与读取数据,**均不需要**
.value
- ref定义的数据:操作数据需要
setup的两个注意点
- setup执行的时机
- 在beforeCreate之前执行一次,this是undefined
- setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了属性
- context:上下文对象
- attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于
this.$attrs
- slots:收到的插槽内容,相当于
this.$slots
- emit:分发自定义事件的函数,相当于
this.$emit
- attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于
自定义hook函数
- 什么是hook?
- 本质是一个函数,把setup函数中使用的Composition API进行了封装
- 类似于vue2.x中的mixin
- 自定义hook的优势:复用代码,让setup中逻辑更清楚易懂
toRef
- 作用:创建一个ref对象,其value值指向另一个对象中的某个属性值
- 语法:
const name=toRef(person,'name')
- 应用:要将响应式对象中的某个属性单独提供给外部使用时
- 扩展:
toRefs
与toRef
功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
Vue的响应式原理
Vue2.x的响应式
实现原理:
对象类型:通过
Object.defineProperty
对属性的读取、修改进行拦截(数据劫持)数组类型:通过重写更新数据的一系列方法来实现拦截。(对数组的更新方法进行了包裹)
1
2
3
4Object.difineProperty(data,'count',{
get(){},
set(){}
})
存在问题:
- 新增属性,删除属性,界面不会更新
- 直接通过下标修改数据,界面不会自动更新
Vue3.0的响应式
实现原理:
通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射):对被代理对象的属性进行操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14new Proxy(data,{
//拦截读取属性值
get(target,prop) {
return Reflect.get(target,prop)
},
//拦截设置属性值或添加新属性
set(target,prop,value){
return Reflect.set(target,prop,value)
},
//拦截删除属性
deleteProperty(target,prop) {
return Reflect.deleteProperty(target,prop)
}
})
计算属性与监视
计算属性(computed)
与Vue2.x中computed的配置功能一致
写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import { computed } from 'vue'
setup() {
...
//计算属性-简写
let fullName = computed(()=>{
return xxx
})
//计算属性完整
let fullName =computed({
get(){
return xxx
},
set(Value){
const nameArr = value.split("-")
person.firstName=nameArr[0]
person.firstName=nameArr[1]
}
})
}
监视(watch)
- 与Vue2.x中watch配置功能一致
- 两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取,强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效
1 | import { watch } from 'vue' |
watchEffect函数
- watch的套路是:既要指明监视的属性,也要指明监视的回调
- watchEffect的的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
- watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用些返回值
1 | //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调 |
生命周期
- Vue3.0可以继续使用Vue2.x中的生命周期钩子,但是有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
- Vue3.0也提供了Compostion API形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate
===>setup
created
===>setup
beforeMount
===>onBeforeMount
mounted
===>onMounted
beforeUpdate
===>onBeforeUpdate
updated
===>onUpdated
beforeUnmount
===>onBeforeUnmount
unmounted
===>onUnmounted
官方APi
$nextTick
$nextTick
指定的回调函数会在DOM节点更新之后,再去加载
语法
1 | this.$nextTick(回调函数) |
作用
在下一次更新结束后执行其指定的回调
什么时候用?
当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行【例如input隐藏了,后面需要显示时需要聚焦】