文章目录
学习资料
【尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通】
Vue官网:https://cn.vuejs.org/
Vue的特点
组件化模式
采用组件化模式,提高代码复用率,且让代码更好维护.vue
文件包含HTML、CSS、JS
,如果想复用某个模块可以直接复制该vue文件
就可以达到代码复用的需求,并且改此.vue
文件只会影响此模块,并不会影响其他模块,使得代码更易维护。
声明式编码
假设有一组persons
数据,并且有一个ul
容器,要将数据塞进这个容器里,最终实现如下的效果。
我们原来学的编码方式是命令式编码方式
,每执行一步,就是一个命令,首先准备好html字符串
,然后遍历数据拼接html字符串
,然后再获取list元素
,最后再修改内容(亲自操作DOM)
,才能完成数据的展示,非常的麻烦。
与之对立的概念是声明式编码方式
,ul
里面想放个li
那就放个li
,但是我们都知道li
不是一个,有多少数据就有多少li
所以需要遍历,而vue
里有个指令叫v-for
,而v-for
里的内容就是p in persons
,persons
就是数据源,p
就是数据源里面的每一个数据,这样我们就可以从数据源中取出每一个数据。
使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点
还是上面的例子,假设首先我们拿到了3个数据,并且通过list.innerHTML = htmlStr
将数据写进页面里了,那如果又来了一些新的数据,我们再通过list.innerHTML = htmlStr
将数据写进页面里的话,原先的数据就会被覆盖,这种方案并不太好,原先有的会被覆盖,较好的解决方案是原先有的数据继续复用,将新的进行展示,当然这种解决方案原生JavaScript也能实现,但需要自己进行数据对比,处理完后再进行展现,当然对数据处理需要占用一些时间和性能,体验就不太好了。
vue
的解决方式是在中间增加一个虚拟DOM
,vue
首先会将数据变为虚拟DOM
然后再变为真实DOM
进行展示,虚拟DOM就是内存里的数据
之后vue将虚拟DOM会将其转为真实DOM
,虚拟DOM
的好处是在数据有变化的时候,会通过Diff算法
进行比较,通过Diff算法
比较后如果发现数据一致它就会直接复用
了,然后只将新的数据再进行展示就好了。
初识Vue2
安装Vue
怎么安装VSCode在这里就不再讲了,网上可以搜一下
Vue2文档:https://v2.cn.vuejs.org/v2/guide/installation.html#Vue-Devtools
下载VueJS
Hello word示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- {{vue 的 data 插值}} -->
<h1>Hello,{{name}}</h1>
</div>
</body>
<script type="text/javascript">
// 创建Vue实例,传参:配置对象
new Vue({
el:'#root', // el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ // data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
name:'尚硅谷'
}
});
</script>
</html>
并且当我们修改data
中name
值的时候不用刷新
我们的页面,保存文件
后再看页面就立马变了
总结:
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
- root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
- root容器里的代码被称为
【Vue模板】
;
Vue解析流程:
先有的容器
,然后有的Vue实例
,当Vue实例
开始工作的时候,发现写了一些配置
,他就会把整个容器
拿过来,拿过来之后会进行解析
,解析
的时候会去扫描有没有Vue设置的特殊语法
,经过Vue处理后会生成一个全新的容器并替换
掉原先的容器。
容器与实例属于一对一关系
一个实例只能对应一个容器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div class="root">
<!-- {{vue 的 data 插值}} -->
<h1>Hello,{{name}}</h1>
</div>
<div class="root">
<!-- {{vue 的 data 插值}} -->
<h1>Hello,{{name}}</h1>
</div>
</body>
<script type="text/javascript">
// 创建Vue实例,传参:配置对象
new Vue({
el:'.root', // el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ // data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
name:'尚硅谷'
}
});
</script>
</html>
一个容器只能对应一个实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- {{vue 的 data 插值}} -->
<h1>Hello,{{name}},{{address}}</h1>
</div>
</body>
<script type="text/javascript">
// 创建Vue实例,传参:配置对象
new Vue({
el:'#root', // el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ // data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
name:'尚硅谷'
}
});
new Vue({
el:'#root', // el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ // data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
address:'北京昌平'
}
});
</script>
</html>
模板语法
{{js表达式}}插值语法
可以直接读取data
里面所有的值
可以写js表达式
适用于解析标签体内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- {{vue 的 data 插值}} -->
<h1>Hello,{{name}},{{address}},{{1+1}},{{Date.now()}}</h1>
</div>
</body>
<script type="text/javascript">
// 创建Vue实例,传参:配置对象
new Vue({
el:'#root', // el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ // data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
name:'尚硅谷',
address:'北京昌平'
}
});
</script>
</html>
指令语法
v-bind 单向数据绑定
单向数据绑定v-bind
数据只能从data
流向页面
v-bind
绑定,v-bind:href="school.url"
的意思是将表达式执行的结果绑定
给href
v-bind
绑定,v-bind:x="hello"
的意思是将表达式执行的结果绑定
给x
v-bind
可以给标签里的任何一个标签属性动态的绑定值
v-bind
还可以简写为:
,比如:href="school.url"
也是将表达式执行的结果绑定
给href
适用于解析标签(包括:标签属性、标签内容、绑定事件......)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue模板语法</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
<hr/>
<h1>指令语法</h1>
<a v-bind:href="school.url" v-bind:x="hello">点我去{{school.name}}学习1</a>
<a :href="school.url.toUpperCase()" :x="hello" :date="Date.now()">点我去{{school.name}}学习1</a>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name:'jack',
hello:'你好',
school:{
name:'尚硅谷',
url:'http://www.atguigu.com'
}
}
})
</script>
</html>
v-model 双向数据绑定
双向数据绑定v-model
数据不仅能从data
流向页面,还可以从页面流向data
需要注意的是 v-model
只能用于表单类元素(输入类元素)
上,具体原因是要与用户进行数据交互,而不能进行交互的元素则无法使用 v-model 双向数据绑定
v-model
还可以简写为v-model=""
,比如v-model="name"
由于v-model
绑定的就是value
值,所以可以直接简写去掉value
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据绑定</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 普通写法 -->
单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name"><br/>
<!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
<h2 v-bind:x="name">你好啊</h2>
<!-- 简写 -->
单向数据绑定:<input type="text" :value="name"><br/>
双向数据绑定:<input type="text" v-model="name"><br/>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name:'尚硅谷'
}
})
</script>
</html>
el与data的两种写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>el与data的两种写法</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>你好,{{name}}</h1>
</div>
</body>
<script type="text/javascript">
// 指定容器:第一种写法,使用el来指定容器
// new Vue({
// el:'#root',
// data:{
// name:'尚硅谷'
// }
// })
// 指定容器:第二种写法,使用 对象.$mount('容器')来指定容器,这种方式更灵活一些,下面例子延迟1秒再与容器关联进行解析
// const v = new Vue({
// data:{
// name:'尚硅谷'
// }
// })
// setTimeout(() => {
// v.$mount('#root')
// }, 1000);
// data:第一种写法,对象式
// new Vue({
// el:'#root',
// data:{
// name:'尚硅谷'
// }
// })
// data:第二种写法,函数式
new Vue({
el:'#root',
data:function(){
console.log('@@@',this); // 此处的this是Vue实例对象
return{
name:'尚硅谷'
}
}
})
</script>
</html>
MVVM模型
Vue
虽然没有完全遵循 MVVM 模型
,但是 Vue
的设计也受到了它的启发。因此在文档中经常会使用 vm
(ViewModel 的缩写) 这个变量名表示 Vue 实例
。
维基百科(MVVM):https://zh.wikipedia.org/wiki/MVVM
M
:模型(Model):对应 data 中的数据
V
:视图(View):模板
VM
:视图模型(ViewModel):Vue 实例对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue中的MVVM模型</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- View -->
<div id="root">
<h1>学校名称,{{name}}</h1>
<h1>学校地址,{{address}}</h1>
<h1>vm中的$options,{{$options}}</h1>
<h1>vm中的$emit,{{$emit}}</h1>
</div>
</body>
<script type="text/javascript">
// ViewModel
const vm = new Vue({
el:'#root',
data:{
// Model
name:'尚硅谷',
address:'北京'
}
})
console.log(vm);
</script>
</html>
观察发现:
data
中所有属性,最后都出现在了vm
身上
vm
身上所有的属性及Vue
原型上所有属性,在Vue
模板中都可以直接使用
数据代理
回顾Object.defineProperty方法
Object.defineProperty
Object.defineProperty(对象,属性名, {配置项})
给一个对象添加属性
配置项中enumerable:true
控制属性是否可以枚举(迭代遍历),默认值是false
配置项中writable:true
控制属性是否可以修改,默认值是false
配置项中configurable:true
控制属性是否可以删除,默认值是false
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>回顾Object.defineProperty方法</title>
</head>
<body>
</body>
<script type="text/javascript">
let person = {
name:'张三',
sex:'男'
}
// Object.defineProperty(对象,属性名, {配置项}) 给一个对象添加属性
// 默认不可被枚举的(不可被遍历)
Object.defineProperty(person,'age',{
value:18
});
console.log(person);
console.log("遍历:",Object.keys(person));
// Object.defineProperty(对象,属性名, {配置项}) 给一个对象添加属性
Object.defineProperty(person,'height',{
value:183,
enumerable:true, // 控制属性是否可以枚举,默认值是false
writable:true, // 控制属性是否可以修改,默认值是false
configurable:true // 控制属性是否可以删除,默认值是false
});
console.log(person);
</script>
</html>
Object.defineProperty 中 get set方法实现数据代理【面试题】
下面例子与java实体类的get set
异曲同工
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>回顾Object.defineProperty方法</title>
</head>
<body>
</body>
<script type="text/javascript">
// let person = {
// name:'张三',
// sex:'男'
// }
// Object.defineProperty(对象,属性名, {配置项}) 给一个对象添加属性
// 默认不可被枚举的(不可被遍历)
// Object.defineProperty(person,'age',{
// value:18
// });
// console.log(person);
// console.log("遍历:",Object.keys(person));
// Object.defineProperty(对象,属性名, {配置项}) 给一个对象添加属性
// Object.defineProperty(person,'height',{
// value:183,
// enumerable:true, // 控制属性是否可以枚举,默认值是false
// writable:true, // 控制属性是否可以修改,默认值是false
// configurable:true // 控制属性是否可以删除,默认值是false
// });
// console.log(person);
let number = 18;
let person = {
name:'张三',
sex:'男'
}
// person中定义一个值 age
Object.defineProperty(person,'age',{
// 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get(){
console.log('有人读取了age属性');
return number;
},
// 当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value){
console.log('有人修改了age属性,且值是',value);
number = value; // 重新对number赋值
}
})
</script>
</html>
通过一个对象代理对另一个对象属性的操作(读/写)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>何为数据代理</title>
</head>
<body>
</body>
<script type="text/javascript">
let obj = {
x:100
};
let obj2 = {
y:200
}
Object.defineProperty(obj2,'x',{
// 对 obj2 读取 x 就返回 obj 的 x
get(){
return obj.x;
},
// 对 obj2 写入 x 就往 obj 中写入 x
set(value){
obj.x = value;
}
})
</script>
</html>
Vue的数据代理
Vue
中的数据代理:
通过vm
对象来代理data
对象中的操作(读/写)。
Vue
中数据代理的好处:
更加方便的操作data
中的数据。
基本原理:
通过Object.defineProperty()
把data
对象中所有属性添加到vm
上。
为每一个添加到vm
上的属性,都指定一个getter/setter
。
在getter/setter
内部去操作(读/写)data
中对应的属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue中的数据代理</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>学校名称,{{_data.name}}</h1>
<h1>学校地址,{{address}}</h1>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:function(){
return{
name:'尚硅谷',
address:'宏福科技园'
}
}
})
</script>
</html>
我们创建了一个vm
实例对象,Vue
就开始为vm
准备一些东西,如$attrs:xxx,$children:xxx,......
以及_data
,而_data
的数据源自于vm中的data
,而在此时我们去用vm.name
vm.address
取数据时是取不到的,因为vm
中没有,但vm
中有_data
我们可以通过_data
来获取数据,而我们的代码只能写成{{_data.name}}
,我们如果这么写那就疯了,而Vue
又做了这样一件事,首先Vue
拿到这个vm实例对象
后他就开始往里面加东西,加什么呢?加name
,Vue
会通过get
去读取_data
里面的name
,要是有人改就会通过set
去改掉_data
里面的name
,同理address
也是一样的,而Vue的数据代理就干了一件事儿,把_data中的数据放到vm中一份,为的就是方便编码,如果vue没做数据代理,我们写代码就要_data.name.......有了数据代理就可以直接用name,Vue中的数据代理就是通过Object.defineProperty实现的。
事件处理
Vue事件绑定
事件的基本使用:
- 使用
v-on:xxx
或@xxx
绑定事件,其中xxx
是事件名; - 事件的回调需要配置在
methods
对象中,最终会在vm
上; methods
中配置的函数,不要用箭头函数!
否则this
就不是vm
了;methods
中配置的函数,都是被Vue
所管理的函数,this
的指向是vm
或组件实例对象
;@click="demo"
和@click="demo($event)"
效果一致,但后者可以传参;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件的基本使用</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>欢迎来到,{{name}}学习</h1>
<!-- 点击事件 -->
<button v-on:click="showInfo">点我提示信息</button>
<!-- 简写点击事件 -->
<button @click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:function(){
return{
name:'尚硅谷'
}
},
methods: {
// 方法不要放在data里,放在上方data里也可以使用,但Vue会很累,因为在data里的东西都会进行数据代理
showInfo(event){
// console.log(event.target.innerText); // 输出 点我提示信息
// console.log(this == vm); // 此处的this是vm
alert('同学你好');
},
showInfo1(event){
alert('同学你好!');
},
showInfo2(event,number){
console.log(event,number);
alert('同学你好!!');
}
},
})
</script>
</html>
事件修饰符
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件修饰符</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
<style>
*{
margin-top: 20px;
}
.demo1{
height: 50px;
background-color: skyblue;
}
.box1{
padding: 5px;
background-color: skyblue;
}
.box2{
padding: 5px;
background-color: orange;
}
.list{
width: 200px;
height: 200px;
background-color: peru;
overflow: auto; /* 滚动 */
}
li{
height: 100px;
}
</style>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>欢迎来到,{{name}}学习</h1>
<!-- 阻止默认事件(常用)使a标签本身的跳转逻辑不生效,事件修饰符:prevent -->
<button href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</button>
<!-- 阻止事件冒泡(常用)点的是按钮,冒泡冒到div上造成点击事件出现2次,事件修饰符:stop -->
<div class="demo1" @click="showInfo">
<!-- 修饰符可以连续写 -->
<!-- <a href="http://www.atguigu.com" @click.prevent.stop="showInfo">点我提示信息</a> -->
<button @click.stop="showInfo">点我提示信息</button>
</div>
<!-- 事件只触发一次(常用)点击这个按钮只有第一次生效,事件修饰符:once -->
<button @click.once="showInfo">点我提示信息</button>
<!-- 使用事件的捕获模式,我们点击div2的时候默认先输出2在输出1因为先经过的阶段是事件捕获随后才是事件冒泡
,默认情况下事件冒泡才是处理事件的,先捕获1再捕获2然后再冒泡2再冒泡1,就会导致先输出2在输出1
,如果想让再捕获阶段就开始处理事件就可以利用 capture 修饰符,我们再div1上增加此修饰符
,由于是先捕获再冒泡而我们又使用了事件捕获模式就会导致在捕获阶段就开始处理事件了,会先输出1再输出2 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<!-- 只有event.target是当前操作的元素时才触发事件,当我点击button时会弹出2次,我们给div上加上事件修饰符:self
,就可以解决,当我们点击div时才会触发div上的事件,当我们点击button的时候则不会触发div上的事件 -->
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕,当我们添加滚动条事件后,我们做了一堆费时的处理会导致滚动条需要等待一段时间才能滚动
,而加了事件修饰符:passive 就可以立即执行默认行为不会造成滚动条卡顿 -->
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:function(){
return{
name:'尚硅谷'
}
},methods: {
showInfo(e){
// e.preventDefault(); // 阻止默认行为,使a标签本身的跳转逻辑不生效
// e.stopPropagation(); // 阻止事件冒泡(常用)点的是按钮,冒泡冒到div上造成点击事件出现2次
alert('同学你好!');
},
showMsg(msg){
console.log(msg);
},
demo(){
for (let id = 0; id < 10000; id++) {
console.log('#');
}
console.log('累坏了');
}
},
})
</script>
</html>
键盘事件
@keydown
键盘按下去的时候触发
@keyup
键盘按下去再弹回来的时候触发
添加别名:enter
即可表明是按下enter
键触发,而Vue给一些常用的按键都加了
1、Vue
中常用的别名:
回车 ==> enter
删除 ==> delete(捕获“删除”和“退格”键)
退出 ==> esc
空格 ==> space
换行==> tab
上 ==> up
下 ==> down
左 ==> left
右 ==> right
2、Vue
中未提供的别名按键,可以使用按键原始的key
值去绑定,但注意要转为kebab-case(短横线命名)
3、系统修饰键(用法特殊):ctrl、alt、shift、meta
(1)配合keyup
使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才能被触发。
(2)配合keydown
使用:正常触发事件。
4、也可以使用keyCode
去指定具体的按键(不推荐)
5、Vue.config.keyCodes.自定义键名 = 键码
,可以去定制按键别名
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>键盘事件</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>欢迎来到,{{name}}学习</h1>
<!-- @keydown 键盘按下去的时候触发 -->
<!-- @keyup 键盘按下去再弹回来的时候触发 -->
<!-- 添加别名:enter 即可表明是按下enter键触发,而Vue给一些常用的按键都加了
1、Vue中常用的别名:
回车 ==> enter
删除 ==> delete(捕获“删除”和“退格”键)
退出 ==> esc
空格 ==> space
换行 ==> tab
上 ==> up
下 ==> down
左 ==> left
右 ==> right
2、Vue中未提供的别名按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
3、系统修饰键(用法特殊):ctrl、alt、shift、meta
(1)配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才能被触发。
(2)配合keydown使用:正常触发事件。
4、也可以使用keyCode去指定具体的按键(不推荐)
5、Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
-->
<input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo">
<!-- <input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo"> -->
<!-- keyup可以连续写,输入 ctrl+y 的时候提示 -->
<input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="showInfo">
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name:'尚硅谷'
},
methods:{
showInfo(e){
// if(e.keyCode != 13){ // 原始方法,判断按键的keyCode是不是回车键
// return;
// }
console.log(e.target.value);
}
}
})
</script>
</html>
计算属性
插值语法实现
此种方式虽然也能实现,输入姓、名实现全名的拼接,但考虑到业务需要截取前三位姓,并且如果有更复杂的需求,那么此种方式虽然也能实现,但不符合vue风格指南中的规范【模板中简单的表达式】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>姓名案例_插值语法实现</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName"><br/><br/>
名:<input type="text" v-model="lastName"><br/><br/>
<!-- slice截取 -->
全名:<span>{{firstName.slice(0,3)}}-{{lastName}}</span>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
}
})
</script>
</html>
methods实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>姓名案例_methods实现</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName"><br/><br/>
名:<input type="text" v-model="lastName"><br/><br/>
全名:<span>{{fullName()}}</span>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
methods: {
fullName(){
console.log('@---fullName');
return this.firstName.slice(0,3) + '-' + this.lastName;
}
},
})
</script>
</html>
计算属性实现
- 定义:要用的属性不存在,要通过
已有的属性
计算得来。 - 原理:底层借助了
Object.defineproperty
方法提供的getter
和setter
。 get函数
什么时候执行?
(1)初次读取时会执行一次。
(2)所依赖的数据发生变化时会再次调用。优势
:与methods
实现相比,内部有缓存机制(复用)
,效率更高,调试方便。- 备注:
(1)计算属性最终会出现在vm
上,直接读取使用即可。
(2)如果计算属性要被修改,那必须写set函数
去响应修改,且set
中要引起计算时依赖的数据发生改变。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>姓名案例_计算属性实现</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName"><br/><br/>
名:<input type="text" v-model="lastName"><br/><br/>
全名:<span>{{fullName}}</span>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
// 计算属性
computed:{
/*
// 完整写法
// 属性
fullName:{
// get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
// get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
get(){
return this.firstName + '-' + this.lastName;
},
// set什么时候调用?当fullName被修改时。
set(value){
console.log('fullName的set被调用,',value);
const arr = value.split('-');
this.firstName = arr[0];
this.lastName = arr[1];
}
}
*/
// 简写,当只考虑读取而不考虑修改时,可以使用此简写方式
fullName(){
return this.firstName + '-' + this.lastName;
}
}
})
</script>
</html>
监视属性
天气案例_计算属性实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天气案例_计算属性实现</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>今天天气很{{info}},{{x}}</h2>
<!-- 绑定事件的时候:@事件='可以写一些简单的语句' -->
<button @click="isHot = !isHot;x++">切换天气</button>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
isHot:true,
x:1
},
// 计算属性
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
this.x++
}
},
})
</script>
</html>
天气案例_监视属性
监视属性watch
:
- 当被监视的属性
变化时
,回调函数自动调用
,进行相关操作。 - 监视的属性
必须存在
,才能监视!! - 监视属性两种写法:
(1)new Vue
时传入watch
配置
(2)通过vm.$watch
监视
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天气案例_监视属性</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
isHot:true
},
// 计算属性
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
this.x++
}
},
// 监视属性 第一种写法
watch:{
// 监视isHot属性
isHot:{
// immediate:true, // 初始化时让handler调用一下
// handler什么时候调用?当isHot发生改变时
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
}
},
// 监视info属性
info:{
// immediate:true, // 初始化时让handler调用一下
// handler什么时候调用?当isHot发生改变时
handler(newValue,oldValue){
console.log('info被修改了',newValue,oldValue);
}
}
}
});
// 监视属性 第二种写法
/*
vm.$watch('isHot',{
// immediate:true, // 初始化时让handler调用一下
// handler什么时候调用?当isHot发生改变时
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
}
})
*/
</script>
</html>
深度监视
深度监视:
(1)Vue
中的watch
默认不监测对象内部值
的改变(一层)。
(2)配置deep:true
可以监测对象内部值改变(多层)。
备注:
(1)Vue
自身可以监测对象内部值的改变,但Vue
提供的watch默认不可以!
(2)使用watch
时根据数据的具体结构,决定是否采用深度监视。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>深度监视</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h3>a的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让a+1</button>
<hr/>
<h3>b的值是:{{numbers.b}}</h3>
<button @click="numbers.b++">点我让b+1</button>
<button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
numbers:{
a:1,
b:1
}
},
// 监视属性
watch:{
// 监视多级结构中某个属性的变化
// 'numbers.a':{
// // handler什么时候调用?当numbers发生改变时
// handler(newValue,oldValue){
// console.log('info.a 被修改了',newValue,oldValue);
// }
// }
// 监视多级结构中所有属性的变化
numbers:{
deep:true, // Vue的watch默认不监测对象内部值的改变(一层),配置 deep:true 可以监测对象内部值改变(多层)
handler(newValue,oldValue){
console.log('info 被修改了',newValue,oldValue);
}
}
}
});
</script>
</html>
天气案例_监视属性_简写
需要注意的是,简写监视属性
就无法再进行其他项配置
了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天气案例_监视属性_简写</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
isHot:true
},
// 计算属性
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
this.x++
}
},
// 监视属性 第一种写法
watch:{
/*
// 监视isHot属性【正常写法】
isHot:{
// handler什么时候调用?当isHot发生改变时
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
}
}
*/
/*
// 监视isHot属性【简写】
isHot(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
}
*/
}
});
/*
// 监视属性 第二种写法【正常写法】
vm.$watch('isHot',{
// handler什么时候调用?当isHot发生改变时
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
}
})
*/
// 监视属性 第二种写法【简写】
vm.$watch('isHot',function(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue);
})
</script>
</html>
姓名案例_watch实现
computed计算属性
和watch监视属性
之间的区别:
(1)computed
能完成的功能,watch
都可以完成。
(2)watch
能完成的功能,computed
不一定能完成,例如watch
可以进行异步操作
。
两个重要的小原则:
(1)所被Vue
管理的函数,最好写成普通函数,这样this
的指向才是vm
或组件实例对象。
(2)所有不被Vue
所管理的函数(定时器的回调函数,ajax的回调函数等),最好写成箭头函数,这样this
的指向才是vm
或组件实例对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>姓名案例_watch实现</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName"><br/><br/>
名:<input type="text" v-model="lastName"><br/><br/>
全名:<span>{{fullName}}</span>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName(newValue){
// setTimeout一定要写成箭头函数,因为定时器所指向的回调是js引擎帮忙调用的,而且在帮忙调的时候this已经给指定好window了
// 如果我们写成箭头函数,虽然也是js引擎帮忙调用的setTimeout,但是因为写成了箭头函数,这样他就没有了自己的this
//,没有自己的this就会往外找,往外找的外就是firstName的this,而firstName写的是普通函数,普通函数在vue中的this就是vue
setTimeout(() => {
this.fullName = newValue + '-' + this.lastName;
}, 1000);
},
lastName(newValue){
this.fullName = this.firstName + '-' + newValue;
}
}
})
</script>
</html>
绑定样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>绑定样式</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
<style>
.basic{
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy{
background-color:aquamarine;
}
.sad{
background-color:bisque;
}
.normal{
background-color:blueviolet;
}
.atguigu1{
font-weight: bold;
}
.atguigu2{
background-color: yellow;
}
.atguigu3{
border-radius: 10px;
}
</style>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 绑定class样式,字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">
{{name}}
</div>
<br/>
<!-- 绑定class样式,数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">
{{name}}
</div>
<br/>
<!-- 绑定class样式,对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">
{{name}}
</div>
<br/>
<!-- 绑定style样式,对象写法 -->
<div class="basic" :style="styleObj">
{{name}}
</div>
<br/>
<!-- 绑定style样式,数组写法 -->
<div class="basic" :style="styleArr">
{{name}}
</div>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data: {
name:'尚硅谷',
mood: 'normal',
classArr:['atguigu1','atguigu2','atguigu3'],
classObj:{
atguigu1:true,
atguigu2:false,
atguigu3:true
},
styleObj:{
fontSize:'40px'
},
styleArr:[
{
fontSize:'40px'
},
{
backgroundColor:'orange'
}
]
},
methods: {
changeMood(){
const arr = ['happy','sad','normal']
const index = Math.floor(Math.random()*3)
this.mood = arr[index]
}
}
})
</script>
</html>
条件渲染
-
v-if
写法:
(1)v-if="表达式"
(2)v-else-if="表达式"
(3)v-else
适用于:切换频率较低的场景。
特点:不展示DOM
元素直接被移除。
注意:v-if
可以和v-else-if
、v-else
一起使用,但要求结构不能被“打断
”。 -
v-show
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示DOM
元素未被移除,仅仅是使用样式隐藏掉。 -
备注:使用
v-if
的时候,元素可能无法获取到,而使用v-show
一定可以获取到。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>条件渲染</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 使用v-show做条件渲染 -->
<!-- <h2 v-show="false">欢迎来到{{name}}</h2>
<h2 v-show="a">欢迎来到{{name}}</h2>
<h2 v-show="1 === 1">欢迎来到{{name}}</h2> -->
<!-- 使用v-if做条件渲染,与v-show的区别是:v-show是加display,而v-if是整个模块是否保留 -->
<!-- <h2 v-if="false">欢迎来到{{name}}</h2>
<h2 v-if="a">欢迎来到{{name}}</h2>
<h2 v-if="1 === 1">欢迎来到{{name}}</h2> -->
<h2>当前的值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
<!-- 当if条件满足时不再执行下面的v-else-if,不满足时再执行v-else-if,如果都不满足就执行v-else,如果要使用v-else-if、v-else则不允许被打断并且必须存在一个v-if -->
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div>
<!-- template模板在页面渲染显示的时候是不存在此标签的,可以用于包裹判断,但template只能与v-if组合并不能与v-show组合 -->
<template v-if="n === 1">
<h2>你好</h2>
<h2>尚硅谷</h2>
<h2>北京</h2>
</template>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data: {
name:'尚硅谷',
a:false,
n:1
}
})
</script>
</html>
列表渲染
v-for指令实现基本列表
(1)用于展示列表数据
(2)语法:v-for="(item, index) in xxx" :key="yyy"
(3)可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>条件渲染</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<ul>
<li v-for="(p, index) in persons" :key="p.id">
{{index}}-{{p.id}}-{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息</h2>
<ul>
<li v-for="(value, key) in car" :key="key">
{{key}}-{{value}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串</h2>
<ul>
<li v-for="(char, index) in str" :key="index">
{{char}}-{{index}}
</li>
</ul>
<!-- 遍历指定次数 -->
<h2>测试遍历指定次数</h2>
<ul>
<li v-for="(number, index) in 5">
{{number}}-{{index}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data: {
persons:[
{"id":"001","name":"张三","age":18},
{"id":"002","name":"李四","age":19},
{"id":"003","name":"王五","age":20}
],
car:{
name:'奥迪A8',
price:'70万',
color:'黑色'
},
str:'hello'
}
})
</script>
</html>
v-for中:key的原理【面试题】
面试题:react、vue
中的key
有什么作用?(key
的内部原理)
1、虚拟DOM
中key
的作用:
key
是虚拟DOM对象的标识
,当数据发生变化时,Vue
会根据【新数据
】生成【新的虚拟DOM
】随后Vue
进行【新虚拟DOM
】与【旧虚拟DOM
】的差异比较,比较规则如下:
2、对比规则:
旧虚拟DOM
中找到了与新虚拟DOM
相同的key
:-
- 若
虚拟DOM
中内容没变,直接使用之前的真实DOM
!
- 若
-
- 若
虚拟DOM
中内容变了,则生成新的真实DOM
,随后替换掉页面中之前的真实DOM
。
- 若
-
旧虚拟DOM
中未找到与新虚拟DOM
相同的key
创建新的真实DOM
,随后渲染到到页面。
-
3、用index
作为key
可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的
真实DOM
更新,界面效果没问题,但效率低。 - 如果结构中还包含
输入类的DOM
:会产生错误DOM更新
界面有问题。
4、开发中如何选择key
?:
- 最好使用每条数据的
唯一标识作为key
,比如id、手机号、身份证号、学号等唯一值。 - 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用
index
作为key
是没有问题的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Key的原理</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<button @click="add">添加一个老刘</button>
<ul>
<!-- :key给节点进行标识,需要使用数据的唯一标识,Key是Vue内部用的,不会进行DOM展示 -->
<li v-for="(p, index) in persons" :key="index">
{{index}}-{{p.id}}-{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data: {
persons:[
{"id":"001","name":"张三","age":18},
{"id":"002","name":"李四","age":19},
{"id":"003","name":"王五","age":20}
]
},
methods: {
add(){
const p = {id:"004",name:'老刘',age:40}
this.persons.unshift(p);
}
}
})
</script>
</html>
当我们点击添加一个老刘
按钮的时候,因为我们打乱了数据结构,把老刘添加到了最上方,而我们的:key
用的是index索引
,就会出现如下问题,具体原因看下图:
列表过滤
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>列表过滤</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<!-- :key给节点进行标识,需要使用数据的唯一标识,Key是Vue内部用的,不会进行DOM展示 -->
<li v-for="(p, index) in filPerons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
// 用watch实现
/* new Vue({
el:'#root',
data: {
keyWord:'',
persons:[
{"id":"001","name":"马冬梅","age":19,sex:'女'},
{"id":"002","name":"周冬雨","age":20,sex:'女'},
{"id":"003","name":"周杰伦","age":21,sex:'男'},
{"id":"004","name":"温兆伦","age":22,sex:'男'}
],
filPerons:[]
},
watch: {
keyWord:{
immediate:true,
handler(val){
this.filPerons = this.persons.filter((p)=>{
return p.name.indexOf(val) !== -1
})
}
}
}
}) */
// 用computed实现
new Vue({
el:'#root',
data: {
keyWord:'',
persons:[
{"id":"001","name":"马冬梅","age":19,sex:'女'},
{"id":"002","name":"周冬雨","age":20,sex:'女'},
{"id":"003","name":"周杰伦","age":21,sex:'男'},
{"id":"004","name":"温兆伦","age":22,sex:'男'}
]
},
computed: {
filPerons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
}
}
})
</script>
</html>
列表排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>列表过滤</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<!-- :key给节点进行标识,需要使用数据的唯一标识,Key是Vue内部用的,不会进行DOM展示 -->
<li v-for="(p, index) in filPerons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data: {
keyWord:'',
sortType:0,// 0原顺序 1降序 2升序
persons:[
{"id":"001","name":"马冬梅","age":30,sex:'女'},
{"id":"002","name":"周冬雨","age":31,sex:'女'},
{"id":"003","name":"周杰伦","age":18,sex:'男'},
{"id":"004","name":"温兆伦","age":19,sex:'男'}
]
},
computed: {
filPerons(){
const arr = this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
// 判断一下是否需要排序,0为false,其余为true
let sortType = this.sortType;
if(sortType){
arr.sort((p1,p2)=>{
return sortType === 1 ? p2.age-p1.age : p1.age-p2.age
})
}
return arr;
}
}
})
</script>
</html>
Vue监测的原理
更新时的一个问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>更新时的一个问题</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<button @click="updateMei">更新马冬梅信息</button>
<ul>
<!-- :key给节点进行标识,需要使用数据的唯一标识,Key是Vue内部用的,不会进行DOM展示 -->
<li v-for="(p, index) in persons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data: {
persons:[
{"id":"001","name":"马冬梅","age":30,sex:'女'},
{"id":"002","name":"周冬雨","age":31,sex:'女'},
{"id":"003","name":"周杰伦","age":18,sex:'男'},
{"id":"004","name":"温兆伦","age":19,sex:'男'}
]
},methods: {
updateMei(){
// this.persons[0].name = '马老师' // 奏效
// this.persons[0].age = 50 // 奏效
// this.persons[0].sex = '男' // 奏效
this.persons[0] = {"id":"001","name":"马老师","age":50,sex:'男'}
}
}
})
</script>
</html>
先点开Vue开发者工具
,在点更新马冬梅信息按钮
,会发现Vue开发者工具
中没有变
要是先点更新马冬梅信息按钮
,再点开Vue开发者工具
,会发现Vue开发者工具
中变了
这么写就奏效了,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>更新时的一个问题</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<button @click="updateMei">更新马冬梅信息</button>
<ul>
<!-- :key给节点进行标识,需要使用数据的唯一标识,Key是Vue内部用的,不会进行DOM展示 -->
<li v-for="(p, index) in persons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data: {
persons:[
{"id":"001","name":"马冬梅","age":30,sex:'女'},
{"id":"002","name":"周冬雨","age":31,sex:'女'},
{"id":"003","name":"周杰伦","age":18,sex:'男'},
{"id":"004","name":"温兆伦","age":19,sex:'男'}
]
},methods: {
updateMei(){
// this.persons[0].name = '马老师' // 奏效
// this.persons[0].age = 50 // 奏效
// this.persons[0].sex = '男' // 奏效
// this.persons[0] = {"id":"001","name":"马老师","age":50,sex:'男'} // 不奏效
this.persons.splice(0,1,{"id":"001","name":"马老师","age":50,sex:'男'}) // 奏效
}
}
})
</script>
</html>
Vue数据改变原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue监测数据改变原理</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data: {
name:'尚硅谷',
address:'北京',
student:{
name:'tom',
age:{
rAge:40,
sAge:29
},
friends:[
{name:'jerry',age:35}
]
}
}
})
</script>
</html>
模拟一个数据监测
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模拟一个数据监测</title>
</head>
<body>
</body>
<script type="text/javascript">
let data = {
name:'尚硅谷',
address:'北京'
}
// 创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
// 准备一个vm实例对象
let vm = {}
vm._data = data = obs
function Observer(obj){
// 汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
// 遍历
keys.forEach((k)=>{
Object.defineProperty(this,k,{
get(){
return obj[k]
},
set(val){
console.log(`${k}被改变了,我要去解析模板,生成虚拟DOM......我要开始忙了`)
obj[k] = val
}
})
})
}
</script>
</html>
Vue.set的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue.set的使用</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr/>
<h2>姓名:{{student.name}}</h2>
<h2>性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="(f, index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data: {
name:'尚硅谷',
address:'北京',
student:{
name:'tom',
age:{
rAge:40,
sAge:29
},
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
}
})
</script>
</html>
我们直接往vm._data
中添加一个没有定义的属性,虽然在vm
中可以查到,但没有响应式
想要实现响应式,可以使用Vue.set
方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue.set的使用</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>学校信息</h1>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr/>
<h1>学生信息</h1>
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>姓名:{{student.name}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="(f, index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data: {
name:'尚硅谷',
address:'北京',
student:{
name:'tom',
age:{
rAge:40,
sAge:29
},
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
// 方式1
// this.$set(this.student,'sex','男')
// 方式2
Vue.set(this.student,'sex','男')
}
},
})
</script>
</html>
但是Vue.set
并不是万能的,它必须用于响应式对象上添加新 property
,因为Vue
无法探测普通的新增property
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue.set的使用</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>学校信息</h1>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<h2>校长是:{{leader}}</h2>
<hr/>
<h1>学生信息</h1>
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>姓名:{{student.name}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="(f, index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data: {
name:'尚硅谷',
address:'北京',
student:{
name:'tom',
age:{
rAge:40,
sAge:29
},
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
// 方式1
// this.$set(this.student,'sex','男')
// 方式2
Vue.set(this.student,'sex','男')
}
},
})
</script>
</html>
而我们改成对象,这就可以了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue.set的使用</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>学校信息</h1>
<h2>学校名称:{{school.name}}</h2>
<h2>学校地址:{{school.address}}</h2>
<h2>校长是:{{school.leader}}</h2>
<hr/>
<h1>学生信息</h1>
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>姓名:{{student.name}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="(f, index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data: {
school:{
name:'尚硅谷',
address:'北京'
},
student:{
name:'tom',
age:{
rAge:40,
sAge:29
},
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
// 方式1
// this.$set(this.student,'sex','男')
// 方式2
Vue.set(this.student,'sex','男')
}
},
})
</script>
</html>
Vue监测数据改变的原理_数组
push
新增
pop
删除最后一个元素
shift
删除第一个元素
unshift
往前面加一个元素
splice
替换数组的某个元素
sort
排序
reverse
反转数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue监测数据改变的原理_数组</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>学校信息</h1>
<h2>学校名称:{{school.name}}</h2>
<h2>学校地址:{{school.address}}</h2>
<h2>校长是:{{school.leader}}</h2>
<hr/>
<h1>学生信息</h1>
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>姓名:{{student.name}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<h2>爱好</h2>
<ul>
<li v-for="(h, index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h2>朋友们</h2>
<ul>
<li v-for="(f, index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data: {
school:{
name:'尚硅谷',
address:'北京'
},
student:{
name:'tom',
age:{
rAge:40,
sAge:29
},
hobby:['抽烟','喝酒','烫头'],
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
// 方式1
// this.$set(this.student,'sex','男')
// 方式2
Vue.set(this.student,'sex','男')
}
}
})
</script>
</html>
Vue
是怎么知道我们调用的是push
这些命令的呢?是因为Vue
对这些命令进行了封装
,封装
完就已经不再是js原型对象
上的push
了,而是Vue
的push
。
Vue官方文档
也可以使用Vue.set
方法来对数组进行操作
总结Vue数据监测
Vue
监视数据的原理:
1、vue
会监视data
中所有层次的数据。
2、如何监测对象中的数据?
通过setter
实现监视,且要在new Vue
时就传入要监测的数据。
- 对象中后追加的属性,
Vue
默认不做响应式处理。 - 如需给后添加的属性做响应式,请使用如下API:
-
Vue.set(target,propertyName/index,value)
或vm.$set(target,propertyName/index,value)
3、如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新。
- 重新解析模板,进而更新页面。
4、在Vue
修改数组中某个元素一定要用如下方法:
- 使用这些API:
push()、pop()、shift()、unshift()、splice()、sort()、reverse()
Vue.set()
或vm.$set()
特别注意:Vue.set()
和vm.$set
不能给vm
或vm
的根数据对象添加属性!!!
数据劫持
:如果有人修改了某个属性,那马上就被set劫持
了,劫持到后做了两件事,第一件事:正常给你改数据,第二件事:重新解析模板。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>总结Vue数据监测</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">年龄+1岁</button>
<button @click="addSex">添加性别属性,默认值:男</button>
<button @click="student.sex = '未知'">修改性别</button>
<button @click="addFriend">在列表首位添加一个朋友</button>
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button>
<button @click="addHobby">添加一个爱好</button>
<button @click="updateHobby">修改第一个爱好为:开车</button>
<h2>姓名:{{student.name}}</h2>
<h2>年龄:{{student.age}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>爱好</h2>
<ul>
<li v-for="(h, index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h2>朋友们:</h2>
<ul>
<li v-for="(f, index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data: {
student:{
name:'tom',
age:18,
hobby:['抽烟','喝酒','烫头'],
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
// Vue.set(this.student,'sex','男')
this.$set(this.student,'sex','男')
},
addFriend(){
this.student.friends.unshift({name:'jack',age:70})
},
updateFirstFriendName(){
this.student.friends[0].name = '张三';
},
addHobby(){
this.student.hobby.push('学习')
},
updateHobby(){
// this.student.hobby.splice(0,1,'开车')
Vue.set(this.student.hobby,0,'开车')
}
},
})
</script>
</html>
收集表单数据
若:<input type="text”>
,则v-model
收集的是value
值,用户输入的就是value
值。
若:<input type="radio“/>
,则v-model
收集的是value
值,且要给标签配置value
值。
若:<input type="checkbox"/>
1.没有配置input
的value
属性,那么收集的就是checked
(勾选 or 未勾选,是布尔值
)
2.配置input
的value
属性:
(1)v-model
的初始值是非数组,那么收集的就是checked
(勾选 or 未勾选,是布尔值
)
(2)v-model
的初始值是数组,那么收集的的就是value
组成的数组。
备注:v-model
的三个修饰符:
lazy
:失去焦点再收集数据。
number
:输入字符串转为有效的数字。
trim
:输入首尾空格过滤。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>收集表单数据</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<form @submit.prevent="demo">
<!-- v-mmodel.trim去掉前后的空格 -->
账号:<input type="text" v-model.trim="account"><br/><br/>
密码:<input type="password" v-model="password"><br/><br/>
<!-- v-model.number强制Vue数据类型是数字 -->
年龄:<input type="number" v-model.number="age"><br/><br/>
性别:
男:<input type="radio" name="sex" v-model="sex" value="male">
女:<input type="radio" name="sex" v-model="sex" value="female"><br/><br/>
爱好:
学习<input type="checkbox" v-model="hobby" value="study">
打游戏<input type="checkbox" v-model="hobby" value="game">
吃饭<input type="checkbox" v-model="hobby" value="eat"><br/><br/>
所属校区:
<select v-model="city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
<option value="wuhan">武汉</option>
</select><br/><br/>
其他信息:
<!-- v-model.lazy懒加载,等失去焦点的一瞬间再收集数据 -->
<textarea v-model.lazy="other"></textarea><br/><br/>
<input type="checkbox" v-model="agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
<button>提交</button>
</form>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
account:'',
password:'',
age:18,
sex:'female',
hobby:[],
city:'beijing',
other:'',
agree:false
},methods: {
demo(){
console.log(JSON.stringify(this._data));
}
}
})
</script>
</html>
过滤器
定义:
对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
1.注册过滤器:Vue.filter(name,callback)
或 new Vue{filters:{}}
2.使用过滤器:{{ xxx | 过滤器名}}
或 v-bind:属性="xxx | 过滤器名"
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据,是产生新的对应的数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>过滤器</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
<!-- 引入dayjs -->
<script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 计算属性实现 -->
<h3>现在是:{{fmtTime}}</h3>
<!-- methods实现 -->
<h3>现在是:{{getFmtTime()}}</h3>
<!-- 过滤器实现 -->
<h3>现在是:{{time | timeFormater()}}</h3>
<h3>现在是:{{time | timeFormater('YYYY-MM-DD')}}</h3>
<h3>现在是:{{time | timeFormater('YYYY-MM-DD') | mySlice}}</h3>
<!-- 单向数据绑定可以用过滤器,双向数据绑定不可以用 -->
<h3 :x="msg | mySlice">尚硅谷</h3>
</div>
<div id="root2">
<h2>{{msg | mySlice}}</h2>
</div>
</body>
<script type="text/javascript">
// 全局过滤器,必须在new Vue之前
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
new Vue({
el:'#root',
data:{
time:1694941938417, // 时间戳
msg:'你好,尚硅谷'
},
computed: {
fmtTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
}
},
methods: {
getFmtTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
}
},
// 局部过滤器
filters: {
timeFormater(value,str = 'YYYY-MM-DD HH:mm:ss'){
return dayjs(value).format(str)
},
mySlice(value){
return value.slice(0,4)
}
}
})
new Vue({
el:'#root2',
data:{
msg:'hello,atguigu!'
}
})
</script>
</html>
内置指令
v-test指令
v-test
指令会把所有的内容都看做是文本,并把所有原有的内容覆盖
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v-test指令</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<div>{{name}}</div>
<div v-text="name">你好,</div>
<div v-text="str"></div>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name:'尚硅谷',
str:'<h3>你好啊!</h3>'
}
})
</script>
</html>
v-html指令
v-html
会替换掉节点中所有的内容,插值语法
则不会。v-html
可以识别HTML
结构。- 严重注意:
v-html
有安全性问题!!! -
- 在网页上动态渲染任意
HTML
都是非常危险的,容易导致XSS
攻击。
- 在网页上动态渲染任意
-
- 一定要在可信的内容上使用
v-html
,永远不要用在用户提交的内容上!
- 一定要在可信的内容上使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v-html指令</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<div>{{name}}</div>
<div v-html="str"></div>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name:'尚硅谷',
str:'<h3>你好啊!</h3>'
}
})
</script>
</html>
v-cloak指令
v-cloak
指令(没有值):
1、本质是一个特殊属性,Vue
实例创建完毕并接管容器后,会删掉v-cloak
属性。
2、使用css
配合v-cloak
可以解决网速慢时页面展示出{{xxx}}
的问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v-cloak指令</title>
<style>
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- v-cloak指令(没有值):
1、本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
2、使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。 -->
<div v-cloak>{{name}}</div>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name:'尚硅谷'
}
})
</script>
</html>
v-once指令
v-once
指令(没有值):
1、v-once
所在节点在初次动态渲染后,就视为静态内容了。
2、以后数据的改变不会引起v-once
所在结构的更新,可以用于优化性能。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v-once指令</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- v-once指令:
1、v-once所在节点在初次动态渲染后,就视为静态内容了。
2、以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。 -->
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
n:1
}
})
</script>
</html>
v-pre指令
v-pre
指令(没有值):
1、跳过其所在节点的编译过程。
2、可利用它跳过:没有使用指令语法
、没有使用插值语法
的节点,会加快编译。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v-pre指令</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- v-pre指令(没有值):
1、跳过其所在节点的编译过程。
2、可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。 -->
<h2 v-pre>Vue其实很简单</h2>
<h2 v-pre>当前的n值是:{{n}}</h2>
<button v-pre @click="n++">点我n+1</button>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
n:1
}
})
</script>
</html>
自定义指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义指令</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
需求1: 定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2: 定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
-->
<!-- 准备好一个容器 -->
<div id="root">
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big="n"></span></h2>
<button @click="n++">点我n+1</button>
<hr/>
<input type="text" v-fbind:value="n">
</div>
<div id="root2">
<input type="text" v-fbind:value="x">
</div>
</body>
<script type="text/javascript">
// 全局自定义指令
Vue.directive('fbind',{
// 指令与元素成功绑定时
bind(element,binding){
console.log('big',this) // 注意此处的this是window
element.value = binding.value
},
// 指令所在的元素被插入页面时
inserted(element,binding){
element.focus()
},
// 指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
element.focus()
}
})
new Vue({
el:'#root',
data:{
n:1
},
directives:{
// big函数何时会被调用?1.指令与元素成功绑定时。2.指令所在的模板被重新解析时。
big(element,binding){
element.innerText = binding.value * 10
},
/* fbind:{
// 指令与元素成功绑定时
bind(element,binding){
console.log('big',this) // 注意此处的this是window
element.value = binding.value
},
// 指令所在的元素被插入页面时
inserted(element,binding){
element.focus()
},
// 指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
element.focus()
}
} */
}
})
new Vue({
el:'#root2',
data:{
x:1
}
})
</script>
</html>
自定义指令总结:
配置对象中常用的3个回调:
(1)、bind
:指令与元素成功绑定时调用。
(2)、inserted
:指令所在元素被插入页面时调用。
(3)、update
:指令所在模板结构被重新解析时调用。
备注:
1、指令定义时不加v-
,但使用时要加v-
;
2、指令名如果是多个单词,要使用kebab-case(短横杠)命名方式
,不要用camelCase(驼峰式)命名
。
生命周期
引出生命周期
生命周期
:
1.又名:生命周期回调函数
、生命周期函数
、生命周期钩子
。
2.是什么: Vue
在关键时刻帮我们调用的一些特殊名称的函数。
3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
4.生命周期函数中的this
指向是vm
或组件实例对象
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>引出生命周期</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
生命周期:
1.又名:生命周期回调函数、生命周期函数、生命周期钩子。
2.是什么: Vue在关键时刻帮我们调用的一些特殊名称的函数。
3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
4.生命周期函数中的this指向是vm 或 组件实例对象。
-->
<!-- 准备好一个容器 -->
<div id="root">
<h2 v-if="a">你好啊</h2>
<h2 :style="{opacity}">欢迎学习Vue</h2>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
a:false,
opacity:0.5
},
methods: {
},
// Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted(){
this.a = true
setInterval(()=>{
this.opacity -= 0.01
if(this.opacity <= 0){
this.opacity = 1
}
},16)
}
})
// 通过外部的定时器实现(不推荐)
/* setInterval(()=>{
vm.opacity -= 0.01
if(vm.opacity <= 0){
vm.opacity = 1
}
},16) */
</script>
</html>
分析生命周期
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>分析生命周期</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" :x="n">
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
// template:`
// <div>
// <h2>当前的n值是:{{n}}</h2>
// <button @click="add">点我n+1</button>
// </div>
// `,
data:{
n:1
},
methods: {
add(){
console.log('add')
this.n++
},
bye(){
console.log('bye')
this.$destroy()
}
},
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
},
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
},
beforeDestroy() {
console.log('beforeDestroy')
},
destroyed() {
console.log('destroyed')
},
})
</script>
</html>
总结生命周期
常用的生命周期钩子:
1.mounted
:发送ajax请求
、启动定时器
、绑定自定义事件
、订阅消息
等【初始化操作
】。
2.beforeDestroy
:清除定时器
、解绑自定义事件
、取消订阅消息
等【收尾工作
】。
关于销毁Vue
实例:
1.销毁后借助Vue
开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件
依然有效。
3.一般不会再beforeDestroy
操作数据,因为即便操作数据,也不会再触发更新流程了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>总结生命周期</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
常用的生命周期钩子:
1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例:
1.销毁后借助Vue开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
-->
<!-- 准备好一个容器 -->
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
opacity:0.5
},
methods: {
stop(){
// clearInterval(this.timer)
this.$destroy()
}
},
// Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted(){
this.timer = setInterval(()=>{
this.opacity -= 0.01
if(this.opacity <= 0){
this.opacity = 1
}
},16)
},
// 销毁之前
beforeDestroy() {
console.log('vm即将驾鹤西游了')
clearInterval(this.timer)
}
})
</script>
</html>
组件
对组件的理解
- 模块
-
- 理解:向外提供特定功能的
js
程序,一般就是一个js
文件
- 理解:向外提供特定功能的
-
- 为什么:
js
文件很多很复杂
- 为什么:
-
- 作用:复用
js
,简化js
的编写,提高js
运行效率
- 作用:复用
- 组件
-
- 理解:用来实现局部(特定)功能效果的代码集合(
html/css/js/image.....
)
- 理解:用来实现局部(特定)功能效果的代码集合(
-
- 为什么:一个界面的功能很复杂
-
- 作用:复用编码,简化项目编码,提高运行效率
- 模块化
-
- 当应用中的
js
都以模块来编写的,那这个应用就是一个模块化应用。
- 当应用中的
- 组件化
-
- 当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。
基本使用
Vue
中使用组件的三大步骤:
定义组件(创建组件)
注册组件
使用组件(写组件标签)
-
一:如何定义一个组件?
-
- 使用
Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的那个options
几乎一样,但也有点区别;
- 使用
-
- 区别如下:
-
-
- 1.
el
不要写,为什么? —— 最终所有的组件都要经过一个vm
的管理,由vm
中的el
决定服务哪个容器。
- 1.
-
-
-
- 2.
data
必须写成函数,为什么? —— 避免组件被复用时,数据存在引用关系。
- 2.
-
-
- 备注:使用
template
可以配置组件结构。
- 备注:使用
-
二:如何注册组件?
-
- 1.局部注册:靠
new Vue
的时候传入components
选项
- 1.局部注册:靠
-
- 2.全局注册:靠
Vue.component('组件名',组件)
- 2.全局注册:靠
-
三:编写组件标签:
-
<school></school>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>基本使用</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
Vue中使用组件的三大步骤:
定义组件(创建组件)
注册组件
使用组件(写组件标签)
一:如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
1.el不要写,为什么? —— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,为什么? —— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。
二:如何注册组件?
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)
三:编写组件标签:
<school></school>
-->
<!-- 准备好一个容器 -->
<div id="root">
<hello></hello>
<hr>
<h1>{{msg}}</h1>
<!-- 第三步:编写组件标签 -->
<school></school>
<hr>
<!-- 第三步:编写组件标签 -->
<student></student>
<student></student>
</div>
<div id="root2">
<hello></hello>
</div>
</body>
<script type="text/javascript">
// 第一步:创建school组件
const school = Vue.extend({
// el:'#root', // 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,有vm决定服务于那个容器。
template:`
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.schoolName)
}
},
})
// 第一步:创建student组件
const student = Vue.extend({
// el:'#root', // 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,有vm决定服务于那个容器。
template:`
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
studentName:'张三',
age:18
}
}
})
// 第一步:创建hello组件
const hello = Vue.extend({
template:`
<div>
<h2>你好啊!{{name}}</h2>
</div>
`,
data(){
return {
name:'Tom'
}
}
})
// 第二步:全局注册组件
Vue.component('hello',hello)
new Vue({
el:'#root',
data:{
msg:'你好啊!'
},
// 第二步:注册组件(局部注册)
components:{
school,
student
}
})
new Vue({
el:'#root2'
})
</script>
</html>
几个注意点
- 1.关于组件名:
-
- 一个单词组成:
-
-
- 第一种写法(首字母小写):
school
- 第一种写法(首字母小写):
-
-
-
- 第二种写法(首字母大写):
school
- 第二种写法(首字母大写):
-
-
- 多个单词组成:
-
-
- 第一种写法(
kebab-case命名
):my-school
- 第一种写法(
-
-
-
- 第_种写法(
Camelcase命名
):MySchool
(需要Vue脚手架
支持)
- 第_种写法(
-
-
- 备注:
(1).组件名尽可能回避HTML
中已有的元素名称,例如:h2
、H2
都不行。
(2).可以使用name
配置项指定组件在开发者工具中呈现的名字。
- 备注:
- 2.关于组件标签:
-
- 第一种写法:
<school></school>
- 第一种写法:
-
- 第二种写法:
<school/>
- 第二种写法:
-
- 备注:不用使用脚手架时,
<school/>
会导致后续组件不能渲染。
- 备注:不用使用脚手架时,
- 3.一个简写方式:
-
const school = Vue,extend(options)
可简写为:const school = options
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>几个注意点</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
几个注意点:
1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):school
多个单词组成:
第一种写法(kebab-case命名):my-school
第_种写法(Camelcase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。
2.关于组件标签:
第一种写法:<school></school>
第二种写法:<school/>
备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。
3.一个简写方式:
const school = Vue,extend(options) 可简写为: const school = options
-->
<!-- 准备好一个容器 -->
<div id="root">
<h1>{{msg}}</h1>
<school/>
<school/>
<school/>
</div>
</body>
<script type="text/javascript">
// 定义组件
const school = {
name:'atguigu',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
}
}
/* const school = Vue.extend({
name:'atguigu',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
}
}) */
new Vue({
el:'#root',
data:{
msg:'欢迎学习Vue!'
},
components:{
school
}
})
</script>
</html>
组件的嵌套
最顶层
组件名称为app
,app
组件管理所有的组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件的嵌套</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
</div>
</body>
<script type="text/javascript">
// 定义student组件
const student = {
name:'student',
template:`
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
name:'尚硅谷',
age:18
}
}
}
// 定义school组件
const school = {
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
components:{
student
}
}
// 定义一个hello组件
const hello = {
name:'hello',
template:`<h1>{{msg}}</h1>`,
data(){
return {
msg:'欢迎来到尚硅谷学习!'
}
}
}
// 定义app组件
const app = Vue.extend({
template:`
<div>
<hello></hello>
<school></school>
</div>
`,
components:{
school,
hello
}
})
// 创建vm
new Vue({
el:'#root',
template:'<app></app>',
// 注册组件(局部)
components:{
app
}
})
</script>
</html>
VueComponent
- 1.
school
组件本质是一个名为VueComponent
的构造函数
,且不是程序员定义的,是Vue.extend
生成的。 - 2.我们只需要写
<school/>
或<school></school>
,Vue
解析时会帮我们创建school
组件的实例对象。
即Vue
帮我们执行的:new VueComponent(options)
。 - 3.特别注意:每次调用
Vue.extend
,返回的都是一个全新的VueComponent
!!! - 4.关于
this
指向: -
- (1).组件配置中:
data函数
、methods中的函数
、watch中的函数
、computed中的函数
它们的this
均是【VueComponent实例对象
】
- (1).组件配置中:
-
- (2).
new Vue(options)
配置中:data函数
、methods中的函数
、watch中的函数
、computed中的函数
它们的this
均是【Vue实例对象
】。
- (2).
- 5.
VueComponent
的实例对象,以后简称vc
(也可称之为:组件实例对象
)。Vue
的实例对象,以后简称vm
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VueComponent</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象。
即Vue帮我们执行的:new VueComponent(options)。
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
4.关于this指向:
(1).组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】
(2).new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
5.VueComponent的实例对象,以后简称vc(也可称之为: 组件实例对象)。Vue的实例对象,以后简称vm。
-->
<!-- 准备好一个容器 -->
<div id="root">
<school></school>
<hello></hello>
</div>
</body>
<script type="text/javascript">
// 定义school组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
methods: {
showName(){
console.log('showName',this)
alert(this.name)
}
},
})
// 定义test组件
const test = Vue.extend({
template:`<h2>{{msg}}</h2>`,
data(){
return {
msg:'你好啊!'
}
}
})
// 定义hello组件
const hello = Vue.extend({
template:`
<div>
<h2>{{msg}}</h2>
<test></test>
</div>
`,
data(){
return {
msg:'你好啊!'
}
},
components:{
test
}
})
console.log('@',school)
console.log('#',hello)
// 创建vm
const vm = new Vue({
el:'#root',
// 注册组件(局部)
components:{
school,
hello
}
})
</script>
</html>
一个重要的内置关系
1.一个重要的内置关系:VueComponent.prototype._proto === Vue.prototype
。
2.为什么要有这个关系: 让组件实例对象(vc
) 可以访问到 Vue
原型上的属性、方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>一个重要的内置关系</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
1.一个重要的内置关系:VueComponent.prototype._proto === Vue.prototype。
2.为什么要有这个关系:让组件实例对象(vc) 可以访问到 Vue原型上的属性、方法。
-->
<!-- 准备好一个容器 -->
<div id="root">
<school></school>
</div>
</body>
<script type="text/javascript">
// 定义school组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
}
})
// 创建一个vm
new Vue({
el:'#root',
data:{
msg:'你好'
},
components:{
school
}
})
console.log(school.prototype.__proto__ === Vue.prototype)
</script>
</html>
单文件组件
创建School.vue
组件
<template>
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
name:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>
创建Student.vue
组件
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
}
</script>
创建App.vue
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
// 引入组件
import School from './School.vue'
import Student from './Student.vue'
export default {
name:'App',
components:{
School,
Student
}
}
</script>
创建main.js
入口文件
import App from './App.vue'
new Vue({
el:'#root',
template:`<App></App>`,
components:{
App
}
})
创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>练习一下单文件的语法</title>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root"></div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
Vue CLI脚手架
初始化脚手架
Vue脚手架
是Vue
官方提供的标准化开发工具(开发平台)。
文档:https://cli.vuejs.org/zh/
需要ndoeJs环境,并且配置好npm的淘宝镜像:
npm config set registry https://registry.npm.taobao.org
具体步骤如下:
第一步(仅第一次执行):全局安装@vue/cli
,打开cmd输入以下指令即可
npm install -g @vue/cli
第二步:切换到你要创建项目的目录
,然后使用命令创建项目
vue create 项目名
出现下图,就代表创建成功了
如果卡死不动
,执行如下命令切换源,然后就可以了
# 安装nrm源管理器
npm install -g nrm
# 看看有哪些源可以供我们使用
nrm ls
# 测试各个源的速度
# nrm test 源的名字,如:
nrm test taobao
# 切换镜像
nrm use taobao
第三步:启动项目
npm run serve
项目启动成功
render函数
- 关于不同版本的Vue:
-
- 1.
vue.js
与`vue.runtime.xxx.js``的区别:
- 1.
-
-
- (1).
vue.js
是完整版的Vue
,包含:核心功能+模板解析器。
- (1).
-
-
-
- (2).
vue.runtime.xxxjs
是运行版的Vue
,只包含:核心功能;没有模板解析器。
- (2).
-
-
- 2.因为
vue.runtime.xxx.js
没有模板解析器,所以不能使用template
配置项,需要使用render
函数接收到的createElement
函数去指定具体内容。
- 2.因为
mian.js
文件
/**
* 该文件是整个项目的入口文件
*/
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false
/*
关于不同版本的Vue:
1.vue.js与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxxjs是运行版的Vue,只包含:核心功能;没有模板解析器。
2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
*/
// 创建Vue的实例对象--vm
new Vue({
el:'#app',
// 将App组件放入容器中
render: h => h(App),
})
文件结构
ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在
html标签
上获取的是真实DOM元素
,应用在组件标签
上是组件实例对象(vc)
- 3.使用方式:
-
- 打标识:
<h1 ref="xxx">.....</h1>
或<School ref="xxx"></School>
- 打标识:
-
- 获取:
this.$refs.xxx
- 获取:
App.vue代码:
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch"/>
</div>
</template>
<script>
// 引入组件
import School from './components/School'
export default {
name:'App',
data(){
return {
msg:'欢迎学习Vue!'
}
},
components:{
School
},
methods:{
showDOM(){
console.log(this.$refs.title) // 真实DOM元素
console.log(this.$refs.btn) // 真实DOM元素
console.log(this.$refs.sch) // School组件的实例对象(vc)
}
}
}
</script>
props配置
Student.vue代码
<!--
功能:让组件接收外部传过来的数据
(1).传递数据:
<Demo name="xxx"/>
(2).接收数据:
第一种方式(只接收)props:['name']
第二种方式(限制类型) :
props:{
name:Number
}
第三种方式(限制类型、限制必要性、指定默认值)
props:{
name:(
type:String, //类型
required:true, //必要性
default:'老王' //默认值
}
}
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
-->
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{myAge+1}}</h2>
<button @click="updateAge">尝试修改收到的年龄</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
msg:'我是一个尚硅谷的学生',
myAge:this.age
}
},
methods:{
updateAge(){
this.myAge++
}
},
// 简单声明接收
// props:['name','age','sex']
// 接收的同时对数据进行类型限制
/* props:{
name:String,
age:Number,
sex:String
}*/
// 接收的同时对数据进行类型限制+默认值的指令+必要性的限制
props:{
name:{
type:String, // name的类型是字符串
required:true // name是必要的
},
age:{
type:Number,
default:99 // 默认值
},
sex:{
type:String,
required:true
}
}
}
</script>
App.vue代码
<template>
<div>
<Student name="李四" sex="女" :age="18"/>
</div>
</template>
<script>
// 引入组件
import Student from './components/Student'
export default {
name:'App',
components:{
Student
}
}
</script>
mixin(混入)
功能:可以把说个组件共用的配置提取成一个混入对象
使用方式:
第一步定义混合,例如:
{
data(){...},
methods:{...}
}
第二步使用混入,例如:
(1).全局混入: Vue.mixin(xxx)
(2).局部混入: mixins:[xxx]
mixin.js
export const hunhe = {
methods:{
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
}
}
export const hunhe2 = {
data() {
return {
x:100,
y:200
}
}
}
Student.vue
<template>
<div>
<h2 @click="showName">学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
</div>
</template>
<script>
// 引入一个hunhe
// import {hunhe,hunhe2} from '../mixin'
export default {
name:'Student',
data(){
return {
name:'张三',
sex:'男'
}
},
// mixins:[hunhe,hunhe2]
}
</script>
School.vue
<template>
<div>
<h2 @click="showName">学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
// 引入一个hunhe
// import {hunhe,hunhe2} from '../mixin'
export default {
name:'School',
data(){
return {
name:'尚硅谷',
address:'北京',
x:666
}
},
// mixins:[hunhe,hunhe2],
}
</script>
main.js
/**
* 该文件是整个项目的入口文件
*/
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false
import {hunhe,hunhe2} from './mixin'
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
// 创建Vue的实例对象--vm
new Vue({
el:'#app',
// 将App组件放入容器中
render: h => h(App),
})
插件
功能:用于增强Vue
本质:包含install
方法的一个对象,install
的第一个参数是Vue
,第二个以后的参数是插件使用者传递的数据。
定义插件:
对象.install = function (Vue, options) {
// 1.添加全局过滤器
Vue.filter(....)
// 2.添加全局指令
Vue.directive(....)
// 3.配置全局混入(合)
Vue.mixin(....)
// 4.添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
使用插件:Vue.use()
plugins.js
export default {
install(Vue,x,y,z){
console.log(x,y,z)
// 全局过滤器,必须在new Vue之前
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
// 全局自定义指令
Vue.directive('fbind',{
// 指令与元素成功绑定时
bind(element,binding){
console.log('big',this) // 注意此处的this是window
element.value = binding.value
},
// 指令所在的元素被插入页面时
inserted(element,binding){
element.focus()
},
// 指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
element.focus()
}
})
// 定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
}
})
// 给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
Student.vue
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<input type="text" v-fbind:value="name">
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
sex:'男'
}
}
}
</script>
School.vue
<template>
<div>
<h2>学校名称:{{name | mySlice}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="test">点我测试一个hello方法</button>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
name:'尚硅谷atguigu',
address:'北京'
}
},
methods: {
test(){
this.hello()
}
}
}
</script>
main.js
/**
* 该文件是整个项目的入口文件
*/
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 引入插件
import plugins from './plugins'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 应用(使用)插件
Vue.use(plugins,1,2,3)
// 创建Vue的实例对象--vm
new Vue({
el:'#app',
// 将App组件放入容器中
render: h => h(App),
})
scoped样式
作用:让样式在局部生效,防止冲突。
写法:<style scoped>
解决问题:防止冲突,css样式
会在全局生效,导致在Vue
里不能重名,重名就会按照引入顺序来就近选择,使用<style scoped>
可以解决这个问题,让css样式
只在局部生效。
School.vue
<template>
<div class="demo">
<h2 class="title">学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
name:'尚硅谷atguigu',
address:'北京'
}
}
}
</script>
<style scoped>
.demo{
background-color: skyblue;
}
</style>
Student.vue
<template>
<div class="demo">
<h2 class="title">学生姓名:{{name}}</h2>
<h2 class="atguigu">学生性别:{{sex}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
sex:'男'
}
}
}
</script>
<style lang="less">
.demo{
background-color: orange;
.atguigu{
font-size: 40px;
}
}
</style>
App.vue
<template>
<div>
<School/>
<hr>
<Student/>
</div>
</template>
<script>
// 引入组件
import School from './components/School'
import Student from './components/Student'
export default {
name:'App',
components:{
Student,School
}
}
</script>
<style>
.title{
color:red;
}
</style>
组件的自定义事件
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('atguigu',this.test)
}
(3)若想让自定义事件只能触发一次,可以使用 once修饰符
,或 $once方法
。
4.触发自定义事件:this.$emit('atguigu',数据)
5.解绑自定义事件:this.$off('atguigu')
6.组件上也可以绑定原生DOM事件,需要使用 native修饰符
7.注意: 通过 this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数
,否则this指向会出问题!
App.vue
<template>
<div class="app">
<h1>{{msg}}学生的姓名是:{{studentName}}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<!-- <Student @atguigu="getStudentName" @demo="m1"/> -->
<!-- 只触发一次 -->
<!-- <Student @atguigu.once="getStudentName"/> -->
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student" @click.native="show"/>
</div>
</template>
<script>
// 引入组件
import School from './components/School'
import Student from './components/Student'
export default {
name:'App',
components:{
Student,School
},
data() {
return {
msg:'你好啊!',
studentName:''
}
},
methods: {
getSchoolName(name){
console.log('App收到了学校名:',name)
},
getStudentName(name,...params){
this.studentName = name
console.log('App收到了学生名:',name,params)
},
m1(){
console.log('demo事件被触发了!')
},
show(){
alert(123)
}
},
mounted() {
this.$refs.student.$on('atguigu',(name,params)=>{
this.studentName = name
console.log('App收到了学生名:',name,params)
}) // 绑定自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName) // 绑定自定义事件(一次性)
},
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
School.vue
<template>
<div class="school">
<h2 class="title">学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
name:'School',
props:['getSchoolName'],
data(){
return {
name:'尚硅谷atguigu',
address:'北京'
}
},
methods: {
sendSchoolName(){
this.getSchoolName(this.name)
}
}
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>当前求和为:{{number}}</h2>
<button @click="add">点我number++</button>
<button @click="sendStudentName">把学生名给App</button>
<button @click="unbind">解绑atguigu事件</button>
<button @click="death">销毁当前Student组件的实例(vc)</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
sex:'男',
number:0
}
},
methods: {
add(){
console.log('add回调被调用了')
this.number++;
},
sendStudentName(){
// 触发Student组件实例对象身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
this.$emit('demo')
},
unbind(){
// this.$off('atguigu') // 解绑一个自定义事件
// this.$off(['atguigu','demo']) // 解绑多个自定义事件
this.$off() // 解绑所有的自定义事件
},
death(){
this.$destroy() // 销毁了当前Student组件的实例,销毁后所有的Student实例的自定义事件全都不奏效。
}
}
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
全局事件总线(GlobalEventBus)
1.一种组件间通信的方式,适用于任意组件间通信
2.安装全局事件总线:
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
3.使用事件总线:
(1)接收数据:A组件想接收数据,则在A组件中给$bus
绑定自定义事件,事件的回调留在A组件自身
。
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
(2)提供数据:this.$bus.$emit('xxxx',数据)
4.最好在beforeDestroy
钩子中,用$off
去解绑当前组件所用到的
事件。
beforeDestroy() {
this.$bus.off('xxxx')
},
main.js
/**
* 该文件是整个项目的入口文件
*/
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 创建Vue的实例对象--vm
new Vue({
el:'#app',
// 将App组件放入容器中
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this // 安装全局事件总线
}
})
School.vue
<template>
<div class="school">
<h2 class="title">学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
props:['getSchoolName'],
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
mounted() {
// console.log('School',this)
this.$bus.$on('hello',(data)=>{
console.log('我是一个School组件,收到了数据',data)
})
},
beforeDestroy() {
this.$bus.off('hello')
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
Student.vue
<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',666)
}
},
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
nextTick
1.语法:this.$nextTick(回调函数)
2.作用:在下一次DOM
更新结束后执行其指定的回调。
3.什么时候用:当改变数据后,要基于更新后的新DOM
进行某些操作时,要在nextTick
所指定的回调函数
中执行。
axios请求
安装axios
npm i axios
<template>
<div>
<button @click="getStudents">获取学生信息</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name:'App',
methods: {
getStudents(){
axios.get('http://localhost:9000/students').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了',error.message)
}
)
}
},
}
</script>
跨域
就是违背了同源策略
(协议名
、主机名
、端口号
)
如果出现跨域问题,建议后端处理,前端只能通过代理的方式解决https://cli.vuejs.org/zh/config/#devserver-proxy
跨域java解决方案如下:
package com.it.common.cors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class MyCorsFilter {
@Bean
public CorsFilter corsFilter() {
// 1.创建 CORS 配置对象
CorsConfiguration config = new CorsConfiguration();
// 支持域
config.addAllowedOriginPattern("*");
// 是否发送 Cookie
config.setAllowCredentials(true);
// 支持请求方式
config.addAllowedMethod("*");
// 允许的原始请求头部信息
config.addAllowedHeader("*");
// 暴露的头部信息
config.addExposedHeader("*");
// 2.添加地址映射
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", config);
// 3.返回 CorsFilter 对象
return new CorsFilter(corsConfigurationSource);
}
}
插槽
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件 ===> 子组件
。
默认插槽
Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title']
}
</script>
<style>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
img{
width: 100%;
}
</style>
App.vue
<template>
<div class="container">
<Category title="美食">
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg">
</Category>
<Category title="游戏">
<ul v-for="(item, index) in games" :key="index">
<li>{{item}}</li>
</ul>
</Category>
<Category title="电影">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name:'App',
components:{
Category
},
data() {
return {
foods:['火锅','烧烤','小龙虾','牛排'],
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
}
},
}
</script>
<style>
.container{
display: flex;
justify-content: space-around;
}
video{
width: 100%;
}
</style>
具名插槽
Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title']
}
</script>
<style>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
img{
width: 100%;
}
</style>
App.vue
<template>
<div class="container">
<Category title="美食">
<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg">
<a slot="footer" href="http://www.atguigu.com">更多美食</a>
</Category>
<Category title="游戏">
<ul slot="center" v-for="(item, index) in games" :key="index">
<li>{{item}}</li>
</ul>
<div class="foot" slot="footer">
<a href="http://www.atguigu.com">单机游戏</a>
<a href="http://www.atguigu.com">网络游戏</a>
</div>
</Category>
<Category title="电影">
<video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
<template v-slot:footer>
<div class="foot">
<a href="http://www.atguigu.com">经典</a>
<a href="http://www.atguigu.com">热门</a>
<a href="http://www.atguigu.com">推荐</a>
</div>
<h4>欢迎前来观影</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name:'App',
components:{
Category
},
data() {
return {
foods:['火锅','烧烤','小龙虾','牛排'],
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
}
},
}
</script>
<style>
.container,.foot{
display: flex;
justify-content: space-around;
}
video{
width: 100%;
}
</style>
作用域插槽
Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot :games="games" msg="hello">我是默认的一些内容</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
}
}
</script>
<style>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
img{
width: 100%;
}
</style>
App.vue
<template>
<div class="container">
<Category title="游戏">
<template scope="atguigu">
<ul>
<li v-for="(g, index) in atguigu.games" :key="index">{{g}}</li>
</ul>
</template>
</Category>
<Category title="游戏">
<template scope="atguigu">
<ol>
<li style="color:red" v-for="(g, index) in atguigu.games" :key="index">{{g}}</li>
</ol>
<h4>{{atguigu.msg}}</h4>
</template>
</Category>
<Category title="游戏">
<template slot-scope="{games}">
<h4 v-for="(g, index) in games" :key="index">{{g}}</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name:'App',
components:{
Category
}
}
</script>
<style>
.container,.foot{
display: flex;
justify-content: space-around;
}
video{
width: 100%;
}
</style>
Vuex
Vuex是什么
-
概念:专门在
Vue
中实现集中式状态(数据)管理的一个Vue插件
,对vue
应用中多个组件的共享
状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。 -
GitHub地址:https://github.com/vuejs/vuex
什么时候使用Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
安装Vuex
Vue2对应的是Vuex3
Vue3对应的是最新的Vuex4
安装命令:
npm i vuex@3
搭建Vuex环境
- 创建文件
src/store/index.js
// 该文件用于创建Vuex中最为核心的store
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 应用Vuex插件
Vue.use(Vuex)
// 准备actions——用于响应组件中的动作
const actions = {
}
// 准备mutations——用于操作数据(state)
const mutations = {
}
// 准备state——用于存储数据
const state = {
}
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
- 在
main.js
中创建vm
时传入store
配置项
······
// 引入store
import store from './store/index'
······
// 创建Vue的实例对象--vm
new Vue({
el:'#app',
// 将App组件放入容器中
render: h => h(App),
store
})
Vuex基本使用
- 初始化数据,配置
actions
、配置mutations
,操作文件/src/store/index.js
// 该文件用于创建Vuex中最为核心的store
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 应用Vuex插件
Vue.use(Vuex)
// 准备actions——用于响应组件中的动作
const actions = {
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
console.log('处理了一些事情--jiaOdd')
context.dispatch('demo1',value)
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(() => {
context.commit('JIA',value)
}, 500)
},
demo1(context,value){
console.log('处理了一些事情--demo1')
context.dispatch('demo2',value)
},
demo2(context,value){
console.log('处理了一些事情--demo2')
if(context.state.sum % 2){
context.commit('JIA',value)
}
}
}
// 准备mutations——用于操作数据(state)
const mutations = {
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
}
}
// 准备state——用于存储数据
const state = {
sum:0 // 当前的和
}
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
- 组件中读取
vuex
中的数据:$store.state.sum
- 组件中修改
vuex
中的数据:$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions
,即不写dispatch
直接编写commit
。
getters的使用
- 概念:当
state
中的数据需要经过加工后再使用时,可以使用getters
加工。 - 在
store.js
中追加getters
配置
// 准备getters——用于将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
// 创建并暴露store
export default new Vuex.Store({
······
getters
})
- 组件中读取数据
$store.getters.bigSum
四个map方法的使用
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
mapState方法
用于帮助我们映射state
中的数据为计算属性
computed:{
// 借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'})
// 借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject'])
}
mapGetters方法
用于帮助我们映射getters
中的数据为计算属性
computed:{
// 借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'})
// 借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
}
mapActions方法
用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数
methods:{
// 借助mapActions生成:incrementOdd、incrementWait(对象写法)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
// 借助mapActions生成:jiaOdd、jiaWait(数组写法)
...mapActions(['jiaOdd','jiaWait'])
}
mapMutations方法
用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数
methods:{
// 借助mapMutations生成:increment、decrement(对象写法)
...mapMutations({increment:'JIA',decrement:'JIAN'})
// 借助mapMutations生成:JIA、JIAN(数组写法)
...mapMutations(['JIA','JIAN'])
}
模块化+命名空间
- 目的:让代码更好维护,让多种数据分类更加明确。
- 修改
main.js
,引入store
/**
* 该文件是整个项目的入口文件
*/
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 引入store
import store from './store/index'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 创建Vue的实例对象--vm
new Vue({
el:'#app',
// 将App组件放入容器中
render: h => h(App),
store
})
- 创建
/src/store/index.js
// 该文件用于创建Vuex中最为核心的store
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 引入求和相关的配置
import countOptions from './count'
// 引入人员管理相关的配置
import personOptions from './person'
// 应用Vuex插件
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
modules:{
countAbout:countOptions,
personAbout:personOptions
}
})
- 创建
/src/store/count.js
// 求和相关的配置
export default {
namespaced:true, // 开启命名空间
actions:{
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(() => {
context.commit('JIA',value)
}, 500)
}
},
mutations:{
JIA(state,value){
console.log('mutations中的JIA被调用了')
state.sum += value
},
JIAN(state,value){
console.log('mutations中的JIAN被调用了')
state.sum -= value
}
},
state:{
sum:0, // 当前的和
school:'尚硅谷',
subject:'前端',
},
getters:{
bigSum(state){
return state.sum*10
}
}
}
- 创建
/src/store/person.js
// 人员管理相关的配置
export default {
namespaced:true, // 开启命名空间
actions:{
addPersonWang(context,value){
if(value.name.indexOf('王') === 0){
context.commit('ADD_PERSON',value)
}else{
alert('添加的人必须姓王!')
}
}
},
mutations:{
ADD_PERSON(state,value){
console.log('mutations中的ADD_PERSON被调用了')
state.personList.unshift(value)
}
},
state:{
personList:[{'id':'001','name':'张三'}]
},
getters:{
firstPersonName(state){
return state.personList[0].name
}
}
}
- 创建
/src/components/Count.vue
<template>
<div>
<h1>当前求和为:{{sum}}</h1>
<h3>当前求和放大10倍为:{{bigSum}}</h3>
<h3>我在{{school}},学习{{subject}}</h3>
<h3>Person组件的总人数是:{{personList.length}}</h3>
<select v-model="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
name:'Count',
data() {
return {
n:1 // 用户选择的数字
}
},
computed:{
// 借助mapState生成计算属性,从state中读取数据。(对象写法)
// ...mapState({sum:'sum',school:'school',subject:'subject'}),
// 借助mapState生成计算属性,从·state中读取数据。(数组写法)
...mapState('countAbout',['sum','school','subject']),
...mapState('personAbout',['personList']),
// 借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
// ...mapGetters({bigSum:'bigSum'})
// 借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters('countAbout',['bigSum'])
},
methods: {
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
// ...mapMutations(['JIA','JIAN']),
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}),
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
// ...mapActions(['jiaOdd','jiaWait']),
}
}
</script>
<style>
button{
margin-left: 5px;
}
</style>
- 创建
/src/components/Person.vue
<template>
<div>
<h1>人员列表</h1>
<h3>Count组件求和为:{{sum}}</h3>
<h3>列表中第一个人的名字是:{{firstPersonName}}</h3>
<input type="text" placeholder="请输入名字" v-model="name">
<button @click="add">添加</button>
<button @click="addWang">添加一个姓王的人</button>
<ul>
<li v-for="p in personList" :key="p.id">
{{p.name}}
</li>
</ul>
</div>
</template>
<script>
import { nanoid } from 'nanoid'
export default {
name:'Person',
data() {
return {
'name':''
}
},
computed: {
personList(){
return this.$store.state.personAbout.personList
},
sum(){
return this.$store.state.countAbout.sum
},
firstPersonName(){
return this.$store.getters['personAbout/firstPersonName']
}
},
methods:{
add(){
const personObj = {id:nanoid(),name:this.name}
this.$store.commit('personAbout/ADD_PERSON',personObj)
this.name = ''
},
addWang(){
const personObj = {id:nanoid(),name:this.name}
this.$store.dispatch('personAbout/addPersonWang',personObj)
}
}
}
</script>
<style>
</style>
- 修改
App.vue
<template>
<div>
<Count/>
<hr/>
<Person/>
</div>
</template>
<script>
import Count from './components/Count'
import Person from './components/Person'
export default {
name:'App',
components:{
Count,
Person
}
}
</script>
- 开启命名空间后,组件中读取
state
数据:
// 方式一:自己读取
this.$store.state.personAbout.personList
// 方式二:借助mapState读取【推荐】
...mapState('countAbout',['sum','school','subject'])
- 开启命名空间后,组件中读取
getters
数据:
// 方式一:自己读取
this.$store.getters['personAbout/firstPersonName']
// 方式二:借助mapGetters读取【推荐】
...mapGetters('countAbout',['bigSum'])
- 开启命名空间后,组件中调用
dispatch
// 方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',personObj)
// 方式二:借助mapActions【推荐】
......mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
- 开启命名空间后,组件中调用
commit
// 方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',personObj)
// 方式二:借助mapMutations【推荐】
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'})
路由vue-router
相关理解
vue-router的理解
vue的一个插件库,专门用来实现SPA应用
对SPA应用的理解
- 单页Web应用(single page web application, SPA)。
- 整个应用只有
一个完整的页面
。 - 点击页面中的导航链接
不会刷新
页面,只会做页面的局部更新
。 - 数据需要通过ajax请求获取。
路由的理解
什么是路由?
- 一个路由就是一组映射关系(key-value)
- key为路径,value可能是function或component
路由的分类
-
后端路由:
(1)理解:value是function,用于处理客户端提交的请求。
(2)工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。 -
前端路由:
(1)理解:value是component,用于展示页面内容。
(2)工作过程:当浏览器的路径改变时,对应的组件就会显示。
基本路由
-
安装
vue-router
,命令npm i vue-router
-
应用插件:
Vue.use(VueRouter)
// 引入VueRouter
import VueRouter from 'vue-router'
// 引入路由器
import router from './router/index'
// 应用插件
Vue.use(VueRouter)
new Vue({
el:'#app',
// 将App组件放入容器中
render: h => h(App),
router:router
})
- 编写
router
配置项(创建/src/router/index.js
):
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
import About from '../components/About'
import Home from '../components/Home'
// 创建并暴露一个路由器
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
- 实现切换(active-class可配置高亮样式)
<!-- Vue借助router-link标签实现路由的切换 -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
- 指定显示位置
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
嵌套(多级)路由
- 配置路由规则,使用children配置项:
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[ // 通过children配置子级路由
{
path:'news', // 此处一定不要写 “/”
component:News
},
{
path:'message', // 此处一定不要写 “/”
component:Message
}
]
}
]
- 跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
路由的query参数
- 传递参数:
<!-- 跳转路由并携带query参数,to的字符串写法 -->
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
- 接收参数:
{{$route.query.id}}
{{$route.query.title}}
路由的props配置
作用:让路由组件更方便的接收到参数
path:'detail',
component:Detail,
// 第一种写法,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
}
}
接收使用:
<template>
<ul>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}}</li>
</ul>
</template>
<script>
export default {
name:'detail',
props:['id','title']
}
</script>
replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式。
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
。 - 如何开启
replace
模式:<router-link replace .......>News</router-link>
编程式路由导航
- 作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活 - 具体编码:
// $router的两个API
this.$router.push({
query:{
id:xxx,
title:xxx
}
})
this.$router.replace({
query:{
id:xxx,
title:xxx
}
})
this.$router.forward() // 前进
this.$router.back() // 后退
this.$router.go(3) // 可前进也可后退,负数是后退几,正数是前进几
缓存路由组件
- 作用:让不展示的路由组件保持挂载,不被销毁。
- 具体编码:
<!-- include用的是组件名 -->
<!-- 单个 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
<!-- 多个 -->
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>
两个新的生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字:
(1)activated
路由组件被激活时触发。
(2)deactivated
路由组件失活时触发。
路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫、独享守卫、组件内守卫
- 全局守卫:
// 全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
console.log('前置路由守卫',to,from)
if(to.meta.isAuth){ // 判断是否需要鉴权
if(localStorage.getItem('school')==='atguigu'){
next()
}else{
alert('学校名不对,无权限查看')
}
}else{
next()
}
})
// 全局后置路由守卫
router.beforeEach((to,from)=>{
console.log('后置路由守卫',to,from)
document.title = to.meta.title || '硅谷系统'
})
- 独享守卫:
beforeEnter:(to,from,next) => {
console.log('独享路由守卫',to,from)
if(to.meta.isAuth){ // 判断是否需要鉴权
if(localStorage.getItem('school')==='atguigu'){
next()
}else{
alert('学校名不对,无权限查看')
}
}else{
next()
}
}
- 组件内守卫:
// 进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
// 离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
Vue UI 组件库
移动端常用 UI 组件库
- Vant:https://youzan.github.io/vant
- Cube UI:https://didi.github.io/cube-ui
- Mint UI:https://mint-ui.github.io
PC端常用 UI 组件库
- Element UI:https://element.eleme.cn
- IView UI:https://www.iviewui.com