文章目录
- 前言
- 一、基础
- 二、组件化编程
- 三、Vue-cli —— Vue脚手架
- 四、Vue中的AJAX
- 五、Vuex —— 在应用很复杂时用此保管数据
- 六、vue-router vue中的前端路由
- 七、element-ui ——Vue UI组件库
前言
Vue是什么?
Vue是一套用于构建用户界面的渐进式JavaScript框架
渐进式
:Vue可以自底向上逐层应用,它的核心库轻量,只关心图层,便于与第三方库或既有项目整合。
- 应对简单应用:只需一个轻量小巧的核心库
- 应对复杂应用:可以引入Vue插件
Vue作者 —— 尤雨溪
Vue特点
1、组件化
网页的每个组件模块都是一个vue文件
,其中包含了该组件的html、css、js文件,这种模式提高了代码复用率,便于维护和别处引用
2、声明式编码
编码人员无需直接操作DOM,提高了了开发效率
3、使用虚拟DOM
和优秀的Diff算法
,尽量复用DOM节点
Vue官方库
用于使用官方提供的组件实现某些功能
Awesome-Vue – Github
和Vue相关的包
如何安装Vue
一、基础
钩子函数
1.1、模板语法
1.1.1、插值语法{{xxx}}
-
语法:
{{xxx}}
、xxx可以是JS表达式,也可以是变量名或属性名,且可以读取到data中的所有区域 -
功能:解析标签体内容
-
注意:容器与Vue实例之间是严格的
一一对应
关系
绑定Vue对象中的data
数据,从而可以通过data动态
修改HTML设置的插值表达式中变量的值.插值表达式中允许用户输入JS代码片段
下面<div>容器内的代码又称
Vue模板
,其中的代码符合HTML规范的同时,加入了一些Vue语法
<body>
<div id="root">
<!--通过vue动态修改-->
<!--若{{}}传入内传入JS表达式,则会输出其返回值-->
<h1>Hello {{name}},{{age}},{{1+0}},{{Date.now()}}</h1>
</div>
</body>
必须创建一个Vue实例并传入一个配置对象,才能让Vue工作
<script>
//创建Vue实例
new Vue({//只传一个参数:配置对象
el: '#root',//el即element,用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
//id选择器用#,类选择器用.,以此类推
//这里还有一种写法:el:document.getElementById("root");
data: {
name:'Vue',
age: 18
},
})
</script>
1.1.2、指令语法v-xxx
- 语法:
v-???
,以v-bind为例,<a v-bind:href="xxx">
或是<a :href="xxx">
(v-bind简写为:
),xxx
是JS表达式,可以直接读取到data中的所有属性 - 功能:解析标签,包括标签属性、标签体内容、绑定事件等
- 注意:Vue中,指令的形式都是
v-
开头
1.1.2.1、v-bind:
—— 单向数据绑定
作用:响应式地更新 HTML 属性,给html标签中任何属性动态绑定
值
下面演示中,v-bind
指令将<a>
元素的href
属性与url值
绑定,从而实现动态改变该超链接
<h2>指令语法</h2>
<a id="link1" v-bind:href="url">点击前往Vue文档</a>
<!--或者简写为-->
<a id="link1" :href="url">点击前往Vue文档</a>
<!--将url绑定给href-->
<script>
new Vue({
el:"link1",
data:{
url:'https://v2.cn.vuejs.org/v2/guide/instance.html',
}
})
</script>
1.2、数据绑定
1.2.1、单向数据绑定 - 使用v-bind:
数据只能从data
流向页面
,即:可以通过改变data而改变页面上对应部分的值,但不能通过改变页面的某部分值来改变其对应的data
单向数据绑定<input type="text" :value="name">
1.2.2、双向数据绑定 - 使用v-model
v-model
只能作用域表单类元素(输入类元素)上,它们都有 value 值
v-model
的三个修饰符
lazy
:当失去焦点时再收集数据number
:输入字符串转化为有效的数字trim
:输入首尾空格过滤
数据在data
和页面
之间的流动是双向的,可以通过其中任意一方而影响另外一方
双向数据绑定<input type="text" v-model:value="name">
//v-model简写如下,即删掉:value
双向数据绑定<input type="text" v-model="name">
<div id="root">
单向数据绑定<input type="text" :value="name">
<br>
双向数据绑定<input type="text" v-model:value="name">
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
new Vue({//单向数据绑定和双向数据绑定都依赖这里的name
el:'#root',
data:{
name:"jack"
}
})
</script>
1.1.3、简写
v-bind:
可以简写为:
v-on:
可以简写为@
v-model:value=“xxx”
可以简写为v-model=xxx
- 动态参数缩写:
v-on:event
可以缩写为@[event]
1.2、el 与 data 的两种写法
原则: 由Vue
管理的函数(比如data),不应当写为=>函数,否则this就不再是Vue实例
1.2.1、el 的两种写法
el:'xxx'
vm.$mount('xxx');
<div id="root">
<h1>你好, {{name}}</h1>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#root', //第一种写法
data:{
name:"jack"
}
})
vm.$mount('#root');//第二种写法
</script>
第二种写法的实际应用
setTimeout(()=>{
v.$mount('root');
},2000);
可以发现第二种方法更加灵活
1.2.2、data的两种写法
- 对象式——
data:{}
- 函数式——
data:function(){ return{} }
,函数式必须返回一个对象,在组件中必须使用函数式
//data的第二种写法:函数式
data:function(){//普通函数
console.log(this);//Vue
return{
name:'jack'
}
}
data:()=>{// =>函数
console.log(this);//window
return{
name:'rory'
}
}
一般不使用data的=>函数写法
,而是使用简化的普通函数写法
data(){
return{
name:'jack'
}
}
在组件中,data必须使用函数式,否则会报错
1.3、MVVM模型
释义
1、M:Model——模型,对应data中的数据
2、V:View——视图,对应模板
3、VM:ViewModel——视图模型,对应Vue实例对象
4、data中所有的属性,最后都会出现在vm
上
5、vm上所有的属性以及Vue
原型上所有的属性,在Vue模板
中都可以直接使用
1.4、Object.defineProperty()方法
为对象添属性和对应的值,该方法添加的属性和值默认不可以被枚举、遍历
,可以通过设置enumerable
的值使其可以枚举
Object.defineProperty()方法的配置项
enumerable
:控制属性是否可以被枚举,默认值是false
writable
:控制属性是否可以被修改,默认值为false
configurable
:属性是否可以被删除,默认值为fasle
get:function(){}
:一般简称为getter
,简写为get(){}
,当读取
由Object.defineProperty()方法添加的属性时,get函数就会被调用,且返回值就是Object.defineProperty()方法添加的属性的值set:function(value){}
:一般简称为setter
,简写为set(value){}
,当修改
由Object.defineProperty()方法添加的属性时,set函数就会被调用,且会受到修改的具体的值
//Object.defineProperty()为对象添加属性,传入三个参数
Object.defineProperty(对象, 添加的属性名, 配置数据)
//举例,之后举例各配置项时省略此obj
var obj = {
name:'jack',
sex:'male'
}
Object.defineProperty(obj,'age',{
value:19
})
for(let x in obj){
console.log(x);
}
修改enumerable
为true
后,Object.defineProperty()方法添加的属性可以被遍历
Object.defineProperty(obj,'age',{
value:19,
enumerable:true
})
设置getter
,便于读取并改变Object.defineProperty()方法添加的属性
let number = 18;
Object.defineProperty(obj,'age',{
get: function() {
console.log('有人读取了age属性值');
return number;
}
})
设置setter
,便于修改Object.defineProperty()方法添加的属性的值
let number = 18;
Object.defineProperty(obj,'age',{
set(value){
console.log('有人修改了age属性值,且修改后的值是'+ value);
number = value;
}
})
1.5、数据代理
定义:通过一个对象代理对另一个对象
中属性的操作(读 / 写),被配置在data
中的东西才会做数据劫持和数据代理
举例:通过vm对象来代理data对象中属性的操作。vm将data对象中的属性存在了vm._data
中,所以可以通过vm._data
实现对data对象中属性的操作
优点:可以更加方便地操作data对象
中的数据
实现原理:
- 通过
object.defineProperty()
把data对象中所有属性添加到vm上 - 每一个添加到vm上的属性,都会被设置一个
getter
和setter
- 通过
getter
和setter
在内部去操作data中对应的属性
一个简单的数据代理
let obj1 = {
x:100
};
let obj2 = {
y:200
};
Object.defineProperty(obj2,'x',{//通过obj2操作obj1中的X属性
get: function() {
return obj1.x;
},
set: function(value) {
obj1.x = value;
}
})
使用vm修改data内属性的实例
<div id="role-info">
<h1>角色: {{name}}</h1>
<h1>年龄: {{age}}</h1>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
let data = {
name: "jack",
age: 18
};
const vm = new Vue({
el:'#role-info',
data: data//在外定义data再引入钩子函数,避开数据劫持
//这里的data相当于vm._data
})
</script>
1.6、事件处理
1.6.1、定义及方法
定义:
方法:
1、使用v-on:event
或@event
绑定事件(以下均使用@简写
),例如:v-on:click 绑定一个鼠标点击事件
2、为绑定的事件绑定回调函数:@click="functionName(parameter1,,parameter2,……)"
,根据需要传参,若不传参,则函数名后不加()
。使用占位符$
传入event,即$event
,可以传入event对象
<button @click="showInfo2(55,$event)">点我在控制台输出传入的参数</button>
<!--下面是不传参的写法-->
<button @click="showInfo2">点我在控制台输出传入的参数</button>
3、事件的回调函数配置在methods对象
中,其最终会在vm上。methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
4、methods对象中配置的函数,不可以用=>
函数
<div id="info">
<h1>欢迎来到{{name}}</h1>
<button v-on:click="showInfo1">点击提示信息</button>
<button @click="showInfo2(55,$event)">点我在控制台输出传入的参数</button>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#info',
data: {
name: '德莱联盟',
},
methods:{
showInfo1(event){
//此处的this是vm
alert('欢迎来到德莱联盟~');
},
showInfo2(number,event){
console.log(number,event);
}
}
})
</script>
1.6.2、事件修饰符
修改事件触发时的行为
格式:
- 事件绑定时:
@event.修饰符="fn"
- 事件回调函数中调用相关API
Vue中的事件修饰符
prevent
:阻止默认事件stop
:阻止事件冒泡once
:事件只触发一次capture
:使用事件的捕获模式self
:只有event.target是当前操作的元素时才触发事件passive
:事件的默认行为为立即执行,不需要等待事件回到执行完毕
一个事件可以赋予多个修饰符,比如:
@mouseover.prevent.stop.once="fn"
,设置的修饰符顺序会影响起作用的顺序
演示如下
<style>
*{
margin-top: 20px;
}
</style>
prevent
修饰符演示
<div id="info">
<h1>{{name}}示例</h1>
<!--阻止默认事件(常用)-->
<a href="https://blog.csdn.net/liushi21?spm=1000.2115.3001.5343" @click.prevent="showInfo">prevent修饰符阻止超链接的自动跳转</a>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#info',
data: {
name: '事件修饰符',
},
methods:{
showInfo(e){
alert('欢迎来到我的博客~~');
}
}
})
</script>
stop
修饰符演示
<div id="info">
<h1>{{name}}示例</h1>
<!--阻止事件冒泡(常用)-->
<div class="demo1" style="height: 60px; width:500px; background-color: rgb(163, 163, 228);" @click="showInfo">
<button @click.stop="showInfo">包含我的div也设置了点击事件,点击我体验stop修饰符阻止事件冒泡</button>
</div>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#info',
data: {
name: '事件修饰符',
},
methods:{
showInfo(e){
alert('欢迎来到我的博客~~');
}
}
})
</script>
once
修饰符演示
<div id="info">
<h1>{{name}}示例</h1>
<!--事件只触发一次-->
<button @click.once="showInfo">事件只触发一次,该button触发一次后再次点击就会无效</button>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#info',
data: {
name: '事件修饰符',
},
methods:{
showInfo(e){
alert('欢迎来到我的博客~~');
}
}
})
</script>
capture
修饰符演示
<div id="info">
<h1>{{name}}示例</h1>
<!--使用事件捕获模式-->
<div @click.capture="showMsg(1)" style="background-color: skyblue; padding: 5px;">
div1 - 在捕获阶段处理事件
<div @click="showMsg(2)" style="background-color: rgb(253, 149, 105); padding: -5px;">
div2
</div>
</div>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#info',
data: {
name: '事件修饰符',
},
methods:{
showMsg(Msg) {
console.log(Msg);
}
}
})
</script>
self
修饰符演示
<div id="info">
<h1>{{name}}示例</h1>
<!--只有event.target是当前操作的元素时才触发事件-->
<div @click.self="showInfo_1" style="background-color: rgb(18, 179, 243); padding: 5px;">
<button @click="showInfo_1">event.target是当前操作的元素时才触发事件</button>
</div>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#info',
data: {
name: '事件修饰符',
},
methods:{
showInfo_1(e){
console.log(e.target);
}
}
})
</script>
passive
修饰符演示
<div id="info">
<h1>{{name}}示例</h1>
<!--事件的默认行为为立即执行,不需要等待事件回到执行完毕-->
<h3>passive事件的默认行为为立即执行对比示例</h3>
<ul @wheel.passive="demo" style="background-color: rgb(233, 187, 141);width: 60px; height: 200px;overflow: auto;">
<!--scoll是滚动条滚动(键盘上下键或者鼠标滚轮或者拖动),wheel是鼠标滚轮滚动或者拖动-->
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#info',
data: {
name: '事件修饰符',
},
methods:{
demo(){
for (let i = 0; i < 100000; i++){
console.log('正在滚动');
}
//鼠标滚轮滚动触发上面循环,上面循环执行完后页面滚轮才会滚动一点并触发下面代码,这会造成卡顿
console.log('到底了,滚不动了');
}
}
})
</script>
– 添加passive
前,可以发现很卡
– 添加passive
后,可以发现卡顿现象得到了好转,但是在关闭页面时仍然很卡,说明passive存在局限,它只是能在一定程度上进行优化,但不能从根源上解决卡顿的问题,在移动端上的项目,常用到passive
1.6.3、键盘事件
1、Vue中常用的按键别名
Vue中常用按键 | 别名 |
---|---|
回车 | Enter |
删除 | Delete(退格键 或者 删除键) |
退出 | Esc |
空格 | Space |
换行 | Tab(必须使用 keydown 绑定) |
上 | Up |
下 | Down |
左 | Left |
右 | Right |
当用名字为组合词的按键绑定事件时,键的名字应该拆分、都变为小写,并用-
连接,才会产生想要的效果,比如大小写切换键CapsLk
在绑定事件时应该写为caps-lock
2、Vue未提供别名的按键,可以使用按键原始的key值去绑定,但要注意转为kebab-case
3、系统修饰键(用法特殊):Ctrl
、Alt
、Shift
、Meta(也即Win键)
上面四个特殊
- 配合
keyup使用
: 效果为按下修饰键同时,再按下其他键,随后释放其他键,事件才会触发 - 配合
keydown
使用: 效果为正常触发事件,二者同时按下就会触发
<input type="text" placeholder="按下ctrl+c并松开触发" @keyup.ctrl.c="showInfo">
4、指定具体的按键还可以使用keyCode
(已废弃)
5、定制按键别名:Vue.config.keyCodes.自定义键名 = 键码
,然后就可以使用自定义键名去绑定
<div id="root">
<h2>键盘事件</h2>
<input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo">
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
new Vue({
el:"#root",
methods: {
showInform(e) {
console.log(e.target.value);
}
}
})
</script>
1.7、计算属性
1、定义:本身不存在,而是通过已有的属性计算得来(可随网页操作动态变化)
2、原理:底层借助了Object.defineproperty()
方法提供的getter
和setter
3、get函数和set函数执行的时间详见下面示例代码的注释
4、优势:与methods实现相比,内部有缓存机制(复用),效率高,调试方便
5、注意:
- 计算属性最终会出现在vm上,所以直接读取使用即可,例如直接读取使用计算属性fullInfo:
{{fullInfo}}
- 修改计算属性必须通过set函数去响应修改,且set中要引起计算时依赖的属性发生改变
<div id="root">
账户: <input type="text" v-model="userName"></br>
密码: <input type="text" v-model="passwords"></br>
<h2>用户账号完整信息: {{fullinfo}}</h2>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:"#root",
data: {
userName: 'userName',
passwords: 'passwords',
},
//计算属性
computed: {//计算属性存在缓存,而methods不存在缓存
fullinfo: {
//get作用:当有人读取fullInfo时,get就会被调用,且返回值就作为fullName的值
//get调用的时机:1、初次读取计算数据时,2、计算属性所依赖的数据发生变化时
get() {//this = Vue
console.log('get被调用');
return this.userName + '-' + this.passwords;
},
//set作用,当fullInfo被修改时,
set(value) {
const arr = value.split('-');
this.userName = arr[0];
this.password = arr[1];
}
}
}
})
</script>
1.7.1、计算属性的简写
只考虑读取而不考虑修改时,才能使用简写
//计算属性
computed: {//计算属性存在缓存,而methods不存在缓存
fullinfo: function(){
//只读不改时才能使用简写
console.log('get被调用了');
return this.userName +'-'+this.passwords;
}
}
})
1.8、监视属性
监视属性 - watch
1、监视某属性的变化,监视属性发生变化时自动调用回调函数
2、监视的属性必须存在才能进行监视
3、监视的两种方法
- new Vue时
引入watch配置
:适用于创建vm时监视的对象已经确定 - 通过
vm.$watch
监视:
new Vue时引入watch配置
<div id="root">
<h2>天气好热啊, 想去{{info}}</h2>
<button @click="changeTodo">切换todo</button>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:"#root",
data:{
ischange:true
},
computed:{//计算属性
info(){
return this.ischange ? '游泳' : '打游戏'
}
},
methods:{
changeTodo(){
this.ischange = !this.ischange
}
},
watch:{//监视方法一,适用于创建vm时监视的对象已经确定
info:{
//当ischange发生改变时,调用hander函数
handler(newValue, oldValue){
//oldValue 和 newValue是对应的是watch所监视的info的旧值和新值
console.log('info被修改了' ,oldValue, newValue);
}
}
}
})
</script>
通过vm.$watch
监视(更灵活)
<div id="root">
<h2>天气好热啊, 想去{{info}}</h2>
<button @click="changeTodo">切换todo</button>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:"#root",
data:{
ischange:true
},
computed:{//计算属性
info(){
return this.ischange ? '游泳' : '打游戏'
}
},
methods:{
changeTodo(){
this.ischange = !this.ischange
}
}
})
//监视方法二,适用于创建vm时监视的对象不确定,需要后续根据用户的行为进行监视
vm.$watch('ischange',{
handler(newValue, oldValue){
//oldValue 和 newValue是对应的是watch所监视的info的旧值和新值
console.log('info被修改了' ,oldValue, newValue);
}
})
</script>
设置配置项
immediate
为true,可以在初始化时让监视属性中设置的回调函数调用一次
watch:{
info:{
immediate:true,//
//当ischange发生改变时,调用hander函数
handler(newValue, oldValue){
//oldValue 和 newValue是对应的是watch所监视的info的旧值和新值
console.log('info被修改了' ,oldValue, newValue);
}
}
}
1.8.1、深度监视
监视多级结构中属性的变化
1、监视多级结构中某个属性的变化:'对象名.属性名'
- 将其写在字符串里
const vm = new Vue({
el:"#root",
data:{
ischange:true,
numbers:{//注意这里numbers中存放的是num1和num2的地址,而不是他们的值
num1:1,
num2:2
}
},
watch:{
'numbers.num1':{
handler(){
console.log('num1发生了变化');
}
}
}
})
2、监视多级结构中所有属性的变化,设置配置项deep:true
const vm = new Vue({
el:"#root",
data:{
ischange:true,
numbers:{
num1:1,
num2:2
}
},
numbers:{
deep:true,//设置后,numbers对象内部的任意值改变都会触发 handler()
handler(){
console.log('numbers改变了');
}
}
})
Vue可以监测到多级结构中属性的改变,但
watch
默认不监测对象内部值的改变(它只监测第一层而不深入),为watch设置deep:true
后,watch才能监测对象内部值的改变
1.8.2、监视的简写形式
简写的前提:不需要immediate
也不需要deep
,只需要回调函数
完整写法:
watch:{//监视方法一,适用于创建vm时监视的对象已经确定
info:{
//当ischange发生改变时,调用hander函数
handler(newValue, oldValue){
//oldValue 和 newValue是对应的是watch所监视的info的旧值和新值
console.log('info被修改了' ,oldValue, newValue);
}
}
}
vm.$watch('ischange',{
handler(newValue, oldValue){
console.log('info被修改了' ,oldValue, newValue);
}
})
简写:
watch:{
info(newValue, oldValue) {//直接用监视的属性作为函数名
console.log('info被修改了,从 '+oldValue+' 被修改为 '+newValue);
}
}
vm.$watch('ischange',function() {//第二个参数传入一个函数,不能写=>函数
console.log('info被修改了,从 '+oldValue+' 被修改为 '+newValue);
})
1.8.3、computed 与 watch 之间的区别
- watch 可以完成 computed 的功能
- watch 有 computed 不能完成的功能,比如:watch可以进行异步操作
Vue中注意的原则
- 所有被Vue管理的函数都写成普通函数形式
- 所有不被Vue管理的函数都写成=>函数形式
1.9、绑定样式
在应用界面中,class绑定和style绑定专门用来实现元素的动态样式效果
1.9.1、class绑定
写法:
class = 'xxx',xxx可以是字符串、对象或数组
- 字符串写法适用于:类名不确定,需要动态获取
- 对象写法适用于:需要绑定多个样式,但样式的个数、名字不确定
- 要绑定多个样式,样式的个数名字都确定,但是需要根据实际情况决定哪些用哪些不用
- 字符串写法, 适用于:样式类名不确定,需要动态指定
<div id="root">
<!--字符串写法, 适用于:样式类名不确定,需要动态指定-->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
</div>
<style>
.basic{
width: 400px;
height: 100px;
background-color: skyblue;
}
.liushi1{
width: 400px;
height: 100px;
background-color: lightsalmon;
border-radius: 30%;
}
.liushi2{
width: 400px;
height: 100px;
background-color: crimson;
}
.liushi3{
width: 400px;
height: 100px;
background-color: cyan;
border: solid 2px;
border-radius: 10%;
}
</style>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#root',
data: {
name: 'class样式绑定',
mood: 'basic',
},
methods: {
changeMood(){
const styleArr = ['lishi1','liushi2','liushi3'];
this.mood = styleArr[Math.floor(Math.random()*3)];
}
}
})
</script>
- 数组写法,适用于:要绑定的样式个数不确定,名字也不确定(数组中的样式会一起作用与标签)
<div id="root">
<!--数组写法, 适用于:要绑定的样式个数不确定,名字也不确定-->
<div class="basic" :class="classArr">{{name}}</div>
<!--也可以写成以下形式,但不推荐-->
<div class="basic" :class="['style1','style2']">{{name}}</div>
</div>
<style>
.basic{
width: 400px;
height: 100px;
background-color: skyblue;
}
.style1{
width: 400px;
height: 100px;
border: solid 3px black;
}
.style2{
border-radius: 50%;
}
</style>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#root',
data: {
name: 'class样式绑定',
mood: 'basic',
//样式绑定的数组写法,
classArr: ['style1','style2']
}
})
</script>
- 对象写法,适用于:要绑定的样式名字、个数都确定, 但要动态决定有些样式用不用
<div id="root">
<!--对象写法,适用于:要绑定的样式名字、个数都确定, 但要动态决定有些样式用不用-->
<div class="basic" :class="classObj">{{name}}</div>
<!--也可以写成以下形式,但不推荐-->
<div class="basic" :class="{liushi1:false,liushi2:true,liushi3:false}">{{name}}</div>
</div>
<style>
.basic{
width: 400px;
height: 100px;
background-color: skyblue;
}
.liushi1{
width: 400px;
height: 100px;
background-color: lightsalmon;
border-radius: 30%;
}
.liushi2{
width: 400px;
height: 100px;
background-color: crimson;
}
.liushi3{
width: 400px;
height: 100px;
background-color: cyan;
border: solid 2px;
border-radius: 10%;
}
</style>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#root',
data: {
name: 'class样式绑定',
mood: 'basic',
classObj:{
liushi1:false,
liushi2:true,
liushi3:false,
}
}
})
</script>
1.9.2、style绑定
写法:
:style="fontSize: xxx"
,其中xxx是动态值,且为字符串
:style="[a,b]"
,其中a、b是样式对象注意:样式对象中,属性名在CSS中若为用
-
连接的合成词,那么应当去掉-
,并使用小驼峰
写法
- 对象写法
<div id="root">
<!--对象写法-->
<div class="basic" :style="styleObj">{{name}}</div><br/>
</div>
<style>
.basic{
width: 400px;
height: 100px;
background-color: skyblue;
}
</style>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#root',
data: {
name: 'Style样式绑定',
mood: 'basic',
styleObj: {
fontSize:'50px',//字符串
color: 'orange',
}
})
</script>
- 数组写法
<div id="root">
<!--数组写法-->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
<style>
.basic{
width: 400px;
height: 100px;
background-color: skyblue;
}
</style>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
const vm = new Vue({
el:'#root',
data: {
name: 'Style样式绑定',
mood: 'basic',
styleArr: [//数组中存对象,一起起作用
{
fontSize: '50px',
color: 'red',
},
{
backgroundColor:'orange'
}
]
}
})
</script>
1.10、条件渲染
符合了某些条件,那么就对其渲染某些东西
涉及v-if
、v-else
、v-else-if
、v-show
1、 v-show
- 适用于:切换评率较高的场景
- 特点:只是通过修改display为none来隐藏样式,并没有移除DOM元素。v-show一定可以获取到元素
v-show="xxx"
,xxx是一个可以被转化为布尔值的表达式,为true,则启动后面的渲染,否则不渲染
<h2 v-show="false">欢迎来到条件渲染</h2>
<h2 v-show="3 === 3">第二段话被渲染</h2>
<!--v-show通过调整display属性控制显示和隐藏-->
2、v-if
、v-else-if
、v-else
适用于:切换频率较低的场景
特点:会直接移除不展示的DOM元素。v-if可能获取不到元素,因为v-if会将不展示的元素从DOM中移除
v-if
可以单独使用,也可以和其他两个组合使用
v-if="xxx"
,v-else-if="xxx"
,xxx是一个可以被转化为布尔值的表达式;v-else
后面不用跟条件
<div id="root">
<h2>当前num的值为:{{num}}</h2><br/>
<button @click="num++">点击触发n++</button><br/><br/>
<div v-if="num === 1">abc</div>
<div v-else-if="num === 2">def</div>
<div v-else-if="num === 3">ghi</div>
<div v-else>num已经大于3</div>
</div>
<script>
Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示
new Vue({
el:'#root',
data:{
num:0
}
})
</script>
<template>
搭配v-if
渲染分组
当要用v-if
渲染多个元素时,可以使用<template>
标签作为不可见的包裹元素包裹需要被渲染的多个元素,最终页面上的渲染结果不会包含<template>
<template v-if="num === 1">
<div>
<h1>abc</h1>
<h1>def</h1>
<h1>hgi</h1>
</div>
</template>
<div v-else>num已经大于1</div>
1.11、列表渲染
1.11.1、v-for指令
作用:用于展示列表数据,对标签使用v-for
,会根据遍历对象的length
生成对应数量的相同标签
语法;v-for="(item, property, index) in items" :key="xxx"
- items
是遍历的源数据数
- item
是被迭代的数组元素的别名
- property
为键名
- index
为当前项的索引
- key
可以是index,也可以是遍历对象的唯一标识
- 可以用of
代替in
遍历范围:数组、对象、字符串、指定次数
v-for
遍历数组
<ul id="root">
<li v-for="(item,index) in items" :key="item.info">
{{arrName}}[{{index}}]: {{item.info}}
</li>
</ul>
new Vue({
el:'#root',
data:{
arrName: 'Persons',//v-for可以访问所有父作用域的属性
items: [
{info: 'Rory'},
{info: 'Jack'},
{info: 'Tom'}
]
}
})
v-for
遍历对象
参数至少要传入一个第一个value来获取对象中属性的值
<ul id="root">
<li v-for="(value, property, index) in Obj" :key="value.name">
{{index}}: {{property}} - {{value}}
</li>
</ul>
new Vue({
el:'#root',
data:{
Obj:{
name: 'Rory',
age: 18,
school: 'sc1'
}
})
v-for
遍历字符串
<ul id="root">
<li v-for="(char, index) in str">
{{index}}: {{char}}
</li>
</ul>
new Vue({
el:'#root',
data:{
str: 'hello v-for'
}
})
v-for
遍历指定次数
<ul id="root">
<li v-for="(num, index) in 5" :key="nums">
{{index}}: {{num}}
</li>
</ul>
1.11.2、key
的作用与原理
key
用于给节点标签做一个标识,以便区分
标识原则:尽量使用数组内元素来标识
注意:如果没写key,则遍历时,Vue会自动将index作为key的值
key的内部原理:
-
虚拟DOM中key的作用:
key是虚拟DOM中对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下: -
对比规则:
– 旧虚拟DOM中找到了与新虚拟DOM相同的key:
— 若虚拟DOM中内容没变, 直接使用之前的真实DOM
— 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
– 旧虚拟DOM中未找到与新虚拟DOM相同的key:创建新的真实DOM,随后渲染到到页面 -
用index作为key可能会引发的问题:
– 若对数据进行逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 --> 界面效果没问题, 但效率低
– 若结构中还包含输入类的DOM:
会产生错误DOM更新 --> 界面有问题 -
开发中如何选择key?
– 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
– 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用index作为key是没有问题的
1.11.3、列表过滤
在v-for
中,根据一定原则不显示一些数据,从而显示一个数组经过过滤后的版本,而不更改或重置原始数据
<div id="root">
<h2>角色列表</h2><br/>
<input type="text" placeholder="请输入角色名字" v-model="keyWord"><br/>
<ul id="root">
<li v-for="(p,index) in filpersons" :key="persons.id">
<!--注意这里遍历的是过滤后的数组-->
{{p.name}} {{p.age}}
</li>
</ul>
</div>
- 方法一:利用计算属性 -
computed
new Vue({
el:'#root',
data:{
keyWord:'',
persons: [
{id: '001', name: 'doctor', age: 5000},
{id: '002', name: 'Rory', age: 26},
{id: '003', name: 'Amy', age: 25}
],
},
computed:{
filpersons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
}
}
})
- 方法二:数据监视 -
watch
new Vue({
el:'#root',
data:{
keyWord:'',
persons: [
{id: '001', name: 'doctor', age: 5000},
{id: '002', name: 'Rory', age: 26},
{id: '003', name: 'Amy', age: 25}
],
filpersons:[]//过滤后的数组,存放过滤后的元素
},
watch: {
//监视输入的keyWord
keyWord:{
immediate:true,
handler(val){
this.filpersons = this.persons.filter((p)=>{
return p.name.indexOf(val) !== -1
})
}
}
}
})
1.12、列表排序
在计算属性conputed
中利用数组的sort()
方法
1.13、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中修改数组中的某个元素时,应当使用以下方法
-JS数组自带的API: push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
-Vue.set()
、vm.$set()
,注意,这两个方法不能为 vm
或者 vm 的根数据对象(vm._data
)添加属性
<div id="root">
<h2>学生信息</h2>
<button @click="student.age++">年龄+1岁</button><br/>
<button @click="addSex">添加性别属性,默认为男</button><br/>
<button @click="addFridend">在列表首位添加一个朋友</button><br/>
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button><br/>
<button @click="addHobby">添加一个爱好</button><br/>
<button @click="updataHobby">修改第一个爱好为: 游泳</button><br/>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别: {{student.sex}}</h3>
<h3>爱好</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}} -- {{f.age}}
</li>
</ul>
</div>
new Vue({
el:'#root',
data:{
student:{
name:'Rory',
age:18,
hobby:['学习','打游戏','旅游'],
friends:[
{name: 'Amy', age: 18},
{name: 'Doc', age: 19}
]
}
},
methods: {
addSex(){
Vue.set(this.student,'sex','男');
},
addFridend(){
this.student.friends.unshift({name: 'Jack',age: 18});
},
updateFirstFriendName(){
this.student.friends[0].name = '张三';
},
addHobby(){
this.student.hobby.push('唱歌');
},
updataHobby(){
this.student.hobby.splice(0,1,'游泳');
//或者:this.$set(this.student.hobby,0,'游泳');
//或者: Vue.set(this.student.hobby,0,'游泳');
}
}
})
1.14、收集表单数据
- 若:
<input type="text"/>
,则v-model收集的是value值,用户输入的内容就是value值 - 若:
<input type="radio"/>
,则v-model收集的是value值,且要给标签配置value属性 - 若:
<input type="checkbox"/>
– 没有配置value属性,那么收集的是checked属性(勾选 or 未勾选,是布尔值)
– 配置了value属性:
----- v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
----- v-model的初始值是数组,那么收集的就是value组成的数组
<div id="root">
<form @submit.prevent="demo">
账号: <input type="text" v-model="account"><br/><br/>
密码:<input type="password" v-model="passwords"><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="number" v-model.number="age"><br/><br/>
爱好:
学习<input type="checkbox" v-model="hobby" value="学习">
游泳<input type="checkbox" v-model="hobby" value="游泳">
唱歌<input type="checkbox" v-model="hobby" value="唱歌"><br/><br/>
所属校区
<select v-model="city">
<option value="">请选择校区</option>
<option value="Chengdu">成都校区</option>
<option value="Shenzhen">深圳校区</option>
</select><br/><br/>
其他信息:
<textarea v-model.lazy="otherInfo"></textarea><br/><br/>
<input type="checkbox" v-model="agree"> 阅读并接受<a href="https://www.51miz.com/so-wendang/11668709.html?utm_term=7164585&utm_source=baidu6&bd_vid=9350972721321806231" >《用户协议》</a>
<button>提交</button>
</form>
</div>
new Vue({
el:'#root',
data:{
account:'',
passwords:'',
sex: '',
age:'',
hobby:[],//涉及多选时,hobby应当设置为数组
city:'',
otherInfo:'',
agree:''
},
methods: {
demo(){
console.log(JSON.stringify(this._data));
}
}
})
1.15、filters
- 过滤器(Vue3不再支持)
-
作用:对要显示的数据进行特定方式处理后再显示(适用于一些简单逻辑的处理)
-
分类:过滤器分为全局过滤器和局部过滤器
– 全局过滤器:在全局起作用,写在new Vue
外
– 局部过滤器:在其所在的vm内起作用,写在某个new Vue
内 -
语法:
– 注册过滤器:Vue.filter(name.callback)
或者new Vue{filters:{}}
– 使用过滤器:{{xxx | 过滤器名}}
或{{xxx | filter(args1, 'args2')}}
或v-bind:属性 = "xxx | 过滤器名"
-
注意:
– 当全局过滤器与局部过滤器重名时,会采用局部过滤器
– 过滤器可以接收额外参数,多个过滤器也可以串联:{{msg | filter1 | filter2 | ...}}
,这种情况下,filter1
被定义为接收单个参数msg
的过滤器函数,并将filter1
的结果作为参数传给filter2
……
– 过滤器并没有改变原本的数据,而是借原数据产生新的对应数据
–
例子:获取时间戳并显示出当前时间
<div id="root">
<h2>时间</h2>
<h3>当前时间戳:{{time}}</h3>
<h3>转换后时间:{{time | timeFormater()}}</h3>
<h3>转换后时间:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3>
<h3>截取年月日:{{time | timeFormater() | mySlice}}</h3>
</div>
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,11)
})
new Vue({
el:'#root',
data:{
time:1626750147900,
},
//局部过滤器
filters:{
timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){
return dayjs(value).format(str)//这里用了DAY.js,在BootCDN可以下载
}
}
})
1.16、内置指令
已经提到的内置指令
v-bind
:单向数据绑定,简写为:
v-model
: 双向数据绑定v-for
:遍历数组、对象、字符串v-on
:绑定事件监听,简写为@
v-if
、v-else-if
、v-else
:条件渲染,动态控制节点是否存在v-show
:条件渲染,动态控制节点是否显示v-once
:该指令所在节点在初次渲染后,就被视为静态内容,以后数据的改变都不会引起v-once
所在结构的更新
其他内置指令
v-text
– 语法:v-text = "xxx"
– 作用:xxx
的值是一个字符串,这个字符串会被当做文本去解析并替换掉v-text
所在标签的原有文本
<div v-text="words">你好,v-text</div>
new Vue({
el:'#root',
data:{
words:'内容已替换,现在显示新内容'
},
})
v-html
- 语法:
v-html="xxx"
,xxx
是html标签 - 作用:支持结构解析,向指定节点中渲染包含html结构的内容
- 与插值语法区别:
–v-html
会替换掉节点中所有的内容,{{xx}}
则不会
–v-html
:可以识别html结构 v-html
的安全性问题
– 在网站上动态渲染任意HTML都很危险,这容易导致XSS攻击
– 使用v-html
应当在可信任的内容上使用,不能在用户提交的内容上使用
v-cloak
v-cloak指令是解决初始化慢而导致页面渲染完毕时闪动一下的一个方案,一般和 display:none; 一起使用.它只是一个属性名,没有值
<div v-cloak>{{name}}</div>
<style>
[v-cloak]{
display: none !important;
}
</style>
v-pre
作用:跳过该节点的编译过程
优势:可利用它跳过没有使用指令语法、插值语法的节点,加快编译速度
1.17、自定义指令
<div id="root">
<div v-text="words">你好,v-text</div>
<h2>n为{num}}, 放大10倍后的n值是: <span v-big="num"></span></h2>
<input type="text" v-f-bind:value="num">
<!--自定义指令名若为多个单词组成,那么可以在不同单词之间用短横线 - 连接-->
</div>
1.17.1、函数式
- 定义局部指令 -
directives{}
new Vue({
directives:{指令名:配置对象 / 回调函数}
})
- 定义全局指令 -
Vue.directive()
Vue.directive(指令名, 配置对象)
Vue.directive(指令名, 回调函数)
- 调用时机:指令与元素成功绑定时、指令所在模板被重新解析时
- 注意:
– 定义指令时不加v-
,使用指令时才用v-
– 最好在定义指令时给指令名加上''
– 指令名若为多个单词,则使用-
连接,而不是用小驼峰命名法,此时定义的指令名必须写为字符串形式
directives:{
big(element,binding){//自定义指令名(指令所在html元素, 指令绑定的变量)
//函数体
element.innerText = binding.value*10;
//注意使用binding的值需要调用binding的value属性
}
}
1.17.2、对象式
- 三个常用函数,Vue会在不同时刻调用他们
–bind(){}
:指令与元素成功绑定时调用
–inserted(){}
:指令所在元素被插入页面时调用
–update(){}
:指令所在模块被重新解析时调用 - 注意:指令的
this
是window
new Vue({
el:'#root',
data:{
num:10,
},
directives:{
'f-bind':{//当指令名出现了短横线-,那么这里的指令名就应该写为字符串形式
bind(){//指令与元素成功绑定时调用
//函数体
element.value = binding.value;
},
inserted(){//指令所在元素被插入页面时调用
//函数体
element.focus();
},
uodate(){//指令所在模板被重新解析时调用
//函数体
element.value = binding.value;
}
}
}
})
1.18、生命周期
Vue实例的生命周期:每一个vue实例从创建到销毁的过程,这个过程包含八个阶段:
开始创建
、初始化数据
、编译模板
、挂载Dom
、渲染
→更新
→渲染
、卸载
- 生命周期又称生命周期回调函数、生命周期函数、
生命周期钩子
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写
- 生命周期函数中的this指向是
vm
或组件实例对象
八个生命周期钩子,其他三个钩子在路由部分
1.18.1、挂载流程
1.18.1.1、beforeCreate()
实例、数据监测数数据代理被创建出来之前,这个阶段不能通过vm访问 data
和 methods
等
1.18.1.2、created()
实例、数据监测和数据代理被创建完毕,这个阶段 data
和 methods
已经被初始化,数据也被绑定,可以通过vm对其进行访问和调用,但仍然是虚拟DOM,不可用$el
1.18.1.3、beforeMount()
挂载前,模板template
已创建完成,但虚拟DOM尚未变为真实DOM
,此时可以在其变为真实DOM前进行最后一次数据更改而不会触发其他的钩子函数,一般可以在此时进行初始数据的获取。
1.18.1.4、mounted()
挂载后,即Vue完成模板的解析并把初始的真实DOM元素放入页面后时。此时模板已经被渲染为真实DOM,用户在页面可以看到渲染完成的效果
new Vue({
el:'#root',
data:{
//data
},
mounted(){
//函数体
}
})
1.18.2、更新流程
只有在view层上面的数据变化才会触发下面的
beforeUpdate
和updated
1.18.2.1、beforeUpdate()
View层的数据变化,但页面尚未同步变化
1.18.2.2、updated()
在更新新数据、生成新的虚拟DOM后,新的虚拟DOM与旧的虚拟DOM比较,最晚完成Model —> View的更新,此时updated阶段即即数据与页面都完成更新
1.18.3、销毁流程
此步之后,页面上只剩下vm曾运行的结构,但是vm功能消失
1.18.3.1、beforeDestroy()
执行销毁操作之前进行($destroy
方法被调用前),这个阶段一般进行关闭vm:关闭定时器、取消订阅消息、解除自定义事件等
vm.$destroy()
:完全销毁一个实例,清除它与其他实例的连接,解除它的全部指令及事件监听器(此处事件指的是自定义事件,不是原生DOM事件)
1.18.3.2、Destroyed
卸载watcher,事件监听,子组件
二、组件化编程
定义:实现应用中局部
功能的代码和资源
的集合,组件是可复用的Vue实例
作用:复用代码,将一个复杂的界面的编码简单化,提高运行效率
模块化:应用中的 JS 都以模块来编写的,那这个应用就是一个模块化的应用
组件化:应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
2.1、非单文件组件
2.1.1、定义及使用
定义:一个文件中包含有n个组件
使用步骤;
- 第一步:创建组件,使用
Vue.extend(options)
, 配置项options
需要注意:
– 不写el
,组件的el
由vm
统一管理,由vm
设置el
决定组件服务于哪个容器
–data
必须写为函数式,避免组件复用时,数据存在引用关系
– 组件结构的配置由template
完成
//第一步:创建person组件
const person = Vue.extend({
//组件定义时不写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
template:`
<div>
<h2>姓名: {{name}}</h2>
<h2>年龄: {{age}}</h2>
<button @click="showThis">点我提示学校名</button>
</div>
`,
data(){//注意这里的写法
return {
name:'Rory',
age:18
}
},
methods:{
showThis(){
console.log(this)//VueComponent
}
}
})
//创建school组件
const schoolInfo = Vue.extend({
template:`
<div>
<h2>学校: {{school}}</h2>
<h2>班级: {{Grade}}</h2>
</div>
`,
data(){
return{
school:'sc1',
Grade:1
}
}
})
- 第二步:注册组件(这里以
局部组件
为例,注意:局部组件只能被注册到一个Vue实例中)
– 局部注册语法:new Vue
中传入components
配置项,具体如下
//注册vm
new Vue({
el:'#root',
//第二步:注册组件(局部注册)
components:{
//格式:组件标签:组件名
Person:person,
School:schoolInfo
}
})
- 第三步,组件引入HTML
<div id="root">
<!--第三步:编写组件标签-->
<Person></Person>
<hr>
<School/>
</div>
- 全局组件注册:可被多个Vue实例使用
– 语法:Vue.component('组件名', 组件);
//创建一个全局组件all
const all = Vue.extend({
template:`
<div>
<h2>{{msg}}</h2>
</div>
`,
data(){
return{
msg:'你好呀'
}
}
})
//全局注册
Vue.component('all', all);
直接引用到HTML
<all></all>
2.1.2、注意事项:
1、组件名命名:
- 名字由多单词组成时
–-
连接,:my-pic
–大驼峰(需要Vue脚手架支持)
;MyPic - 注意
– 回避HTML中已有的元素名称
– 可以使用name配置项指定组件在开发者工具中呈现的名字
2、组件标签写法
– 第一种:<School><School>
– 第二种:<School/>
, 注意:这种方式必须使用脚手架,否则会导致后续组件不能渲染
3、组件创建的简写形式
const School = Vue.extend(options)
—> const School = options
2.1.3、组件嵌套
- 子组件定义在父组件之前,否则会报错
Cannot access '子组件名' before initialization
- 子组件定义后注册在父组件中
- 在父组件的
template
中引入子组件的组件标签 - 以此类推,套娃
<div id="root"></div>
先定义子组件
//定义student子组件,子组件定义在父组件之前
const student = Vue.extend({
name: "student",
template:`
<div>
<h2>学生姓名: {{stuName}}</h2>
<h2>学生年龄: {{age}}</h2>
</div>
`,
data() {
return {
stuName: "Jack",
age: "19"
}
}
})
再定义父组件,并将子组件注册在其中,在插值函数中插入子组件的组件标签
//定义school父组件
const school = Vue.extend({
name: "school",
template: `
<div>
<h2>学校名: {{schoolName}}</h2>
<student></student>
</div>
`,//插入子组件的组件标签<student></student>
data(){
return {
schoolName: "sc1",
}
},
//局部注册组件
components: {//在父组件里注册子组件,进行组件嵌套
student
}
})
创建vm,绑定,注册、插入父组件
//创建vm
new Vue({
el:'#root',
template:`
//插入父组件
<school/>
`,
//注册局部组件
components: {
school//组件标签与组件名相同时简写
}
})
2.1.4、组件的本质 —— VueComponent 构造函数加粗样式
用console.log输出上面的父组件student后,得到
可以看到组件本质上是一个名为VueComponent
的构造函数
- 组件本质是一个名为
VueComponent
的构造函数,且不是程序员定义,而是Vue.extend生成
- 只需要写组件的组件标签,Vue解析时就会帮我们创建对应组件的实例对象,即Vue帮助程序员执行
new VueComponent(opyions)
步骤 - 注意:每次调用
Vue.extend
时,返回的都是新的VueComponent
- 正如Vue的实例对象可以简称vm一样,VueComponent的实例也可以简称
vc
,即组件实例对象
2.1.5、组件的 this
- 在组件配置中:
–data函数
、methods
中的函数、监视属性watch
中的函数、计算属性computed
中的函数,四者的this
指向均为VueComponent实例对象
- 在 new Vue()配置中
– 以上四者的this
指向均为Vue实例对象
2.1.6、重要的内置关系
- 关系:
VueComponent.prototype.__proto__ === Vue.prototype
- 作用:让组件实例对象可以访问 Vue 原型上的属性、方法
2.2、单文件组件
1、定义:一个文件中只包含有一个组件
2、文件格式:vue,例如:test.vue
3、组件结构:
<template>
<div>
<!--组件的结构-->
</div>
</template>
<script>
//组件交互相关的代码(数据、方法等)
</script>
<style>
/*组件的样式*/
</style>
4、设置暴露
, 便于在别出引用组件
以默认暴露为例,其他暴露方式看ES6
export default {
name:'person',//组件名
data(){
return {
name:'V',
年龄:19
}
},
methods: {
showName(){
alert(this.name);
}
},
}
一个完整的简单Vue组件:
<template>
<div class="demo">
<!--组件的结构-->
<h2>人物: {{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="showName">点击我, 显示人物名</button>
</div>
</template>
<script>
//组件交互相关的代码(数据、方法等)
export default {//简写的默认暴露
name:'person',//组件名
data(){
return {
name:'V',
年龄:19
}
},
methods: {
showName(){
alert(this.name);
}
},
}
</script>
<style>
/*组件的样式*/
.demo{
background-color:rgb(238, 152, 103);
}
</style>
最后使用一个组件引入所有组件汇总
import person from './persons.vue'
export default {
name: 'App',
components:{
person
}
}
三、Vue-cli —— Vue脚手架
3.1、 Vue-CLI安装
3.2、项目创建
1、在需要创建vue项目的文件夹中打开cmd
2、输入cd 目录名
,切换到想要创建Vue项目的目录
3、输入指令vue create xxx
,创建你的Vue项目,xxx
是Vue项目名,应当避开流行的框架名称
4、选择项目使用Vue2还是Vue3,回车键表示选择
babel
负责将ES6转ES5,eslint
负责语法检查
5、出现如下语句后表示创建完毕
6、根据提示,输入cd 项目文件夹名
转到项目文件夹,然后输入npm run serve
启动项目
vscode中,在终端输入
npm run serve
也可启动项目并出现以下结果
将地址CV到浏览器地址栏然后进入,即可看到Vue准备的<HelloWorld>组件
7、输入在之前的cmd界面输入Ctrl + C
, 可暂停项目
3.3、脚手架文件结构
- public
– favicon.ico:页面标签logo
– index.html:主页面的结构 - src
– assets:存放静态资源,比如图片、视频、音频等
– components:存放所有的子组件(相对于最大的父组件App.vue)
– App.vue:最大的父组件,汇总所有组件
– main.js:JS入口文件,网页加载时首先访问的JS文件,其他JS文件通过它得以被访问 - .gitignore:git版本管制忽略的配置
- babel.config.js:babel配置文件
- package.json:应用包配置文件
- README.md:应用描述文件,markdown语法格式
- package-lock.json:包版本控制文件
- vue.config.js:配置文件
3.3.1、render()函数
new Vue({
//将App组件放入容器中
render: h => h(App),
}).$mount('#app')
上面例子中render()
函数的完整写法:
render(createElement){
return createElement(App);//传入组件
}
作用:因为 运行版本的vue文件:vue.runtime.xxx.js
不是完整的,它只有核心功能而没有模板解析器,所以它不能使用 template
配置项,这时候就需要使用 render()
函数接收到的createElement
函数去指定具体内容
3.3.2、修改默认配置
3.4、ref属性
- id的替代者,被用来给元素或子组件注册引用信息
- 应用在html标签上获取到的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)
做标识:ref = "xxx"
<!--html标签-->
<h1 ref="xxx">文本</h1>
<!--组件标签-->
<test ref="xxx"/>
获取:this.$refs.xxx
this.$refs.xxx
3.5、props配置项
1、功能:让组件接受外部传入的数据
2、数据接收,直接在组件标签中传
- 例一(
props数组
接收传入的数据,只接收):<test name="Jack"/>
, 注意:存储传入数据的属性应该写在props
配置项中,而不是data中,例如:props:['name']
- 例二(
props对象
接收,限制数据类型):props:{name:String, age:B=Number}
props:{
name: String,
age:Number
}
- 例三(
props对象
接收,限制类型、必要性,指定默认值):props:{ name{} }
props:{
name:{
type:String,//name的类型应当是String
required:true,//name是必须要传的,required设置必要性
default:"Mike"//如果没有传name,则name为默认值“Mike”
},
age:{
type:Number,//传入age的数据类型应当为Number
default:20
}
}
3、注意:由props
声明的属性xxx
是只读
的, 传入其中的值不可以修改,如果直接对props
进行修改,那么会报错。如果想修改,那么需要把props
中的属性复制到data
中,然后修改data中的数据,如下:
data(){
return {
theName: this.name,
theAge: this.age++
}
}
3.6、mixin - 混入(混合) 配置项
1、作用:当多个组件中存在设置相同的配置项
时,可以使用mixin配置项
将这相同的部分专门提取出来设置为一个混入对象,然后在需要用到它的组件中引用即可
– 在js文件
中定义混入:
//分别暴露
export const mixin = {
methods: {
showName(){
alert(this.name)
}
}
}
2、 混合过程中,如果混入的组件没有mixin
中的配置,则mixin
为组件添加;如果有,则mixin
以组件中的为主,不会对其覆盖,即:混入过程中与组件发生冲突时,以组件优先
3、 mixin
需要设置暴露,再在组件文件里引入,可以引用多个mixin
,最后在vm
中设置mixin配置项
,并以数组形配置需要引入的混入对象
import {mixin} from './mixin'//引入
export default{
name:'roleInfo',
data(){
return {
name: 'Jack',
age:18,
msg:'666'
}
},
mixins: [mixin]//在混入配置项中以数组形式配置混入对象
}
4、 混入的形式
- 局部混入:
mixin:[xxx,yyy,......]
- 全局混入:
Vue.mixin(xxx,yyy,......)
3.7、plugin - 插件
1、作用:增强Vue的功能,比如,你可以将过滤器、全局指令、全局混入、自定义方法等加入到插件中
2、位置:写在src
文件夹下
3、本质:插件是包含install
方法的一个对象,install的第一个参数是Vue
,第二个及以后的参数是插件使用者传递的数据
4、插件的定义与设置暴露:
pluginName.install = function(Vue, options){
//插件功能模块
}
//暴露,以便引用
export {pluginName};
5、main.js
中国引入并使用插件,使用插件用Vue.use(xxx)
//引入插件
import plugins from './plugins'
//使用插件
Vue.use(plugins,1,'str');//第二个参数开始都是数据,可以被插件里的方法使用
3.8、scoped样式
1、前言:在组件中通过<style>添加的样式,会汇总在一起渲染,这种情况下有可能出现不同组件间<style>的冲突,
2、作用:scoped样式
相当于给样式划定作用域,让样式成为当前组件的局部样式,只在其所处的组件内生效,从而避免了汇总时的潜在冲突问题
3、使用语法:写在组件的<style>标签内即可:<style scoped>
4、注意:scoped样式
一般不在App.vue
内使用
3.9、TodoList案例
占坑
3.10、WebStorage - 浏览器本地存储
1、存储内容大小:一般为5M左右,不同浏览器有差异
2、浏览器端实现本地存储机制所依赖的属性:window.sessionStorage
、window.localStorage
window.sessionStorage
本地存储空间:关闭浏览器后,存储的数据会被清空window.localStorage
会话存储空间:管理浏览器后,存储的数据不会被清空,需要手动清空或者手动设置浏览器清楚浏览数据选项才会被清除
3、相关API(二者共用)
setItem('key', 'value')
:接收一个键和值作为参数,把键值对添加到存储中,若键名存在,则更新其对应的值getItem('xxx')
:接收一个键名作为参数,返回键名xxx
对应的值removeItem('key')
:接收一个键名作为参数,并把该键名从存储中删除clear()
:清空存储中的所有数据
4、备注
getItem('xxx')
:如果xxx
对应的value
获取不到,那么getItem
的返回值是nullJSON.parse(null)
结果是null
- 传入参数都是字符串形式
5、查看浏览器本地存储数据的位置
使用实例:
<h2>LocalStorage</h2>
<button onclick="saveData()">点击我保存一个数据</button>
<button onclick="readData()">点击我读取一个数据</button>
<button onclick="deleteData()">点击我删除一个数据</button>
<button onclick="deleteAll()">点我清空所有数据</button>
<script>
let p = {name: '张三',age:30}
function saveData(){
window.localStorage.setItem('msg1',JSON.stringify(p));
localStorage.setItem('msg2','hello!');
localStorage.setItem('msg3',666);
}
function readData() {
localStorage.getItem('msg1');
const res = localStorage.getItem('msg1');
console.log(JSON.parse(res));
}
function deleteData(){
localStorage.removeItem('msg1');
}
function deleteAll() {
localStorage.clear();
}
</script>
3.11、组件的自定义事件
1、一种组件间的通信方式,适用于 子组件为父组件传数据
2、适用场景:A为父组件,B为子组件,B想给A传数据,那么就要在A中给B绑定自定义事件,并在父组件A中设置事件的回调函数
3、自定义事件的绑定:
- 方法一、父组件中:举例:
<goods @showPrice="getGoodsPrice"/>
或<test :getRoleName="getRoleName"/>
- 方法二、父组件中, 使用
ref
,计算属性中,使用this.$refs.xxx.$on('eventName',this.回调函数名)
,xxx
是用ref为标签注册的信息
<goods ref="goodsDemo"/>
vm中设置计算属性:
<script>
mounted() {
//this.$refs.goodsDemo.$on('showPrice', this.getGoodsPrice)
setTimeout(()=>{
this.$refs.goodsDemo.$on('showPrice', this.getGoodsPrice)
}, 1000)
}
</script>
- 注意:如果想让自定义事件只触发一次,那么可以使用指令修饰符
once
或者使用$once
方法
4、自定事件的触发: 在事件所绑定的组件中的对应回调函数下设置this.$emit('eventName', 参数)
5、自定义事件的解绑:在对应的解绑回调函数中设置this.$off('eventName')
- 注意:①如果想一次解绑多个事件,那么需要解绑的事件名以数组形式传入。②不传入参数时,意味着解绑该组件下绑定的所有自定义事件
6、组件上绑定原生DOM事件:在指令后添加native
修饰符
7、通过this.$refs.xxx.$on('eventName', 回调)
绑定自定义事件时,回调函数只能配置在methods
中,或者使用=>函数
, 以免this指向出问题
父组件App.vue
<template>
<div class="app">
<h1>{{msg}}</h1>
<!--通过父组件给子组件传递函数类型的额props,实现子组件给父组件传递数据-->
<test :getRoleName="getRoleName"/>
<hr/>
<!--通过父组件给子组件绑定一个自定义事件实现:子组件给父组件传递数据,写法一: 使用v-on-->
<goods @showPrice="getGoodsPrice"/>
<!--通过父组件给子组件绑定一个自定义事件实现:子组件给父组件传递数据,写法二: 使用ref
<goods ref="goodsDemo"/> -->
</div>
</template>
<script>
import test from './components/test.vue'
import goods from './components/goods.vue'
export default {
name: 'App',
components: { test, goods},
data(){
return {
msg: '测试信息'
}
},
methods: {
getRoleName(name) {
console.log('App收到了角色名: ', name)
},
getGoodsPrice(price){
console.log('App收到了商品价格: ', price);
}
},
/* mounted() {
//this.$refs.goodsDemo.$on('showPrice', this.getGoodsPrice)
setTimeout(()=>{
this.$refs.goodsDemo.$on('showPrice', this.getGoodsPrice)
}, 1000)
} */
}
</script>
<style scoped>
.app{
background-color: gray;
}
</style>
子组件一:goods.vue
<template>
<div class="demo">
<h2>商品:{{name}}</h2>
<h2>价钱: {{price}}</h2>
<button @click="sendGoodsPrice">点我把商品价格给App</button>
<button @click="unbind">解绑showPrice事件</button>
</div>
</template>
<script>
export default{
name:'goods',
data(){
return{
name:'book',
price: 29.8
}
},
methods: {
sendGoodsPrice() {
this.$emit('showPrice',this.price)
},
unbind(){
this.$off('showPrice');
//不传参就是解绑该组件下绑定的所有自定义事件
}
}
}
</script>
<style>
.demo{
background-color: aqua;
}
</style>
子组件二:test.vue
<template>
<div class="roleInfo">
<h2>角色姓名:{{name}}</h2>
<h2>角色年龄: {{age}}</h2>
<h1>其他信息: {{msg}}</h1>
<button @click="sendRoleName">把角色名给App</button>
</div>
</template>
<script>
export default{
name:'roleInfo',
props:['getRoleName'],
data(){
return {
name: 'Jack',
age:18,
msg:'666'
}
},
methods: {
sendRoleName() {
this.getRoleName(this.name);
}
}
}
</script>
<style>
.roleInfo{
background-color: salmon;
}
</style>
3.12、全局事件总线(GlobalEventBus) - 任意组件间通信方式
3.12.1、说明
1、全局事件总线是一种可以实现任意组件间相互通信的方式,如图,如果想要 Achild组件 向Bchild组件 传递数据,那么可以通过全局事件总线先在 Bchild组件 中绑定一个用于实现这种功能的自定义事件,然后设置参数用于接收数据,在数据传递开始时,由传递者 Achild组件 通过全局事件总线对此自定义事件进行触发,开始传递参数。
- 注意:
– 传递数据时,谁接收数据,谁绑定相关自定义事件;谁发送数据,谁触发相关自定义事件
– 全局事件总线相当于一个数据的中转站
– 发送者和接收者必须是成对出现的
2、它本质上是一个对象,它需要满足两个要求
- 所有的组件对象可以访问到它
- 它必须可以使用
$on
、$emit
、$off
去绑定、触发、解绑事件
–$on()
: 传入两个参数:绑定事件的事件名、回调函数
–$emit()
:传入两个参数:触发的事件名、发出的数据
–$off
: 传入一个参数:解绑的事件的事件名
3.12.2、定义及使用
出于全局事件总线需要满足可以被的组件访问这一要求,所以它应当被安装或定义在组件的原型对象上
组件原型对象的的重要关系;VueComponent.prototype === Vue.prototype
在main.js
中安装全局事件总线:
new Vue({
//前面省略
//全局事件总线应当在 beforeCreate 这一钩子中安装
beforeCreate(){
Vue.prototype.$bus = this;//this为当前vm,$bus是安装的全局事件总线
},
})
Achild组件 发送数据,Bchild组件 设置参数接收数据
Bchild.vue
使用$on()方法
绑定自定义事件并设置接收数据的参数
export default {
//省略
mounted(){
//绑定自定义事件
this.$bus.$on('eventName', (接收参数)=>{
//函数体,对接收到的参数进行处理
})
}
}
Achild.vue
使用$emit()方法
发送数据,触发自定义事件
export default {
//省略
methods:{
//触发事件的方法
sendData(){
this.$bus.$emit('eventName', 传递的参数);
//eventName与接收数据的组件的对应eventName一致,即: 成对
}
}
}
事件解绑,提高性能
Bchild组件
接收数据的组件接收到数据后,需要在钩子 beforeDestory() 中使用$off()方法
解绑对应事件,提高性能
export default {
//省略
mounted(){
//绑定自定义事件
this.$bus.$on('eventName', (接收参数)=>{
//函数体
})
},
beforeDestory(){
this.$bus.$off('eventName');//解绑对应事件
}
}
解绑后,一次组件间的数据传递就算完成
3.12.3、利弊
1、优点:
- 全局使用,任何组件都可以使用
- 代码量少,比较灵活
- 不需要使用缓存的跨组件数据传递
2、缺点:
- 必须成对
- 如果事件量大,那么维护会变得很困难
- 每一次数据传递完成,都需要手动销毁对应事件,否则会引发多次执行,产生bug
- 复用性差
综上:全局事件总线更适用于小项目
3.13、消息订阅与发布
1、介绍
组件B
作为消息发布者,发布了一个名为demo
的消息;组件A
作为消息订阅者,订阅了组件B
中的demo
消息,并设置了一个回调getInfo()
用来获取demo
中想要它想要的数据。当这种关系建立起来后,组件B
一旦发布demo
消息,便会触发组件A
中getInfo()
回调函数,使组件A
通过getInfo()
获取到它订阅的demo
消息中它想要获取的参数。
2、注意
- 需要安装第三方库:
pubsub.js
– 安装方法: 管理员身份运行vscode,npm i pubsub-js
- 在订阅消息和发送消息的组件中都引入:
import pubsub from 'pubsub-js'
,否则会报错 - 引入的pubsub是一个对象
3、订阅消息
订阅者使用 pubsub.js 中的subscribe()
方法接收数据并设置响应回调,该方法传入两个参数——第一个是订阅的消息名字符串,第二个是回调函数,应当写为=>函数
或是用this调用methods中的回调函数,回调函数中的参数是接收的数据
methods: {
//订阅事件的回调函数
demo(msgName, Data){
console.log('goods组件订阅了test组件的'+msgName+'消息, test组件已发布该消息,goods组件已成功接收其中数据:'+Data);
}
},
mounted() {
//订阅消息,这里用this.pubId保存了该订阅的 id
this.pubId = pubsub.subscribe('GoodsName', this.demo);
/* 还可以将回调函数写为 =>函数 的形式
pubsub.subcribe('GoodsName',(msgName,Data)=>{
console.log('goods组件订阅了test组件的'+msgName+'消息, test组件已发布该消息,goods组件已成功接收其中数据:'+Data);
})
*/
},
注意:每订阅一个消息,都有一个专属于此订阅的
id
,取消此订阅必须用这个id
去取消
4、发送消息
发送消息的组件在methods中创建一个方法,在其中用 pubsub.js 中的publish()
方法发送订阅者所订阅事件中的数据,这个方法传入两个参数——第一个是订阅者订阅的消息名字符串,第二个是发送的数据
methods: {
sendGoodsName(){//为订阅者发送数据的方法名
//数据处理过程省略
pubsub.publish('GoodsName', this.name)//发送数据
}
}
5、取消订阅(在订阅消息的组件中取消)
一般在生命周期的beforeDestroy
阶段取消顶用,使用 pubsub-js 的unsubscribe()
方法,传入的参数是订阅消息的id
beforeDestroy() {
//取消订阅
pubsub.unsubscribe(this.pubId)
}
进行消息订阅与发送的两个组件的完整代码如下
订阅者:test.vue
<template>
<div class="roleInfo">
<h2>角色姓名:{{name}}</h2>
<h2>角色年龄: {{age}}</h2>
<h1>其他信息: {{msg}}</h1>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default{
name:'roleInfo',
data(){
return {
name: 'Jack',
age:18,
msg:'666'
}
},
methods: {
demo(msgName, Data){
console.log('goods组件订阅了test组件的'+msgName+'消息, test组件已发布该消息,goods组件已成功接收其中数据:'+Data);
}
},
mounted() {
//订阅消息
this.pubId = pubsub.subscribe('GoodsName', this.demo);
},
beforeDestroy() {
//取消订阅
pubsub.unsubscribe(this.pubId)
}
}
</script>
<style>
.roleInfo{
background-color: salmon;
}
</style>
发送者:goods.vue
<template>
<div class="demo">
<h2>商品:{{name}}</h2>
<h2>价钱: {{price}}</h2>
<button @click="sendGoodsName">发送商品名给test组件</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default{
name:'goods',
data(){
return{
name:'book',
price: 29.8
}
},
methods: {
sendGoodsName(){
pubsub.publish('GoodsName', this.name)
}
}
}
</script>
<style>
.demo{
background-color: aqua;
}
</style>
效果:
3.14、$nextTick - 下一轮执行
- 作用:一个API, 指定一个回调函数,让其在
下一次
DOM更新完毕后再执行。传入一个参数,参数是其指定的回调函数 - 使用场合:在改变数据后,需要基于更新后的DOM进行一些操作时,这些操作需要在
$nextTick
所指定的回调函数中进行 - 语法如下:
this.$nextTick(fn(){//fn 是指定的回调函数
//函数内容
});
//也可以使用不设置时间的定时器实现
setTimeout(()=>{
//函数内容
})
3.15、Vue封装的过渡与动画
1、作用:在插入、更新或者移除DOM元素时,在合适的时机给元素添加样式类名
2、过程图示:
2、写法:
①、样式准备
-
元素进入时各阶段样式名:
–v-enter
:进入过程的起点
–v-enter-active
:进入过程中
–v-enter-to
:进入过程的终点 -
元素离开时各阶段样式名:
–v-leave
:;离开过程的起点
–v-leave-active
:离开过程中
–v-leave-to
:离开过程的终点
<style>
/*动画时间0.5s,匀速*/
.hello2-enter-active, .hello2-leave-active{
transition: 0.5s linear;
}
/*进入的起点、离开的终点*/
.hello2-enter, .hello2-leave-to{
transform: translateX(-100%);
}
/*进入的终点、离开的起点*/
.hello2-enter-to,.hello2-leave{
transform: translateY(0);
}
</style>
②、过渡元素使用<transituion>
包裹,并为其配置name
属性
<transition name="hello" appear>
<h1 v-show="isShow">动画效果</h1>
</transition>
appear
属性:DOM渲染完成时自动播放动画
③、多个元素过渡动画,绑定同一动画效果:
使用<transition-group>
,其中每个元素都要设置key
值
<transition-group name="hello2" appear>
<h1 v-show="!isShow" key="1">动画效果</h1>
<h1 v-show="isShow" key="2">过渡效果</h1>
</transition-group>
3、常用的第三方动画库
– Animate.css
,一个CSS3动画库,目前最通用的动画库
– Anime.js
:一个轻量级的用于制作动画的JavaScript库,适用于任何CSS属性,单个CSS转换,SVG或任何DOM属性以及JavaScript对象
– Hover.css
:CSS悬停效果动画库,提供CSS、Sass、LESS
– WOW.js
:滚动展示动画,依赖于animate.css,只能播放一次
– scollReveal.js
:页面滚动展示动画的JavaScript库,但它的动画没有播放次数限制,也不依赖其他任何文件
– Magic.css
:CSS3动画特效包
– Move.js
:一个小型JavaScript库,用于通过js控制CSS动画的执行顺序
– Greensock
:一个JavaScript动画库,对HTML元素进行动画处理, 用于创建高性能,零依赖性,跨浏览器动画,优点是具有高性能动画效果、轻量模块化、无依赖、动画序列可重叠
– Velocity.js
:适用于大量模板使用的场景,支持复杂的逻辑运算,包含 本数据类型、变量赋值和函数等功能。支持客户端和服务器使用。
– three.js
:一个易于使用、轻量级、跨浏览器的通用 JavaScript 3D 库,让开发者能够轻易在浏览器制作 3D 绘图。
四、Vue中的AJAX
需要安装axios库:
npm install axios
4.1、Vue脚手架的配置代理
如果前端应用和后端 API 服务器没有运行在同一个主机上,需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js
中的 devServer.proxy
选项来配置。
1、 方法一:在vue.config.js
中开启代理服务器
module.exports = defineConfig({
pages: {
idnex:{
//入口
entry:'src/main.js'
},
},
//添加如下配置开启代理服务器
devServer: {
proxy:'https://localhost:5000'
}
})
- 优点
– 配置容易,请求资源时直接发给前端就行 - 缺点:
– 不能配置多个代理
– 不能灵活控制请求是否走代理 - 工作方式:
– 优先匹配前端资源
2、方法二:编写vue.config.js
配置具体的代理规则
devServer: {
proxy:{
'/api':{// '/api'是请求前缀
target: '<url>',//请求作用目标
ws:true,// 用于支持websocket, 一种客户端和服务器的通信方式
changeOrigin:true, //
},
//配置其他代理
'/foo':{
target: '<other_url>'
//省略
}
}
}
- 优点
– 可以配置多个代理
– 可以灵活控制请求是否走代理 - 缺点
– 有些繁琐
4.2、Vue - resource:一个发送AJAX的库
一个Vue的插件库,用于发送AJAX,多用于Vue1.x,现在多用axios
安装:npm i vue-resource
在main.js
中引入插件:
import xxx from 'vue-resource'
//xxx 一般用 vueResource
使用插件:
Vue.ues(xxx)
axios.get('xxx').then();
//将axios替换为this.$http,返回值还是一样的
this.$http.get('xxx').then();
4.3、slot插槽 - 传递动态结构
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信方式
以下举例中,父组件为App.vue, 子组件为CateGory.vue
4.3.1、默认插槽
需要在组件标签中插入别的标签时,要提前在组件中放入<slot></slot>
插槽,这样vue解析组件标签中插入的标签后,就会将其填充到插槽中显示,如果不使用插槽,那么插入组件标签中的标签不会显示
以下以插入图片举例:
组件CateGory.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot>插槽,等待填充</slot>
</div>
</template>
App.vue
<template>
<div class="container">
<CateGroy title="美食" :listData="foods">
<img src="" alt="">
</CateGroy>
</div>
</template>
<img>
标签在App.vue
中被解析后将会填充在<slot>
中显示,如果不传标签,那么显示插槽中的默认文本- 由于动态插入的标签是在App.vue中解析,所以其样式可以写在对应插槽的组件中,也可以写在解析它的App组件中
4.3.2、 具名插槽 - 用于设置多个插槽时,控制标签与插槽的对应关系
即具有名字的插槽
1、slot="xxx"
在标签中设置属性slot="slotName"
,选择其要目标插槽,在组件的插槽中设置属性name="slotName"
,为插槽命名
App.vue
<img slot="foodImg" src="" alt="">
<a slot="film_01" href=""></a>
组件CateGory.vue
<slot name="foodImg">具名插槽,等待 foodImg 填充</slot>
<slot name="film_01">具名插槽,等待 film_01 填充</slot>
<img>
会填充到名为foodImg
的插槽中,而<a>
会填充到名为film_01
的插槽中,如果在使用多个插槽的情况下不使用具名插槽,那么会出现需要填充的标签绑定为一个整体填充入每一个插槽的情况。
2、v-slot
- 如果使用
<template>
包含需要填充如插槽的标签,那么还可以在<template>
中设置属性v-slot:slotName
来代替slot="slotName"
- 注意:
v-slot
只能写在App.vue
的<template>
中
App.vue
<template v-slot:films>
<div>
<a href="">xxx</a>
<a href="">xxx</a>
</div>
</template>
组件CateGory.vue
<slot name="films">具名插槽,等待对应标签填充</slot>
4.3.3、作用域插槽
v-solt="{ArrayName}"
, 或者:slot-scope="{ArrayName}"
数据在子组件自身,但根据数据生成的结构需要组件的使用者来决定
App.vue
<CateGroy title="美食">
<template slot-scope={foods}>
<!--foods是组件CateGory中储存信息的数组名-->
<div>
<ul>
<li v-for="(g,index) in foods" :key="index">{{g}}</li>
</ul>
</div>
</template>
</CateGroy>
CateGory.vue
export default {
name: "CateGory",
props: ['title'],
data(){
return {
foods : ['糖醋里脊', '滑蛋虾仁', '蒜蓉粉丝虾']
}
}
}
五、Vuex —— 在应用很复杂时用此保管数据
5.1、理解Vuex
5.1.1、概念
专门在Vue中实现集中式状态(或数据)管理的一个 Vue 插件,对Vue应用中多个组件的共享状态进行集中式管理(读 / 写),也是一种组件间通信的方式,且适用于任意组件间的通信。
5.1.2、什么时候使用?
- 多个组件依赖于同一状态
- 来自不同组价的行为需要变更同一状态
5.1.3、原理
5.1.4、安装
vue2使用vuex3版本
npm i vuex@3
vue3使用vuex4版本
npm i vuex@4
5.1.5、引入和使用
main.js
//引入
import Vuex from 'vuex'
//使用
Vue.use(Vuex)
自此创建vm时,就可以创建store配置项
vuex的代码一般放在一个专门的文件夹里,官网建议其文件夹名为
store
,store的代码放在文件index.js
中
src/store/index.js
:此处引入Vue,创建、引入Vuex插件并应用
//改文件用于创建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: actions,//配置
mutations: mutations,//配置
state: state,//初始化数据
getter:getter
})
main.js
,创建vm时引入store配置项
import Vue from 'vue'
import App from './App.vue'
import vueResource from 'vue-resource'
import store from './store'
Vue.config.productionTip = false
Vue.use(vueResource)
new Vue({
el: '#app',
render: h => h(App),
store,//使用 store: store, 的简写形式
beforeRender(){
Vue.prototype.$bus = this;
}
})
之后vc和vm就可以使用$store
5.1.6、store
store
是一个容器,包含着应用中大部分的state
,官方文档中说明,Vuex与单纯的全局对象有两点不同
- Vuex的状态存储是响应式。Vue组件从
store
中读取状态时,如果store
的状态发生改变,那么响应组件也会迅速更新 store
中的状态无法直接改变,只能通过显式地提交(commit
)mutation
来改变
创建一个简单的store
const store = new Vue.Store({
state:{
cnt: 0
},
mutations:{
increment(state){//传入 state 对象作为参数
state.cnt++
//通过 state 调用 cnt
}
}
})
获取状态对象:store.state.xxx
,触发状态变更:store.commit('xxx')
在Vue组件中访问store原型
this.$store.state.xxx;
this.$store.commit('xxx');
---
组件中调用store中的状态只需要在计算属性中返回即可,触发变化只需要在组件的methods
中提交mutation
5.2、Vuex核心概念和API
state、getters、mutations、actions、modules 都放在store里面
5.2.1、state
state
在Vuex中用于存储数据,且存储在Vuex中的数据与存储在Vue实例中data
上的数据遵循相同的规则,这种方式会及时触发相关联的 DOM 更新
const state - {
sum:0,
subject:'math'
}
在根组件App.vue
注册store
,由此将其“注入”到所有子组件中,在子组件中通过this.$store
访问store原型
,再通过计算属性computed
返回某个状态来读取store
实例中状态
const vc = {
template: `<h1>{{number}}</h1>`,
computed: {
number(){
return this.$store.state.num
}
}
}
这种方式比在每个组件中都注册一次各自的store
再使用要简单得多
mapState辅助函数
帮助映射
state
中的数据为计算属性
- 作用:协助生成计算属性,简化声明过程,使用前需要从
vuex
中引入 - 使用情景:一个组件需要多个状态,并要将这些状态都声明为计算属性时
- 格式:
const x = mapState({keyName_01:'value_01',keyName_02:'value_02'})
,参数传入一个对象,对象中是键值对,值必须用''
,再用...对象展开运算符
将此mapState
放入计算属性computed
中
对象写法
import {mapState} from 'vuex'
export default {
//省略
computed:{
//使用展开运算符将 mapState 在计算属性中展开
//借助 mapState 生成计算属性,从 state 中读取数据 ---对象写法
...mapState({he:'sum',subject:'math'}),
//也可以 ...x
},
mounted(){
const x = mapState({he:'sum',subject:'math'})
//sum必须用'',he可以不用,解析的时候会加上
}
}
mapState({xxx})
返回一个对象
字符串数组写法
当键值名相同且其在state中存在时,还可以使用简写方式,但应当用''
state
const state = {
sum:0,
subject:'math'
}
mapState()
import {mapState} from 'vuex'
export default {
//省略
computed:{
...mapState(['sum','subject']), //----字符串数组写法
}
}
//混入 computed 后展开相当于于:
/*
* sum(){
* return this.$store.state.sum //0
* },
* subject(){
* return this.$store.state.subject //'math'
* }
*/
5.2.2、getters
作用:加工stata
中的数据,注意,这没有改变其本身的值
const getters = {
getRes(state){
return state.sum+50
}
}
mapGetters
与 mapState类似,使用前也要引入,但其映射的是getters
中的数据为计算属性
5.2.3、mutations
更改Vuex的store
中state
状态的唯一方法是提交mutation
,其中包含一个个字符串的 事件类型type
和对应的回调函数,通过回调函数对state
中的数据进行更改,回调函数接收的第一个参数是state
const store = new Vuex.Store({
state:{
number: 13
},
mutations:{
reduce(state){//第一个参数是 state, 事件类型名是 reduce
//通过 state 调用其中的数据并修改
state.number--
}
}
})
使用store.commit()
方法调用相应的type
对state中的状态进行修改,注意type
的名字是是字符串形式
store.commit('reduce')
还可以向事件类型中传入额外的形参,相应的,向store.commit()
中也要传入对应类型的实参,这个参数可以是一个String或Number型变量,但更多时候最好是一个对象,这个额外的参数又叫载荷Payload
const store = new Vuex.Store({
state:{
number: 13
},
mutations:{
reduce(state, obj){//将整个对象作为载荷
state.number += obj.num1
}
}
})
//向对应的commit传参
store.commit('reduce',{//将事件类型名作为第一个实参
num1: 100
//因为 reduce 中传入的形参是对象,所以这里实参也该是对象
})
提交
mutation
的另一种方式:直接使用包含type
属性的对象
store.commit({//传入一个对象型实参
type: 'reduce',//将事件类型名放到该对象的 type 属性中
num1: 66
})
在对象上添加新属性
- 使用
set
方法:Vue.set(obj, 'newStr', 123, ...)
- 使用解构赋值:
state.obj = {...state.obj02, 'str', 123}
可以使用常量替代事件类型
mutation.js
export const getSum = 'getSum'
store.js
import Vuex from 'vuex'
import {getSum} from './mutation.js'
const store = new Vuex.Store({
state:{
num: 10
},
mutation:{
//用一个常量作为函数名
[getSum](state, n){
state.num += n
}
}
})
mutation必须是同步函数
mapMutations辅助函数
用于在组件中提交mutation
时,将组件中的 methods 映射为store.commit
,这种写法需要提前在根组件注入store
import {mapMutations} from 'vuex'
export default{
//省略
methods:{
...mapMutations([
'reduce',//将 this.reduce() 映射为 this.$store.commit('reduce')
//支持传入载荷
'increase',//假设有个载荷为 num, 即原本为 this.increase(num)
//将其映射为: this.$store.commit('increase', num)
])
}
}
5.2.4、actions
Action 类似于 Mutation,但有区别,主要是下面两点
- actions 用于提交 mutation, 而非像mutation那样变更状态
- action 可以包含任意异步操作,而mutation只能包含同步操作
actions函数接受一个context对象
作为参数,这个对象与store实例
具有相同的属性和方法,但不是store实例本身。可以通过context对象来调用commit提交mutation等
action:{
reduce(context){
context.commit('reduce')
}
}
需要多次使用某方法时,也可以使用参数解构来简化代码
actions:{
reduce( {commit} ){
commit('reduce')
}
}
分发Action
触发action需要使用store.dispatch('xxx')
,它返回一个Promise
,actions可以用载荷或者对象方式进行分发
//载荷方式
store.dispatch('reduce',{
num: 30
})
//对象方式
store.dispatch({
type:'reduce',
num: 30
})
在actions内进行异步操作
actions:{
reduceAsync( {commit} ){
setTimeout(()=>{
commit('reduce')
}, 2000)
}
}
组件中分发Action
- 使用
this.$store.dispatch('xxx')
分发 - 使用
mapActions
函数将组件的 methods 映射为store.dispatch
调用
import { mapActions } from 'vuex'
export default{
...mapActions(['reduce', 'incrementBy']),//mapActions同样支持载荷
...mapActions({
add:'increment'//将 this.add() 映射为 this.$store.dispatch('increment')
})
}
组合Action
当一个
store.dispatch
在不同模块中触发的全部action函数
都完成后,它自己返回的Promise
才会执行
可以通过组合多个action,来处理复杂的异步流程。这之前,需要了解到: store.dispatch()
可以用来处理被触发的 action 的处理函数返回的Promise
,并且它自己也返回Promise
,比如
store.dispatch('actionA').then(()=>{
commmit('reduce')
})
使用async
组合多个action实现异步流程
action:{
async actionA ( {commit} ){
commit('fn_1', await fn_1())
},
async actionB ( {dispatch,commit} ){
await dispatch('actionA');//actionA完成后再继续
commit('fn_2', await fn_2())
}
}
5.2.5、modules
如直观的意义一样。modules就是将store分割为数个模块
,每个模块都有各自的state
、mutation
、action
、getter
,同时每个模块还能继续向下分隔。
但值得注意的是,modules的声明应当在store之前。
const moduleA = {
state: () => ({
//...
}),
mutations: {
//...
},
actions: {
//...
},
getters: {
//..
}
}
const moduleB = {
state: () => ({
//...
}),
mutations: {
//...
},
actions: {
//...
},
getters: {
//..
}
}
const store = new Vue.Store({
mudules: {
firModule: moduleA,
secModule: moduleB
}
})
//使用模块:
console.log(store.state.firModule);//输出 firModuleA 的 state
模块的局部状态
1、在模块内部的mutation
和getter
中,接收的首个参数state
是其所在模块内的局部状态对象
const moduleA = {
state:()=>({
number: 30
}),
mutations: {
reduce(state){//此处的 state 是这个模块内的 state
state.number++
}
}
}
2、模块内的action
,局部状态通过context.state
暴露,其根节点状态通过context.rootState
暴露
const moduleA = {
actions:{
isSame( {state, commit, rootState} ){
if((state.number + rootState.num)%2 === 1){
commit('reduce')
}
}
}
}
3、模块内部的getter
,根节点状态rootState
作为第三个参数暴露
const moduleA = {
getters:{
getSum (state, getter, rootState){
return state.number + rootState.num
}
}
}
命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间,这使多个模块能够对同一 mutation 或者 action 做出响应。
如果希望某个模块具有高度的封装度且更加方便被复用,那么可以添加namespaced: true
,使其成为带命名空间的模块
const store = new Vuex.Store({
modules: {
moduleA: {
namespaced: true,//这个模块被命名为moduleA, 需要使用 moduleA 对其调用
state: {
num1: 10
},
getter: {...},
actions: {...},
mutations: {...},
//在一个模块中嵌套子模块,继承父模块命名空间
modules: {
//嵌套的模块不设置namespaced,则会自动继承父模块的命名空间
//换而言之,不设置namespaced的子模块在父模块的命名空间内
moduleB: {
state: {
num2: 50 //组件中获取: this.$store.state.moduleA.num2
}
},
//嵌套子模块,不继承父模块命名空间
moduleC: {
namespaced: true,
state:{...},
getters: {
fn(){...} //组件中获取: this.$store.getters['moduleA/moduleC/fn']
}
}
}
}
}
})
在带命名空间的模块内访问全局内容
1、想要使用全局的 state 和 getter, 则需要将rootState
作为第三参数,rootGetters
作为第四参数传入getter
,也会通过context
对象的属性
传入action
modules:{
A: {//该模块命名空间为A
namespaced: true,
getters: {
//该A模块的 getter 中,getters被局部化,只属于这个这个A模块
theGetter(state, gettersm rootState, rootGetters){
//通过模块A访问getState
getters.getState; //对应路径: 'A/getState'
//通过根节点访问getState
rootGetters.getState //对应路径: 'getState'
},
getState:state => {...}
}
}
}
2、需要在全局命名空间内分发 action 或者提交 mutation 时,需要将对象{root: true}
作为第三个参数传给dispatch
或commit
modules: {
A: {
namespaced: true,
actions: {
//在带命名空间的模块中, dispatch 和 commit 均被局部化
//可以使用 root 属性访问 根dispatch 和 根commit
theAction ( {dispatch, commit, getters, rootGetters} ){
dispatch('otherAction', null, {root: true}) //对应路径: 'otherAction'
commit('otherMutation', null, {root: true}) //对应路径: 'otherMutation'
}
},
otherAction(context, payLoad){...}
}
}
在带命名空间的模块内注册全局action
需要添加{root: true}
,并将这个需要注册的 action 的定义放在 handler
内
const moduleA = {
namespaced: true,
theAction: {
root: true, //条件1:添加{root: true}
handler(namespacedContext, payLoad){ //条件2:将要注册的theAction放入handler
console.log(namespacedContext);//上下午信息
console.log(payLoad)
}
}
}
带命名空间的绑定函数
用于简化函数mapState
、mapGetters
、mapActions
、mapMutations
绑定带命名空间模块的操作
方法:将模块的空间名称字符串作为第一个参数传递给以上函数,由此,所有绑定都会自动将该模块作为上下文
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
此外,还可以使用createNamespacedHelpers
创建基于某个命名空间辅助函数,它会返回一个对象,其中有新的绑定在给定命名空间值上的组件绑定辅助函数
//首先引入createNamespacedHelpers
import {createNamespacedHelpers} from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
模块动态注册
创建store
后,可以使用store.registerModule
方法进行注册模块,这个方法接受3个参数,第一个参数是模块名,第二个参数是模块的内容。
如果不注册嵌套模块,则第一个参数直接传字符串形式的模块名;如果注册嵌套模块,则第一个参数传入一个数组,其中依次存放父模块名、子模块名
import Vuex from 'vuex'
//创建store
const store = new Vue.Store({...})
//动态注册非嵌套模块
store.registerModule('moduleName',{
//...
})
//注册嵌套模块
store.registerModule(['fatherModule', 'childModule'],{
//...
})
访问状态:
//访问非嵌套模块
store.state.moduleName
//访问嵌套模块
store.state.fatherModule.childModule
模块动态卸载:
store.unregisterModule(moduleName)
检测模块是否注册成功
store.hasModule(moduleName)
保留state
store.registerModule()
接收的第三个参数,是一个对象,可以在其中设置perserveState:true
,从而在注册信模块时,保留过去的state
——即这个模块的state
不会和其action
、mutation
、getter
一起被添加到store
里
模块重用
需要创建一个模块的多个实例时,通过使用模块重用的方法来解决可能出现的状态对象被修改时,store或模块间数据相互污染的问题
解决方式是:使用一个函数来声明模块的状态(vue2.3.0及以上支持)
const moduleA = {
state: ()=> ({
//...
}),
//mutation、action、getter等同理
}