Vue2复习&Vue3学习
一、Vue简介
1. Vue特点
- 采用组件化模式,提高代码复用率让代码更好维护
- 声明式编码,无需操作DOM,提高开发效率
- 使用虚拟DOM+Diff算法,尽量复用DOM节点
2. 起步
初学者不建议使用vue-cli安装,请用script标签引入
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
下载地址
- 开发版本:https://cn.vuejs.org/js/vue.js
- 生产版本:https://cn.vuejs.org/js/vue.min.js
3. 初识
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue学习</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>{{username}}</h2>
<h2>{{nickname}}</h2>
<h2>{{age}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el:"#root" ,//绑定页面DOM
data:{
username:"zhiyuan",
nickname:"絷缘",
age:26
},
});
</script>
</body>
</html>
Vue中一个实例对应一个页面DOM容器,一个DOM容器无法对应多个实例,一个实例也不能绑定多个DOM容器
4. 模板语法
4.1 插值语法(插值表达式)
①. 插值表达式
- 格式:{{ 表达式 }}
注意:插值表达式中必须是JS代码或JS表达式或Vue实例中声明的data属性
4.2 指令语法(属性绑定)
②. 属性绑定
-
格式:
v-bind:[attributeName]
-
例
<div id="root"> <img v-bind:src="avatar"/> </div> <script type="text/javascript"> new Vue({ el:"#root", data:{ avatar:"https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&size=100" } }); </script>
-
页面效果
-
-
简写:
:[attributeName]
-
例
<div id="root"> <img :src="avatar"/> </div> <script type="text/javascript"> new Vue({ el:"#root", data:{ avatar:"https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&size=100" } }); </script>
-
②. 双向绑定
-
格式:
v-model:value
-
例
<div id="root"> <img :src="avatar"/> <input type="text" v-model:value="avatar"/> </div> <script type="text/javascript"> new Vue({ el:"#root", data:{ avatar:"https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&size=100" } }); </script>
如下代码是错误的,因为v-model只能应用表单类元素上,我们认为只有可以被改变value的元素双向绑定才有意义
-
-
简写:
v-model
- v-model默认绑定就是value属性
5. 容器绑定两种方式
5.1 方式一:el绑定
new Vue({
el:"DOM元素标志",//使用el挂载DOM容器
data:{
key:value,
......
}
});
5.2 方式二:mount挂载
const vm = new Vue({
data:{
key:value,
......
}
});
vm.$mount('DOM元素标志');//使用实例对象挂载DOM容器
6. data属性的两种写法
6.1 方式一:对象形式
new Vue({
el:"#container",
data:{
key1:value1,
key2:value2,
key...:value...
}
});
6.2 方式二:函数形式
new Vue({
el:"#container",
data:function(){
return {
key1:value1,
key2:value2,
key...:value...
}
}
//简写
data(){
return {
key1:value1,
key2:value2,
key...:value...
}
}
});
注意:函数方式返回data数据时,不可使用箭头函数,因为箭头函数没有自己的this对象,会向上寻找父类的this,就会拿到window对象,但是我们书写返回函数需要被Vue对象所调用,故而不能使用箭头函数
应用场景:组件封装时,我们必须使用函数式写法返回data值
二、MVVM简介
1. MVVM模型组成
1.1 M:模型(Model)
- 对应data中的数据
1.2 V:视图(View)
- 对应模板渲染
1.3 VM:视图模型
- 对应Vue实例对象
2. Object.defineProperty
2.1 ES6为对象添加属性
- Object.defineProperty(originObject,attributeName,attributeValue)
let person = {
name:"zhiyuan",
gender:"male"
}
Object.defineProperty(person,'age',{
value:18
});
注意:通过defineProperty直接添加的对象属性,是无法进行枚举的,即遍历对象无法取得该值,且我们无法修改或删除这个属性
如何直观的看到?
- Object.keys(object)
- 使用keys函数可以将指定对象的key值抽取为一个数组,我们会发现新加的属性没有出现在数组中
如何解决这个问题?
Object.defineProperty(person,'age',{ value:18, enumerable:true, //将enumrable置为true,就可以对其进行枚举了 //为了方便使用,我们将writable,configurable属性也置为true,这样就可以对该属性进行修改删除了 writable:true, configurable:true });
-
为属性增加getter和setter方法
let age = 18; let user = { username:"zhiyuan", nickname:"絷缘", password:"123456", gender:"male" } Object.defineProperty(user,'age',{ get:function(){ return age; }, set:function(age){ this.age = age; } });
2.2 数据代理
概念:指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
Vue实例中的data就是实现了数据代理,data在Vue实例中实际存在的属性名称为_data
3. 事件处理
3.1 methods
const vm = new Vue({
el:"#container",
data:{
user:{
username:"zhiyuan",
nickname:"絷缘",
password:"123456",
gender:"male",
age:18
}
},
methods:{
methodName(event){
//methodContent
console.log(this===vm) //true 此处this为vm实例对象
}
}
}};
3.2 事件绑定
- 格式:
v-on:[event]
<div id="container">
<button v-on:click="showInfo($event)">按钮</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el:"#container",
data:{
user:{
username:"zhiyuan",
nickname:"絷缘",
password:"123456",
gender:"male",
age:18
}
},
methods:{
showInfo(event){
alert("按钮被点击");
}
}
});
</script>
- 简写:
@[event]
-
例:
v-on:click
==@click
<button @click="showMessage($event,传递参数)">点我提示信息</button>
new Vue({ el:"#root", data:{}, methods:{ showMessage(event,传递参数){ console.log(event.target.innerText,传递参数); } } });
-
3.3 事件修饰符
- 阻止默认行为:
.prevent
- 阻止事件冒泡:
.stop
- 事件只触发一次:
.once
- 使用事件捕获模式:
.capture
- 只有event.target是当前操作的元素时才出发事件:
.self
- 立即执行事件默认行为,无需等待事件回调执行完毕:
.passive
@wheel
:鼠标滚轮滚动事件@scroll
:滚动条滚动事件
3.4 键盘事件
-
Vue中提供的常用按键别名
enter
:回车键delete
:退格键和删除键esc
:ESC键space
:空格键tab
:Tab键up
:方向键上down
:方向键下left
:方向键左right
:方向键右
Vue未提供别名的按键,可以使用原始key值绑定,但要注意转换为短横线命名方式,如:caps-lock
注意:tab键在@keyup事件中无效,因为tab键默认会切换焦点区域,必须配合@keydown才能正常使用
-
系统修饰键
ctrl
alt
shift
win
:windows徽标键
注意:系统修饰键配合@keydown才可以正常触发事件,如果是@keyup需要配合其他按键才能正常触发
-
Vue中自定义按键别名
Vue.config.keyCodes.huiche = 13;
4. 计算属性
4.1 基本概念
所谓的计算属性就是用几个data中已有的属性进行处理计算后得到一个全新的属性
const vm = new Vue({
el:"#container",
data:{
firstName:"絷缘",
lastName:"解"
},
methods:{},
computed:{
fullName:{
get(){
return this.firstName + "-" + this.lastName;
},
set(value){
cosnt array = value.split("-");
this.firstName = array[0];
this.lastName = array[1];
}
}
}
})
4.2 getter和setter调用时机
- get调用时机
- 初次读取计算属性时
- 计算属性所依赖的其他属性发生变化时
- set调用时机
- 当计算属性被修改时
4.3 简写形式
-
简写
- 应用场景:计算属性只读不写,默认当做getter使用
const vm = new Vue({ el:"#container", data:{ firstName:"絷缘", lastName:"解" }, methods:{}, computed:{ fullName(){ return this.firstName + "-" + this.lastName; } } })
5. 监视属性
5.1 在watch属性中书写监视内容
- 监视属性不仅可以监视data中声明的属性,还可以监视计算属性
const vm = new Vue({
el:"#container",
data:{
isLogin:true,
},
methods:{},
computed:{},
watch:{
'isLogin':{
//初始化时让handler执行一次
immediate:true,
//当被监视属性改变时会调用handler方法
handler(newValue,oldValue){
console.log(isLogin==true?"已登录":"未登录");
}
}
}
});
5.2 vm实例调用$watch方法书写监视内容
vm.$watch('isLogin',{
//初始化时让handler执行一次
immediate:true,
//当被监视属性改变时会调用handler方法
handler(newValue,oldValue){
console.log(isLogin==true?"已登录":"未登录");
}
})
5.3 深度监视
- Vue的watch默认不监视对象内部值的改变,要想让watch可以监视到对象内部属性的变化,需要配置
deep:true
watch中默认监视外层对象,无法监视多级结构的内容改变,当外层对象改变才会执行handler,内部成员的改变不会触发handler,如果我们想要监视内部成员的改变,需要在watch对应监视属性中添加
deep:true
const vm = new Vue({
el:"#container",
data:{
user:{
username:"zhiyuan",
gender:"male"
}
}
watch:{
'user':{
deep:true,
handler(){
console.log("user被改变");
}
}
}
})
此时我们改变user中的属性值,handler也会被触发执行。
5.4 简写形式
watch使用简写方式的代价就是无法进行除了handler方法外的配置,比如深度监视,比如初始化执行
const vm = new Vue({
el:"#container",
data:{
isLogin:true
}
watch:{
isLogin(newValue,oldValue){
console.log('isLogin被修改',newValue,oldValue);
}
}
})
6. 计算属性与监视属性的区别
6.1 区别
- computed可以完成的功能,watch都可以完成
- watch可以完成的功能,computed不一定能够完成,例如watch可以进行异步操作
6.2 注意事项
注意:
- 被Vue所管理的函数应该写成普通函数,这样this的指向才是vue实例对象
- 所有不被Vue管理的函数(定时器回调函数,ajax回调函数,Promise的回调函数等),最好写成箭头函数,这样this的指向才是vue实例对象,否则它们将指向约定的this为window对象
7. 绑定样式
7.1 绑定class
步骤
- 先定义好对应选择器的样式
- 将选择器的名称声明到data数据中
- 将data数据中的选择器名称与class绑定
7.2 绑定style
①. 写法一
<div id="container" :style="{fontSize:fontSize+'px'}">
内容
</div>
<script type="text/javascript">
const vm = new Vue({
el:"#container",
data:{
fontSize:40
}
});
</script>
②. 写法二
<div id="container" :style="styleObj">
内容
</div>
<script type="text/javascript">
const vm = new Vue({
el:"#container",
data:{
styleObj:{
fontSize:'40px',
color:'#666'
}
}
});
</script>
③. 写法三
<div id="container" :style="[styleObj1,styleObj2]">
内容
</div>
<script type="text/javascript">
const vm = new Vue({
el:"#container",
data:{
styleObj1:{
fontSize:'40px'
},
styleObj2:{
color:'#666'
}
}
});
</script>
8. 条件渲染
8.1 v-if
<div class="container">
<h2 v-if="isLogin === false">登录注册</h2>
<h2 v-else>已登录</h2>
</div>
<script type="text/javascript">
const vm = new Vue({
el:".container",
data:{
isLogin:false
}
});
</script>
8.2 v-show
<div class="container">
<h2 v-show="isLogin === false">登录注册</h2>
<h2 v-show="isLogin">已登录</h2>
</div>
<script type="text/javascript">
const vm = new Vue({
el:".container",
data:{
isLogin:false
}
});
</script>
8.3 v-if与v-show的区别
- 区别
- v-if 适用于切换频率比较低的场景,因为它是通过DOM渲染节点来完成的显示与隐藏,当不满足条件时,页面元素会被移除,满足条件时又会被创建,所以当大量的元素需要频繁切换的时候,v-if不是很好的解决方案
- v-show 适用于切换频率较高的场景,因为它是通过元素的css样式
display:none
属性来实现元素的显示与隐藏的,当满足条件时,display:none
被移除,元素显示,不满足条件时,display:none
被添加,元素隐藏
8.4 条件相同时条件渲染书写方式
-
例
<div class="container"> <h2 v-if="isLogin === false">登录</h2> <h2 v-if="isLogin === false">注册</h2> </div> <!--v-if条件相同重复书写,进一步优化--> <div class="container"> <div v-if="isLogin === false"> <h2>登录</h2> <h2>注册</h2> </div> </div>
上面的优化虽然实现了效果也避免了条件渲染重复,但是加入了新的没有必要的页面元素,一定程度影响了布局,不是非常合适,那么有没有一种办法解决这个问题呢?
Vue为我们提供了一个名为
<template>
的标签,这个标签可以与条件渲染v-if
结合使用而且不影响页面结构,注意只能与v-if
结合使用
9. 列表渲染
9.1 基本列表
<div class="container">
<!-- 遍历数组 -->
<ul class="list">
<li class="list-item" v-for="(user,index) in userList" :key="user.uid">
{{ user.uid + "-" + user.username + "-" + user.age}}
</li>
</ul>
<!-- 遍历对象 -->
<ul class="list">
<li class="list-item" v-for="(value,key) of admin" :key="key">
{{ key + "-" + value }}
</li>
</ul>
<!-- 遍历字符串 -->
<ul class="list">
<li class="list-item" v-for="(char,index) of username" :key="index">
{{ index + "-" + char }}
</li>
</ul>
<!-- 遍历指定次数 -->
<ul class="list">
<li class="list-item" v-for="(number,index) of 5">
{{ index + "-" + number }}
</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el:'.container',
data:{
userList:[
{uid:'001',username:'zhiyuan001',age:18},
{uid:'002',username:'zhiyuan002',age:20},
{uid:'003',username:'zhiyuan003',age:23},
],
admin:{
username:"zhiyuan",
role:"admin",
nickname:"絷缘",
email:"1234567890@gmail.com"
},
username:"zhiyuan"
}
});
</script>
9.2 key的作用
之前代码中,我们使用index作为key,其实这是不正确的,虽然当前我们确实没有发现问题,但是当我们对原有数据进行打乱顺序的操作后,如果继续以index作为key,那么会导致vue对DOM元素的复用错乱,进而导致页面数据错乱的结果。
比如:当我们往数组的头部追加一项内容时,列表渲染会按索引与之前的DOM进行比较,查看是否可复用,但是我们现在的DOM元素的顺序已经发生了改变,在对其进行比较复用,就会导致元素和数据的错乱。
那么我们不写怎么样?
Vue会默认将当前数据的索引值作为当前DOM绑定的key值,所以造成的结果是一样的。
正确的key值绑定
key值一定是可以唯一标识当前元素的信息,例如数据的ID值,这样无论我们变换顺序或者做其它操作,都不会对最终列表渲染的结果产生影响。
key的作用
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新的数据生成新的虚拟DOM,随后与之前的虚拟DOM进行差异比较,比较规则如下:
- 旧的虚拟DOM中找到了与新的虚拟DOM相同的key
- 若是虚拟DOM的内容没有发生改变,直接使用旧的虚拟DOM所生成的旧的真实DOM元素
- 若是虚拟DOM的内容已经发生改变,则由新的虚拟DOM生成新的真实DOM元素
- 旧的虚拟DOM中未找到与新的虚拟DOM相同的key
- 根据新的虚拟DOM创建新的真实DOM元素
10. 列表过滤
10.1 方式一:监视属性
<div class="container">
<input type="text" v-model="keyWord" placeholder="搜索...">
<ul class="list">
<li class="list-item" v-for="(user,index) in tempList" :key="user.uid">
{{ user.uid + "-" + user.username + "-" + user.age}}
</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el:".container",
data:{
userList:[
{uid:'001',username:'胡歌',age:18,gender:'male'},
{uid:'002',username:'霍建华',age:20,gender:'male'},
{uid:'003',username:'焦恩俊',age:23,gender:'male'},
{uid:'004',username:'胡一菲',age:20,gender:'female'},
{uid:'005',username:'胡图图',age:6,gender:'male'},
{uid:'006',username:'刘诗诗',age:21,gender:'female'},
{uid:'007',username:'刘亦菲',age:22,gender:'female'},
{uid:'008',username:'刘德华',age:22,gender:'male'}
],
keyWord:'',
tempList:[]
},
watch:{
'keyWord':{
immediate:true,
handler(newValue,OldValue){
this.tempList = this.userList.filter((user)=>{
return user.username.indexOf(newVlaue) != -1;
})
}
}
}
});
</script>
10.2 方式二:计算属性
<div class="container">
<input type="text" v-model="keyWord" placeholder="搜索...">
<ul class="list">
<li class="list-item" v-for="(user,index) in tempList" :key="user.uid">
{{ user.uid + "-" + user.username + "-" + user.age}}
</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el:".container",
data:{
userList:[
{uid:'001',username:'胡歌',age:18,gender:'male'},
{uid:'002',username:'霍建华',age:20,gender:'male'},
{uid:'003',username:'焦恩俊',age:23,gender:'male'},
{uid:'004',username:'胡一菲',age:20,gender:'female'},
{uid:'005',username:'胡图图',age:6,gender:'male'},
{uid:'006',username:'刘诗诗',age:21,gender:'female'},
{uid:'007',username:'刘亦菲',age:22,gender:'female'},
{uid:'008',username:'刘德华',age:22,gender:'male'}
],
keyWord:''
},
computed:{
tempList:{
get(){
return this.userList.filter((user)=>{
return user.username.indexOf(this.keyWord) != -1;
});
}
}
}
});
</script>
11. 列表排序
<div class="container">
<input type="text" v-model="keyWord" placeholder="搜索...">
<input type="button" value="年龄降序" @click="sortType = 1">
<input type="button" value="年龄升序" @click="sortType = 2">
<input type="button" value="原顺序" @click="sortType = 0">
<ul class="list">
<li class="list-item" v-for="(user,index) in tempList" :key="user.uid">
{{ user.uid + "-" + user.username + "-" + user.age}}
</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el:".container",
data:{
userList:[
{uid:'001',username:'胡歌',age:18,gender:'male'},
{uid:'002',username:'霍建华',age:20,gender:'male'},
{uid:'003',username:'焦恩俊',age:23,gender:'male'},
{uid:'004',username:'胡一菲',age:20,gender:'female'},
{uid:'005',username:'胡图图',age:6,gender:'male'},
{uid:'006',username:'刘诗诗',age:21,gender:'female'},
{uid:'007',username:'刘亦菲',age:22,gender:'female'},
{uid:'008',username:'刘德华',age:22,gender:'male'}
],
keyWord:'',
//sortType为0时正常排序,为1时按年龄降序,为2时按年龄升序
sortType:0
},
computed:{
tempList:{
get(){
const arr = this.userList.filter((user)=>{
return user.username.indexOf(this.keyWord)!=-1;
});
if(this.sortType){
arr.sort((u1,u2)=>{
return this.sortType===1 ? u2.age-u1.age :u1.age-u2.age;
});
}
return arr;
}
}
}
});
</script>
12. Vue监测数据的原理
- Vue会监视data中所有层次的数据
- 如何监测对象中的数据?
- 通过setter实现监视,且要在new Vue时就传入要监测的数据
- 对象中后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API
Vue.set(target,propertyName/index,value)
this.$set(target,propertyName/index,value)
- 如何监测数组中的数据?
- 通过包裹数组更新元素的方法实现,本质上就是做了两件事
- 调用原生JS对应的数组方法进行更新
- 重新解析模板,更新页面
- 在Vue修改数组中的某个元素一定要用如下方法(即Vue重新包装过的方法):
push
pop
shift
unshift
splice
sort
reverse
Vue.set()
this.$set()
13. 收集表单数据
v-model的三个修饰符
lazy
:失去焦点再收集数据number
:输入字符串转为有效的数字trim
:输入首尾空格过滤
收集表单数据注意事项:
- v-model搜集的是value值,用户的输入就是value值
- v-model搜集的是value值,而且需要给标签设置value值
- 1. 如果没有设置value值,默认搜集checked的值,即true or false 2. 设置了value值 1. data中搜集此项的属性默认值为字符串,那么vue还是会搜集checked的值 2. data中搜集此项的属性默认值为数组,那么vue搜集的就是value组成的数组
14. 过滤器
过滤器
- 定义
- 对要展示的数据进行特定格式化后再显示,适用场景,后端商品价格数据,时间戳格式修改为前端展示格式
- 语法
- 注册过滤器:
Vue.filter(name,callback)
或new Vue({filters:{...}})
- 使用过滤器:
{{ 参数 | 过滤器名 }}
或v-bind:属性名=" 参数 | 过滤器名"
- 备注
- 过滤器也可以接受额外参数,多个过滤器可以串联使用,后一个过滤器的参数为前一个过滤器的返回结果
- 过滤器并没有改变原本的数据,只是产生了格式化后的新数据
14.1 局部过滤器
<div id="container">
<h3>{{formatTime}}</h3>
<h3>{{getFormatTime()}}</h3>
<h3>{{time|timeFormatFilter('YYYY-MM-DD')}}</h3>
</div>
<script src="./js/dayjs.min.js"></script>
<script type="text/javascript">
const vm = new Vue({
el:"#container",
data:{
time:1641475263843
},
computed:{
formatTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
}
},
methods:{
getFormatTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
}
},
filters:{
timeFormatFilter(value,formatStr='YYYY-MM-DD HH:mm:ss'){
return dayjs(value).format(formatStr);
}
}
});
</script>
14.2 全局过滤器
<script type="text/javascript">
Vue.filter('filterName',function(value){
return value.slice(0,3);
});
</script>
过滤器只能与插值表达式或者v-bind使用,无法与v-model结合使用
15. 其他内部指令
15.1 v-text
- 向其所在的节点中渲染文本内容,不过它会替换掉节点中已有的内容
<div id="container">
<div v-text="text"></div>
</div>
<script type="text/javascript">
const vm = new Vue({
el:"#container",
data:{
text:"文本内容"
}
});
</script>
15.2 v-html
<div id="container">
<div v-html="html"></div>
</div>
<script type="text/javascript">
const vm = new Vue({
el:"#container",
data:{
html:"<h1>html内容</h1>"
}
});
</script>
15.3 v-cloak
- 当容器被Vue实例接管的时候,自动删除本属性
使用场景:在Vue实例未接管之前,隐藏没有数据的DOM元素
[v-cloak] { display: none; }
15.4 v-once
v-once指令在DOM节点初次动态渲染后,就变为静态内容了,之后无论绑定的数据发生何种变化,都不会引起当前内容的更新,用于优化性能
15.5 v-pre
v-pre指令会跳过当前节点的编译过程,如果没有使用指令语法、插值表达式的节点,添加v-pre,可以加快编译。使用过的节点则会使所使用的语法失效。
16. 自定义指令
16.1 函数式写法
<div id="container">
<span v-text="number"></span>
<span v-big="number"></span>
</div>
<script type="text/javascript">
new Vue({
el:"#container",
data:{
number:1
},
directives:{
big(element,bindObject){
element.innerText = bindObject.value * 10;
}
}
});
</script>
自定义指令函数何时被调用?
- 指令与元素成功绑定时,调用一次
- 指令所在的模板被重新解析时,调用一次
16.2 对象式写法
<div id="container">
<input type="text" v-fbind:value="number"/>
</div>
<script type="text/javascript">
new Vue({
el:"#container",
data:{
number:1
},
directives:{
fbind:{
//指令与元素成功绑定时被调用
bind(element,bindObject){
element.value = bindObject.value;
},
//指令所在元素被插入页面时
insert(element,bindObject){
element.focus();
},
//指令所在的模板被重新解析时
update(element,bindObject){
elelment.value = bindObject.value;
}
}
}
});
</script>
16.3 常见问题
- 自定义指令命名时不要使用驼峰命名法,要转换为横线命名法,且在声明该指令函数时将函数名用引号包裹,避免Vue解析出错
- 自定义指令回调函数中的this是window对象,因为是由我们直接操作真实DOM的,Vue不会再管理this
- 一个Vue实例中的自定义指令只能被当前实例索使用,若想被其他vue实例使用,必须声明为全局指令
16.4 全局自定义指令
<script type="text/javascript">
Vue.directive('name',{
bind(element,bindObject){
......
},
inserted(element,bindObject){
......
},
update(element,bindObject){
......
}
});
</script>
三、生命周期
所谓生命周期函数就是Vue在一些关键的节点时刻为我们调用的特殊名称的回调函数
1. beforeCreate
刚刚完成初始化生命周期、事件,数据代理未开始,此时无法通过实例访问到data中的数据,methods中的方法
2. created
刚刚完成初始化数据监视、数据代理,此时可以通过实例访问到data中的数据,methods中的方法
3. beforeMount
刚刚完成模板解析,在内存中生成虚拟DOM,还未经Vue编译为真实DOM放入到页面,此时对DOM元素的所有操作最终都无效
4. mounted
Vue将虚拟DOM编译为真实DOM元素放入页面后,调用mounted函数
5. beforeUpdate
刚刚完成数据的修改,但是页面还没有改变,此时会调用beforeUpdate函数
6. updated
数据被修改,页面也得到了更新,此时调用updated函数
7. beforeDestory
实例中所有的属性(data、methods等)都处于可用状态,马上要执行销毁过程,调用beforeDestory函数,一般在此时:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
注意:虽然此时所有的属性都可用,但对属性的更新不会再触发页面更新
8. destoryed
实例已经被销毁,实例的监听器,与其关联的子组件以及自定义事件全部被销毁
注意:
- 销毁后借助Vue开发工具看不到任何信息
- 销毁后所有自定义的事件失效,但是原生DOM事件仍然有效
- 一般不会在beforeDestory中操作数据,因为即使操作数据,也不会再触发更新了
四、组件化编程
- 组件:实现应用中局部功能代码和资源的集合
组件中的data必须为函数形式,避免引用数据相互影响导致页面混乱
组件名注意事项:
- 一个单词组成
- 首字母小写,即全小写
- 首字母大写
- 多个单词组成
- 短横线命名
- 所有单词首字母大写(需要Vue脚手架支持)
- 注册组件时,尽量回避HTML原有的标签名,否则可能出现意想不到的错误,可以使用name属性在创建组件时指定组件名称
在页面中使用组件的方式:
<componentName></componentName>
<componentName/>
(需要Vue脚手架支持)
1. 非单文件组件
1.1 创建组件
<script type="text/javascript">
const headerNavigation = Vue.extend({
template:`<h1>{{title}}</h1>`,
data(){
return {
title:"标题"
}
},
methods:{}
});
</script>
1.2 注册组件
局部注册
<script type="text/javascript">
const vm = new Vue({
el:"#container",
components:{
'header-navigation':headerNavigation
}
});
</script>
全局注册
<script type="text/javascript">
Vue.component('header-navigation',headerNavigation);
</script>
1.3 使用组件
<body>
<div id="container">
<header-navigation></header-navigation>
</div>
</body>
2. VueComponent构造函数
组件本质是一个名为
VueComponent
的构造函数,是由Vue.extend
生成的每次调用
Vue.extend
返回的都是一个全新的VueComponent
在创建组件的过程中,
this
是VueComponent
实例对象
VueComponent.propertype.__proto__ === Vue.propertype
3. 单文件组件
3.1 示例代码
<template>
<!--组件结构-->
<div class="school">
<h1>SchoolVueComponent</h1>
</div>
</template>
<script>
//组件交互相关代码
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data(){
return {
schoolName: 'IMOOC',
address: 'beijing'
}
},
methods:{},
computed:{},
watch:{},
mixins:{}
}
</script>
<style>
//组件样式
*{
padding:0;
margin:0;
}
</style>
五、VueCLI
1. 准备工作
1.1 安装NodeJS
下载地址:https://nodejs.org/dist/v16.13.2/node-v16.13.2-x64.msi
1.2 安装NPM
换源
- 方式一
npm install -g nrm
nrm ls
nrm use taobao
- 方式二
npm config set registry https://registry.npm.taobao.org
npm config get registry
- 方式三:临时指定下载源
npm --registry https://registry.npm.taobao.org install [name]
- 方式三
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install [name]
1.3 设置自定义缓存目录和模块下载安装目录
npm config set prefix E:\Development\NodeJS\npm_prefix
npm config set cache E:\Development\NodeJS\npm_cache
1.4 安装VueCLI
npm install -g @vue/cli
1.5 创建Vue项目
vue create [option] projectName
1.6 运行vue项目
vue serve
2. render函数
2.1 应用场景
当我们因为一些需求引入的并非是完整版的 vue 时,没有模板解析器,无法识别 template 项,导致页面无法展示,使用render函数可以将模板内容利用 createElement 函数创建并返回到页面中
import Vue from 'vue'
Vue.config.productionTip = false;
new Vue({
el:'#app',
render(createElement){
return createElement('h1','标题内容');
}
});
render函数没有用到this,所以可以简写为箭头函数,又因为函数中只有一条返回语句,所以可以直接简写为如下格式:
import Vue from 'vue' Vue.config.productionTip = false; new Vue({ el:'#app', render: h=>h('h1','标题内容'); });
2.2 总结
vue.js
与vue.runtime.xxx.js
的区别vue.js
是完整版的Vue
,包含核心功能和模板解析器vue.runtime.xxx.js
是运行版的Vue
,只包含核心功能没有模板解析器
vue.runtime.xxx.js
没有模板解析器,所以不能使用template
配置,需要使用render
函数接收到的createElement
函数去指定模板内容
3. 覆写脚手架配置
3.1 查看脚手架配置
vue inspect > output.js
3.2 覆写配置 vue.config.js
module.exports = {
pages:{
index:{
//入口
entry:'src/yourJs.js'
}
},
//关闭语法检查
lintOnSave:false
}
npm run serve
3.3 具体配置项
详见:https://cli.vuejs.org/zh/
4. ref属性
-
被用来给元素或子组件注册引用信息(id的替代者)
-
当我们为对应元素设置
ref
后,可以直接通过this
调用$refs
获取到对应名称的元素,如果为html元素则返回真实DOM对象,如果为组件元素则返回组件实例对象
<template>
<img alt="Vue logo" src="./assets/logo.png" ref="img">
<HelloWorld msg="Welcome to Your Vue.js App" ref="hw"/>
<School ref="school"/>
<button @click="showElement" ref="btn">点我输出元素对象</button>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import School from './components/School.vue';
export default {
name: 'App',
components: {
HelloWorld,School
},
methods:{
showElement(){
console.log(this.$refs.img); //真实DOM元素
console.log(this.$refs.btn); //真实DOM元素
console.log(this.$refs.school); //组件实例对象
console.log(this.$refs.hw); //组件实例对象
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
5. props配置
props属性:用于接收组件调用方使用组件时传入的数据
5.1 props简单接收
<template>
<!--组件结构-->
<div class="school">
<h1>{{ msg }}</h1>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{schoolAddress}}</h2>
<h2>学校面积:{{schoolArea*100}}</h2>
</div>
</template>
<script>
//组件交互相关代码
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data(){
return {
msg:"SchoolComponentMsg"
}
},
//简单接收
props:['schoolName','schoolAddress','schoolArea']
}
</script>
<style>
//组件样式
*{
padding:0;
margin:0;
}
</style>
5.2 props接收时限定传入的数据类型
<template>
<!--组件结构-->
<div class="school">
<h1>{{ msg }}</h1>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{schoolAddress}}</h2>
<h2>学校面积:{{schoolArea*100}}</h2>
</div>
</template>
<script>
//组件交互相关代码
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data(){
return {
msg:"SchoolComponentMsg"
}
},
//接收的同时对数据类型进行限制
props:{
schoolName:String,
schoolAddress:String,
schoolArea:Number
}
}
</script>
<style>
//组件样式
*{
padding:0;
margin:0;
}
</style>
5.3 props接收时限定传入数据的各项属性
<template>
<!--组件结构-->
<div class="school">
<h1>{{ msg }}</h1>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{schoolAddress}}</h2>
<h2>学校面积:{{schoolArea*100}}</h2>
</div>
</template>
<script>
//组件交互相关代码
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data(){
return {
msg:"SchoolComponentMsg"
}
},
props:{
schoolName:{
type: String, //数据类型
required: true //必须传入
//default:"慕课网" 默认值
},
schoolAddress:{
type:String, //数据类型
required: true //必须传入
//default: "上海" 默认值
},
schoolArea:{
type: Number, //数据类型
required: true //必须传入
//default: 200 默认值
}
}
}
</script>
<style>
//组件样式
*{
padding:0;
margin:0;
}
</style>
5.4 修改传入数据的解决方案
Vue 默认不推荐我们直接修改传入的数据,虽然我们可以修改成功,但是Vue会报错,因为这个过程可能导致Vue 产生错误,但是我们在业务开发中仍然有这样的需求,解决方案就是我们通过在组件定义对应的承接传入数据的data属性,并让页面绑定我们组件内部的data属性,日后修改时只需要修改我们组件内部拥有传入数据的data属性即可重新完成模板解析,实现我们想要的效果
6. mixin混入
mixin混入:一段配置让多个组件共用时,可以使用混入完成
6.1 书写需要混入的内容
export const mixin = {
data(){
return {
username:"zhiyuan",
email:"zhiyuanworkemail@163.com",
index:"https://www.zync.top"
}
}
}
6.2 引入需要混入的内容
6.2.1 局部引入
<template>
<!--组件结构-->
<div class="school">
<h1>{{ msg }}</h1>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{schoolAddress}}</h2>
<h2>学校面积:{{schoolArea*100}}</h2>
</div>
</template>
<script>
import {mixin} from "@/mixin";
//组件交互相关代码
export default {
name: 'School',
data(){
return {
msg:"SchoolComponentMsg"
}
},
mixins:[mixin]
}
</script>
<style>
//组件样式
*{
padding:0;
margin:0;
}
</style>
6.2.2 全局引入
在main.js中作如下修改
import Vue from 'vue'
import App from './App.vue'
import {mixin} from "@/mixin";
Vue.mixin(mixin);
new Vue({
el:"#app",
render: h=>h(App)
})
6.3 注意事项
在混入进行的时候,如果组件内部已经有对应data中同名的属性,则会以组件自己的为准
7. 插件
插件本质上就是一个对象,但是Vue规定这个对象中必须存在一个
install
方法,用于增强Vue,我们可以在其中定义全局的过滤器,定义自己的指令,定义全局的混入等等
7.1 示例
export default{
install(){
console.info('自定义插件');
}
}
7.2 引入插件
//引入自定义插件
import plugins from "@/plugins";
Vue.use(plugins);
7.3 进阶
export default{
install(Vue){
console.info('自定义插件',Vue);
//全局过滤器
Vue.filter('filterName',function (methodParam){
return methodParam;
});
//自定义指令
Vue.directive('fbind',{
bind(element,binding){
element.value = binding.value;
},
inserted(element,binding){
element.focus();
},
updated(element,binding){
element.value = binding.value;
}
});
//定义混入
Vue.mixin({
data(){
return {
username:"zhiyuan",
gender:"male"
}
}
})
}
}
8. scoped样式
当我们书写的vue文件最终被解析为浏览器能够识别的js、css、html时,其实我们所有的css会完成汇总,而汇总样式就会产生一个问题,那就是选择器名称重复时,某些样式可能被覆盖,覆盖的原则是由引入组件的顺序决定的,谁先被引入,谁的样式就会被覆盖,但是我们通常不希望产生这样的结果,所以vue为我们提供了一个解决方案,那就是使用
scoped
属性,来限定css的作用域或者说css有效的范围
<style scoped>
//组件样式
*{
padding:0;
margin:0;
}
</style>
-
实现原理
- Vue在汇总页面样式时,会在对应组件的容器上添加一个名为
data-v-xxxx
的属性,然后通过属性选择器来限定指定样式作用的容器
- Vue在汇总页面样式时,会在对应组件的容器上添加一个名为
-
书写less
-
安装less-loader
npm i less-loader@10.2.0
-
书写less
<style lang="less" scoped> </style>
-
9. ToDoList案例
组件化的编码流程
拆分静态组件:组件按照功能点拆分,命名不要与html元素冲突
实现动态组件:考虑好数据的存放位置,确认数据是一个组件在用还是一些组件都在用
一个组件在用:放在组件自身即可
一些组件在用:放在它们共同的父组件上
实现交互:从绑定事件开始
props适用于
- 父组件与子组件进行通信
- 子组件与父组件进行通信(需要父组件传给子组件一个函数)
使用v-model是要切记:v-model绑定的值不能是props传过来的值,因为props是不建议修改的
props传过来的如果是对象类型的值,修改对象中的属性时Vue不会报错,但是不推荐这么做,因为这只是Vue没有检测到,并不意味着Vue允许我们这么做
10. 浏览器本地存储
10.1 LocalStorage
//存储基本类型到localStorage中最终都会呈现为字符串类型
window.localStorage.setItem('key','value');
//存储对象需要将对象转为JSON
window.localStorage.setItem('key',JSON.stringify(object));
//读取localStorage中存储的对象
JSON.parse(JSON.parse(localStorage.getItem('key'));
//从localStorage中移除设置的信息
window.localStorage.removeItem('key');
//清空localStorage
window.localStorage.clear();
10.2 SessionStorage
//存储基本类型到sessionStorage中最终都会呈现为字符串类型
window.sessionStorage.setItem('key','value');
//存储对象需要将对象转为JSON
window.sessionStorage.setItem('key',JSON.stringify(object));
//读取sessionStorage中存储的对象
JSON.parse(JSON.parse(sessionStorage.getItem('key'));
//从sessionStorage中移除设置的信息
window.sessionStorage.removeItem('key');
//清空sessionStorage
window.sessionStorage.clear();
LocalStorage:浏览器关闭后依然存在
SessionStorage:一次会话结束后就不存在了,即关闭浏览器后就不存在了
11. 组件自定义事件
-
一种组件间通信的方式,适用于子组件与父组件通信
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
-
绑定自定义事件
-
第一种方式,在父组件中
<Demo @事件名="test"/>
或<Demo v-on:事件名="test">
-
第二种方式,在父组件中
<Demo ref="demo"> ...... mounted(){ this.$refs.xxx.$on('事件名',this.test); }
-
若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法
-
-
触发自定义事件
this.$emit('事件名',数据)
-
解绑自定义事件
this.$off('事件名')
-
组件上绑定原生DOM事件,需要使用
native
修饰符 -
注意:通过
this.$refs.xxx.$on('事件名',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题
12. 全局事件总线
任意组件间通信
new Vue({ el:"#app", render:h => h(App), //vm创建之前 beforeCreate(){ Vue.prototype.$bus = this; //安装全局事件总线 } });
-
一种组件间的通信方式,适用于任意组件间通信
-
安装全局事件总线
new Vue({ el:"#app", render:h => h(App), beforeCreate(){ Vue.prototype.$bus = this; //安装全局事件总线,$bus就是当前应用的vm对象 } });
-
使用事件总线
-
接收数据:A组件想接收数据,则在A组件汇总给$bus绑定自定义事件,事件的回调留在A组件自身
methods(){ demo(data){.......} } ...... mounted(){ this.$bus.$on('xxx',this.demo); }
-
提供数据:
this.$bus.$emit('xxx',数据)
-
-
最好在
beforeDestory()
钩子函数中用$off
去解绑当前组件所用到的事件
13. 消息订阅与发布 pubsub
仍然是一种支持任意组件通信的方式
- 安装
pubsub-js
npm i pubsub-js
- 引入pubsub
import pubsub from 'pubsub-js'
- 发布消息
//组件挂载完毕
mounted(){
//发布消息
pubsub.publish('消息名','消息内容');
}
- 订阅消息
//组件挂载完毕
mounted(){
//订阅消息
this.pubId = pubsub.subscribe('消息名',function('消息名','消息内容'){
console.log('有人发布了消息,消息名为:' + 消息名 + '@消息内容为:' + 消息内容);
.....
});
}
//上方写法中存在错误,错在当前this并非是vc对象,老生常谈的,如果我们需要使用第三方js库时,vue不会帮我们在管理对应的函数、对象以及其他信息,如果想要被vue管理到,就需要把普通函数写成箭头函数,这样this就会去父级找到vm或者vc,正确写法有如下两种:
//第一种
methods:{
demo('消息名','消息内容'){
console.log('有人发布了消息,消息名为:' + 消息名 + '@消息内容为:' + 消息内容);
.....
}
}
//组件挂载完毕
mounted(){
//订阅消息
this.pubId = pubsub.subscribe('消息名',this.demo);
}
//第二种
//组件挂载完毕
mounted(){
//订阅消息
this.pubId = pubsub.subscribe('消息名',('消息名','消息内容') => {
console.log('有人发布了消息,消息名为:' + 消息名 + '@消息内容为:' + 消息内容);
.....
});
}
- 取消订阅
beforeDestory(){
//根据订阅消息ID 取消订阅消息
pubsub.unscribe(pubId);
}
14. $nextTick
由
this.$nextTick(回调函数)
指定的回调函数会在DOM节点更新之后执行作用:在下一次DOM更新结束后执行其指定的回调
应用场景:当数据改变后,要基于
数据改变后
渲染的DOM进行某些操作时,可以使用此方法来指定
this.$nextTick(function(){
this.$refs.inputTile.focus();
});
15. 过渡与动画
15.1 动画
- CSS3
@keyframes 动画名称 {
from {
transform: translateX(-100px);
}
to {
transform: translateX(0px);
}
}
.come {
/* animation:动画名称 持续时间 是否匀速 */
animation: 动画名称 1s linear;
}
.go{
animation: 动画名称 1s linear reverse;
}
- Vue
使用
<transition></transition>
标签包裹需要展示动画的内容
@keyframes 动画名称 {
from {
transform: translateX(-100px);
}
to {
transform: translateX(0px);
}
}
.v-enter-active {
animation: 动画名称 1s linear;
}
.v-leave-active {
animation: 动画名称 1s linear reverse;
}
<button @click="isShow = !isShow">显示/隐藏</button>
<transition>
<h1 v-show="isShow">内容</h1>
</transition>
-
appear属性
- 我们发现只有我们点击按钮才会触发动画效果,但是通常我们需要一个动画在页面展示时立即播放一次动画,name我们可以在
<transition></transition>
标签内添加appear
属性
- 我们发现只有我们点击按钮才会触发动画效果,但是通常我们需要一个动画在页面展示时立即播放一次动画,name我们可以在
-
name属性
-
我们可以给动画起名字,来让一个组件内可以有多个动画
<transition name="动画名称"></transition>
-
对应的css动画名称就需要改成如下格式
.动画名称-enter-active {} .动画名称-leave-active {}
-
其实我们默认不写,就是将动画名称默认为了
v
-
15.2 过渡
/* 进入的起点 */
.动画名称-enter {
transform: translateX(-100%);
}
/* 进入的终点 */
.动画名称-enter-to {
transform: translateX(0);
}
/* 离开的起点 */
.动画名称-leave {
transform: translateX(0);
}
/* 离开的终点 */
.动画名称-leave-to {
transform: translateX(-100%);
}
/* 进入的起点,离开的终点 */
.动画名称-enter,.动画名称-leave-to {
transform: translateX(-100%);
}
/* 进入的终点,离开的起点 */
.动画名称-enter-to,.动画名称-leave{
transform: translateX(0);
}
.动画名称-enter-active,.动画名称-leave-active{
transition: 0.5s linear;
}
15.3 多个元素的过渡
<transition></transition>
只能够加在一个单独的元素上,如果需要加在多个元素上需要使用<transition-group></transition-group>
- 而且在使用
transition-group
标签时,必须为其包裹的每一个元素设置一个key
<transition-group name="动画名称" appear>
<h1 v-show="isShow" key="1"></h1>
<h1 v-show="isShow" key="2"></h1>
</transition-group>
15.4 第三方动画
-
安装
animate.css
npm install animate.css
-
引入
animate.css
import 'animate.css'
-
使用方式
<transition-group name="animate__animated animate__bounce" enter-active-class="animate__swing" leave-active-class="animate__backOutUp" appear> </transition-group>
16. 配置代理 axios
-
安装
axios
npm install axios
-
引入
axios
import axios from 'axios'
-
使用
axios
export default{ name: 'App', methods:{ getUserInfo(){ axios.get('http://localhost:8080/cloud-drive/user/10000010').then( response => { console.log("请求成功",response.data); }, error => { console.log("请求失败",error.message); } ); } } }
-
解决跨域问题
- cors:后端放行跨域访问
- json:利用了script标签的src属性在引入外部文件时不受同源策略影响的特性
- 代理服务器
- nginx
- vue-cli
16.1 方式一
- 修改
vue.config.js
文件
module.exports = {
devServer:{
proxy: 'http://localhost:8080'
}
}
这个方法只能是请求地址没有的资源才走代理服务器,并不能解决我们的跨域问题
16.2 方式二
module.exports = {
devServer:{
proxy: {
//匹配所有以/api开头的请求路径
'/cloud-drive':{
target:'http://localhost:8080',
pathRewrite:{'^/cloud-drive':''} // key为书写的路径匹配正则,value为与匹配替换的路径
ws: true, // 开启webscoket支持
changeOrigin: true // 代理功能实现的核心属性,前端解决跨域问题
},
//匹配所有以/foo开头的请求路径
'/admin':{
target: 'http://localhost:8085'
}
}
}
}
17. vue-resource
-
安装
vue-resource
npm install vue-resource
-
引入
vue-resource
//main.js 引入插件 import vueResource from 'vue-resource' Vue.use(vueResource);
官方已经不再维护,不建议使用
18. 插槽
作用:让父组件可以向子组件指定位置插入html结构
应用场景:当一个组件被使用多次,但是组件中部分需求有所不同时,可以使用vue的插槽功能,让不同的需求都可以有对应的实现
18.1 默认插槽
<template>
<h1>demo组件</h1>
<slot>默认插槽</slot>
</template>
<demo>
<h2>使用默认插槽</h2>
</demo>
18.2 具名插槽
<template>
<h1>demo组件</h1>
<slot name="center">我是具名插槽center</slot>
<slot name="footer">我是具名插槽footer</slot>
</template>
<demo>
<h2 slot="center">使用具名插槽center</h2>
<h2 slot="footer">使用具名插槽footer</h2>
</demo>
<demo>
<template slot="center">
<h2>使用具名插槽center</h2>
</template>
<template v-slot:footer>
<h2>使用具名插槽footer</h2>
</template>
</demo>
18.3 作用域插槽
- 数据在组件的自身,但是根据数据生成的结构需要组件的使用者来决定
<template>
<h1>demo组件</h1>
<slot :userList="userList"></slot>
</template>
<template>
<demo>
<template scope="userList">
<ul>
<li v-for="(user,index) in userList" :key="user.id">{{user.name}}</li>
</ul>
</template>
</demo>
<demo>
<template slot-scope="userList">
<h2>{{userList[0].name}}</h2>
</template>
</demo>
</template>
19. vuex
作用:专门在Vue中实现集中式状态数据管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理,也是一种组件间通信方式,且适用于任意组件间通信
地址:https://github.com/vuejs/vuex
应用场景:
- 多个组件依赖于同一状态
- 来自不同的组件的行为需要变更为同一状态
19.1 vuex工作原理图
19.2 快速入门
- 安装
vuex
在2022年2月7日,vue3 成为了默认版本,使用 npm install vue 如果不指定版本号,那么就会直接安装 vue3,并且使用 npm install vuex 不指定版本号,也会直接安装vuex4版本,vue3.0 对应 vuex4.0
npm install vuex@3
-
引入
vuex
import Vue from "vue" import Vuex from "vuex" Vue.use(Vuex);
-
创建src/store目录,并新建index.js文件
//引入Vue import Vue form 'vue' //引入Vuex import Vuex from 'vuex' Vue.use(Vuex); //用于响应组件中的动作 const actions = { } //用于操作数据state const mutations = { } //用于存储数据 const state = { } //创建store const store = new Vuex.Store({ actions:actions, mutations:mutations, state:state }); //暴露store export default store;
-
引入
store
,修改 main.js 文件如下import store from './store'
19.3 getter配置项
const getters = {
......
}
const store = new Vuex.Store({
......
getters:getters
});
19.4 mapState与mapGetters
-
引入
mapState
mapGetters
import {mapState,mapGetters} from 'vuex'
-
使用
mapState
mapGetters
computed:{ //借助mapState生成计算属性,从state中读取数据 ...mapState({sum:'sum',school:'school',subject:'subject'}), ...mapState(['sum','school','subject']) //简写形式 //借助mapGetters生成计算属性,从getters中读取数据 ...mapGetters({sum:'sum',school:'school',subject:'subject'}) ...mapGetters(['sum','school','subject']) }
19.5 mapActions与mapMutations
-
引入
mapMutations
mapActions
import {mapMutations,mapActions} from 'vuex'
-
使用
mapMutations
mapActions
methods:{ //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations中的方法 ...mapMutations({increment:'SUM',decrement:'SUB'}) ...mapMutations(['SUM','SUB']); //简写形式 //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions中 ...mapActions({increment:'sum',decrement:'sub'}) ...mapActions(['sum','sub']) }
19.6 多组件共享数据
19.7 vuex的模块化编码
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const options1 = {
namespaced:true, //开启命名空间
action:{},
mutation:{},
state:{},
getters:{}
}
const options2 = {
namespaced:true, //开启命名空间
action:{},
mutation:{},
state:{},
getters:{}
}
export default new Vuex.Store({
modules:{
a:options1,
b:options2
}
});
...mapState('a',['sum','school','subject']);
...mapState('b',['persionList'])
this.store.state.personAbout.list;
...mapMutations('a',{increment:'SUM',decrement:'SUB'})
this.$store.commit('a/SUM',2);
...mapActions('a',{increment:'sum',decrement:'sub'})
this.$store.dispatch('a/sum',2);
...mapGetters('a',['sum','school','subject'])
this.$store.getters['a/sum'];
20. 路由
SPA(Single Page Web Application):单页面应用
2022年2月7日以后,vue-router的默认版本变为了4,所以如果我们不需要最新版本,就要在安装时指定他的版本
-
安装
vue-router
npm install vue-router
-
引入
vue-router
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter); new Vue({ el: '#app', render: h => h(App), router:'' })
-
创建src/router目录,并新建index.js文件
import VueRouter from 'vue-router' import About from '../components/About' import Home from '../component/Home' //创建并暴露一个路由器 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] });
-
在页面中使用
<router-link>
标签的路由切换 代替<a>
标签的页面跳转<router-link to="/about" active-class="active">About</router-link> <router-link to="/home" active-class="active">Home</router-link>
-
在页面中使用
<router-view>
标签指定路由切换对应组件呈现的位置<router-view></router-view>
20.1 注意事项
- 路由组件一般放在
pages
文件夹,普通组件一般放在components
中 - 通过切换,隐藏了的路由组件,默认是被销毁掉的,需要的时候才会再次挂载
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息 - 整个应用只有一个
router
,可以通过组件的$router
属性获取到
20.2 嵌套路由
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
path:'message',
component:Message
}
]
}
]
});
20.3 路由的query参数
-
传递参数
<!--to的字符串写法--> <router-link :to="/home/message/detail?id=666&title=test">跳转</router-link> <!--to的对象写法--> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'test' } }">跳转 </router-link>
-
接收参数
$route.query.id $route.query.title
20.4 命令路由
- 作用:简化路由的跳转
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
name:'message'
path:'message',
component:Message
}
]
}
]
});
<router-link :to="{name:'message'}">跳转</router-link>
<router-link :to="{
name:'message',
query:{
id:666,
title:'test'
}
}">跳转
</router-link>
20.4 路由的params参数
<router-link :to="{name:'message'}">跳转</router-link>
<router-link :to="{
name:'message',
params:{
id:666,
title:'test'
}
}">跳转
</router-link>
当通过params传递参数时,只能用name,不能用path
20.5 路由的props配置
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
name:'message'
path:'message',
component:Message,
children:[
{
name:'detail',
path:'detail/:id/:title',
component:Detail,
//第一种写法,值为对象,缺点:数据写死,一般不用
props:{
id:'666',
title:'test'
}
//第二种写法,值为boolean,true时会把该路由组件收到的所有params参数以props形式传给Detail组件
props:true
//第三种写法,值为函数
props(){
return {id:'666',title:'test'}
}
}
]
}
]
}
]
});
props($route){
return {
id:$route.query.id,
title:$route.query.title
}
}
20.6 router-link的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录由两种写入方式,分别为
push
和replace
,push是追加历史记录,replace是替换当前记录,路由跳转默认为push - 我们只需要在
<router-link replace>
标签中添加replace属性,就可以实现浏览器没有历史记录即无法后退的场景
20.7 编程式路由导航
-
作用:不用借助
router-link
标签实现路由跳转,让路由跳转更加灵活 -
代码
this.$router.push({ name:'detail', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'detail', params:{ id:xxx, title:xxx } }) //前进 this.$router.forward(); //后退 this.$router.back(); //前进(>0)/后退(<0) this.$router.go(3);
20.8 缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁
-
代码
<!--缓存单个路由组件--> <keep-alive include="News"> <router-view></router-view> </keep-alive> <!--缓存多个路由组件--> <keep-alive :include="['News','Message']"> <router-view></router-view> </keep-alive>
20.9 两个新的生命周期钩子
//激活
actived(){
.......
},
//失活
deactived(){
.......
}
20.10 路由守卫
-
作用:对路由进行权限控制
-
分类:全局守卫、独享守卫、组件内守卫
-
全局前置守卫:初始化时执行,每次路由切换前执行
const router = new VueRouter({options}); router.beforeEach((to,from,next)=>{ if(to.meta.isAuth){ if(localStorage.getItem('role') === 'admin'){ next(); }else{ alert('无权查看'); } }else{ next(); } });
-
全局后置守卫:初始化时执行,每次路由切换后执行
const router = new VueRouter({options}); router.afterEach((to,from)=>{ if(to.meta.title){ document.title = to.meta.title; }else{ document.title = '主页'; } });
-
独享路由守卫
export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { name:'news', path:'news', component:News meta:{title:'新闻'}, beforeEnter:(to,from,next)=>{ if(to.meta.isAuth){ if(localStorage.getItem('role') === 'admin'){ next(); }else{ alert('无权查看'); } }else{ next(); } } }, { name:'message' path:'message', component:Message } ] } ] });
-
组件内路由守卫
//通过路由规则,进入该组件时被调用 beforeRouteEnter(to,from.next){ }, //通过路由规则,离开该组件时被调用 beforeRouteLeave(to,from,next){ }
20.11 history模式与hash模式
- 对于一个url来说,在 # 后面的内容就是hash值
- hash值不会包含在http请求中
- 两种模式
- hash模式
- 地址中永远带着 # 号,不美观
- 地址通过第三方分享,若第三方校验严格,地址会被标记为不合法
- 兼容性好
- history模式
- 地址干净,美观
- 兼容性相较hash模式较差
- 应用部署上线时,需要后端人员支持,解决刷新页面服务端404的问题
- hash模式
六、Element-UI
地址:https://element.eleme.cn/#/zh-CN
七、Vue3
1. 使用 vue-cli 创建工程
2. 使用vite创建工程
3. 分析工程结构
3.1 分析入口文件main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import {createApp} from 'vue'
import App from './App.vue'
//创建应用实例对象(类似于vue2的vm,但是比vm更加轻量)
const app = createApp(App);
app.mount("#app");
3.2 分析App.vue
<template>
<!--vue3组件中的模板结构可以没有根标签-->
<img alt="Vue logo" src="./assets/logo.png" ref="img">
<HelloWorld msg="Welcome to Your Vue.js App" ref="hw"/>
<School ref="school" school-name="黑马程序猿" school-address="北京" v-bind:school-area="10"/>
<button @click="showElement" ref="btn">点我输出元素对象</button>
</template>
4. 安装开发者工具
5. 初识setup
Vue3中一个新的配置项,值为一个函数
组件中用到的数据data,方法methods等,均要配置到setup中
- setup函数有两种返回值
- 返回一个对象,则对象中的属性、方法。在模板中均可以直接使用
- 若返回一个渲染函数,则可以自定义渲染内容
- 注意事项
- 尽量不要与vue2配置混用
- vue2配置(data、methods、computed、watch...)中可以访问到setup中的属性、方法
- 但是setup中不能访问vue2的配置(data、methods、computed、watch...)
- 如果有重名,以setup优先
- setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性
- 后期可以返回一个Promise实例,但是需要Suspense和异步组件的配合
- 尽量不要与vue2配置混用
6. ref函数
-
作用:定义一个响应式数据
-
语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象
- js中操作数据
xxx.value
- 模板中读取数据不需要
.value
,直接{{xxx}}
-
备注
- 接收的数据可以是基本类型,也可以是对象类型
- 基本类型的数据,响应式依然是靠
Object.defineProperty()
的get
与set
完成的 - 对象类型的数据,内部求助了Vue3的一个新函数
reactive
函数
-
import {ref} from 'vue'
7. reactive函数
- 作用:定义一个对象类型的响应式数据(基本类型别用它,用
ref
函数) - 语法:
const 代理对象 = reactive(被代理对象)
接收一个对象(或数组),返回一个代理器对象(Proxy对象) - reactive定义的响应式数据是深层次的
- 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
-
import {reactive} from 'vue'
8. vue3响应式原理
8.1 vue2响应式原理
-
实现原理
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持) -
数组类型:通过重写更新数组的一系列方法来实现拦截
Object.defineProperty(data,'count',{ get(){}, set(){} });
-
-
存在的问题
- 新增属性、删除属性,界面不会更新
- 直接通过数组下标修改数组内容,界面不会更新
8.2 vue3响应式原理
- 实现原理
- 通过Proxy(代理):拦截对象中任意属性的变化,包括(属性值读写、属性添加、属性删除等)
- 通过Reflect(反射):对被代理对象的属性进行操作
- MDN文档中描述的Proxy与Reflect
- Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
- Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
- Proxy
new Proxy(data,{
//有人读取data的某个属性时调用
get(target,attributeName){
return target[attributeName];
},
//有人修改data属性或者给data追加属性时调用
set(target,attributeName,value){
target[attributeName] = value;
},
//有人删除data上某个属性时调用
deleteProperty(target,attributeName){
return delete target[attributeName];
}
});
- Reflect
- ECMA组织正在尝试将Object上的属性方法移植到Reflect身上
9. reactive与ref对比
- 从定义数据角度对比
- ref 用来定义基本数据类型
- reactive 用来定义对象或数组数据类型
- ref 也可以定义对象或数组类型,但是最终内部会通过reactive转为代理对象
- 从原理的角度对比
- ref 通过
Object.defineProperty()
的get
和set
来实现响应式 - reactive 通过使用
Proxy
来实现响应式,并通过Reflect
操作源对象内部的数据
- ref 通过
- 从使用角度对比
- ref 定义的数据,操作数据需要
.value
,模板读取时不需要.value
- reactive 定义的数据,操作数据与读取数据都不需要
.value
- ref 定义的数据,操作数据需要
10. setup的两个注意点
- setup的执行时机
- 在beforeCreate之前执行一次,this是undefined
- setup的参数
- props:值为对象,存储组件外部传递过来并且组件内部声明接收了的属性
- context:上下文对象
- attrs:值为对象,存储组件外部传递过来,但是没有在props中声明接收的属性。相当于
this.$attrs
- slots:收到的插槽内容。相当于
this.$slots
- emit:分发自定义事件的函数。相当于
this.$emit
- attrs:值为对象,存储组件外部传递过来,但是没有在props中声明接收的属性。相当于
props:['','',''...],
emits:['','',''...],
setup(props,context){
...
return {
...
}
}
11. computed计算属性
import {computed,reactive} from 'vue'
setup(){
let person = reactive({
firstName:'张',
lastName:'三'
});
//简写形式,只考虑读取数据
person.fullName = computed(()=>{
return person.firstName + person.lastName;
});
//完整形式,考虑读写
person.fullName = computed({
get(){
return person.firstName + person.lastName;
},
set(value){
const nameArr = value.split('-');
person.firstName = nameArr[0];
person.lastName = nameArr[1] ;
}
});
return {
person
}
}
与Vue2中的computed配置功能一致,只是需要写在setup函数中
12. watch监视属性
- 与Vue2中的watch配置功能一致
- 两个注意事项
- 监视reactive定义的响应数据时,oldValue无法正确获取,强制开启了深度监视,deep属性失效
- 监视reactive定义的响应数据中某个属性时,deep属性有效
import {ref,reactive,watch} from 'vue'
setup(){
let sum = ref(0);
let msg = ref('hello');
let person = reactive({
name:'张三',
age:18,
job:{
name:'JavaEngineer',
salary:20
}
});
//监视ref所定义的一个响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum改变了',newValue,oldValue);
});
//监视ref所定义的多个响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum改变了',newValue,oldValue);
},{immediate:true});
//监视reactive所定义的一个响应式数据,此处oldValue无法正常获取,deep属性失效
watch(person,(newValue,oldValue)=>{
console.log('person改变了',newValue,oldValue);
},{deep:false});
//监视reactive所定义的一个响应式数据的某个普通属性,此处oldValue可以正常获取,deep属性有效
watch(()=>person.age,(newValue,oldValue)=>{
console.log('person.age改变了',newValue,oldValue);
});
//监视reactive所定义的一个响应式数据的某些普通属性
watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
console.log('person.age或name改变了',newValue,oldValue);
});
//监视reaction所定义的一个响应式数据中的对象属性,deep属性有效
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person.job改变了',newValue,oldValue);
},{deep:true});
return {
sum,age,person
}
}
-
watchEffect函数
- watch的套路是:既要指明监视的属性,也要指明监视的回调
- watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性
- watchEffect有点类似与computed
- 但是computed注重的是计算出来的值(回调函数的返回值),所以必须需要写返回值
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
watchEffect(()=>{ const x1 = sum.value; const v2 = person.value; console('watchEffect配置的回调执行了'); });
13. Vue3生命周期
Vue3中可以继续使用Vue2中的生命周期钩子,但是有两个被更名
beforeDestory
改名为beforeUnmount
destoryed
改名为unmounted
Vue3也提供了CompositionAPI形式的生命周期钩子函数,与Vue2对应关系如下:
beforeCreate
===>setup()
created
===>setup()
beforeMount
===>onBeforeMount()
mounted
===>onMounted()
beforeUpdate
===>onBeforeUpdate()
updated
===>onUpdated()
beforeUnmount
===>onBeforeUnmount()
unmounted
===>onUnmounted()
setup(){
onBeforeMount(()=>{
});
onMounted(()=>{
});
onBeforeUpdate(()=>{
});
onUpdated(()=>{
});
onBeforeUnmount(()=>{
});
onUnmounted(()=>{
});
}
14. 自定义hook
Vue中的hook:本质是一个函数,把setup函数中使用的CompositionAPI进行了封装
类似于Vue2中的mixin
自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂
15. toRef与toRefs
- 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性
- 语法:
const name = toRef(person,'name')
- 应用场景:需要将响应式对象中的某个属性单独提供给外部使用时
- 扩展:
toRefs()
与toRefs()
的功能一致,但可以批量创建多个ref对象,语法为:toRefs(person)
return {
person,
...toRefs(person)
}
16. CompositionAPI
16.1 shallowReactive与shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
- 应用场景:
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化,可以使用shallowReactive
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,可以使用shallowRef
16.2 readonly与shallowReadonly
- readonly:让一个响应式数据变为只读(深只读)
- shallowReadonly:让一个响应式数据变为只读(浅只读)
- 应用场景:不希望数据被修改
16.3 toRaw与markRaw
- toRaw
- 作用:将一个由
reactive
生成的响应式对象转为普通对象 - 应用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面的更新
- 作用:将一个由
- markRaw
- 作用:标记一个对象,使其永远不会再成为响应式对象
- 应用场景:
- 有些值不应被设置为响应式,例如复杂的第三方类库等
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
16.4 customRef
-
作用:创建一个自定义的 ref ,并对其依赖项跟踪和更新触发进行显式控制
-
实现防抖效果
<template> <p> This text only updates 1 second after you've stopped typing: </p> <p>{{ text }}</p> <input v-model="text" /> </template> <script setup> import { useDebouncedRef } from './debouncedRef.js' const text = useDebouncedRef('hello', 1000) </script>
import { customRef } from 'vue' export function useDebouncedRef(value, delay = 200) { let timeout return customRef((track, trigger) => { return { get() { track()//通知vue追踪value的变化 return value }, set(newValue) { clearTimeout(timeout) timeout = setTimeout(() => { value = newValue trigger()//通知vue重新解析模板 }, delay) } } }) }
16.5 provide与inject
-
作用:实现祖孙组件间通信
-
套路:父组件有一个
provide
选项来提供数据,子组件有一个inject
选项来开始使用这些数据 -
具体写法
-
祖组件
setup(){ ...... let car = reactive({ name:'奔驰', price:'40万' }); provide('car',car) ...... }
-
后代组件
setup(){ ...... const car = inject('car'); return { car } ...... }
-
16.6 响应式数据的判断
- isRef:检查一个值是否为一个ref对象
- isReactive:检查一个对象是否是由 reactive 创建的响应式代理对象
- isReadonly:检查一个对象是否是由 readonly 创建的只读代理对象
- isProxy:检查一个对象是否是由 reactive 或者 readonly 方法创建的代理对象
17. CompositionAPI优势
17.1 OptionAPI
17.2 CompositionAPI
18. Fragment组件
- 在Vue2中,组件必须有一个根标签
- 在Vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
- 好处:减少标签层级,减少内存占用
19. Teleport组件
-
Teleport 是一种能够将我们组件html结构移动到指定位置的技术
<teleport to="移动位置"> <div>我是一个弹窗</div> </teleport>
20. Suspense组件
-
等待异步组件时渲染一些后备内容,获得更好的用户体验
-
使用步骤
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹组件,并配置好default
fallback
<template> <div class="#app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中......</h3> </template> </Suspense> </div> </template>
-
21. 收尾
21.1 全局API的转移
-
Vue2中有许多全局API和配置
- 例如:注册全局组件,注册全局指令等
-
Vue3中对这些API做出了调整
-
将全局的API,即
Vue.xxx
调整到应用实例app
上Vue2 Vue3 Vue.config.xxx app.config.xxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties Vue.config.keyCodes 移除
-
21.2 其他改变
-
data选项应该始终被声明为一个函数
-
过渡类名的更改
-
Vue2.x写法
.v-enter,.v-leave-to{ opacity: 0; } .v-leave,.v-enter-to{ opacity: 1; }
-
Vue3.x写法
.v-enter-from,v-leave-to{ opacity:0; } .v-leave-from,v-enter-to{ opacity:1; }
-
-
移除 keyCode 作为
v-on
的修饰符,同时也不再支持config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent"></my-component>
-
子组件中声明自定义事件
<script> export default{ emits:['close'] } </script>
-
-
移除过滤器 filter
过滤器虽然看起来很方便,但是它需要一个自定义语法,打破大括号内表达式只是javascript的假设,这不仅有学习成本,而且有实现成本,建议用方法调用或计算属性去替换过滤器
原文作者:絷缘
作者邮箱:zhiyuanworkemail@163.com
原文地址:https://zhiyuandnc.github.io/Vr0OXGyK8/
版权声明:本文为博主原创文章,转载请注明原文链接作者信息