一 实例创建
// 当我们导入vue.js后,在浏览器的内存中就多了一个Vue构造函数
// 函数内部调用要用 this属性名 或 方法名 格式调用
// 原生js需要写在Vue实例对象后面才生效?
// Vue 中 DOM 传输值不加引号就会解析为变量---即:v-if="flag" flag为一个变量
// v-if="'flag'" flag为一个字符串
<div id = "app">
<button @click="show"></button>
</div>
var app = new Vue({
// 表示当前实例控制DOM中的哪个元素区域
el : '#app',
// 设置元素区域内的数据
data :{
msg : "欢迎学习Vue",
},
data(){ return { msg:'项宇真帅' } }
// 提供当前Vue中的所有事件执行方法
methods:{
show:function(){
},
hide(){
},
},
// 私有过滤器
filters:{
// 过滤器名:过滤器函数
focus:function{
return 处理后的结果;
},
},
// 私有自定义指令集
directives:{
// 指令名:对象
color:{
// 当页面加载时触发此函数
// 注:此时绑定的元素还没有渲染到DOM树中,即:还存在在浏览器的解析器中
// 只触发一次
bind:function(el,binding){
// 一般存放元素样式相关的指令
// 第一个参数永远是被绑定的元素,这个元素时原生的DOM对象
/* 第二个参数 binding 是一个对象,存放该函数的各种属性
.name : 指令名
.value:绑定的值-->即:传输过来的值---如果值为表达式,则结果为表达式的值
如果值为变量,则为变量的值
.expression:获取绑定值的字符串形式---如果值为表达式,则为整个表达式(字符串形式)
如果值为变量,则为这个变量名(字符串形式)
*/
},
// 当页面渲染到DOM树上时执行
// 只触发一次
inserted:function(el,binding){
// 一般存放元素行为相关的指令
},
// 绑定元素所在的模块更新(模板刷新)时触发,与绑定值是否变化无关
updated:function(el,binding){
},
}
},
// 私有组件
components:{
// 组件名:对象
login:{
// template:"<p>123<p>",
template:'#tem',
},
},
// 监听属性的值,一般用于双向绑定的时候?
watch:{
// 属性名表达式:回调函数(新值,旧值)
msg(newvalue, oldvalue){}
}
// 生命周期执行顺序
// 创建阶段
// vue准备开始初始化时调用
beforeCreate(){....},
// 初始化完成后调用
created(){....},
// 模板准备渲染到页面上时调用
beforeMount(){.....},
// 模板渲染完成后调用
mounted(){.....},
// 运行阶段--此阶段一直监听
// data中的数据发生改变时触发
beforeUpdate(){....},
// 新数据更新渲染到页面上完成后
updated(){....},
// 销毁阶段
// 当页面关闭时触发---此时数据还没有开始删除
beforeDestroy(){.....}
// 当页面上的数据销毁完成后触发
destroyed(){.....}
// 组件缓存
// keep-alive组件激活时调用
activated(){.....}
// keep-alive组件停用时调用
deactivated(){.....}
});
1. 实例属性
this.$el 返回Vue实例使用的根元素,即:el属性后面的元素
this.$refs 一个对象,返回注册过ref特性的所有DOM元素和组件实例,
可以获取指定ref值的DOM元素,可以节省获取DOM节点的资源消耗,
ref值相同则会变成一个属性,属性值是一个数组,保存着DOM节点
this.$options 用于当前Vue实例的初始化选项,用于获取自定义属性
this.$isServer 当前Vue实例是否运行于服务器
this.$set(target, key, value) Vue.set方法的别名
this.$delete Vue.delete的别名
this.$nextTick 与Vue.nextTick 相同
this.$emit('fnc',要传递的参数) 可主动触发父组件的方法, 可传值
@fnc="show" fnc:子组件标签中自定义事件名 show:父组件的事件
this.$parent 可获取父实例
this.$props 当前实例接收到的props对象
this.$options 用于当前Vue实例的初始化选项,一般用于自定义属性
this.$root 当前组件树的根元素,若没有父实例,则为自己
this.$children 获取当前组件的所有子组件(注意:该获取方法获取的子组件并不保证顺序)
this.$slots 用来访问所有被插槽分发的内容
this.$route 路由参数对象
this.$router 路由实例对象 包含实例方法,钩子函数
$event 可用于事件的参数,代表当前单击的DOM元素,注意:不是触发事件的是点击DOM元素
2. MVC 与 MVVM 的异同
相同点
都是一种设计模式 都是将一个软件,框架的设计分成三个三个设计角色 MVC:Model(模型) -> 数据层 View(视图) -> UI界面 Contoller(控制器) -> 一个中间调度者 MVVM: Model(模型) -> 数据层 View(视图) -> UI界面 ViewModel(视图模型) -> 一个中间调度者
区别
MVC:通常时后端进行软件开发的设计模式(特殊的前段框架:augular1.x使用的是MVC设计模式) MVVM:通常用于前端框架或者项目开发的设计模式(C#最早提出MVVM其实是用于后端开发)
MVC中的控制器(C)中的代码就是所有的业务逻辑,负责M和V之间的数据通信,所有的代码都需要程序员自己手动实现 MVVM中的视图模型(VM)时框架内部提供好的机制,也可以负责M和V之间的数据通信(其中数据通信中的业务逻辑不需要程序员自己去写)
MVC是单项数据通信,MVVM是双向数据绑定 MVC数据在通信的过程中只能从M到V或者从V到M,在数据传递的过程中,必须经由C进行数据的处理 MVVM数据通信过程是双向的,M和V的数据绑定后可以相互影响
双向数据绑定的概念:模型中数据发生变化可以立即更新到视图展示,视图中的数据发生变化可以立即被更新到模型中
3. 双向数据绑定实现原理
<input v-model="searchText">
// 等价于
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
/*
Vue中是ES5中的 Object.defineProperty() 方法进行属性对象的监听,
再用 set 和 get 进行数据劫持来触发对应的回调函数,
Vue3.0后不再使用 Object.defineProperty 而是 Proxy
*/
二 指令
{{msg}} ======》 插件表达式 Vue中所有的指令都以 v- 开头
1 v-cloak
--------解决插件表达式的闪烁问题 --------标签内有其他内容时保留其他内容 --------不能解析属性值中的标签
<style>
[v-cloak] {
display : none;
}
</style>
<p v-colak > {{msg}} ======</p>
<!-- 当浏览器渲染DOM时,v-cloak由于识别只会被解析成一个普通的属性,所以属性样式会生效
当Vue实例开始渲染页面时,v-cloak被识别成一个指令,此时属性样式就不会失效
-->
2 v-text
--------可以解决插件表达式的闪烁问题 --------标签内有其他内容的时候会被覆盖 --------不能解析属性值中的标签
<p v-text="msg">=====</p>
3 v-html
--------标签内有其他元素的时候会被覆盖 --------可以解析属性值中的标签
<p v-html="msg">=====</p>
4 v-bind
---------用于绑定属性的指令 ---------只能实现数据的单项绑定,从M(model数据业务层) 到 V(view视图层) ---------把绑定属性后面的字符串当做表达式/变量来解析 ---------v-bind 简写 :
<p v-bind:class="myclass+123"> ======== </p>
<p :class="myclass+123"> ======== </p>
5 v-on
---------用于绑定事件的指令 ---------v-on 简写 @
<p v-on:click = "show">123</p>
<p v-on:click = "show(item.id)">123</p> // 加括号表示传参
6 v-model
---------用于表单元素和 M(model) 中数据的双向数据绑定 --------- 【注】 v-model 只能用于表单元素中 ---------表单元素:input(radio text password button checkBox submit file hidden rest) textarea select
<p v-model = "msg"></p>
7 v-for
---------迭代数据===>进行标签的迭代渲染添加 遍历:重复执行某一段代码 迭代:下一次的值是根据上一次的值而发生改变 里面不能包含 v-if ,但可以包含 v-show
v-for 的四种迭代方法
<!-- 1.迭代数组 -->
<p v-for="(ele,i) in list">{{ele}}===={{i}}</p>
(数组项,索引) in 数组名
data:{
list:[1,2,3,4,5,6],
}
<!-- 2.迭代对象数组 -->
<p v-for="(item,i) in list">{{item.id}}===={{i}}</p>
(数组项,索引)in 数组名
data:{
list:[
{id:'1',name:'1'},
{id:'2',name:'2'},
{id:'3',name:'3'},
],
}
<!-- 3.迭代对象 -->
<p v-for="(val,key,i) in user">{{key}}==={{val}}==={{i}}</p>
(值,键,索引) in 对象名
data:{
user:{id:'1',name:'2',age:'3'},
}
<!-- 4.迭代数字 -->
<p v-for="count as 10">这是第{{count}}个P标签</p>
【注】迭代数字时,起始位置为1,即:count从1开始
注意: 当数组中的数据项的顺序发生改变时,Vue将记录下特殊元素( 如复选框的选中项 ) 的索引值,而不是DOM元素,所以此时就需要为每一项来提供一个唯一的属性 key 来区分,使之成为一个单独的组件
key属性的注意事项 1. key 属性只能是 Number 类型或者是 String 类型 2. key属性必须使用 v-bind 指令进行绑定才行
key属性的作用 1. key属性可以将DOM和数据进行关联绑定,使DOM具有唯一性,防止添加数据时页面结构的错乱 2. key属性可以提升v-for渲染DOM结构的性能
<!--
例:在添加复选框按钮时(在开头添加),数组顺序发生改变,选中状况的复选框元素也发生改变,
此时Vue会记录选中元素的索引值而不是元素,再对新数组的索引值进行渲染,此时选中的项就会改变,
所以此时就需要一个 key 属性来进行指定
-->
<p v-for="(item,i) in list" ><input type="checkbox">{{item.id}}----{{i}}</p>
<!-- 优先级:v-for > v-show > v-if -->
8 v-if/else
------- 根据所得值的true/false 创建或删除元素 ------- 切换性能消耗较高 ------- 会引起浏览器的重排
<input @click="!flag" value="显示/隐藏">
<p v-if="flag"></p>
data:{ flag:true }
<!-- v-if 与 v-else 与 v-else-if 必须是相邻兄弟元素,否则会报错 -->
<!-- v-else 前面必须有 v-if 或 v-else-if -->
<!-- v-else-if 前面必须有 v-if 或 v-else-if -->
<p v-if = "Math.random() > 0.5">1111</p>
<p v-else>2222</p>
9 v-show
------- 根据所得值的true/false 来显示或者隐藏元素 ------- 使用 display:none 来隐藏元素 ------- 有较高的初始渲染消耗 ------- 会引起浏览器的重绘
<input @click="!flag" value="显示/隐藏">
<p v-show="flag"></p>
data:{ flag:true }
10 v-for 遍历函数
v-for = "(value, index) in search(msg)"
// 通过用户输入的信息来触发的 search 函数
// search 函数返回一个新的数组给 v-for
// 由于 v-for 所遍历的数组发生的变化,导致视图页面也会随之一起渲染
// 该方法可以实现搜索功能
11 v-pre
// 不需要表达式,直接使用
// 跳过这个元素和他的子元素的编译过程,可以用来显示原始标签
// 跳过大量没有指令的节点会加快编译
12 v-once
// 只渲染元素/组件一次,再次渲染时可以跳过该元素/组件
// 元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能
11 数组可以触发视图更新的方法
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
/* 注:
1. 通过索引值而改变数组数据后是不会导致视图的更新
2. 修改数组长度时也不会触发视图的更新
第一种解决方法:
1. Vue.set(数组名,要修改的索引值,要修改的数组项的值)
2. 数组名.splice(索引值 1 要修改的值)
第二种解决方法
vm.items.splice(新的长度)
*/
三 事件修饰符
1 .stop
----------- 直接结束事件冒泡 冒泡:事件从内往外执行
2 .capture
----------- 使事件冒泡变成事件捕获 捕获:事件从外往里执行
3 .self
----------- 阻止自身的冒泡事件和捕获事件,同时他人的冒泡或者捕获影响不了自己 可以保证当前时间只能由自己触发
4 .prevent
----------- 阻止事件默认行为
5 .once
----------- 事件只触发一次
6 .native
----------- 可以将原生事件进行传递,一直穿透到当前组件的根元素上进行绑定,一般用于第三方插件
三 按键修饰符
----------- 只判断是否按下指定键,不判断按下指定键之前是否按下了其他的键
v-on:keyup.enter = "";
v-on:keyup.13 = "";等同于 .enter
.enter
.tab
.delete // 删除键和退格键触发
.esc
.space // 空格键触发
.up // 上
.down // 下
.left // 左
.right // 右
<p @keyup.left="">123</p>
// 特殊修饰键
/*
修饰键可以与其他的按键修饰符一起使用,也可以单独使用
但按下 指定键/组合键 时必须释放其他的按键,否则事件不会触发
*/
.ctrl
.alt
.shift
.meta
例:<p @keyup.ctrl.shift="ctrlshift">需 Ctrl + Shift 一起按下</p>
【注意】此时需释放其他的按键,否则无效
// 自定义全局按键修饰符
Vue.config.keyCodes.f2 = 113 ;
<p @keyup.f2 = ""></p>; ---- 此时按下f2就可以触发事件函数
四 生命周期
生命周期: 从Vue实例创建,运行,到销毁的过程 **生命周期事件:**在Vue实例创建,运行,到销毁的过程,总是会伴随着各种各样的事件,这些事件统称为生命周期事件 生命周期事件 = 生命周期函数 = 生命周期钩子
// 生命周期函数执行顺序:
// <1> 创建阶段
1. 首先 var app = new Vue(); 开始创建一个 Vue对象,并实例化
2. 刚创建完了一个空的Vue对象,里面只有一些默认的生命周期函数 和 默认的事件,其他的数据和事件都还没有被初始化完成
3. 调用第一个生命周期函数
beforeCreate(){
//此时 data中的数据/methods中的方法 还没有初始化
},
4. 数据的初始化过程...........等待data和methods初始化完成
5. 调用第二个生命周期函数,初始化完成后调用
created(){
// 此时 data/methods 已经初始化完成
// 一般用于数据发送异步请求,对数据的初始化
},
6. 编译 el绑定元素 ,使之变成HTML模板--->一个字符串,存放在内存中
7. 调用第三个生命周期函数,模板编译完成后准备挂载(渲染)到网页上时调用
beforeMount(){
// 此时模板还没有渲染到页面上
// 无法访问到真实的DOM
},
8. HTML模板挂载到网页上的过程..........等待完成
9. 调用第四个生命周期函数,模板挂载完成时调用,可以操作真实的DOM
mounted(){
// 此时模板已经渲染到页面上,用户可以看见渲染后的结果
// 获取真实的DOM操作数据
}
注意:此时Vue的实例创建已完成,组件开始进入到运行阶段
// <2> 运行阶段:
1. 当 data 中的数据发生改变后触发运行阶段的第一个生命周期函数
此时数据还没有渲染到页面上
beforeUpdate(){
// 可对数据进行再次操作,在数据渲染到页面之前的最后修改数据的机会
}
2. 更新内存中的模板数据,然后重新渲染到页面上
3. 当页面上的数据重新渲染完成时触发运行阶段的第二个生命周期函数
此时模板已经渲染完成
updated(){
}
4. 等待数据更新,再次执行运行阶段...........
// <3> 销毁阶段:
当页面关闭时Vue就从运行阶段进入到销毁阶段
1. 此时执行一个生命周期函数
注:此时数据还没有被销毁,正准备销毁
beforeDestroy(){ // destroy:销毁
}
2. 数据开始销毁.........等待销毁完成-----除生命周期外的其他所有数据
3. 销毁完成后执行最后一个生命周期函数
destroyed(){
..............
}
五 绑定元素的类样式与内联样式
1. 通过属性为元素绑定类样式
<style>
.active{
fontsize:20px;
}
.thin{
color:red;
}
</style>
// 1. 数组形式 v-bind 绑定
<h1 :class = "[ 'thin','active' ]"></h1>
// 2. 数组 + 三元表达式
<h1 :class = "['thin' , flag ? 'active' : '' ,]"></h1>
// 注:此时 flag 没有加 '' 号,所以 flag 会被解析成一个变量,去data中查找
// 3. 数组 + 对象
<h1 :class = "['thin' , {'active':flag}]"></h1>
// 4. 对象
<h1 :class = "{active:flag}"></h1>
// 对象中的属性名可加'',也可不加
// 5. 对象写入data中并引用
<h1 :class = "obj"></h1>
var app = new Vue({
el:'#app',
data:{
flag:true,
obj:{ active:true, thin:false },
}
});
2. 通过属性为元素绑定内联样式
// 1. 对象
<h1 :style = "{color:rgb,'font-size':'20px'}"></h1>
// 注意:属性带有 - 符号,则需要加引号
// 2. 对象写入data中并引用
<h1 :style = "[flag, flag2]"></h1>
var app = new Vue({
el:'#app',
data:{
rgb:'#000000',
flag:{color:'red','font-size':'20px'},
flag2:{color:'red','font-size':'20px'}
}
});
六 自定义指令
注意:在定义时,指令名称不需要加 v- 前缀,但在调用时,需要加上 v- 前缀 要写在Vue实例前面,原因?
// 定义全局自定义指令
Vue.directive( 指令名称,{
// 注意:参数二为一个对象,对象中有一些特定指令相关的钩子函数,在特定情况下触发特定函数
bind:function(el,binding){
// 页面加载时触发,只触发一次
/* 当页面加载时,绑定的元素还没有插入到DOM树中(即渲染到页面中,此时还在浏览器解析器中),所以
元素相关的行为会在解析器中执行完毕,在页面渲染时就看不到效果,因此一般存放元素样式
相关的样式 */
// 第一个参数 el:指绑定的元素,注:函数中的第一个参数永远都是绑定的元素,这个元素是一个原生的js对象--即DOM对象
/* 第二个参数 binding:一个对象,存放该函数的各种属性
.name :指令名
.value:绑定值;如果为表达式,则为表达式的结果,变量为变量值
.expression:绑定值的字符串形式---如果为表达式,则为整个表达式,变量则为变量名
*/
},
inserted:function(){
// 当元素插入到DOM中时,会执行此函数,触发一次
// 一般存放元素相关的指令
},
update:function(){
// 绑定元素所在的模块更新时触发,而不论绑定值是否变化
},
unbind:function(){
// 指令与元素解绑时触发,只触发一次
}
} );
// bind update 合并简写形式
Vue.directive(指令名,function(){
})
// 例:指定元素页面初始渲染后获得焦点
Vue.directive(focus,{
bind:function(el){
el.style.color = "red";
},
inserted(el){
el.focus();
}
})
<input type="text" v-focus ></input>
七 过滤器
0 全局过滤器必须放在Vue实例之前 1 过滤器是一个通过输入数据,能够及时对数据进行处理并返回一个数据结果的简单函数 2 过滤器函数始终以表达式的值作为第一个参数,带引号的参数视作字符串,而不带引号则视为表达式 3 用户从 input 输入的数据在回传到model之前也可以先处理 **4 当私有过滤器与全局重名时遵循就近原则,即:以私有过滤器为准 ** 5 只能 用于插值表达式 或者 v-bind 指令 6 单项绑定不修改原始数据
// 定义语法
Vue.filter('过滤器名称',function(data){
// 注:过滤器函数的第一个参数永远都是 |(管道符)前面的数据
// 处理过程.....
return 返回处理的结果;
})
<p>{{msg(data 中的变量名) | 过滤器名称 }}<p>
//例
<div id="app">
<p>{{msg | reinforce('呵呵',1)}}</p>
</div>
<script>
// 定义全局过滤器
Vue.filter('reinforce',function(data,b=''){
return data.replace(/单纯/g , b);
});
var app = new Vue({
el:'#app',
data:{
msg:"我曾经单纯的以为我遇到的都是最单纯的人",
},
methods:{
},
// 定义私有过滤器
filters:{ // 私有的过滤器群组 过滤器有两个参数:过滤器名称,过滤器函数
reinforce:function(data, a, b=''){
return data.replace(/单纯/g , b+a);
},
}
});
八 动画
0 transition标签常用属性
name string //设置过度类名的前缀,默认是v
appear boolean //是否在页面初始渲染时实现动画,默认 false
type string //指定过度事件类型,transition或animation,默认为过度事件长的
mode string //控制元素进入/离开的过度序列,out-in/in-out两个值,默认同时执行,一般多个组件时使用
1 使用vue中的过度类实现
1. 使用要包裹的动画元素 是 Vue 中自带的一个标签 2. 设置 v-enter , v-enter-to , v-enter-active , v-leave , v-leave-to , v-leave-action 类样式
// 原理
/*
当元素用transition标签包裹时,vue会自动为该标签创建的元素构建一个动画流程,当删除/隐藏或者
创建/显示时会自动触发该动画
当元素由显示到隐藏/删除时,vue会自动在删除前添加v-leave类,然后开始运行v-leave-active
和v-leave-to类,并删除v-leave类,
在元素删除/隐藏后会把v-leave-active和v-leave-to类也删除,若是隐藏,会添加一个display:none
属性
元素隐藏到显示同理
*/
3. v-move 类 ------ 使移动具有平滑效果 一般使用对象:
/*
用splice删除数组的元素,由于删除的元素经历了一个过渡,始终占据文档流的这个位置,
因此下一个元素要等待其过渡消失后再移动过来,造成一个生硬的效果。
要达到平滑过渡,就要在删除元素leave-active阶段用position:absolute将其移出文档流,
然后使用v-move类,后面的元素才能同时平滑过渡过来。
一般设置:
.v-move{
transition: all 0.6s ease;
}
.leave-active{
position:absolute
}
*/
2 使用第三方类库实现动画
animate.css
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
<!-- 引入插件 -->
<link rel="stylesheet" href="./lib/animate.css">
<style>
</style>
</head>
<body>
<div id="app">
<input type="button" @click="flag=!flag" value="显示/隐藏">
<transition enter-active-class="animated bounceIn" leave-active-class="animated bounceOut" :duration='{enter:200,leave:500}'>
<!-- bounceIn:入场动画
bounceOut:离场动画
duration:定义动画完成一个周期所需要的时间 -->
<p v-show = "flag">123456789</p>
</transition>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
flag:false,
},
methods:{
}
});
</script>
</body>
</html>
3 利用动画的钩子函数实现
<!-- 语法 -->
<transition
v-on:before-enter = "beforeEnter" // 进入之前
v-on:enter = "enter" done() // 进入的结束状态
v-on:after-enter = "afterEnter" // 进入结束后的状态
v-on:enter-cancelled = "enterCancelled" // 进入过程中取消
v-on:before-leave = "beforeLeave" // 离开之前
v-on:leave = "leave" // 离开的结束状态
v-on:after-leave = "afterLeave" // 离开结束后的状态
v-on:leave-cancelled = "leaveCancelled" // 离开过程中取消
>
<p>123456789</p>
</transition>
<!-- ********************* 半场动画 ************************** -->
<div id="app">
<input type="button" @click="flag=!flag" value="加入购物车">
<transition
@before-enter = "beforeEnter"
@enter = "enter"
@after-enter = "afterEnter"
>
<div id="dv" v-show="flag"></div>
</transition>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
flag:false,
},
methods:{
// 进入前的状态,可以设置其位置
beforeEnter(el){
// el:动画钩子函数第一个参数永远都是该函数所在的元素,是一个原生的js对象
el.style.transform = "translate(0,0)";
},
// 进入的过程状态,可以设置移动效果
enter(el , done){
el.offsetHeight; // 保证DOM的实时刷新,也可以改成offsetWidth,无实际效果作用
// 触发浏览器的重绘效果
el.style.transform = "translate(150px , 450px)";
el.style.transition = "all 0.5s ease";
// 回调函数,done()就是afterEnter函数的引用,调用了done()就是调用了afterEnter
// 因为显示或隐藏会触发监听机制?所以必须在enter中调用,否则会重新触发 transition属性的效果,进而有延时出现?
done(); // 必须调用
},
// 进入完成后的状态,可以设置完成后的效果
// 注意:该函数用 enter 中的 done() 调用,防止触发监听机制?
afterEnter(el){
this.flag = !this.flag;
}
},
});
</script>
4 列表动画的实现
**1 用v-for遍历出来的列表,如需添加动画效果,需使用 标签包裹动画元素 ** 2 标签的子元素都需要绑定 :key 属性 3 给标签添加 appear(出现)属性可实现动画入场效果,但只执行一次,只在页面初始渲染时执行 4 由于标签会在页面上默认渲染成 (行内)标签,动画元素是块级元素,不符合W3C规范,所以需要用 tag属性来指定渲染成一个标签,如 tag="ul"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./js/vue-2.4.0.js"></script>
<style>
.v-enter,
.v-leave-to{
opacity: 0;
transform: translateY(50px)
}
.v-enter-active,
.v-leave-active{
transition: all 0.6s ease;
}
.v-move{
transition: all 0.6s ease;
}
.v-leave-active{
position: absolute;
}
li{
height:30px;
line-height: 30px;
margin-top: 5px;
width: 300px;
border: 1px dashed slateblue;
}
</style>
</head>
<body>
<div id="app">
Id:<input type="text" v-model='id'>
name<input type="text" v-model='name'>
<input type="button" @click="add" value="添加">
<transition-group appear tag="ul">
<!-- appear:表示元素在渲染时触发一次 -->
<!-- tag="ul" : 将 transition-group 标签渲染成 ul 标签,transition-group标签在DOM中默认渲染成 span 标签 -->
<li v-for="(item,index) in list" :key="item.id" @click="del(index)" >id:{{item.id}}========name:{{item.name}}----{{index}}</li>
</transition-group>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
id:'',
name:'',
list:[
{id:1,name:1},
{id:2,name:1},
{id:3,name:1},
{id:4,name:1},
{id:5,name:1},
{id:6,name:1},
{id:7,name:1},
{id:8,name:2}
],
},
methods:{
add:function(){
this.list.push({id:this.id,name:this.name});
},
del(index){
console.log(index);
this.list.splice(index,1);
}
},
filters:{},
diractives:{}
});
</script>
</body>
</html>
九 组件
1. 组件的定义
为了拆分vue实例的代码量,把不同的功能定义成一个个不同的组件,需要什么功能就去调用对应的组件即可
2. 组件化和模块化
组件化:从UI视图的角度对代码进行封装,方便UI视图的重用 模块化:从业务逻辑的角度对代码进行封装,方便重用
3. 创建组件
3.1 创建全局组件
【注】所有的全局组件,过滤器,指令 需要在Vue实例之前进行声明 【注】所有的组件必须方法Vue实例模板中才能被解析
// 1. 使用 Vue.extend() 创建一个组件----返回一个组件模板对象(组件名)
var com = Vue.extend({
template:"<p>这是一个全局组件<p>", //通过 template 属性,指定的组件的HTML结构
});
// 2. 使用Vue.component()注册组件
Vue.component(注册组件名,创建的组件对象)
Vue.component("my-com",com);
// 注意:组件名注册时使用驼峰命名法时,组件调用时需要把大写的驼峰改成小写,并用 — 连接
// 3. HTML结构页面直接调用即可
<div><my-com></my-com></div>
// 简写形式
Vue.component("my-com",Vue.expend({
template:"<p>这是一个全局组件<p>",
}))
// 简化至
Vue.component("my-com",{
temlate:"<p>这是一个全局组件<p>",
})
3.2 模板创建组件
// 注:在vue控制元素模块的外面用 template 标签定义 HTML 模板结构
<template id="tem">
<p>这是模板创建的组件<p>
</template>
// 在 script 标签内注册模板
Vue.component("my-tem",{
template:"#tem",
})
3.3 注意
组件有且只有一个根元素,即:组件的最外层只能有一个元素 例: template:“
”, 就会报错 正确的写法: template : ""
4 用template标签创建的组件组件也具有 data 属性和 methods 属性------即:就是一个Vue实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="./js/vue-2.4.0.js"></script>
<title>Document</title>
</head>
<!--实现每次单击 count+1 -->
<body>
<div id="app">
<mytem></mytem><hr>
<mytem></mytem><hr>
<mytem></mytem><hr>
</div>
<template id="tem">
<div>
<input type="button" value="添加" @click="add">
<p>{{count}}</p>
</div>
</template>
<script>
// 定义一个全局组件
// 组件中定义的数据只能用在自己的组件中
Vue.component('mytem',{
template:'#tem', // 表示通过指定的一个ID去查找这个id的template元素的内容当做组件的HTML结构
data:function(){
return {count:0};
// 每次都创建了一个新的对象,所以不会相互影响
},
/*
注意:
1. 组建中的data是一个方法,实例中的data是一个对象
2. 组件中的data必须要返回一个对象,vue中也可以这么写
3. 组件中的data使用方法和实例中的data一样
4. 组件中的data里面的数据要书写在对象
5. 如果组件中的data是一个对象,则会出现,之中的某一处数据改变,其他使用该组件的地方
的数据都会改变,相当于直接改变了堆中的数据
*/
methods:{
add(){
this.count++;
},
},
});
var app = new Vue({
el:'#app',
data:{},
methods:{},
});
</script>
</body>
</html>
5 组件切换---动态组件
// <component :is="组件名变量"></component>
// 使用 component 标签来展示指定的组件,该标签为一个占位符
// 原理:通过 :is 属性绑定的组件名变量来渲染指定的组件
例:组件之间的切换
<transition mode = "out-in"> mode属性 设置动画的进入方式
<component :is = "comName"></component>
<transition>
<!-- 模板解析也有一个 is 属性,来进行标签名的更改 -->
6 父子组件的相互传值
6.1 组件传值方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="./js/vue-2.4.0.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
<home :parent-msg="msg" :parentshow = "aaa" @parentaaa = "aaa"></home>
<button @click = "select">parent</button>
<p ref="parent">父组件中的元素</p>
</div>
<template id="child">
<div ref="son">
<p>这是一个子组件+{{parentMsg}}+{{this.$parent.msg}}</p>
<button @click = "show">调用父组件的方法</button>
</div>
</template>
<script>
// 子组件
var home = {
template:'#child',
data(){
return {msg:"子组件"}
},
props:['parent-msg','parentshow'],
/* 对象的写法: props:{属性名:属性值的类型} */
methods:{
show(){
// 子组件和父组件的相互传值
// 子组件获取父组件的值
//1. this.$parent.aaa()
//2. 通过props和属性绑定来获取父组件的值
// 父组件获取子组件的值
//1. 绑定自定义事件
this.$emit('parentaaa')
// 第一个参数为函数名,后面的参数为传输的值
// @parentaaa = "aaa" @事件名 = 事件处理函数
//2. 通过实例属性 $refs 获取
// 子组件定义一个 ref 属性和值
//3. 插槽
}
}
}
// 父组件
new Vue({
el:'#app',
data:{
msg:'父组件'
},
components:{
home
},
methods:{
aaa(){
console.log('父组件
')
},
select(){
// 父组件获取子组件的值
console.log(this.$refs.son.msg)
}
}
})
</script>
</body>
</html>
6.2 浏览器解析大小写命名规范
/*
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。
例:HTML中的属性@addList会被解析为@addlist,js再用@addList去找,就会找不到
这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的
kebab-case (短横线分隔命名) 命名
但:如果你使用字符串模板,那么这个限制就不存在了。
但是绑定的事件名不存在任何的大小写转换,必须完全匹配才行
*/
7 单项数据流
vue 中的单项数据流是指父组件向子组件传递数据,只能单向绑定:是为了防止子组件无意间修改了父组件的值 子组件需要使用props属性进行接收传过来的数据,父组件传递过来的数据在子组件中是只读的,不能对其进行修改,是为了防止子组件一不小心修改了父组件的值而发生的数据错误而设定的
如果修改了父组件传递过来的数据,会报错误警告
这种自上向下的数据传输方式叫做单项数据流
8 兄弟组件传值
// bus总线
import Vue from 'vue'
const Bus = new Vue()
export default Bus
// 组件一
import Bus from './Bus'
export default {
data() {
return { }
},
methods: {
....
Bus.$emit('log', 120)
},
}
// 组件二
import Bus from './Bus'
export default {
data() {
return { }
},
mounted () {
Bus.$on('log', content => {
console.log(content)
});
}
}
// 注意:注册的总线事件要在组件销毁时卸载,否则会多次挂载,造成触发一次但多个响应的情况
beforeDestroy () {
this.bus.$off('log', this.getData);
}
十 路由
前端路由:监听hash值的变化,根据hash或history值的变化来切换不同的页面,使前端路由做的项目都是单页面应用程序(SPA) hash格式:#hash值
后端路由:根据前端请求的URL不同分发不同的处理函数,然后将相应的处理结果返回到客户端
1. 基本使用语法
<div id = “app”>
<!-- 使用语法 -->
<router-link to="path地址"></router-link>
<router-view></router-view>
</div>
<script>
// 创建语法
let login = {
template : "<h2>登录<h2>"
}
let register = Vue.component("register",{
template : "<h2>注册<h2>"
})
let distroy = Vue.extent({
template : "<h2>销毁<h2>"
})
var router = new VueRouter(
{path:"/login", component:login},
{path:"/register", component:register}
)
new Vue({
el:"#app",
router,
})
</script>
2. 嵌套路由
与ajax有点相同,进行页面的局部渲染,即:公共样式部分不改变 注意:有子路由时,父路由必须为子路由提供一个标签用于占位,也就是让子路由显示出来,可以多层嵌套
<div id = “app”>
<!-- 使用语法 -->
<router-link to="/login"></router-link>
<router-view></router-view>
</div>
<template id="loginTem">
<h2>登录<h2>
<router-link to="/register"></router-link>
<router-link to="/distroy"></router-link>
<!-- 需为子组件提供一个占位符 -->
<router-view></router-view>
</template>
<script>
// 创建语法
let login = {
template : "#loginTem"
}
let register = Vue.component("register",{
template : "<h2>注册<h2>"
})
let distroy = Vue.extent({
template : "<h2>销毁<h2>"
})
var router = new VueRouter(
// 父路由
{
path:"/login", component:login
// 设置子路由 注意:子路由不能加 / 否则就是根目录了
children:[
{path:"register",component:regester},
{path:"distroy",component:distroy},
]
},
)
new Vue({
el:"#app",
router,
})
</script>
3. 路由传参
<!-- 1. params 传参(restful API) -->
<router-link to="/login/5"><router-link>
{path:"/login/#id", component:login}
<!-- 该组件内调用:this.$router.params 获取的是一个键值对形式·对象 -->
<!-- 2. query 字符串传参 -->
<router-link to="/register?id=5"></router-link>
{path:"/register", component:register}
<!-- 该组件内调用:this.$router.query 获取的是一个键值对形式的对象 -->
<!-- 编程式导航传参 -->
<script>
this.$router.push('/home/37')
this.$router.push({path:'/home/37'})
// name 方法可以使地址栏中不显示传递过去的参数
this.$router.push({name:'home',params:{id:37}})
</script>
<!-- 这个地方用query方法传参,和前面用的params获取的
区别在于,用query获取的id值会在url中有显示,可以看到你传过来的值
-->
<!-- props传参 -->
const router = new VueRouter({
routes:[
{path:'/foobar是啥/:answer',
component:foobar,
props:{movie:'拯救大兵瑞恩',epoch:"二战时期",source:"盟军俚语",German:false}},
]
})
const foobar={
props:['epoch','movie','source','German'],
template :"<div>典型电影:{{movie}}<br /><br />\
时期:{{epoch}}<br />\
来源:{{source}}<br />\
<br /><br /><br />\
真的{{German}}是德语\
</div>"
};
4. 路由重定向以及默认高亮类
<div id = “app”>
<!-- 使用语法 -->
<router-link to="path地址"></router-link>
<router-view></router-view>
</div>
<script>
// 创建语法
let login = {
template : "<h2>登录<h2>"
}
var router = new VueRouter(
// redirect 重定向
{path:"/", redirect:"/login"},
{path:"/login", component:login},
// 改写默认类
linkActiveClass:"myClass" // 默认类为:router-link-active
mode: 'history'/'hash'
// hash模式:地址栏上有#号,兼容低版本浏览器
// history模式:地址栏上没有#号,复制地址在新浏览器或新标签页有几率打不开,、
// 解决方案,后端任何时机都返回 index.html
)
new Vue({
el:"#app",
router,
})
</script>
5. 命名视图
一个路由中同时显示多个组件,通过中的name属性来进行控制
<div id = “app”>
<!-- 使用语法 -->
<router-link to="/login"></router-link>
<router-view></router-view>
<router-view name="register"></router-view>
<router-view name="distroy"></router-view>
</div>
<script>
// 创建语法
let login = {
template : "<h2>登录<h2>"
}
let register = Vue.component("register",{
template : "<h2>注册<h2>"
})
let distroy = Vue.extent({
template : "<h2>销毁<h2>"
})
var router = new VueRouter(
// 父路由
{
path:"/login", components:{
login,
"register":register,
"distroy":distroy
}
},
)
new Vue({
el:"#app",
router,
})
</script>
6. 命名路由
设置
this.$router.push(路由地址,params:{key:value})
指定路由中:
this.$route.params.id 获取
7. 路由钩子函数
// 1. 全局路由钩子函数---导航钩子
// 全局前置钩子
router.beforeEach((to, from, next)=>{
// 在每个路由被跳转前调用,通常用来做权限判定
to: 即将进入的路由对象
from:即将离开的路由对象
next:一个函数,调用后表示当前钩子函数的状态为 resolve ,否则为false,终止导航
next() 直接进入 to 所指的路由
next(false) 中断路由
next('/home') 跳转到指定路由
next(error) 如果传入next的参数是一个Error实例,则导航会被终止
且该错误会被传递给router.onError()注册过的回调
})
// 全局后置钩子
router.afterEach((to, from)=>{
})
// 2. 单个路由的钩子函数---路由独享守卫
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
},
beforeLeave: (to, from, next) => {
}
}
// 3. 组件内的钩子函数
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
十一 插槽
1. 基本语法即使用
默认情况下,父组件在子组件中嵌套的内容是会被忽略的,所以为了使这些内容起作用,Vue增加了一个标签,由该标签负责内容的显示
123 默认情况下 123 是不会被显示的,此时需要加 childtem组件内插入 便签进行显示
插槽使用:
2. 编译作用域
父级模板里的所有内容都是在父级作用域中编译的; 子模板里的所有内容都是在子作用域中编译的。
3. 后备内容---即:默认内容
// 例:设置按钮的默认文本
<button> <slot>submit</slot> </button>
// 当组件中不提供插槽内容时---后备内容就会被渲染
<submit-button></submit-button>
显示的内容:<button>submit</button>
// 当组件中有插槽内容时---后备内容被覆盖
<submit-button>reset</submit-button>
显示的内容:<button>reset</button>
4. 具名插槽的使用
<!-- 注:该插槽方法只支持 2.6.0及以后的版本 -->
<div id="app">
<!-- 实际使用 -->
<xy>
<template v-slot:header>
<p>具名插槽头部</p>
</template>
<template v-slot:footer>
<p>具名插槽尾部</p>
</template>
</xy>
</div>
<!-- 模板中使用 -->
<template id="tem">
<div>
<slot name="header"></slot>
<p>具名插槽</p>
<slot name="footer"></slot>
<div>
</template>
<script>
/*
1. 先在模板使用 <slot name="插槽名"> 标签 来定义额外的插槽 例:<slot name="header"></slot>
2. 组件调用时,插槽内容用 <template> 标签包裹,在该标签使用 v-slot 指令,并以 v-slot:参数
的形式来指定插槽名 例:<template v-slat:header></template> 注意:没有""包裹,以:连接
3. 任何没有被包裹在 带有v-slot的 <template> 中的内容都会被视为默认插槽的内容。
*/
</script>
5. 作用域插槽
应用场景:插槽内容为子组件的内容时,父组件可以用子组件的值
1 先把子组件的属性绑定在 标签上作为一个插槽特性,即:插槽prop
格式:<slot name="插槽名" v-bind:自定义特性名 = " 子组件的属性名 "> 2 在组件调用中的