深入响应式
- 追踪变化:
把普通js对象传给Vue实例的data选项,Vue将使用Object.defineProperty把属性转化为getter/setter(因此不支持IE8及以下),每个组件实例都有相应的watcher实例对象,它把属性记录为依赖,当依赖项的setter被调用的时候,watcher会被通知并重新计算,从而更新渲染
变化检测:
Vue不能检测到对象属性的添加或删除,因此属性必须在data对象上存在才能让Vue转换它,才是响应式的
Vue不允许在已经创建的实例上动态添加新的根级响应式属性,但是可以将响应属性添加到嵌套的对象上,使用Vue.set(object,key,value)
Vue.set(vm.someObject,'b',2)
还可以用vm.$set实例方法,是Vue.set的别名:
this.$set(this.someObject,'b',2)
想向已有对象添加一些属性,可以创建一个新的对象,让它包含原对象的属性和新属性:
this.someObject=Object.assign({},this.someObject,{a:1,b:2})
- 声明响应式属性:由于不允许动态添加根级响应式属性,所以初始化实例前要声明,即便是个空值:
var vm = new Vue({
data: {
// 声明 message 为一个空值字符串
message: ''
},
template: '<div>{{ message }}</div>'
})
// 之后设置 `message`
vm.message = 'Hello!'
- Vue是异步更新队列的,目的是缓冲同一个事件循环中所有数据变化去除重复数据,但是问题来了,当设置数据变化时,并不会立即重新渲染,需要排队,可是如果想要在这个变化后紧接着做点什么,就需要一个Vue.nextTick(callback)来表明,这个更新后再执行操作:
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
//组件上使用nextTick
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: 'not updated'
}
},
methods: {
updateMessage: function () {
this.message = 'updated'
console.log(this.$el.textContent) // => '没有更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '更新完成'
})
}
}
})
过渡效果
用transition封装组件,添加过渡,需要有个name属性,会自动生成四个对应类(name为transition的name属性的值)
- name-enter——动画起点
- name-enter-active——动画中点
- name-leave——动画中点的下一帧(默认与上一帧相同)
- name-leave-active——动画终点
(这是Vue官网的图)
css过渡(简单的transition)
<div id="app-01">
<button v-on:click="show=!show">toggle</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
var app01=new Vue({
el:'#app-01',
data:{
show:true
}
})
//css部分,设置对应类的动画
.fade-enter-active,.fade-leave-active{
transition: opacity 3s;
}
.fade-enter, .fade-leave-active{
opacity: 0
}
css动画(animation)
//代码虽然多,但是信息量并不大
//css部分
.bounce-enter-active{
animation: bounce-in 1s
}
.bounce-leave-active{
animation: bounce-out 1s
}
//这里给p设了一个背景色,还设了一个50%的宽度,是为了观测scale的动画效果
p{
background-color: red;
width:50%;
}
@keyframes bounce-in{
0%{
transform: scale(0)
}
50%{
transform:scale(1.5)
}
100%{
transform: scale(1)
}
}
@keyframes bounce-out{
0%{
transform: scale(1)
}
50%{
transform:scale(1.5)
}
100%{
transform: scale(0)
}
}
//html部分
<div id="app-02">
<button v-on:click="show=!show">toggle</button>
<transition name="bounce">
<p v-if="show">look at me</p>
</transition>
</div>
//js部分
var app02=new Vue({
el:'#app-02',
data:{
show:true
}
})
与css过渡的区别:‘在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。’(存疑)
- 自定义过渡类名:结合其他第三方动画库等使用
- enter-class
- enter-active-class
- leave-class
- leave-active-class
//用法
<transition name="bounce"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight">
<p v-if="show">look at me</p>
</transition>
- 使用js钩子,enter和leave各自有4个钩子:
- beforeEnter——一些预设
- enter——进入动画,部分情况需要有回调函数
- afterEnter
- enterCancelled
- beforeLeave
- leave——离开动画,部分情况需要有回调函数
- afterLeave
- leaveCancelled
注:只用js过渡时,enter和leave中的回调函数是必须的,不然动画会立即完成。对于仅适用js过渡的元素,最好添加v-bind:css="false"避免过渡中css的影响
//引入一个velocity库,便于操作dom属性
<script src="vue/velocity.min.js"></script>
<div id="app-03">
<button v-on:click="show=!show">toggle</button>
<transition
//绑定一个beforeEnter函数,用来预设状态
v-on:before-enter="beforeEnter"
//这是进入动画
v-on:enter="enter"
//这是离开动画
v-on:leave="leave"
//排除css影响
v-bind:css="false"
>
<p v-if="show">demo</p>
</transition>
</div>
var app03=new Vue({
el:'#app-03',
data:{
show:false
},
methods:{
beforeEnter:function(el){
el.style.opacity = 0
//预设了旋转中心点是左侧
el.style.transformOrigin='left'
},
enter:function(el,done){
//字体由初始变为1.4em,耗时300毫秒
Velocity(el,
{opacity:1,fontSize:'1.4em'},
{duration:300})
//字体变回1em,并变为静止状态
Velocity(el,{fontSize:'1em'},{complete:done})
},
leave:function(el,done){
//结束动作分为三步:先是旋转50度,x轴偏移15pxs是为了使旋转看起来更自然,并设了600毫秒的时间
Velocity(el,{translateX:'15px',rotateZ:'50deg'},{duration:600})
//第二步:旋转了100度,循环2次
Velocity(el,{rotateZ:'100deg'},{loop:2})
//第三次旋转45度,并结束
Velocity(el,{
rotateZ:'45deg',
translateX:'30px',
translateY:'30px',
opacity:0
},{complete:done})
}
}
})
- rotateZ——3d旋转,绕z轴旋转
- translateX——x轴变化
- translateY——y轴变化
- transformOrigin——变化旋转元素的基点(圆心)
初始渲染的过渡
这里例子没效果,(存疑)
.custom-appear-class{
font-size: 40px;
color: red;
background: green;
}
.custom-appear-active-class{
background: green;
}
<div id="app-03">
<button v-on:click="show=!show">toggle</button>
<transition
appear
appear-class="custom-appear-class"
appear-active-class="custom-appear-active-class"
>
<p v-if="show">demo</p>
</transition>
</div>
var app03=new Vue({
el:'#app-03',
data:{
show:true
}
})
多个元素的过渡
- 用v-if/v-else来控制过渡
<transition>
<table v-if="items.length > 0">
</table>
<p v-else>Sorry, no items found.</p>
</transition>
但是需要注意,当两个过渡元素标签名相同时,需要设置key值来区分,否则Vue会自动优化为,只替换内容,那么就看不到过渡效果了
先来个过渡的例子:
//html部分,设定了两个button,并用toggle来切换值,为了避免Vue的只替换内容,设了两个不同的key值
<div id="app-04">
<button v-on:click="isEditing=!isEditing">toggle</button>
<br>
<transition name="try" mode="in-out">
<button v-if="isEditing" key="save">
in
</button>
<button v-else key="edit">out</button>
</transition>
</div>
//css部分
//首先是对过渡元素进行绝对定位,不然过渡过程中,元素共同出现时位置会有奇怪的问题(这个限定有点麻烦)
#app-04 button{
position: absolute;
left:100px;
}
//参考案例,button进入有个动画,取名move_in
.try-enter-active{
animation: move_in 1s;
}
//动画包含了位移,从右侧到中间,透明度从0到1
@keyframes move_in{
from{left:150px;opacity: 0}
to{left:100px;opacity: 1}
}
//button出去的动画取名move_out
.try-leave-active{
animation:move_out 1s;
//同move_in
@keyframes move_out{
from{left:100px;opacity: 1}
to{left:50px;opacity: 0}
}
//js部分
var app04=new Vue({
el:'#app-04',
data:{
isEditing:true
}
})
多种方法设置不同标签的过渡:
- 通过给同一个元素的key特性设置不同的状态来代替v-if/v-else(这个好棒):
<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
- 把v-if升级为switch,实现不止2个标签的绑定:
<transition>
<button v-bind:key="docState">
{{ buttonMessage }}
</button>
</transition>
computed: {
buttonMessage: function () {
switch (docState) {
case 'saved': return 'Edit'
case 'edited': return 'Save'
case 'editing': return 'Cancel'
}
}
}
- vue还提供了过渡模式,两种,in-out和out-in,用法:
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
算是过渡控制增强
多组件过渡 :动态组件component绑定
//css部分
.component-fade-enter-active,.component-fade-leave-active{
transition:opacity .5s ease;
}
.component-fade-enter,.component-fade-leave-active{
opacity: 0;
}
//html部分
<div id="app-05">
<button v-on:click="view=='v-a'?view='v-b':view='v-a'">toggle</button>
<br>
//设置了out-in后组件可以不用考虑绝对定位的问题了
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
</div>
//js部分
var app05=new Vue({
el:'#app-05',
data:{
view:'v-a'
},
components:{
'v-a':{
template:'<div>Component A</div>'
},
'v-b':{
template:'<div>Component B</div>'
}
}
})
列表过渡
使用transition-group,必须对子项设置特定的key名
- 进入离开过渡
//css部分
#app-06 p{
width: 100%
}
#app-06 span{
//把display设置成inline-block,才可以设置它的translateY,不然没有位移效果
display: inline-block;
margin-right: 10px;
}
.list-enter-active,.list-leave-active{
transition: all 1s;
}
.list-enter,.list-leave-active{
opacity: 0;
//两种写法,一种是如下,另一种是translateY(30px)
transform: translate(0px,30px);
}
//html部分
<div id="app-06">
<button @click='add'>Add</button>
<button @click='remove'>Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" :key="item">
{{item}}
</span>
</transition-group>
</div>
//js部分
var app06=new Vue({
el:'#app-06',
data:{
items:[1,2,3,4,5,6,7,8,9],
nextNum:10
},
methods:{
randomIndex:function(){
return Math.floor(Math.random()*this.items.length)
},
//复习一下splice(a,b,c),a:待增加/删除的项目,b:删除的项目数,c:待增加的项目(可不止一个)
add:function(){
this.items.splice(this.randomIndex(),0,this.nextNum++)
},
remove:function(){
this.items.splice(this.randomIndex(),1)
}
}
})
- 列表的位移过渡
使用新增的v-move特性,它会在元素的改变定位的过程中应用,vue内部是使用了一个叫FLIP的动画队列。
//css部分
//这里对name-move设了一个transition,就可以控制位移过程中的动画效果
.shuffle-list-move{
transition: transform 1s;
}
//html部分
<div id="app-07">
<button @click='shuffle'>Shuffle</button>
<transition-group name="shuffle-list" tag="ul">
<li v-for="item in items" :key="item">
{{item}}
</li>
</transition-group>
</div>
//js部分
var app07=new Vue({
el:'#app-07',
data:{
items:[1,2,3,4,5,6,7,8,9]
},
methods:{
//教程中是引用了lodash的方法库,让我们自己写这个洗牌算法吧(Fisher-Yates shuffle)
shuffle:function(){
let m=this.items.length,
t,i;
while(m){
i=Math.floor(Math.random()*m--);
t=this.items[i];
this.items.splice(i,1,this.items[m]);
this.items.splice(m,1,t);
}
}
}
})
- 进入离开过渡和位移过渡的组合版:
//css部分
#app-06 p{
width: 100%
}
#app-06 span{
display: inline-block;
margin-right: 10px;
transition: all 1s;
}
.list-enter,.list-leave-active{
opacity: 0;
transform: translate(0px,30px);
}
//需要重点注意的是这里:离开动画需要设置一个绝对定位,不然离开动画不圆滑,原因不明(存疑)
.list-leave-active{
position: absolute;
}
.list-move{
transition: transform 1s;
}
//html部分
<div id="app-06">
<button @click='add'>Add</button>
<button @click='remove'>Remove</button>
<button @click='shuffle'>Shuffle</button>
<transition-group name="list" tag="p">
<span v-for="item in items" :key="item">
{{item}}
</span>
</transition-group>
</div>
//js部分
var app06=new Vue({
el:'#app-06',
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)
},
shuffle:function(){
let m=this.items.length,
t,i;
while(m){
i=Math.floor(Math.random()*m--);
t=this.items[i];
this.items.splice(i,1,this.items[m]);
this.items.splice(m,1,t);
}
}
}
})
- 列表升级版——矩阵例子
//css部分
//由于shuffle是整个矩阵混排,所以其实是一个长度为81的列表的混排,矩阵的位置由css的flex来确定
//父元素规定为flex,规定长度,并定义了超出长度时的换行方式
.cellContainer{
display: flex;
flex-wrap: wrap;
width: 238px;
margin-top: 10px;
}
//子元素规定为flex,规定长宽,横向对齐方式,纵向对齐方式,为了视觉好看,重合部分的边需要去重。
.cell{
display: flex;
justify-content: space-around;
align-items: center;
width: 25px;
height: 25px;
border: 1px solid #aaa;
margin-right: -1px;
margin-bottom: -1px;
}
.shuffle-table-move{
transition: transform 1s;
}
//html部分
<div id="app-08">
<button @click='shuffle'>Shuffle</button>
<transition-group name="shuffle-table" tag="div" class="cellContainer">
<div v-for="cell in cells" :key="cell.id" class="cell">
{{cell.number}}
</div>
</div>
//js部分
var app08=new Vue({
el:'#app-08',
data:{
//数组方法,先是创建一个有81项的数组,内容为null,然后用map方法返回每个数组项,包含id和number两个属性
cells:Array.apply(null,{length:81})
.map(function(_,index){
return{
id:index,
number:index%9+1
}
})
},
methods:{
shuffle:function(){
let m=this.cells.length,
t,i;
while(m){
i=Math.floor(Math.random()*m--);
t=this.cells[i];
this.cells.splice(i,1,this.cells[m]);
this.cells.splice(m,1,t);
}
}
}
})
- 列表的渐进过渡
核心思想是设置一个定时器,根据index设置不同的位移序列,从而形成渐进
<div id="app-09">
<input type="text" v-model="query">
//:css禁止css的影响
//监听事件:before-enter/enter/leave
<transition-group name="staggered-fade" tag="ul" v-bind:css="false" v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:leave="leave"
>
<li v-for="(item,index) in computedList" v-bind:key="item.msg" v-bind:data-index="index">{{item.msg}}</li>
</transition-group>
</div>
var app09=new Vue({
el:'#app-09',
data:{
//原始列表
query:'',
list:[
{msg:'Bruce Lee'},
{msg:'Jackie Chan'},
{msg:'Chuck Norris'},
{msg:'Jet Li'},
{msg:'Kung Fury'}
]
},
computed:{
//复合列表,用了一个过滤器,返回查找query不为空的选项
computedList:function(){
var vm=this
return this.list.filter(function(item){
return item.msg.toLowerCase().indexOf(vm.query.toLowerCase())!==-1
})
}
},
methods:{
beforeEnter:function(el){
el.style.opacity=0
el.style.height=0
},
//设置一个delay时间,根据参数值而不同
enter:function(el,done){
var delay=el.dataset.index*150
setTimeout(function(){
Velocity(el,{opacity:1,height:'1.6em'},{complete:done})
},delay)
},
leave:function(el,done){
var delay=el.dataset.index*150
setTimeout(function(){
Velocity(el,{opacity:0,height:0},{complete:done})
},delay)
}
}
})
h5自定义属性dataset用法:
- html中自定义属性:
<div id="example" data-pro="我是pro"></div> - js中引用属性:
var div =document.getElementById(''example")
console.log(div.dataset.pro)
//我是pro
注意两者的小差异,html中是data-name,js中引用时需要写为dataset.name
可复用的过渡
这里提到了函数式组件,需要看完后面的render函数来结合使用
动态过渡
过渡的数据可以动态控制,用js来获取
<div id="app-10">
//input type="range"是滑动条,把值绑定到fadeInDuation
Fade In<input type="range" v-model="fadeInDuation" min="0" v-bind:max="maxFadeDuration">
Fade Out<input type="range" v-model="fadeOutDuation" min="0" v-bind:max="maxFadeDuration">
<transition v-bind:css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave">
//这里有一个对show值的判断,这是控制淡入淡出循环的关键
<p v-if="show">hello</p>
</transition>
<button @click="stop=true">Stop it!</button>
</div>
var app10=new Vue({
el:'#app-10',
data:{
show:true,
fadeInDuation:1000,
fadeOutDuation:1000,
maxFadeDuration:1500,
stop:false
},
mounted:function(){
this.show=false
},
methods:{
beforeEnter:function(el){
el.style.opacity=0
},
enter:function(el,done){
var vm=this
Velocity(el,{opacity:1},{duration:this.fadeInDuation,complete:function(){
done()
if(!vm.stop) vm.show=false
}})
},
leave:function(el,done){
var vm=this
Velocity(el,{opacity:0},{duration:this.fadeOutDuation,complete:function(){
done()
vm.show=true
}})
}
}
})
注意问题:
1、初始show设置为true,mounted钩子里又改为false,而enter和leave中又分别对show有更新,为什么这么复杂?
:测试发现,初始第一次渲染,并不会触发enter事件,而是默认渲染(无语),如果没有mounted钩子的show=false,则无法触发leave事件,元素会停留在初始渲染状态,不会自循环,所以整个循环是从mounted触发leave事件开始的,leave事件又把show=true,转而触发enter事件,enter事件show=false,又触发leave,从而形成循环
2、之前都没太注意Velocity前的那句var vm=this,原因是进入Velocity函数后,在done语句之后,this就不是Vue自己的this了,所以需要存值,done之前的目测还可以用
过渡状态
- 状态动画与watcher
<script src="vue/tween.js"></script>
<div id="app-01">
<input type="number" v-model.number="number" step="20">
<p>{{animatedNumber}}</p>
</div>
var app01=new Vue({
el:'#app-01',
data:{
number:0,
animatedNumber:0
},
watch:{
number:function(newValue,oldValue){
var vm=this
function animate(time){
requestAnimationFrame(animate)
TWEEN.update(time)
}
new TWEEN.Tween({tweeningNumber:oldValue})
.easing(TWEEN.Easing.Quadratic.Out)
.to({tweeningNumber:newValue},1000)
.onUpdate(function(){
vm.animatedNumber=this.tweeningNumber.toFixed(0)
})
.start()
animate()
}
}
})
注意问题
1、百度搜tweenjs,出来的那个creatjs并不是教程里引用的库,google的是:git仓库地址
研究了一圈用法,发现用法很基本,很固定:
- 创建一个tween对象,并传入起始对象:new TWEEN.Tween(起始对象)
- (此项非必须)变化曲线方程:easing(曲线方程),官方提供了31种
- to(终点对象,时间)
- (此项非必须但此案例必须)onUpdate(函数),此案例中就是每次变化,都要执行函数,从而才形成动画效果
- start()开始
- 重点来了,tween并不会自启动,需要用update()来启动,官方也建议加一个requestAnimationFrame,以平滑动画,防止掉帧,于是出现了啰嗦但是必须的animate()函数。
2、上文提到的requestAnimationFrame,字面意思是“请求动画帧”,它的用途张鑫旭大神已经详细说明,附链接:张鑫旭博客,概括说明是,requestAnimationFrame(内容)在下一帧执行动画,与setTimeout的区别是不会掉帧。
3、v-model.number="number"我愣了一下,后来发现是后缀标记,表示把v-model绑定值转化为数字
- 进化版,引入颜色渐变动画
//引入新库,color.js
<script src="vue/color.js"></script>
//对色块样式简单定义
<style>
#app-02 span{
display: block;
width:100px;
height: 100px;
}
</style>
//html部分
<div id="app-02">
//v-on:keyup.enter="updateColor"是绑定一个键盘按键,.enter是13键的别名
<input v-model="colorQuery" v-on:keyup.enter="updateColor" placeholder="Enter a color">
<button v-on:click="updateColor">Update</button>
<p>Preview:</p>
//把样式绑定到tweenedCSSColor
<span v-bind:style="{backgroundColor:tweenedCSSColor}"></span>
<p>{{tweenedCSSColor}}</p>
</div>
//js部分
//又见命名空间,作者叫brehaut
var Color=net.brehaut.Color
var app02=new Vue({
el:'#app-02',
data:{
colorQuery:'',
//注意color是一个包含4个属性的对象
color:{
red:0,
green:0,
blue:0,
alpha:1
},
tweenedColor:{}
},
//这里用了一个原生js的方法,Object.assign(目标对象,源对象),是将源对象的可枚举属性复制进目标对象内,按值复制,返回目标对象,一般用于合并多个对象,此例中只有一个对象,改为this.tweenedColor=this.color也是ok的,或者不用created钩子,在data内初始化tweenedColor也ok
created:function(){
this.tweenedColor=Object.assign({},this.color)
},
//watch很有用,每当color值有变化,都会触发这个函数
watch:{
color:function(){
function animate(time){
requestAnimationFrame(animate)
TWEEN.update(time)
}
//这里,tweenedColor也会被更新掉
new TWEEN.Tween(this.tweenedColor)
.to(this.color,750)
.start()
animate()
}
},
computed:{
//这里用了color.js的一个方法,toCSS(),是把color形式的对象转化为可用的"#123456"的颜色字符串
tweenedCSSColor:function(){
return new Color({
red:this.tweenedColor.red,
green:this.tweenedColor.green,
blue:this.tweenedColor.blue,
alpha:this.tweenedColor.alpha
}).toCSS()
}
},
methods:{
updateColor:function(){
//使用了color.js的一个方法toRGB(),创建新的color对象,传入有效值,转化为color格式的对象并返回
this.color=new Color(this.colorQuery).toRGB()
//这一句是清空了input的上一次输入,可有可无,去掉的话input内会保持上一次输入的值
this.colorQuery=''
}
}
})
总结
1、color.js用到的方法:
创建新的color对象并转成color格式{red:1,green:2,blue:3,alpha:1}:new Color(有效值).toRGB()
转成#123456格式:new Color(有效值).toCSS()
动态状态转换
//css部分
#app-03 svg,#app-03 input{
display: block;
}
//polygon的填色方式不同于其他css语法,用的是fill
#app-03 polygon{
fill:#41b883;
}
//stroke控制边的样式
#app-03 circle{
fill:transparent;
stroke: #35495e;
}
#app-03 input{
width: 90%;
margin-bottom: 15px;
}
//html部分
<div id="app-03">
<svg width="200" height="200">
<polygon v-bind:points="points"></polygon>
<circle cx="100" cy="100" r="90"></circle>
</svg>
<label>Sides: {{sides}}</label>
<input type="range" min="3" max="500" v-model.number="sides">
<label>Minimum Radius: {{minRadius}}%</label>
<input type="range" min="0" max="90" v-model.number="minRadius">
<label>Update Interval: {{updateInterval}} milliseconds</label>
<input type="range" min="10" max="2000" v-model.number="updateInterval">
</div>
//js部分
var app03=new Vue({
el:'#app-03',
data:function(){
var defaultSides=10
var stats=Array.apply(null,{length:defaultSides}).map(function(){return 100})
return {
stats:stats,
points:generatePoints(stats),
sides:defaultSides,
minRadius:50,
interval:null,
updateInterval:500
}
},
watch:{
//这里有个好玩的问题,参考fiddle上的例子,是用了一个for循环来添加删改stats,但是我想啊,watch作为监控,应该是每次sides有变化就会触发,而sides本身又是一个滑条,那么数值必然是依次变化的,所以可否取消for循环,只判断新旧值,每次只添加删除一项。
//但是通过console.log(newSides-oldSides)发现,滑动太快的话,就会输出2啊3啊什么的,这可能和浏览器读取还有watch的监控机制有关
//于是只好乖乖改为for循环了
sides:function(newSides,oldSides){
var sidesDifference = newSides - oldSides
if (sidesDifference > 0) {
this.stats.push(this.newRandomValue())
} else {
this.stats.shift()
}
},
//咦,引用了一个新的缓动库,感觉这个库好用多了,是著名的Greensock绿袜子
stats:function(newStats){
TweenLite.to(
this.$data,
this.updateInterval/1000,
{points:generatePoints(newStats)})
},
updateInterval:function(){
this.resetInterval()
}
},
//清掉这个能看到预设的初始状态
mounted:function(){
this.resetInterval()
},
methods:{
randomizeStats:function(){
var vm=this
this.stats=this.stats.map(function(){
return vm.newRandomValue()
})
},
newRandomValue:function(){
return Math.ceil(this.minRadius+Math.random()*(100-this.minRadius))
},
resetInterval:function(){
var vm=this
clearInterval(this.interval)
this.randomizeStats()
this.interval=setInterval(function(){
vm.randomizeStats()
},this.updateInterval)
}
}
})
function valueToPoint(value,index,total){
var x=0,y=-value*0.9,angle=Math.PI*2/total*index,cos=Math.cos(angle),sin=Math.sin(angle),tx=x*cos-y*sin+100,ty=x*sin+y*cos+100
return {x:tx,y:ty}
}
function generatePoints(stats){
var total=stats.length
return stats.map(function(stat,index){
var point=valueToPoint(stat,index,total)
return point.x+','+point.y
}).join(' ')
}
注意问题:
1、用了svg多边形和圆,多边形传入点的参数,点的参数格式为{x,y x,y}
<svg>
<polygon v-bind:points="points"></polygon>
<circle cx="100" cy="100" r="90"></circle>
</svg>
2、引用了一个新库,叫TweenLite.js,还有一个系列的动画库,简单好用
3、data返回了一个函数,是为了不同实例不共享数据
4、sides监控处出现一个想当然的问题,见代码解释
5、动画的循环是通过resetInterval中的定时器实现的,动画的渐变是监控了stats的变化。
render函数
第一个例子
<div id="app-01">
<anchored-heading :level="2">Hello world!</anchored-heading>
</div>
Vue.component('anchored-heading',{
render:function(createElement){
return createElement(
'h'+this.level,
this.$slots.default)
},
props:{
level:{
type:Number,
required:true
}
}
})
var app01=new Vue({
el:'#app-01',
data:{
level:''
}
})
完整例子
<div id="app-01">
<anchored-heading :level="1">
hello world
</anchored-heading>
</div>
//创建一个查找并递归子文本的函数
var getChildrenTextContent=function (children){
return children.map(function(node){
return node.children?getChildrenTextContent(node.children):node.text
}).join('')
}
Vue.component('anchored-heading',{
render:function(createElement){
var headingId=getChildrenTextContent(this.$slots.default)
.toLowerCase()
//把非字符替换成'-'
.replace(/\W+/g,'-')
//把开头结尾的‘-’替换为空
.replace(/(^\-|\-$)/g,'')
return createElement(
'h'+this.level,
[
createElement('a',{
attrs:{
name:headingId,
href:'#'+headingId
}
},this.$slots.default)
]
)
},
props:{
level:{
type:Number,
required:true
}
}
})
var app01=new Vue({
el:'#app-01',
data:{
level:''
}
})
深入分析createElement()
- 三部分组成:
- 标签名,必须,此例中是'h'+this.level和'a'
- data object参数,不是必须的,{},即各种属性设定,class/style/attrs/props/domProps/on/nativeOn/directives/scopedSlots/slot/key/ref(class和style级别最高)
- 子节点或者内容,必须,如果是子节点,因为不止一个,所以需要加一个[]表示为数组形式,(但是每个子元素必须唯一)子节点就是嵌套的createElement(),如果是内容,直接就是字符串,例子是,this.$slots.default。
- render的形式
render:function(createElement){
一些预操作
return createElement(组成内容)
使用js来代替模板功能
- v-if&v-for变为原生的if/else&for(map)
<div id="app-01">
<component-vif :items="items"></component-vif>
</div>
Vue.component('component-vif',{
props:["items"],
//由于items使用了map和length,所以应该为一个数组对象,且包含name属性
render:function(createElement){
if(this.items.length){
return createElement('ul',this.items.map(function(item){
return createElement('li',item.name)
}))
} else{
return createElement('p','No items found.')
}
}
})
var app01=new Vue({
el:'#app-01',
data:{
level:'1',
items:[
{name:'aaa'},
{name:'bbb'}
]
}
})
- v-model要自己实现相应逻辑
<div id="app-01">
<component-vmodel :orivalue="value"></component-vmodel>
</div>
Vue.component('component-vmodel',{
render:function(createElement){
var self=this
return createElement('p',[
createElement('input',{
domProps:{
value:self.value,
},
on:{
input:function(event){
self.value=event.target.value
}
}
}),
createElement('span',self.value)
])
},
props:['orivalue'],
data:function(){
var value=this.orivalue
return {value}
}
})
var app01=new Vue({
el:'#app-01',
data:{
level:'1',
items:[
{name:'aaa'},
{name:'bbb'}
],
value:''
}
})
注意问题
由于存在对组件内value赋值的问题,第一次只有prop没有data的时候,后台友好提示,“Avoid mutating a prop directly since the value will be overwritten”,于是加一个data进行一次赋值,这里用了函数式data
原例子中组件只有一个input元素,然而怎么看出来绑定成功没有呢?我加了一个span来看值的对应修改,这里发现,属性上domProps下设置innerHTML和第三项上内容绑定,目测没什么区别嘛
事件&按键修饰符
** .capture/.once**——对应!/~,可组合
capture是捕获的意思,capture模式是捕获阶段触发回调,区别于默认的冒泡阶段,这个解释segmentfault上有个很好的例子-
其他修饰符没前缀,可以自己使用事件方法:
- .stop——event.stopPropagation()停止传播
- .prevent——event.preventDefault()阻止默认行为
- .self——if(event.target==event.currentTarget) return 限定触发事件的是事件本身,target是事件目标,currentTarget是当前对象(父级)
- .enter(13)——if(event.keyCode!==13)return
- .ctrl/.alt/.shift/.meta——if(event.ctrlKey) return
<div id="app-01">
<com-keymod >
<com-keymod></com-keymod>
</com-keymod>
</div>
Vue.component('com-keymod',{
render:function(createElement){
var vm=this
return createElement(
'div',
{
on:{
'!click':this.doThisInCapturingMode,
'~mouseover':this.doThisOnceInCapturingModeOver,
'~mouseleave':this.doThisOnceInCapturingModeLeave
}
},
[
createElement('input',{
on:{
keyup:function(event){
if(event.target!==event.currentTarget)
this.value=1
if(!event.shiftKey||event.keyCode!==13)
this.value=2
}
}
}),
vm.value,
this.$slots.default
]
)
},
data:function(){
return {value:0}
},
methods:{
doThisInCapturingMode:function(){
this.value=3
},
doThisOnceInCapturingModeOver:function(){
this.value+=1
},
doThisOnceInCapturingModeLeave:function(){
this.value-=1
}
}
})
var app01=new Vue({
el:'#app-01'
})
- slots
- 静态内容:this.$slots.default
template:'<div><slot name="foo"></slot></div>'
相当于:
render:function(createElement){
return createElement('div',this.$slots.foo)
}
- 作用域slot,子组件
template:'<div><slot :text="msg"></slot></div>'
相当于:
render:function(createElement){
return createElement('div',[
this.$scopedSlots.default({
text:this.msg
})])
}
- 作用域slot,父组件
template:'<child><template scope="props"><span>{{props.text}}</span></template></child>'
相当于:
render:function(createElement){
return createElement('child',{
scopedSlots:{
default:function(props){
return createElement('span',props.text)
}
}
})
}
学到此处,我默默回头复习了一下组件内slot部分
- JSX
为了把render内的语句写的更接近模板一点,可以用JSX语法,安装babel的插件实现
- 函数化组件
(存疑)例子有问题
- 模板编译:vue的模板实际是编译成了render函数
####自定义指令
- 第一个简单例子,同样需要补充完整:
<input id="app-01" v-focus>111
Vue.directive('focus',{
inserted:function(el){
el.focus()
}
})
var app01=new Vue({
el:'#app-01',
//写在实例作用域内
/*
directives:{
focus:{
inserted:function(el){
el.focus()
}
}
}
*/
})
类似于组件的写法,两种写法,一种是全局自定义指令,另一种是定义在实例作用域内部
- 钩子函数和函数参数
<div id="app-02" v-demo:hello.a.b="message"></div>
var app02=new Vue({
el:'#app-02',
data:{
message:"hello"
},
directives:{
demo:{
bind:function(el,binding,vnode){
var s=JSON.stringify
el.innerHTML=
'name:'+s(binding.name)+'
'+
'value:'+s(binding.value)+'
'+
'expression:'+s(binding.expression)+'
'+
'argument:'+s(binding.arg)+'
'+
'modifiers:'+s(binding.modifiers)+'
'+
'vnode keys:'+Object.keys(vnode).join(',')
}
}
}
})
钩子函数有:
- bind:只调用一次,指令第一次绑定到元素时调用
- inserted:被绑定元素插入父节点时调用(父节点存在即可调用)
- update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。
- componentUpdated:被绑定元素所在模板完成一次更新周期时调用
- unbind:只调用一次,指令与元素解绑时调用
钩子函数参数有:
- el:指令所绑定的元素
- binding:一个对象,包含以下属性:
- name:指令名(不含v-前缀)
- value:指令的绑定值(计算后的)
- expression:绑定值的字符串形式
- oldValue:指令绑定的前一个值
- arg:传给指令的参数
- modifiers:一个包含修饰符的对象
- vnode:Vue编译生成的虚拟节点
- oldValue:上一个虚拟节点
- 函数简写:
如果只想用bind和update钩子,可以省略钩子名称这一步,直接写:
//这是实例内directives内
swatch:function(el,binding,vnode){
el.style.backgroundColor=binding.value
}
- 对象字面量
之前的例子都是传入对象名称,比如:
<div id="app-02" v-swatch="color">111</div>
var app02=new Vue({
el:'#app-02',
data:{
message:"hello",
color:'#123456'
},
directives:{
swatch:function(el,binding,v){
el.style.backgroundColor=binding.value
}
}
})
也可以直接传入一个js对象字面量,比如:
<div id="app-02" v-swatch="{color:'#125678'}">111</div>
var app02=new Vue({
el:'#app-02',
data:{
message:"hello"
},
directives:{
swatch:function(el,binding,v){
//注意这里,因为传入的是一个对象,因此需要在value后面加上对象属性,才能索引到对应的值
el.style.backgroundColor=binding.value.color
}
}
})
####混合
- 第一个简单例子:
//定义一个混合对象
var myMixin = {
created:function(){
this.hello()
},
methods:{
hello:function(){
console.log('hello from mixin!')
}
}
}
var Component = Vue.extend({
mixins:[myMixin]
})
var component=new Component()
>这个例子中出现了不熟悉的语句,于是我决定去复习一下组件的创建和注册
- 首先,平常的组件创建,都是直接注册的,使用的以下形式
Vue.component("name","arg")
这其实是把两个步骤合为一个步骤
- 步骤一,创建组件构造器
//这是Vue构造器的扩展,创建了一个组件构造器
var a=Vue.extend(各种参数,比如template)
- 步骤二,注册组件
Vue.component("name","组件构造器(a)")
所以平时输入的arg其实就是一个组件构造器,在需要复用组件的组成的时候,就可以把arg拆出来复用一下
- 创建——注册——挂载,组件使用的三步骤:
- 最常见:创建和注册同时完成,挂载利用其它vue实例,在其内部使用
<div id="app-01"><mycom></mycom></div>
//创建+注册
Vue.component("mycom",{
我是组件构造器的内容
})
//利用其它vue实例实现挂载
var app01=new Vue({
el:'#app-01'
})
- 不注册也存在:不注册组件,只创建,通过实例化,后台可以看到console语句
mycom=Vue.extend({
我是组件构造器的内容
})
new mycom()
>回到开始的例子,首先定义了一个混合对象myMixinm,然后定义了一个组件构造器,命名为Component,然后用这个构造器创建了一个实例,由于没有挂载,也没注册组件,所以只能后台看到它确实存在
- 选项合并和优先性:
- 同名钩子函数将被合并为一个数组,混合对象的钩子优先调用
var myMixin = {
template:'<p>2222</p>',
created:function(){
this.hello()
},
methods:{
hello:function(){
console.log('hello from mixin!')
}
}
}
var customcom = Vue.extend({
created:function(){
console.log("hello from component")
},
mixins:[myMixin]
})
new customcom()
//hello from mixin!
//hello from component
- 值为对象的选项,将被合并为同一个对象,键名冲突时,取组件对象的键值对:
var myMixin = {
methods:{
hello:function(){
console.log('hello from mixin!')
},
foo:function(){
console.log('foo')
}
}
}
var customcom = Vue.extend({
methods:{
hello:function(){
console.log('hello from component!')
},
bar:function(){
console.log('bar')
}
},
mixins:[myMixin]
})
var vm=new customcom()
vm.foo()
vm.bar()
vm.hello()
//foo
//bar
//hello from component!
- 全局注册混合对象:会影响所有之后创建的Vue实例,慎用:
Vue.mixin({
template:'<p>222</p>',
created:function(){
var myOption=this.$options.myOption
if(myOption){
console.log(myOption)
}
}
})
var app01=new Vue({
el:'#app-01',
myOption:'hello!'
})
//页面:222
//后台:hello!
- 自定义选项的混合策略:默认是覆盖已有值
var myMixin = {
myOption:"hello from mixin!"
}
var customcom = Vue.extend({
myOption:"hello from vue!",
created:function(){
var myOption=this.$options.myOption
if(myOption){
console.log(myOption)
}
},
mixins:[myMixin]
})
new customcom()
//hello from vue
修改的方式没测试出来(存疑)
####插件
- vue-element
- vue-touch
- vuex
- vue-router
社区:[awesome-vue](https://github.com/vuejs/awesome-vue#libraries--plugins)
***
接下来的内容需要结合webpack和vue的插件,进阶部分到此结束啦
11111111111
1111111111111111111
111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111
1111111111111111
1111111111111111
111111111111111111
111111111111111111
111111111111111
11111111111111111
111111111111111111
11111111111111111
111111111111111111
11111111111111111
11111111111111111
11111111111