Vue概述
渐进式JavaScript框架
声明式渲染—>组件系统—>客户端路由—>集中式状态管理—>项目构建
- 易用:熟悉HTML、CSS、JavaScript可快速上手Vue
- 灵活:在一个库和一套完整框架之间自如伸缩
- 高效:20KB运行大小,超快的虚拟DOM
Vue虚拟DOM
-
前言:Vue2.0引入了虚拟DOM,比Vue1.0的初始渲染速度提升了2~4倍,并大大降低了内存消耗
-
为什么要提出虚拟DOM:随着时代的发展,页面上的功能越来越多,我们需要实现的需求也越来越复杂,DOM的操作也越来越频繁,通过js操作DOM的代价很高,因为会引起页面的重排重绘,增加浏览器的性能开销,降低页面渲染速度
-
为什么虚拟DOM可以提高渲染速度:传统方式用js操作DOM会有很多额外的DOM操作,例如一个ul标签下有很多个li标签,其中只有一个li有变化,这种情况下如果使用新的ul去替代旧的ul,其实除了那个发生变化的li节点之外,其他节点都不需要重新渲染,由于DOM操作比较慢,所以这些DOM操作在性能上会有一定的浪费,避免这些不必要的DOM操作会提升很大一部分性能(减少重排重绘从而节省浏览器的性能开销)
其实虚拟DOM在Vue.js中主要做了两件事: -
提供与真实DOM节点所对应的虚拟节点vnode
- 将虚拟节点vnode和旧虚拟节点oldVnode进行比对,对两个虚拟节点进行比对是虚拟DOM中最核心的算法即patch算法,patch算法的核心是diff算法(借助diff算法,通过对比前后虚拟DOM的差异,进行有针对性的打补丁渲染),它可以判断出哪些节点发生了变化,从而只对发生了变化的节点进行更新操作
Vue实例参数
- el:元素的挂载位置(值可以是CSS选择器或者DOM元素)
- data:模型数据(值是一个对象)
Vue模板语法
前端渲染: ajax发起请求对应的后台接口,服务端返回数据给前端,前端把请求回来的数据填充到HTML标签中
前端渲染方式:
- 原生js拼接字符串
- 使用前端模板引擎(如art-template)
- 优点:相比于拼接字符串,代码明显规范很多,代码可读性明显提高了,方便后期的维护
- 缺点:没有专门提供事件机制
- 使用Vue特有的模板语法
模板语法概览:
- 插值表达式
- 指令
- 事件绑定
- 属性绑定
- 样式绑定
- 分支循环结构
插值表达式
插值表达式支持基本的计算操作
<div id="container">
<div>{{msg}}</div>
<div>{{1+2}}</div>
<div>{{"Vue——"}+msg}</div>
</div>
<script>
var vm=new Vue({
el:'#container',
data:{
msg:'Hello World'
}
})
</script>
指令
概念:
- 指令的本质就是自定义属性
- 指令的格式:以v-开始
v-cloak指令用法
- 插值表达式存在"闪动"的问题,使用v-cloak指令可以解决该问题
- 原理:先通过样式隐藏内容,然后在内存中进行值替换,替换好之后再显示最终的结果
数据绑定指令
- v-text填充纯文本(相比插值表达式更加简洁)
- v-html填充HTML片段
- 存在安全问题
- 本网站内部数据可以使用,来自第三方的数据不可用
- v-pre填充原始信息(显示原始信息,跳过编译过程)
v-cloak和数据绑定指令使用
<style>
[v-cloak] {
display: none;
}
</style>
<div id="container" v-cloak>
<div v-text="msg"></div>
<div v-html="msg1"></div>
<div v-pre>{{msg}}</div>
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
msg: "Hello World",
msg1: "<h1>Hello World</h1>"
}
})
</script>
数据响应式
htm5和Vue响应式区别:
- html5中的响应式(屏幕尺寸的变化导致样式的 变化)
- Vue中的数据响应式(数据的变化导致页面内容的变化)
- 比如导航菜单,data中的isCollapse为false时导航菜单不折叠收起,为true时导航菜单折叠收起
Vue修改响应式数据
Vue无法检测到数组和对象的property添加或移除,由于Vue会在初始化实例时对property执行 getter/setter 转化,所以 property必须在
data
对象上存在才能让Vue将它转换为响应式的。对于已经创建的实例,Vue不允许动态添加根级别的响应式 property(如:vm.fruits[2] = "lemon"
)
Vue提供了vm.$set(vm.items,index,value) 或者 Vue.set(vm.items,index,value)方法向嵌套对象添加响应式property
- vm.items表示要处理的数组名称或者json对象名称
- index表示要处理的数组的索引或者json对象的属性名
- value表示要处理的数组的值或者json对象的属性值
<script>
var vm = new Vue({
el: "#container",
data: {
fruits: ["apple", "banana", "orange"],
info: {
name: "张三",
age: 18
}
}
})
// vm.fruits[2] = "lemon" 这样修改的数据不是响应式的(data中数据是改变了,但是页面数据还是没变)
vm.$set(vm.fruits, 2, "lemon")
// vm.info.gender = "male" 这样添加的数据不是响应式的(data中数据是改变了,但是页面数据还是没变)
vm.$set(vm.info, "gender", "male")
</script>
v-once指令
- v-once指令只编译一次(即显示内容之后不再具有响应式功能)
<div v-once></div>
如果显示的内容后续不需要再修改,可以使用v-once来提高性能(Vue需要监听响应式的数据是否变化,这样会消耗性能)
MVC设计思想
- V(view)视图层:直接面向最终用户,它是提供给用户的操作界面
- M(model)数据层:程序需要操作的数据或信息
- C(controller)控制层:它负责根据用户从视图层输入的指令,选取数据层中的数据,然后对其进行相应的操作
MVVM设计思想
Vue是以数据为驱动的,Vue自身将DOM和数据进行绑定,一旦创建绑定,DOM和数据将保持同步,每当数据发生变化,DOM就会跟着变化,每当DOM发生变化,数据也会跟着变化
- M(model)数据层
- V(view)视图层
- VM(view-model)数据模型层即Vue实例
v-model指令
实现Vue双向数据绑定,input输入框的数据和data中msg数据实现了同步
<div id="container">
<input type="text" v-model="msg">
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
msg: 123
}
})
</script>
v-model低层实现原理:
方法1:
<div id="container">
<input type="text" v-bind:value="msg" v-on:input="handle">
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
msg: 123
},
methods: {
handle: function($event){
//使用input输入框中的最新的数据覆盖原来的数据
this.msg = $event.target.value;
}
}
})
</script>
方法2:
<div id="container">
<input v-bind:value="msg" v-on:input="msg=$event.target.value">
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
msg: 123
}
})
</script>
Vue事件绑定
data中定义了一个数据msg,vue实例上访问这个数据有两种方式,this. d a t a . m s g ( t h i s . data.msg(this. data.msg(this.data[msg])和 this.msg(vm.msg)
<div id="container">
<div>{{num}}</div>
<!-- v-on指令方法 -->
<button v-on:click="num++">按钮1</button>
<!-- v-on指令简写方法 -->
<button @click="num++">按钮2</button>
<!-- 直接绑定函数名方法(默认第一个参数为$event) -->
<button @click="fun1">按钮3</button>
<!-- 调用函数方法 -->
<button @click="fun2(1,2,$event)">按钮4</button>
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
num: 0
},
methods: {
fun1: function () {
this.num++;
},
fun2: function (a, b, $event) {
this.num++;
console.log(a+b);
//BUTTON(触发事件的对象的标签名)
console.log($event.target.tagName);
}
}
})
</script>
-
事件修饰符
-
.stop 阻止冒泡
<a v-on:click.stop="handle">跳转</a>
-
.prevent 阻止默认行为
<a v-on:click.prevent="handle">跳转</a>
-
.capture 使用事件捕获模式
<div v-on:click.capture="handle"></div>
-
.self 事件不是从内部元素触发的
<div v-on:click.self="handle"></div>
-
.once 事件只会触发一次
<a v-on:click.once="handle"></a>
-
-
按键修饰符
使用
<input v-on:keyup.13="submit">
也是允许的.enter 回车键
<input v-on:keyup.enter="submit">
Vue提供了绝大多数常用的按键码的别名:
.enter
.tab
.delete
(“删除”和“退格”键).esc
.space
.up
.down
.left
.right
-
自定义按键修饰符
全局config.keyCodes对象
<input @keyup.aaa="handle"> <!-- 对应的值必须是按键对应的event.keyCode值(65对应的就是键盘上a这个按键) --> Vue.config.keyCodes.aaa=65
-
系统修饰键
注意修饰键与常规按键不同,在和
keyup
事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住ctrl
的情况下释放其它按键,才能触发keyup.ctrl
,而单单释放ctrl
也不会触发事件.ctrl
.alt
.shift
.meta
<!-- Alt + C --> <input v-on:keyup.alt.67="clear"> <!-- Ctrl + Click --> <div v-on:click.ctrl="doSomething"></div>
-
.exact
修饰符.exact
修饰符允许你控制由精确的系统修饰符组合触发的事件<!-- 即使Alt或Shift被一同按下时也会触发 --> <button v-on:click.ctrl="onClick"></button> <!-- 有且只有Ctrl被按下的时候才触发 --> <button v-on:click.ctrl.exact="onCtrlClick"></button> <!-- 没有任何系统修饰符被按下的时候才触发 --> <button v-on:click.exact="onClick"></button>
-
鼠标按钮修饰符
.left
.right
.middle
Vue属性绑定
每种标签内置属性不同,如img有内置属性width和height,可通过动态绑定内置属性来动态修改img宽高(动态修改样式)
<img :width="isCollapse?'45px':'100px'" :height="isCollapse?'45px':'100px'" src="images/admin.jpg">
-
v-bind指令
<a v-bind:herf="url">跳转</a>
-
缩写形式
<a :href="url">跳转</a>
Vue样式绑定
-
style样式动态绑定
凡是有-的属性名都要变成驼峰式,比如font-size要变成fontSize
属性名的值要用引号,比如fontSize: '18px’而不是fontSize: 18px
-
对象形式
:style="{color:activeColor,fontSize:fontSize+'px}"
:style="{display:(activeName=='first'?'flex':'none')}"
-
数组形式
<div v-bind:style="[objStyle1,objStyle2]"></div>
data: { objStyle1: { width: "100px", height: "100px", border: "1px solid red" }, objStyle2: { background: "blue" } }
结果渲染为:
<div style="width: 100px; height: 100px; border: 1px solid red; background: blue;"></div>
-
三目运算符形式
:style="{fontSize:(isCollapse?'14px':'16px')}"
:style="[{fontSize:(isCollapse?'14px':'16px')},{color:'#ffffff'}]"
-
多重值形式
:style="{display:['-webkit-box','-ms-flexbox','flex']}"
-
-
class样式动态绑定
-
对象形式
<div v-bind:class="{ active: isActive }"></div>
<div v-bind:class="{ active1: isActive1,active2: isActive2}"></div>
data: { isActive1: true, isActive2: true, }
结果渲染为:
<div class="active1 active2"></div>
-
数组形式
<div v-bind:class="[activeClass, errorClass]"></div>
data: { activeClass: 'active', errorClass: 'text-danger' }
结果渲染为:
<div class="active text-danger"></div>
-
三目运算符形式
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
-
Vue分支循环结构
分支结构:
- v-if
- v-else
- v-else-if
- v-show
<div id="container">
<div v-if="score>=90">优秀</div>
<!-- 只有满足条件的div才会被渲染到页面,score>=80&&score<90为true -->
<div v-else-if="score>=80&&score<90">良好</div>
<div v-else>一般</div>
<!-- v-show为true时元素才会显示 -->
<div v-show=flag>v-show</div>
</div>
<script>
var vm = new new Vue({
el: "#container",
data: {
score: 85,
flag: true
}
})
</script>
注意点:
-
v-else元素必须紧跟在带v-if或者v-else-if的元素的后面,否则它将不会被识别
-
v-if控制元素是否渲染到页面
-
v-show控制元素是否显示(已经渲染到了页面),不支持
<template>
元素,也不支持v-else -
v-if 与 v-show的区别:
- v-if是“真正”的条件渲染,在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
- v-if也是惰性的,如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块
- 使用v-show元素总是会被渲染,并且只是简单地基于CSS的display进行切换
- v-if有更高的切换开销,而v-show有更高的初始渲染开销
- 需要非常频繁地切换,则使用v-show较好
- 在运行时条件很少改变,则使用v-if较好
循环结构:
-
v-for遍历数组
必须绑定key值,key的作用是帮助Vue区分不同的元素,从而提高性能
<li :key="item.id" v-for="(item,index) in myfruits"> {{item.id+"---"+item.ename+"---"+item.cname+"---"+index}} </li>
data: { myfruits: [{ id: "a", ename: "apple", cname: "苹果" }, { id: "b", ename: "banana", cname: "香蕉" }, { id: "o", ename: "orange", cname: "橘子" }] }
-
v-for遍历对象
<!-- name是key,"张三"是value,index是索引从0开始 --> <div v-for="(key,value,index) in obj"></div>
data: { obj: { name: "张三", age: 18 } }
分支结构和循环结构结合使用:
<ul>
<!-- 只有满足if条件的li才能被渲染到页面 -->
<li v-if="value==18" v-for="(key,value,index) in obj">{{key+"---"+value+"---"+index}}</li>
</ul>
data: {
obj: {
name: "张三",
age: 18
}
}
Vue常用特性
- 表单操作
- 自定义指令(应用场景:获得表单焦点)
- 计算属性(应用场景:统计图书数量)
- 侦听器(应用场景:验证图书的存在性)
- 过滤器(应用场景:格式化日期)
- 生命周期(应用场景:图书数据处理)
基于Vue的表单操作
通过value的值来控制显示
<form id="form1" action="XXXXXX" method="post">
<div>
<span>姓名:</span>
<span>
<input type="text" name="xingming" id="" v-model="username">
</span>
</div>
<div>
<span>性别:</span>
<span>
<input type="radio" name="xingbie" id="male" value="男" v-model="gender"><label for="male">男</label>
<input type="radio" name="xingbie" id="female" value="女" v-model="gender"><label for="female">女</label>
</span>
</div>
<div>
<span>爱好:</span>
<span>
<input type="checkbox" name="aihao" id="ball" value="篮球" v-model="hoddy"><label for="ball">篮球</label>
<input type="checkbox" name="aihao" id="game" value="游戏" v-model="hoddy"><label for="game">游戏</label>
<input type="checkbox" name="aihao" id="code" value="写代码" v-model="hoddy"><label for="code">写代码</label>
</span>
</div>
<div>
<span>职业:</span>
<!-- 或者multiple=true -->
<select name="zhiye" id="" v-model="occupation" multiple>
<option value="教师">教师</option>
<option value="程序员">程序员</option>
<option value="职业选手">职业选手</option>
</select>
</div>
<div>
<span>个人简介:</span>
<textarea name="gerenjianjie" id="" cols="30" rows="10" v-model="brief"></textarea>
</div>
<div>
<span><input type="submit" value="提交" @click.prevent="fun"></span>
</div>
</form>
data:{
username:"小米",
gender:"男",
hoddy:["篮球","游戏"],
occupation:["教师","程序员"],
brief:"我是中国人"
}
表单域修饰符
-
number:转化为数值
<input v-model.number="age">
-
trim:去掉开始和结尾的空格
<input v-model.trim="msg">
-
lazy:将input事件切换为change事件
<!-- 默认情况下v-model用的是iput事件 --> <input v-model.lazy="msg">
Vue自定义指令
-
全局自定义指令
-
不带参数的全局自定义指令
el:表示指令所绑定的元素
使用autofocus属性与focus()方法功能相同,一个页面上只能有一个autofocus属性,不能滥用
<div id="container"> <!-- 实现页面一加载就自动获得焦点 --> <input type="text" v-setfocus> </div> <script> Vue.directive("setfocus",{ inserted: function(el){ el.focus(); } }) var vm=new Vue({ el:"#container" }) </script>
-
带参数的全局自定义指令
el:表示指令所绑定的元素
binding:表示一个对象{ name: “setcolor”, value: { color: “blue”}}
<div id="container"> <!-- 实现将input输入框的背景颜色设置为蓝色 --> <input type="text" v-setcolor="msg"> </div> <script> Vue.directive("setcolor",{ bind: function (el,binding){ el.style.backgroundColor = binding.value.color; } }) var vm=new Vue({ el:"#container", data:{ msg:{ color: "blue" } } }) </script>
-
-
局部自定义指令
局部自定义指令只能在本组件中使用
-
不带参数的局部自定义指令
<div id="container"> <input type="text" v-setfocus> </div> <script> var vm = new Vue({ el: "#container", directives: { setfocus: { inserted: function (el) { el.focus(); } } } }) </script>
-
带参数的局部自定义指令
<div id="container"> <input type="text" v-setcolor="msg"> </div> <script> var vm = new Vue({ el: "#container", data: { msg: { color: "blue" } }, directives: { setcolor: { bind: function (el, binding) { el.style.backgroundColor = binding.value.color; } } } }) </script>
-
计算属性
- 使模板内容更加简洁
- 是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值
<div id="container">
<input type="text" v-model="msg">
<div>{{reverseString1()}}</div>
<div>{{reverseString1()}}</div>
<div>{{reverseString2}}</div>
<div>{{reverseString2}}</div>
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
msg: "hello"
},
methods: {
reverseString1: function () {
console.log("reverseString1");
return this.msg.split("").reverse().join("");
}
},
computed: {
reverseString2: function () {
console.log("reverseString2");
return this.msg.split("").reverse().join("");
}
}
})
</script>
计算属性和methods方法的区别:
对于任何复杂逻辑,都应当使用计算属性
如果函数本身比较复杂,需要计算很长时间,两者的性能差异就会显现出来
-
计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值,这就意味着只要 msg 还没有发生改变,多次访问 reverseString2 计算属性会立即返回之前的计算结果(返回缓存),而不必再次执行函数
-
methods方法,每当触发重新渲染时,调用方法将总会再次执行函数
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
<div id="container">
{{ fullName }}
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: {
// getter(获取值)
get: function () {
return this.firstName +' '+ this.lastName
},
// setter(fullName设置了新的值,firstName和lastName对应设置新的值)
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
})
</script>
侦听器
计算属性和侦听器的区别:
使用侦听器实现加法计算器的代码是命令式且重复的,使用计算属性明显简洁了且不重复,虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器,当需要在数据变化时执行异步或开销较大的操作时,使用侦听器是最有用的
-
使用侦听器实现加法计算器:
<div id="container"> <input type="number" v-model="a"> <span>+</span> <input type="number" v-model="b"> <span>=</span> <span>{{sum}}</span> </div> <script> var vm = new Vue({ el: "#container", data: { a: 0, b: 0, sum: 0 }, // a或b的数据一旦发生变化就会通知侦听器属性来响应数据的变化 watch: { // val为a的最新值 a: function (val) { this.sum = parseInt(this.a) + parseInt(this.b) }, // val为b的最新值 b: function (val) { this.sum = parseInt(this.a) + parseInt(this.b) } } }) </script>
-
使用计算属性实现加法计算器
<div id="container"> <input type="number" v-model="a"> <span>+</span> <input type="number" v-model="b"> <span>=</span> <span>{{sum}}</span> </div> <script> var vm = new Vue({ el: "#container", data: { a: 0, b: 0, }, computed: { sum: function(){ return parseInt(this.a) + parseInt(this.b) } }, }) </script>
侦听器的立即执行(immediate)与深度监听(deep):
watch最初绑定的时候是不会执行的,只有当侦听的数据发生改变才会执行
var vm = new Vue({
el: '#app',
data: {
obj: {
id: 1,
name: "张三",
age: 18
}
},
watch: {
// 改成了一个对象,属性值handler固定写法
obj: {
// 监视函数
handler: function (newVal, oldVal) {
console.log(newVal);
console.log(oldVal);
console.log('objChanged')
},
// 代表开启深度监视(数据的任何一个属性发生变化,监视函数都会执行)
deep: true,
// immediate设置为true(代码一加载,立马执行监视函数)
immediate: true
}
}
})
// 修改obj的age
vm.$set(vm.obj, "age", 20)
深度监听会有性能问题,可以使用字符串形式监听:
'obj': {
}
//或
'obj.id': {
}
//或
'obj.age': {
}
过滤器
-
全局自定义过滤器(使用过滤器格式化日期)
-
在main.js中添加全局的时间过滤器
Vue.filter('dateFormat', function (originVal) { const dt = new Date(originVal); const y = dt.getFullYear(); const m = (dt.getMonth() + 1 + '').padStart(2, '0'); const d = (dt.getDate() + '').padStart(2, '0'); const hh = (dt.getHours() + '').padStart(2, '0'); const mm = (dt.getMinutes() + '').padStart(2, '0'); const ss = (dt.getSeconds() + '').padStart(2, '0'); return `${y}-${m}-${d} ${hh}:${mm}:${ss}` })
-
使用时间过滤器
<template slot-scope="scope"> {{scope.row.date | dateFormat}} </template>
-
-
局部自定义过滤器(使用过滤器实现首字母和尾字母大写并拼接原msg值)
<div id="container"> <!-- AbcDabcd --> {{msg | filterFirst | filterLast(msg)}} </div> <script> var vm = new Vue({ el: "#container", data: { msg: "abcd" }, filters: { filterFirst: function (msg) { if (!msg) return ""; msg = msg.toString() return msg.charAt(0).toUpperCase() + msg.slice(1) }, filterLast: function (msg,arg1) { if (!msg) return ""; msg = msg.toString() lastIndex = msg.length-1 return msg.slice(0,lastIndex) + msg.charAt(lastIndex).toUpperCase() + arg1 } } }) </script>
Vue实例的生命周期
Vue所有的生命周期钩子自动绑定在this上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法,这是因为箭头函数绑定了父上下文,因此this与你期待的Vue实例不同
**主要阶段:**创建前后,挂接前后,更新前后,显示和隐藏,销毁前后
Vue实例的产生过程:
- beforeCreate:在实例初始化之后,数据观测和事件配置之前被调用
- created:在实例创建完成之后被立即调用(数据观测和事件配置已经完成)
- beforeMount:在挂载开始之前被调用,相关的render函数首次被调用
- mounted:el被新创建的vm.$el替换,并挂载到实例上去之后被调用
- beforeUpdate:数据更新时被调用,发生在虚拟DOM打补丁之前
- updated:由于数据更改导致虚拟DOM重新渲染和打补丁,在这之后会被调用
- activated:keep-alive组件激活时调用
- deactivated:keep-alive组件停用时调用
- beforeDestroy:实例销毁之前被调用(实例仍然完全可用)
- destroyed:实例销毁后被调用
<div id="container">
<div>{{msg}}</div>
<button @click="update">更新</button>
<button @click="destroy">销毁</button>
</div>
<script>
var vm = new Vue({
el: "#container",
data: {
msg: "Vue实例的生命周期"
},
methods: {
update: function () {
this.msg = "更新了"
},
destroy: function () {
this.$destroy()
}
},
beforeCreate: function () {
console.log("beforeCreate")
},
created: function () {
console.log("create")
},
beforeMount: function () {
console.log("beforeMount")
},
mounted: function () {
console.log("Mount")
},
// 当data中的数据发生变化时beforeUpdate和updated就会被调用
beforeUpdate: function () {
console.log("beforeUpdate")
},
updated: function () {
console.log("updated")
},
// 当this.$destroy()被执行了beforeDestroy和destroyed就会被调用
beforeDestroy: function () {
console.log("beforeDestroy")
},
destroyed: function () {
console.log("destroyed")
}
})
</script>
Vue组件化开发之组件注册
Vue全局组件注册
全局组件注册之后可以用在任何新创建的Vue根实例的模板中,a和b子组件在各自内部也都可以相互使用
<div id="app">
<component-a></component-a>
</div>
<script>
Vue.component("component-a", {
// data必须是个函数(形成闭包的环境,保证每一个组件拥有一份独立的数据)
data: function () {
return {
msg: "这是组件a"
}
},
template:
`
<div>
<div>this is component-a</div>
<div>{{msg}}</div>
<component-b></component-b>
</div>
`
})
Vue.component("component-b", {
data: function () {
return {
msg: "这是组件b"
}
},
template:
`
<div>
<div>this is component-b</div>
<div>{{msg}}</div>
</div>
`
})
var vm = new Vue({
el: "#app",
data: {}
})
</script>
Vue局部组件注册
<div id="app">
<component-a></component-a>
<component-b></component-b>
</div>
<script src="js/vue.js"></script>
<script>
var ComponentA = {
data: function () {
return {
msg: "ComponentA"
}
},
template: `<div>this is {{msg}}</div>`
};
var ComponentB = {
data: function () {
return {
msg: "ComponentB"
}
},
template: `<div>this is {{msg}}</div>`
};
var vm = new Vue({
el: "#app",
data: {},
components: {
"component-a": ComponentA,
"component-b": ComponentB
}
});
</script>
使用场景:如何在App.vue中将我们Users组件进行展示出来
-
全局组件注册:
-
在main.js中全局组件注册
import Users from './components/Users' Vue.component("users",Users)
-
就可以在App.vue中直接引入users组件了
<users></users>
-
-
局部组件注册:
<template> <div id="app"> <users></users> </div> </template> <script> import Users from './components/Users' export default { name: 'App', components: { "users": Users } } </script> <style></style>
Vue组件间数据交互
Vue父子组件间传值
-
父组件给子组件传值方法,使用props
- 自定义属性arry动态绑定来自父组件的值(即 :arry = fruits)
- 子组件通过props接收父组件传过来的值(即 props: [“array”])
-
子组件给父组件传值方法,使用$emit
- 子组件通过使用$emit触发自定义监听事件add,并将子组件的值(即’橙子’)通过参数传给了父组件
- 父组件通过push方法接收子组件传过来的值
<div id="app">
<children-com :arry="fruits" @add="push($event)"></children-com>
</div>
<script>
Vue.component("children-com", {
props: ["arry"],
template:
`
<div>
<ul>
<li :key="index" v-for="(item,index) in arry">{{item}}</li>
</ul>
<button @click="$emit('add','橙子')">添加水果</button>
</div>
`
});
var vm = new Vue({
el: "#app",
data: {
fruits: ["苹果", "香蕉", "橘子"]
},
methods: {
push: function (val) {
this.fruits.push(val);
}
}
})
</script>
Vue非父子组件间传值
-
事件中心:管理组件间的通信
var eventHub = new Vue()
-
监听事件(通过事件中心监听a组件)
eventHub.$on("component-a", (val) => { this.num += val; //val作为形参接收传过来的实参1 });
-
触发事件(通过事件中心触发监听a组件的事件)
eventHub.$emit("component-a", 1); //1作为实参传给监听a组件的事件
-
销毁监听事件(将监听a组件的事件销毁)
eventHub.$off("component-a");
<div id="app">
<button @click="disappear">销毁监听事件</button>
<component-a></component-a>
<component-b></component-b>
</div>
<script>
var eventHub = new Vue();
Vue.component("component-a", {
data: function () {
return {
num: 0
}
},
template:
`
<div>
<div>component-a:{{num}}</div>
<button @click="fun">使b组件加1</button>
</div>
`,
methods: {
fun: function () {
eventHub.$emit("component-b", 1);
}
},
mounted: function () {
eventHub.$on("component-a", (val) => {
this.num += val;
});
}
});
Vue.component("component-b", {
data: function () {
return {
num: 0
}
},
template:
`
<div>
<div>component-b:{{num}}</div>
<button @click="fun">使a组件加1</button>
</div>
`,
methods: {
fun: function () {
eventHub.$emit("component-a", 1);
}
},
mounted: function () {
eventHub.$on("component-b", (val) => {
this.num += val;
});
}
});
var vm = new Vue({
el: "#app",
methods: {
disappear: function () {
eventHub.$off("component-a");
eventHub.$off("component-b");
}
}
});
</script>
Vue插槽
在2.6.0中,为具名插槽和作用域插槽引入了一个新的统一的语法(即
v-slot
指令),它取代了slot
和slot-scope
,这两个目前已被废弃但未被移除
若a组件的template中没有
<slot>
元素,直接在<component-a></component-a>
之间写入任何内容都不会显示出来(任何内容都会被抛弃),<slot>
元素作为承载分发内容的出口
若a组件的template中有
<slot>
元素,<component-a></component-a>
之间可以包含任何代码,甚至其他的组件
Vue组件插槽:
<component-a></component-a>
之间填充的任何内容都会替换<slot></slot>
标签
<div id="app">
<component-a>
Your Profile
</component-a>
</div>
<script>
Vue.component('component-a', {
template:
`
<div>
<slot></slot>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {}
})
</script>
插槽后备内容:
当
<component-a></component-a>
中没提供内容时,可在<slot></slot>
中设置后备内容
<div id="app">
<component-a></component-a>
</div>
<script>
Vue.component('component-a', {
template:
`
<div>
<slot>Your Profile</slot>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {}
})
</script>
Vue具名插槽:
<base-layout></base-layout>
中的<template>
元素中的所有内容都将会被传入相应的插槽
<div id="app">
<base-layout>
<template v-slot:header>
<h1>这是header</h1>
</template>
<template v-slot:default>
<p>这是main</p>
</template>
<template v-slot:footer>
<h1>这是footer</h1>
</template>
</base-layout>
</div>
<script>
Vue.component('base-layout', {
template:
`
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {}
})
</script>
Vue编译作用域:
父级模板里的所有内容都是在父级作用域中编译的,子模板里的所有内容都是在子作用域中编译的
<div id="app"></div>
中的所有内容都是在vm实例作用域中编译的,所以<component-a></component-a>
中v-show指令绑定的isShow是vm实例中的,而子组件a里的所有内容都是在子组件a作用域中编译的
<div id="app">
<component-a v-show="isShow"></component-a>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component('component-a', {
data: function () {
return {
isShow: false
}
},
template:
`
<div>
<h1>我是子组件a</h1>
<h1 v-show="isShow">我是子组件a</h1>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {
isShow: true
}
})
</script>
Vue作用域插槽:
如下代码:a组件已经封装好了,但是a组件没有提供插槽,a组件结构是固定死的,使用
<component-a></component-a>
永远都是一种结构,扩展性不强
<div id="app">
<component-a></component-a>
<component-a></component-a>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component('component-a', {
data: function () {
return {
language: ['C', 'C++', 'C#', 'Js', 'Java']
}
},
template:
`
<div>
<ul>
<li v-for="item in language">{{item}}</li>
</ul>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {}
})
</script>
需求:
- 子组件a有一组数据:language: [‘C’, ‘C++’, ‘C#’, ‘Js’, ‘Java’]
- 子组件a的数据在页面中以多种形式展示出来:如以列表形式或者以水平方向展示
分析:
- 这时候就需要子组件a提供插槽了
- 然后通过
<template></template>
中的slot-scope="slot"
来引用插槽对象,从而拿到子组件a中的数据 - 然后在
<template></template>
中将子组件a的数据以span
结构形式来展示
一句话总结Vue作用域插槽:父组件替换插槽的标签,但是内容是由子组件来提供
<div id="app">
<component-a></component-a>
<component-a>
<!-- slot-scope="slot"会引用插槽对象(即获取到了插槽对象) -->
<template slot-scope="slot">
<span v-for="item in slot.data">{{item}}——</span>
</template>
</component-a>
</div>
<script src="js/vue.js"></script>
<script>
Vue.component('component-a', {
data: function () {
return {
language: ['C', 'C++', 'C#', 'Js', 'Java']
}
},
template:
`
<div>
<slot :data="language">
<ul>
<li v-for="item in language">{{item}}</li>
</ul>
</slot>
</div>
`
})
let vm = new Vue({
el: '#app',
data: {}
})
</script>
Vue2.6.0 中使用
v-slot:default="slotProps"
替代了slot-scope="slot"
处理边界情况
在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的
访问根实例
new Vue({
data: {
foo: 1
},
created: function () {
// 写入根组件的数据
this.$root.foo = 2
// 获取根组件的数据
console.log(this.$root.foo)
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
},
computed: {
bar: function () {
console.log("bar")
}
},
methods: {
baz: function () {
console.log("baz")
}
}
})
访问父组件实例
父组件通过prop向子组件传值,子组件也可通过**$parent**访问父组件的值
访问子组件或子元素
通过 $refs
访问子组件,注意:$refs
只会在组件渲染完成之后生效
<div id="app">
<input type="text" ref="input">
</div>
<script>
new Vue({
el: "#app",
data: {},
mounted() {
this.$refs.input.focus()
}
})
</script>
通过 $el
获取Vue实例关联的DOM元素
如果是html元素直接
this.$refs.input.offsetTop
获取,如果是Element-UI封装的元素或者el: "#app"
挂载,需要通过 e l 来 获 取 D O M 元 素 ( 如 : ‘ t h i s . r e f s . e l F o r m . el来获取DOM元素(如:`this.refs.elForm. el来获取DOM元素(如:‘this.refs.elForm.el.offsetHeight`)
<div id="app">
<input type="text" ref="input">
</div>
<script>
new Vue({
el: "#app",
data: {},
mounted() {
console.log(this.$refs.input.offsetTop) //10
console.log(this.$el) //<div id="app">...<div>
}
})
</script>
依赖注入
使用props是父组件向子组件共享数据,而使用依赖注入是父组件向所有的子孙组件共享数据(可跨层级的分享数据)
// 父组件中抛出要分享的数据
provide: function () {
return {
getMap: this.getMap
}
}
// 在子组件中注入一下就可以用了
inject: ['getMap']
过渡&动画
Vue在插入、移除或者更新DOM时,提供多种不同方式的应用过渡效果,包括以下工具:
- 在 CSS 过渡和动画中自动应用 class
- 可以配合使用第三方 CSS 动画库,如 Animate.css
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
当插入或删除包含在 transition
组件中的元素时,Vue 将会做以下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是则在恰当的时机添加/删除 CSS 类名
- 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用
- 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行(注意:此指浏览器逐帧动画机制,和 Vue 的
nextTick
概念不同)
单元素/组件的过渡
<div id="app">
<button @click="isShow = !isShow">toggleShow</button>
<transition name="move">
<p v-if="isShow">hello</p>
</transition>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
isShow: true
}
}
})
</script>
<style>
/* 显示的过渡效果 */
.move-enter-active {
transition: all 1s
}
/* 隐藏的过渡效果 */
.move-leave-active {
transition: all 3s
}
/* 隐藏时的样式 */
.move-enter,.move-leave-to {
opacity: 0
transform: translateX(20px)
}
</style>
过渡的类名
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>
,则 v-
是这些类名的默认前缀。如果你使用了 <transition name="my-transition">
,那么 v-enter
会替换为 my-transition-enter
CSS 动画
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter
类名在节点插入 DOM 后不会立即删除,而是在 animationend
事件触发时删除
<div id="app">
<button @click="show = !show">toggleShow</button><br>
<transition name="bounce">
<p v-if="show" style="display: inline-block;">hello world</p>
</transition>
</div>
<script>
new Vue({
el: '#app',
data: {
show: true
}
})
</script>
<style>
.bounce-enter-active {
animation: bounce-in 1s
}
.bounce-leave-active {
animation: bounce-in 1s reverse
}
@keyframes bounce-in {
0% {
transform: scale(0)
}
50% {
transform: scale(1.5)
}
100% {
transform: scale(1)
}
}
</style>
显性的过渡持续时间
<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
列表过渡
<div id="list-demo" class="demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
<script>
new Vue({
el: '#list-demo',
data: {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
}
}
})
</script>
<style>
.list-item {
display: inline-block
margin-right: 10px
}
.list-enter-active,.list-leave-active {
transition: all 1s
}
.list-enter,.list-leave-to {
opacity: 0
transform: translateY(30px)
}
</style>
Vue插件的使用
插件通常用来为Vue添加全局功能。插件的功能范围没有严格的限制,一般有以下几种:
- 添加全局方法或者属性,如 vue-custom-element(Vue自定义元素插件)
- 添加全局资源:指令/过滤器/过渡等,如 vue-touch(Vue移动端触摸插件)
- 通过全局混入来添加一些组件选项,如 vue-router(Vue路由插件)
- 添加Vue实例方法,通过把它们添加到
Vue.prototype
上实现 - 一个库,提供自己的API,同时提供上面提到的一个或多个功能,如 vue-router(Vue路由插件)
使用插件
通过全局方法 Vue.use()
使用插件,它需要在调用 new Vue()
启动应用之前完成
Vue.use(MyPlugin)
//或者
Vue.use(MyPlugin,{
option: xxx,
option: xxx,
option: xxx
})
Vue脚手架
Vue CLI用于快速生成Vue项目基础架构
安装3.x版本的Vue CLI:
npm install -g @vue/cli
基于3.x版本的脚手架创建Vue项目:
-
基于交互式命令行的方式创建:
vue create my-project
-
基于图形化界面的方式创建:
vue ui
选择模板(选择手动配置)
- default(默认配置)
- Manually select features(手动配置)
选择配置
选择:Router,Vuex,CSS Pre-processors,Linter
是否使用路由的 history 模式
选择:yes
选择css预处理器
选择:Less
选择Eslint代码验证规则
选择:ESLint + Standard config // 基本配置
选择什么时候进行代码规则检测
选择:Lint on save // 保存就检测
把babel,postcss,eslint这些配置文件放哪
选择:In dedicated config files // 独立文件放置
是否保存配置
选择:yes
给配置起名字
使用yarn还是npm进行包管理
选择:yarn
打开项目
yarn serve启动项目
基于2.x的旧模板创建旧版Vue项目:
npm install -g @vue/cli-init
vue init webpack my-project
Vue脚手架创建的实际开发项目目录结构
.
├── README.md ------------------------ 说明文件
├── package.json ----------------------- 项目配置
├── vue.config.js ------------------------ webpack配置入口
├── public --------------------------------- 入口文件
├── ├── favicon.ico ---------------- 网页图标
├── └── index.html --------------- 入口页面
└── src ------------------------------------- 源码目录
├── apis --------------------------------- 网络请求与请求的所有接口(分模块)
├── ├── index.js ----------------- 引入所有api
├── ├── config.js ----------------- 网络请求设置
├── ├── setup.js ----------------- 封装的所有网络请求
├── └── modules/xx.js ------- 按模块划分api
├── assets ----------------------------- 项目资源文件目录(图片、字体等)
├── ├── images ------------------ 所有图片
├── ├── less ----------------------- 所有样式
├── ├── ├─ index.less ------------------- 所有样式引入入口
├── ├── ├─ common/_base ----------- 公共基础样式
├── ├── ├─ common/_common ------ 通用公共样式
├── ├── ├─ common/_components - 公共样式部件
├── ├── ├─ common/_iconfont -------- 公共icon
├── ├── ├─ common/_mixins ---------- 公共混入样式
├── ├── ├─ common/_resetElement- 重置elementUI样式
├── ├── └─ common/_theme ---------- 主题色配置
├── ├── svgs ---------------------- 所有svg图片
├── components ------------------- 业务模块集合目录(组件)
├── ├── index.js ----------------- 注册所有全局组件
├── router ---------------------------- 路由
├── store ------------------------------ vuex(分模块)
├── utils ------------------------------ 工具函数
├── ├── commonData ------- 挂载在$common上的数据
├── ├── directive --------------- 所有指令
├── ├── filter -------------------- 所有过滤器
├── └── veeValidate ----------- 表单校验配置
├── plugins -------------------------- 引入的插件自动生成的配置
├── views ---------------------------- 页面集合目录
├── ├── xx.vue -------------------- 页面
├── └── children -----------------拆分出的子页面
├── App.less ------------------------ 主样式
├── App.vue ------------------------ 主组件
└── main.js ------------------------- 项目级入口配置文件
Vue项目目录结构
main.js和index.html是如何关联的?
- main.js为入口文件
- 运行npm run dev后,若不做特殊设置,实际index.html页面中仅挂载了app.js一个脚本,所有组件去哪儿了,app.js是如何形成的?
- Vue实例化在main.js中,但在index.html中并没有引入main.js,main.js和index.html是如何关联的?
vuecli搭建的项目本质是一个集成预设置的webpack项目
- 入口文件是一个webpack概念,入口文件是webpack构建内部依赖图的起点
- main.js为入口文件,app.js是由webpack打包生成的输出文件
- 将app.js挂载到index.html这一过程是由webpack的一个插件html-webpack-plugin完成的,html-webpack-plugin会自动帮你在dist目录下生成一个html文件,并且引用相关的assets文件(如css、js文件)
Vue项目打包后dist文件目录详解
css文件夹:
- app.css文件是自己写的css样式
- chunk-vendors.css是ui框架的css样式
js文件夹:
- app.js是自己写的逻辑代码
- chunk-vendors.js是通过import导入的第三方依赖包。防止该文件体积过大,可以使用webpack的externals配置,凡是声明在externals中的第三方依赖包都不会被打包,同时可以在index.html文件头部使用CDN链接外部资源
- 其他的.js文件是使用路由懒加载打包后的文件,按需导入路由,文件的名字是路由懒加载配置中的分组名称
- .js.map是一个Source map文件,Source map就是一个信息文件,里面存储着位置信息,转换后的代码的每一个位置所对应的转换前的位置。项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知哪里的代码报错。有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列报错,方便开发调试使用
render函数的作用
render函数是Vue通过JS渲染DOM结构的函数createElement,约定可以简写为h
render: function (createElement) {
return createElement(App)
}
简写为:
render: h => h(APP)
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app')
实际渲染为:
import App from './App'
new Vue({
el: '#app',
template: '<App></App>',
components: {
App
}
})