三、组件
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML
元素,封装可重用的代码。
定义组件
Vue自定义组件分为两种:全局注册和局部注册,全局组件可以在任何地方引用,局部组件只能在当前Vue实例使用。
全局注册
使用Vue.component(tagName, options)来定义:
/*定义全局组件*/
Vue.component('my-component',{
template:'<h4>我是自定义组件</h4>'
});
注意,HTML
特性是不区分大小写的,所有在定义组件时尽量使用中划线"-"来指定组件名。即使,使用了驼峰标示命名如:myComponent,在页面引用时仍然要使用<my-component>进行引用。
2)局部注册
在Vue实例中使用components属性来定义:
var app = new Vue({
el:'#app',
//使用components关键字
components:{
'inner-component':{
template:'<h4>我是局部注册组件</h4>'
}
}
});
2、使用组件
<div id="app">
<!-- 使用组件 -->
<my-component></my-component>
<inner-component></inner-component>
</div>
完整代码:
<div id="app">
<!-- 使用组件 -->
<my-component></my-component>
<inner-component></inner-component>
</div>
<script>
/*定义全局组件*/
Vue.component('my-component',{
template:'<h4>我是自定义组件</h4>'
});
/***
* 此种定义方式全局注册,在任何地方都可以通过自定义标签名进行引用
* */
var app = new Vue({
el:'#app',
//使用components关键字
components:{
'inner-component':{
template:'<h4>我是局部注册组件</h4>'
}
}
});
</script>
3、is属性
在table标签中直接使用自定义组件,无法正常显示。DOM解析时会解析到<table>标签的外部:
<table id="app">
<my-component></my-component>
</table>
DOM解析:
原因是:table/ol/ul/select
这种html标签有特殊的结构要求,不能直接使用自定义标签。他们有自己的默认嵌套规则,比如:
table> tr> [th, td];
ol/ul > li;
select > option
解决上述问题,可以使用is进行标签转换,形式:
< is="my-component">
<table id="app">
<tr is="my-component"></tr>
</table>
完整代码:
<table id="app">
<!-- 使用组件 -->
<!-- <my-component></my-component>-->
<!-- 使用 is -->
<tr is="my-component"></tr>
</table>
<script>
/***
* 原因是: table/ol/ul/select 这种特殊结构要求,不能使用自定义标签:
* 比如: table> tr> [th, td]; ol/ul > li; select > option
* 可以使用is进行标签转换
* */
/*定义组件1*/
Vue.component('my-component',{
template:'<h4>我是自定义组件</h4>'
});
var app = new Vue({
el:'#app'
});
</script>
4.模板
当模板的html结构比较复杂时,直接在template属性中定义就不现实了,效率也会很低,此时我们可以使用模板,定义模板的四种形式:
直接使用字符串定义
使用<script type="text/x-template">
使用<template>标签
使用.vue组件,需要使用模块加载机制
在使用直接字符串模板时、x-template和.vue组件时,不需要is进行转义。
直接字符串
var temp = '<h4>直接字符串</h4>';
Vue.component('my-component1',{
template:temp
});
2)x-template模板
<!-- 使用x-template -->
<script type="text/x-template" id="template2">
<ul>
<li>01</li>
<li>02</li>
</ul>
</script>
Vue.component('my-component2',{
template:'#template2'
});
3)template标签
<!-- 使用template标签 -->
<template id="template3">
<ol>
<li>a</li>
<li>b</li>
</ol>
</template>
Vue.component('my-component3',{
template:'#template3'
});
4)单标签引用
<!-- 单标签使用 -->
<template id="template4">
<my-component1></my-component1>
<!-- 等效 -->
<my-component1/>
</template>
5)省略is
<!-- 使用x-template -->
<script type="text/x-template" id="template5">
<table>
<my-component1></my-component1>
</table>
</script>
Vue.component('my-component6',{
template:'#template5'
});
除了<template>模板,其他的都可以直接嵌套,不用is指定。
5、data属性
通过data属性指定自定义组件的初始数据,要求data必须是一个函数,如果不是函数就会报错。
Vue.component('my-component',{
template:'<button @click="count += 1">计数{{count}}</button>',
data: function () {
return{count:0}
}
});
综合例子:
<div id="app">
<my-component></my-component>
<my-component></my-component>
<my-component></my-component>
</div>
<script>
/***
* 定义组件内部data: 必须通过函数定义
* */
Vue.component('my-component',{
template:'<button @click="count += 1">计数{{count}}</button>',
data: function () {
return{count:0}
}
});
var app = new Vue({
el:'#app'
});
</script>
6.prop属性
组件可以嵌套使用,叫做父子组件。那么父组件经常要给子组件传递数据这叫做父子组件通信。父子组件的关系可以总结为 prop向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。看看它们是怎么工作的:
1)声明prop
Vue.component('child', {
//声明props
props:['a','b'],
//使用父组件传递的数据
template:'<span>{{a}} == {{b}}</span>'
});
2)父组件
var app = new Vue({
el:'#app',
data:{
msg:'来自父组件的消息',
greetText:'你好Child'
}
});
3)用指定prop传递数据
<div id="app">
<!-- v-bind:a 简写成 :a -->
<child :a="msg" :b="greetText"></child>
</div>
4)完整代码
<div id="app">
<!-- v-bind:a 简写成 :a -->
<child :a="msg" :b="greetText"></child>
</div>
<script>
/***
* 父子组件通信: 父组件通过prop属性向子组件进行数据传递
* 使用方式: 子组件定义时用props指定接收参数名
* */
Vue.component('child', {
//声明props
props:['a','b'],
//使用父组件传递的数据
template:'<span>{{a}} == {{b}}</span>'
});
var app = new Vue({
el:'#app',
data:{
msg:'来自父组件的消息',
greetText:'你好Child'
}
});
</script>
7.prop校验
子组件在接收父组件传入数据时,
可以进行prop校验,来确保数据的格式和是否必传。可以指定一下属性:
1) type: 指定数据类型 String Number Object
...注意不能使用字符串数组,只能是对象大写形式
2) required: 指定是否必输
3) default: 给默认值或者自定义函数返回默认值
4) validator: 自定义函数校验
形式如下:
Vue.component('example', {
props: {
// 基础类型检测 (`null` 指允许任何类型)
propA: Number,
// 可能是多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数值且有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
})
完整代码:
<div id="app">
<child :a="msg" :c="greetText" :f="hello"></child>
</div>
<script>
/***
* 子组件在接收父组件传入数据时, 可以进行prop校验
* 1) type: 指定数据类型 String Number Object ...注意不能使用字符串数组,只能是对象大写形式
* 2) required: 指定是否必输
* 3) default: 给默认值或者自定义函数返回默认值
* 4) validator: 自定义函数校验
* */
Vue.component('child', {
//声明props 检验
props:{
'a': String,
'b': [Number,String],
'c': {
required:true
},
'd':{
default:100
},
e:{
type: Number,
default: function () {
return 1;
}
},
f:{
type:Number,
validator: function (value) {
return value < 100;
}
}
},
template:'<span>{{a}} == {{d}} == {{e}} == {{f}}</span>'
});
var app = new Vue({
el:'#app',
data:{
msg:'来自父组件的消息',
greetText:'你好Child',
hello:12
}
});
</script>
8、非prop属性
引用子组件时,非定义的prop属性,自动合并到子组件上,class和style也会自动合并。
<div id="app">
<child data-index="0" class="cont" style="font-size: 20px;"></child>
</div>
<script>
/***
* 引用子组件: 非定义的prop属性,自动合并到子组件上,class和style也会自动合并
* */
Vue.component('child', {
template:'<span class="item" style="color:red;">我是child</span>'
});
var app = new Vue({
el:'#app'
});
</script>
9、自定义事件
父组件给子组件传值使用props属性,
那么需要子组件更新父组件时,要使用自定义事件$on和$emit:
$on监听: 不能监听驼峰标示的自定义事件,
使用全部小写(abc)或者-(a-b-c)$emit主动触发: $emit(事件名,传入参数)
1)声明父组件
var app = new Vue({
el:'#app',
data:{
count:0
},
methods:{
//定义计数方法
changeCount:function(value){
console.log(value);
//计数
this.count += 1;
}
}
});
2)自定义事件
<div id="app">
<!-- 自定义事件 -->
<child v-on:update-count="changeCount"></child>
<p>{{count}}</p>
</div>
在事件v-on:update-count中的update-count就是自定义事件的名字,不要使用驼峰标示,html不区分大小写,会导致子元素无法主动触发父组件的自定义事件。
3)定义子组件
Vue.component('child', {
template:'<button v-on:click="update">子组件Child</button>',
methods:{
update: function () {
console.log('update');
//主动触发事件执行
this.$emit('update-count', '子组件参数');
}
}
});
子组件child中定义的update方法,内部通过$emit('update-count')主动触发父元素事件的执行。
完整代码:
<div id="app">
<!-- 自定义事件 -->
<child v-on:update-count="changeCount"></child>
<p>{{count}}</p>
</div>
<script>
Vue.component('child', {
template:'<button v-on:click="update">子组件Child</button>',
methods:{
update: function () {
console.log('update');
//主动触发事件执行
this.$emit('update-count', '子组件参数');
}
}
});
var app = new Vue({
el:'#app',
data:{
count:0
},
methods:{
//定义计数方法
changeCount:function(value){
console.log(value);
//计数
this.count += 1;
}
}
});
</script>
4)主动挂载
自定义事件不仅可以绑定在子组件,也可以直接挂载到父组件,使用$on绑定和$emit触发。
var app = new Vue({
el:'#app',
data:{
count:0
},
methods:{
changeCount:function(value){
console.log(value);
//计数
this.count += 1;
}
}
});
//主动挂载
app.$on('update-count', function(value){
console.log(value);
//计数
this.count += 1;
});
app.$emit('update-count',123);
四、插槽分发
父子组件使用时,有时需要将父元素的模板跟子元素模板进行混合,这时就要用到slot插槽进行内容分发,
简单理解就是在子模板中先占个位置<slot>等待父组件调用时进行模板插入。
1、slot插槽
1)子组件插槽
<template id="child-template">
<div>
<div>我是子组件</div>
<div>{{msg}}</div>
<!-- 定义slot插槽进行占位 -->
<slot>我是默认内容,父组件不传入时我显示</slot>
</div>
</template>
Vue.component('child', {
template:'#child-template',
props:['msg']
});
在子组件模板中使用<slot>标签定义插槽位置,标签中可以填写内容,当父组件不传入内容时显示此内容。
2)父组件分发
<div id="app">
<!-- 传入数据 -->
<child :msg="msgText">
<!-- 传入模板,混合子模板 -->
<h4>父组件模板</h4>
<h5>模板混入....</h5>
</child>
</div>
在引用<child>子组件时,标签中的内容会放在子组件的<solt>插槽中。
完整代码:
<div id="app">
<!-- 传入数据 -->
<child :msg="msgText">
<!-- 传入模板,混合子模板 -->
<h4>父组件模板</h4>
<h5>模板混入....</h5>
</child>
</div>
<template id="child-template">
<div>
<div>我是子组件</div>
<div>{{msg}}</div>
<!-- 定义slot插槽进行占位 -->
<slot>我是默认内容,父组件不传入时我显示</slot>
</div>
</template>
<script>
Vue.component('child', {
template:'#child-template',
props:['msg']
});
var app = new Vue({
el:'#app',
data:{
msgText:'父组件数据'
}
});
</script>
2、具名插槽
具名插槽slot,
就是给插槽起个名字。在子组件定时可以定定义多个<slot>插槽,同时通过name属性指定一个名字,如:<slot
name='header'>,父组件引用时使用< slot='header'>进行插槽选择。
1)定义具名插槽
<template id="child-template">
<div>
<!-- 插槽header -->
<slot name="header"></slot>
<div>我是子组件</div>
<div>{{msg}}</div>
<!-- 插槽footer -->
<slot name="footer"></slot>
</div>
</template>
模板定义了两个插槽header和footer,分别使用name属性进行名称的指定。
2)父组件分发
<div id="app">
<!-- 传入数据 -->
<child :msg="msgText">
<!-- 传入模板,混合子模板 -->
<h4 slot="header">头部</h4>
<h4 slot="footer">底部</h4>
</child>
</div>
通过slot属性,来确定内容需要分发到那个插槽里面。
完整代码:
<div id="app">
<!-- 传入数据 -->
<child :msg="msgText">
<!-- 传入模板,混合子模板 -->
<h4 slot="header">头部</h4>
<h4 slot="footer">底部</h4>
</child>
</div>
<template id="child-template">
<div>
<!-- 插槽header -->
<slot name="header"></slot>
<div>我是子组件</div>
<div>{{msg}}</div>
<!-- 插槽footer -->
<slot name="footer"></slot>
</div>
</template>
<script>
Vue.component('child', {
template:'#child-template',
props:['msg']
});
var app = new Vue({
el:'#app',
data:{
msgText:'父组件数据'
}
});
</script>
3、slot-scope
作用域插槽slot-scope,父组件通过<slot>插槽混入父组件的内容,
子组件也可以通过slot作用域向插槽slot内部传入数据,使用方式:<slot
text='子组件数据'>,父组件通过<template
slot-scope="props">进行引用。
1)子组件定义
<template id="child-template">
<div>
<!-- 插槽text值 -->
<slot text="子组件数据" ></slot>
</div>
</template>
在slot标签中指定属性值,类似于prop属性的使用。
2)父组件使用
<div id="app">
<!-- 传入数据 -->
<child>
<template slot-scope="props">
<div>{{msgText}}</div>
<div>{{props.text}}</div>
</template>
</child>
</div>
引用时用template标签指定,slot-scope属性指定接收数据的变量名,就可以使用花括号形式取值了。
完整代码:
<div id="app">
<!-- 传入数据 -->
<child>
<template slot-scope="props">
<div>{{msgText}}</div>
<div>{{props.text}}</div>
</template>
</child>
</div>
<template id="child-template">
<div>
<!-- 插槽text值 -->
<slot text="子组件数据" ></slot>
</div>
</template>
<script>
Vue.component('child', {
template:'#child-template'
});
var app = new Vue({
el:'#app',
data:{
msgText:'父组件数据'
}
});
</script>
3)版本更新
在2.5+之后,可以不局限于<template>,
任何元素都可以,同时可以使用解构赋值的方式进行数据解析。
子组件:
<template id="child-template">
<div>
<!-- 插槽text值 -->
<slot name="head" text="header"></slot>
<slot name="foot" text="footer" value="18"></slot>
<slot name="cont" text="content" title="main"></slot>
</div>
</template>
父组件使用:
<div id="app">
<!-- 传入数据 -->
<child>
<!-- div标签使用slot-scope -->
<div slot="head" slot-scope="props">子组件数据: {{props.text}} <span>{{fa}}</span></div>
<div slot="foot" slot-scope="props">{{props.text}} == {{props.value}}</div>
<!-- 结构赋值 -->
<div slot="cont" slot-scope="{text, title}">{{text}} == {{title}}</div>
</child>
</div>
js部分:
Vue.component('child', {
template:'#child-template'
});
var app = new Vue({
el:'#app',
data:{
fa:'father 数据'
}
});
五、动态组件
使用<component>标签的is属性,动态绑定多个组件到一个挂载点,通过改变is绑定值,切换组件。
1、使用方式
1)定义多个子组件
Vue.component('index', {
template:'<h5>首页</h5>'
});
Vue.component('news', {
template:'<h5>新闻页</h5>'
});
Vue.component('login', {
template:'<h5>登陆页</h5>'
});
2)使用component引用
<component :is="page"></component>
3)指定导航
/ <a href='#' @click.prevent="page='index'">首页</a>
/ <a href='#' @click.prevent="page='news'">新闻</a>
/ <a href='#' @click.prevent="page='login'">登陆</a>
4)完整代码
<div id="app">
/ <a href='#' @click.prevent="page='index'">首页</a>
/ <a href='#' @click.prevent="page='news'">新闻</a>
/ <a href='#' @click.prevent="page='login'">登陆</a>
<hr>
<component :is="page"></component>
</div>
<script>
/***
* 使用<component>标签的is属性,动态绑定多个组件到一个挂载点,
* 通过改变is绑定值,切换组件
* */
Vue.component('index', {
template:'<h5>首页</h5>'
});
Vue.component('news', {
template:'<h5>新闻页</h5>'
});
Vue.component('login', {
template:'<h5>登陆页</h5>'
});
var app = new Vue({
el:'#app',
data:{
page:'index'
}
});
</script>
2、keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令。
<div id="app">
/ <a href='#' @click.prevent="page='index'">首页</a>
/ <a href='#' @click.prevent="page='news'">新闻</a>
/ <a href='#' @click.prevent="page='login'">登陆</a>
<hr>
<keep-alive>
<component :is="page"></component>
</keep-alive>
</div>
使用keep-alive嵌套component。
Vue.component('index', {
template:'<h5>首页</h5>',
mounted: function () {
console.log('挂载...首页');
}
});
Vue.component('news', {
template:'<h5>新闻页</h5>',
mounted: function () {
console.log('挂载...新闻页');
}
});
Vue.component('login', {
template:'<h5>登陆页</h5>',
mounted: function () {
console.log('挂载...登陆页');
}
});
用生命周期中的mounted(挂载)钩子函数进行组件渲染监听,当组件第一次被渲染后就保存在内存中,下次切换不会被重新渲染。
3、refs
使用ref
给每个组件起一个固定的名字,方便后续直接引用操作,在父组件中使用$refs访问子组件。
<div id="app">
<child ref="btn1"></child>
<child ref="btn2"></child>
</div>
<script>
/***
* ref 给每个组件起一个固定的名字,方便后续直接引用操作
* */
Vue.component('child', {
template:'<button>{{count}}</button>',
data:function(){
return {count:0}
}
});
var app = new Vue({
el:'#app'
});
app.$refs.btn1.count = 1;
app.$refs.btn2.count = 2;
</script>
六、动画过渡
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。
包括以下工具:
在 CSS 过渡和动画中自动应用 class
可以配合使用第三方 CSS 动画库,如 Animate.css
在过渡钩子函数中使用 JavaScript 直接操作 DOM
可以配合使用第三方 JavaScript 动画库,如 Velocity.js
Vue组件添加动画效果使用<transition name='v'>
标签,并指定一个name名引用css动画,如:fade,然后通过以下6个class定义动画过程:
v-enter: 显示动画初始状态
v-enter-active: 显示动画执行状态
v-enter-to: 显示动画结束状态
v-leave: 隐藏动画初始状态
v-leave-active: 隐藏动
v-leave-to: 隐藏动画结束状态
1、基本使用
1)使用动画
<transition name="fade">
<h6 v-show="show">{{greetText}}</h6>
</transition>
定义class
<style>
.fade-enter-active, .fade-leave-active{
transition: opacity 1s;
}
.fade-enter, .fade-leave-to{
opacity: 0;
}
</style>
js部分
var app = new Vue({
el:'#app',
data:{
show:true,
greetText:'Hello Vue!'
}
});
5)使用css动画
<style>
.fade-enter-active, .fade-leave-active{
transition: all 1s;
}
.fade-enter, .fade-leave-to{
transform: translateX(10px);
opacity: 0;
}
</style>
2.自定义过渡类名
可以手动指定过渡类名:
enter-class
enter-active-class
enter-to-class (2.1.8+)
leave-class
leave-active-class
leave-to-class (2.1.8+)
配合animate.css库一起使用:
<link rel="stylesheet" href="animate.min.css">
<div id="app">
<button @click="show = !show" class="">切换显示</button>
<transition
enter-active-class="animated tada"
leave-active-class="animated flipOutX"
>
<h6 v-show="show">{{greetText}}</h6>
</transition>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
show:true,
greetText:'Hello Vue!'
}
});
</script>
3、初始过渡
通过 appear 特性设置节点的在初始渲染的过渡
<transition appear name="fade">
<h6 v-show="show">{{greetText}}</h6>
</transition>
4、过渡状态
通过mode属性可以指定动画切换效果先后顺序:
in-out:新元素先进行过渡,完成之后当前元素过渡离开。
out-in:当前元素先进行过渡,完成之后新元素过渡进入。
1)指定过渡状态
<div id="app">
<button @click="show = !show" class="">切换显示</button>
<br>
<br>
<transition appear name="fade" mode="out-in">
<!-- <button v-if="show" key="login">登陆</button>
<button v-else key="register">注册</button>-->
<button :key="show">{{show ? '登陆' : '注册'}}</button>
</transition>
</div>
2)定义css
<style>
.fade-enter-active, .fade-leave-active{
transition: all .5s;
position: absolute;
}
.fade-enter{
transform: translateX(43px);
opacity: 0;
}
.fade-leave-to{
transform: translateX(-43px);
opacity: 0;
}
</style>
3)js部分
/****
* 初始过渡 appear:
* mode: in-out out-in
* */
var app = new Vue({
el:'#app',
data:{
show:true
}
});
5、列表过渡
<transition-group>标签会以一个真实元素呈现:默认为一个 <span>。你也可以通过 tag 特性更换为其他元素。而且
内部元素需要提供唯一的 key 属性值。
css:
<style>
.item {
margin-right: 10px;
/* 必须是块元素才行*/
display: inline-block;
}
.fade-enter-active, .fade-leave-active {
transition: all 1s;
}
.fade-enter, .fade-leave-to {
transform: translateY(30px);
opacity: 0;
}
</style>
html:
<div id="app">
<button @click="add">添加</button>
<button @click="remove">删除</button>
<transition-group tag="div" name="fade" mode="out-in">
<span class="item" v-for="i in list" :key="i">{{i}}</span>
</transition-group>
</div>
js:
<script>
/****
* 列表过渡:
* <transition-group>
* */
var app = new Vue({
el: '#app',
data: {
list: [1, 2, 3, 4, 5, 6, 7]
},
methods: {
randomIndex: function () {
return parseInt(Math.random() * this.list.length);
},
add: function () {
var newNum = parseInt(Math.random() * 10);
var index = this.randomIndex();
console.log(newNum, index);
this.list.splice(index, 0, newNum);
},
remove: function () {
this.list.splice(this.randomIndex(), 1);
}
}
});
</script>
七、数据处理
1、watch属性
在Vue组件中,使用watch属性来监听数据的变化,同时可以指定监听那个属性。
<div id="root"></div>
<script>
var vm = new Vue({
el:'#root',
data:{
name:'Tim'
},
watch:{
name: function (newValue, oldValue) {
console.log('newValue:'+newValue+';oldValue'+oldValue);
}
}
});
//改变name
vm.name = 'Cat';//newValue:Cat;oldValueTim
</script>
例子中通过watch监听name属性的变化,回调函数中第一个参数是新值,第二个参数是旧值。
<div id="root">
<p>
firstName: <input type="text"
v-bind:value="firstName"
@keyup="changeFirstName($event);">
</p>
<p>
lastName: <input type="text"
v-bind:value="lastName"
@keyup="changeLastName($event)">
</p>
<h4>{{fullName}}</h4>
</div>
<script>
var vm = new Vue({
el:'#root',
data:{
firstName:'Hello',
lastName:'Kitty',
fullName:'Hello Kitty'
},
watch:{
firstName: function (newValue, oldValue) {
console.log('newValue:'+newValue+';oldValue'+oldValue);
this.fullName = newValue+' '+this.lastName;//更新fullname
},
lastName: function (newValue, oldValue) {
console.log('newValue:'+newValue+';oldValue'+oldValue);
this.fullName = this.firstName+' '+newValue;//更新fullname
}
},
methods:{
changeFirstName: function (event) {
this.firstName = event.target.value;
},
changeLastName: function (event) {
this.lastName = event.target.value;
}
}
});
</script>
fullName由firstName和lastName共同决定,当改变firstName时需要重新计算fullName,改变lastName也一样。那么就需要监听这两个属性的变化去更新fullName,这时候就可以使用watch监听。
2.$watch
除了在组件内部使用watch也可以使用内部命令$watch进行属性监听。
<div id="root"></div>
<script>
var vm = new Vue({
el:'#root',
data:{
name:'Tim'
}
});
//$watch 使用
var unwatch = vm.$watch('name',function (newValue, oldValue) {
console.log('newValue:'+newValue+';oldValue'+oldValue);
});
//改变name
vm.name = 'Cat';
//unwatch(); 取消监听
</script>
$watch第一个参数是需要监听的属性,第二个是回调函数用法和watch一样。需要取消监听只需拿到监听对象的引用,这个引用是返回一个函数对象,执行该对象就可以取消监听。
同时监听多个属性,可以不指定属性:
<div id="root"></div>
<script>
var vm = new Vue({
el:'#root',
data:{
name:'Tim',
age:12
}
});
//$watch 使用
vm.$watch(function(){
return this.name+this.age;
},function (newValue, oldValue) {
console.log('newValue:'+newValue+';oldValue'+oldValue);
});
//改变name
vm.name = 'Cat';
vm.age = 15;
//newValue:Cat15;oldValueTim12
</script>
3、computed属性
computed计算属性用于定义比较复杂的属性计算,比如上边计算fullName的时候,需要使用watch两个属性:firstName和lastName,比较繁琐,但是使用computed就很简单。
<div id="root">
<p>
firstName: <input type="text"
v-bind:value="firstName"
@keyup="changeFirstName($event);">
</p>
<p>
lastName: <input type="text"
v-bind:value="lastName"
@keyup="changeLastName($event)">
</p>
<h4>{{fullName}}</h4>
</div>
<script>
var vm = new Vue({
el:'#root',
data:{
firstName:'Hello',
lastName:'Kitty'
},
computed:{
fullName: function () {
return this.firstName+' '+this.lastName;//计算fullname
}
},
methods:{
changeFirstName: function (event) {
this.firstName = event.target.value;
},
changeLastName: function (event) {
this.lastName = event.target.value;
}
}
});
</script>
此时注意,不在data中定义fullName而是在computed属性中指定,回调函数返回值就是fullName的值,这样不管firstName和lastName谁发生变化都会更新fullName。
computed和methods区别:
计算属性使用computed定义, 方法使用methods定义
计算属性使用时不加括号执行符
计算属性是基于它们的依赖进行缓存的,计算属性只有在它的相关依赖发生改变时才会重新求值。否则返回之前计算好的值,性能更高!
修改之前的代码
<h4>{{fullName}}</h4>
改为:
<h4>{{fullName()}}</h4>
在methods中定义fullName,使用效果和使用computed一致:
fullName: function () {
return this.firstName+' '+this.lastName;//计算fullname
}
4.getter和setter
在computed中,同样可以指定setter进行数据更新。上述例子中都是通过firstName和lastName来计算fullName,那么也可以通过fullName来更新firstName和lastName。
<div id="root">
<p>
fullName: <input type="text"
v-bind:value="fullName"
@keyup="changeFullName($event);">
</p>
<h4>firstName: {{firstName}}</h4>
<h4>lastName: {{lastName}}</h4>
</div>
<script>
var vm = new Vue({
el: '#root',
data: {
firstName: 'Hello',
lastName: 'Kitty'
},
computed: {
fullName: {
//getter
get: function () {
return this.firstName + ' ' + this.lastName;//计算fullname
},
//setter
set: function (newValue) {
var names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];
}
}
},
methods: {
changeFullName: function (event) {
this.fullName = event.target.value;
}
}
});
</script>
此时改变fullName就可以同步更新lastName和firstName。
八、生命周期
每个 Vue
实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到
DOM、在数据变化时更新 DOM
等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们自己的代码。
比如 created 钩子可以用来在一个实例被创建之后执行代码;
常用的生命周期钩子函数有:
1) created: 实例创建完成后被立即调用
2) mounted: 实例挂载完成后被立即调用
3) beforeUpdate: 实例需要渲染之前调用
4) updated: 实例更新后调用
5) destroyed: Vue 实例销毁后调用
<div id="root">
{{name}}
</div>
<script>
var vm = new Vue({
el:'#root',
data:{
name:'Tim'
},
created: function () {
console.log('实例创建...');
},
mounted:function () {
console.log('实例挂载...');
},
beforeUpdate:function () {
console.log('实例将要更新...');
},
updated:function () {
console.log('实例已更新...');
},
destroyed:function(){
console.log('实例卸载...');
}
});
//改变name
vm.name = 'Cat';
//vm.$destroy();//卸载
</script>
九、自定义指令
除了默认设置的核心指令 (v-model 和 v-show),Vue 也允许注册自定义指令。
1、基本使用
1)定义
//自定义全局指令v-focus
Vue.directive('focus',{
//当绑定元素插入到DOM调用
inserted: function (el) {
//元素获取焦点
el.focus();
}
});
使用directive定义,第一个参数为指令名,使用时加上v-前缀才能生效。inserted属性指当绑定元素插入到DOM时调用。
定义局部指令使用directives:
var app = new Vue({
el:'#app',
directives:{
focus:{
inserted: function (el) {
//元素获取焦点
el.focus();
}
}
}
});
2)使用
<div id="app">
<input type="text" v-focus>
</div>
2、钩子函数
指令定义函数提供了几个钩子函数 (可选):
bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
inserted:被绑定元素插入父节点时调用 (父节点存在即可调用,不必存在于
document 中)。update:所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode
更新之前。指令的值可能发生了改变也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
。componentUpdated:所在组件的 VNode 及其孩子的 VNode 全部更新时调用。
unbind:只调用一次,指令与元素解绑时调用。
钩子函数的参数有三个:
el:当前指令绑定元素
binding:当前指令绑定的所有信息对象,有以下属性:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive="1 + 1", value 的值是 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:绑定值的字符串形式。例如 v-my-directive="1 +
1" ,expression 的值是 "1 + 1"。arg:传给指令的参数。例如 v-my-directive:foo,arg 的值是 "foo"。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar,
修饰符对象 modifiers 的值是 { foo: true, bar: true }。
3)vnode:Vue 编译生成的虚拟节点
4)oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated
钩子中可用。
<div id="app">
<input type="text" v-demo:arg.a.b="1+1">
</div>
<script>
Vue.directive('demo',{
bind: function (el,binding) {
console.log(el);
console.log(binding);
}
});
var app = new Vue({
el:'#app'
});
</script>
3.实例图片懒加载
谷歌图片的加载做得非常优雅,在图片未完成加载前,用随机的背景色占位,图片加载完成后才直接渲染出来,用自定义指令可以非常方便的实现这个功能。
1)样式
<style>
.item, .item img{
width: 200px;
height: 120px;
float: left;
}
</style>
2)自定义v-img指令
//定义全局自定义指令v-img
Vue.directive('img',{
bind: function (el,binding) {
//生成随机颜色
var color = parseInt(Math.random()*0xFFFFFF).toString(16);
//设置当前元素的背景,提前进行占位等待图片加载
el.style.background = '#'+color;
//setTimeout模拟图片加载的延时情况
setTimeout(function () {
//创建图片对象
var img = new Image();
//通过binding对象获取真实的图片url
img.src = binding.value;
//将图片元素插入DOM结构
el.appendChild(img);
//随机延时
},Math.random()*3000+500);
}
});
3)模拟数据
var app = new Vue({
el:'#app',
data:{
//定义模拟数据
imgs:[
{url:'img/01.jpg'},
{url:'img/02.jpg'},
{url:'img/03.jpg'},
{url:'img/04.jpg'}
]
}
});
4)使用
<div id="app">
<div v-img="item.url" v-for="item in imgs" class="item"></div>
</div>
5)效果
十、过滤器
Vue允许自定义过滤器,可被用作一些常见的文本格式化。
过滤器可以用在两个地方:mustache 插值和 v-bind 表达式 (后者从 2.1.0+
开始支持)。
过滤器应该被添加在 JavaScript 表达式的尾部,由"管道"符指示:
<!-- in mustaches -->
{{ message | capitalize }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>
过滤器可以串联:
{{ message | filterA | filterB }}
使用Vue.filter定义全局过滤器,filters在组件内指定局部过滤器。
<div id="root">
<h4>{{name | upperCase | length | test('A-','-B')}}</h4>
</div>
<script>
/***
* filter:过滤器
* */
var vm = new Vue({
el: '#root',
data: {
name: 'hello'
},
filters: {
upperCase: function (value) {
return value.toUpperCase();
},
length: function (value) {
return value+value.length;
},
test: function (value, begin, end) {
console.log(value, begin, end);
return begin+value+end;
}
}
});
</script>
十一、路由
随着(SPA)单页应用的不断普及,前后端开发分离,目前项目基本都使用前端路由,在项目使用期间页面不会重新加载。
优点:
用户体验好,和后台网速没有关系,不需要每次都从服务器全部获取,界面展现快。
可以再浏览器中输入指定想要访问的url路径地址。
实现了前后端的分离,方便开发。有很多框架都带有路由功能模块。
缺点:
对SEO不是很友好
在浏览器前进和后退时候重新发送请求,没有合理缓存数据。
初始加载时候由于加载所有模块渲染,会慢一点。
1、手动实现路由
前端路由目前主要有两种方法:
1)利用url的hash,就是常用的锚点(#)操作,类似页面中点击某小图标,返回页面顶部,JS通过hashChange事件来监听url的改变,IE7及以下需要轮询进行实现。一般常用框架的路由机制都是用的这种方法,例如Angualrjs自带的ngRoute和二次开发模块ui-router,react的react-route,vue-route...
2)利用HTML5的History模式,使url看起来类似普通网站,以"/"分割,没有"#",但页面并没有跳转,不过使用这种模式需要服务器端的支持,服务器在接收到所有的请求后,都指向同一个html文件,通过historyAPI,监听popState事件,用pushState和replaceState来实现。
由于使用hash方法能够兼容低版本的IE浏览器,简单的的自己搭建前端路由。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定义路由</title>
<script>
function Router() {
this.routes = {};
this.currentUrl = '';
}
//route 存储路由更新时的回调到回调数组routes中,回调函数将负责对页面的更新
Router.prototype.route = function (path, callback) {
this.routes[path] = callback || function () { };
};
//refresh 执行当前url对应的回调函数,更新页面
Router.prototype.refresh = function () {
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl]();
};
//init 监听浏览器 url hash 更新事件
Router.prototype.init = function () {
//load事件在当前页面加载时触发
window.addEventListener('load', this.refresh.bind(this), false);
//hashchange事件在当前页面URL中的hash值发生改变时触发
window.addEventListener('hashchange', this.refresh.bind(this), false);
};
window.Router = new Router();
window.Router.init();
</script>
<title></title>
</head>
<body>
<ul>
<li><a href="#/">首页</a></li>
<li><a href="#/home">主页</a></li>
<li><a href="#/detail">详情页</a></li>
</ul>
<div id="page"></div>
<script>
var body = document.querySelector('body');
//切换hash的事件
function changePage(page) {
document.querySelector('#page').innerHTML = page;
}
//切换
Router.route('/', function () {
changePage('这是首页');
});
Router.route('/home', function () {
changePage('这是主页');
});
Router.route('/detail', function () {
changePage('这是详情页');
});
</script>
</body>
</html>
2.vue-router
vue-router是Vue官方提供的路由,用 Vue.js + vue-router
创建单页应用,是非常简单的。使用 Vue.js
,我们已经可以通过组合组件来组成应用程序,当你要把 vue-router
添加进来,我们需要做的是,将组件(components)映射到路由(routes),然后告诉
vue-router 在哪里渲染它们。下面是个基本例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-router</title>
<script src="../vue.js"></script>
<script src="../vue-router.js"></script>
</head>
<body>
<div id="app">
<h3>Hello App!</h3>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/index">Go to index</router-link>
<router-link to="/news">Go to news</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
<script>
// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Index = { template: '<div>首页</div>' }
const News = { template: '<div>新闻页</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
{ path: '/index', component: Index },
{ path: '/news', component: News }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写)相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app');
// 现在,应用已经启动了!
</script>
</body>
</html>
3.动态路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-router</title>
<script src="../vue.js"></script>
<script src="../vue-router.js"></script>
</head>
<body>
<div id="app">
<h3>Hello App!</h3>
<p>
<router-link to="/user/12">User12</router-link>
<router-link to="/user/13">User13</router-link>
</p>
<router-view></router-view>
</div>
<script>
// 1. 定义(路由)组件。
const User = { template: '<div>用户: {{ $route.params.id }}</div>' };
// 2. 定义动态路由
const routes = [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
];
// 3. 创建 router 实例
const router = new VueRouter({
routes
});
// 4. 创建和挂载根实例
const app = new Vue({
router
}).$mount('#app');
// 现在,应用已经启动了!
</script>
</body>
</html>
十二、Ajax
1、vue-resource介绍
vue-resource插件具有以下特点:
1. 体积小
vue-resource非常小巧,在压缩以后只有大约12KB,服务端启用gzip压缩后只有4.5KB大小,这远比jQuery的体积要小得多。
2. 支持主流的浏览器
和Vue.js一样,vue-resource除了不支持IE
9以下的浏览器,其他主流的浏览器都支持。
3.
支持Promise API和URI
Templates
Promise是ES6的特性,Promise的中文含义为"先知",Promise对象用于异步计算。
URI Templates表示URI模板,有些类似于ASP.NET MVC的路由模板。
4. 支持拦截器
拦截器是全局的,拦截器可以在请求发送前和发送请求后做一些处理。
拦截器在一些场景下会非常有用,比如请求发送前在headers中设置access_token,或者在请求失败时,提供共通的处理方式。
2、使用规则
引入vue-resource后,可以基于全局的Vue对象使用http,也可以基于某个Vue实例使用http。
1)基于全局Vue对象使用http
Vue.http.get('/someUrl',[options]).then(successCallback, errorCallback);
Vue.http.post('/someUrl',[body],[options]).then(successCallback,errorCallback);
Vue.http.jsonp('/someUrl',[options]).then(successCallback,errorCallback);
2)在一个Vue实例内使用$http
this.$http.get('/someUrl',[options]).then(successCallback,errorCallback);
this.$http.post('/someUrl',[body],[options]).then(successCallback,errorCallback);
this.$http.jsonp('/someUrl', [options]).then(successCallback, errorCallback);
在发送请求后,使用then方法来处理响应结果,then方法有两个参数,第一个参数是响应成功时的回调函数,第二个参数是响应失败时的回调函数。
3)实例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../vue.min.js"></script>
<script src="../vue-resource.js"></script>
</head>
<body>
<div id="root"></div>
<script>
var vm = new Vue({
el: '#root',
data: {
apiUrl:'https://api.github.com/users/octocat/gists'
},
mounted: function () {
this.$http.get(this.apiUrl).then(function (result) {
console.table(result.data);
}, function (result) {
console.error(result);
});
}
});
</script>
</body>
</html>