Vue.js学习笔记-进阶部分+完整实现代码

深入响应式

  • 追踪变化:

把普通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: '
{{ message }}
' }) // 之后设置 `message` vm.message = 'Hello!'
  • Vue是异步更新队列的,目的是缓冲同一个事件循环中所有数据变化去除重复数据,但是问题来了,当设置数据变化时,并不会立即重新渲染,需要排队,可是如果想要在这个变化后紧接着做点什么,就需要一个Vue.nextTick(callback)来表明,这个更新后再执行操作:
{{message}}
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: '{{ message }}', 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.js学习笔记-进阶部分+完整实现代码_第1张图片
屏幕截图.jpg

(这是Vue官网的图)

css过渡(简单的transition)

hello

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部分

look at me

//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
//用法

    

look at me

  • 使用js钩子,enter和leave各自有4个钩子:
  • beforeEnter——一些预设
  • enter——进入动画,部分情况需要有回调函数
  • afterEnter
  • enterCancelled
  • beforeLeave
  • leave——离开动画,部分情况需要有回调函数
  • afterLeave
  • leaveCancelled

注:只用js过渡时,enter和leave中的回调函数是必须的,不然动画会立即完成。对于仅适用js过渡的元素,最好添加v-bind:css="false"避免过渡中css的影响

//引入一个velocity库,便于操作dom属性

demo

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;
}

demo

var app03=new Vue({ el:'#app-03', data:{ show:true } })
多个元素的过渡
  • 用v-if/v-else来控制过渡

  

Sorry, no items found.

但是需要注意,当两个过渡元素标签名相同时,需要设置key值来区分,否则Vue会自动优化为,只替换内容,那么就看不到过渡效果了

先来个过渡的例子:

//html部分,设定了两个button,并用toggle来切换值,为了避免Vue的只替换内容,设了两个不同的key值

//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(这个好棒):

  

  • 把v-if升级为switch,实现不止2个标签的绑定:

  

computed: {
  buttonMessage: function () {
    switch (docState) {
      case 'saved': return 'Edit'
      case 'edited': return 'Save'
      case 'editing': return 'Cancel'
    }
  }
}
  • vue还提供了过渡模式,两种,in-out和out-in,用法:

  

算是过渡控制增强

多组件过渡 :动态组件component绑定
//css部分
.component-fade-enter-active,.component-fade-leave-active{
  transition:opacity .5s ease;
 }
 .component-fade-enter,.component-fade-leave-active{
  opacity: 0;
 }
//html部分

//设置了out-in后组件可以不用考虑绝对定位的问题了
//js部分 var app05=new Vue({ el:'#app-05', data:{ view:'v-a' }, components:{ 'v-a':{ template:'
Component A
' }, 'v-b':{ template:'
Component B
' } } })
列表过渡

使用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部分
{{item}}
//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部分
  • {{item}}
  • //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部分
    
    {{item}}
    //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部分
    
    {{cell.number}}
    //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设置不同的位移序列,从而形成渐进
    //:css禁止css的影响 //监听事件:before-enter/enter/leave
  • {{item.msg}}
  • 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中自定义属性:
    • js中引用属性:
      var div =document.getElementById(''example")
      console.log(div.dataset.pro)
      //我是pro
      注意两者的小差异,html中是data-name,js中引用时需要写为dataset.name
    可复用的过渡

    这里提到了函数式组件,需要看完后面的render函数来结合使用

    动态过渡

    过渡的数据可以动态控制,用js来获取

    //input type="range"是滑动条,把值绑定到fadeInDuation Fade In Fade Out //这里有一个对show值的判断,这是控制淡入淡出循环的关键

    hello

    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
    
    

    {{animatedNumber}}

    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
    
    //对色块样式简单定义
    
    //html部分
    
    //v-on:keyup.enter="updateColor"是绑定一个键盘按键,.enter是13键的别名

    Preview:

    //把样式绑定到tweenedCSSColor

    {{tweenedCSSColor}}

    //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部分
    
    //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}

    
        
        
    
    

    2、引用了一个新库,叫TweenLite.js,还有一个系列的动画库,简单好用
    3、data返回了一个函数,是为了不同实例不共享数据
    4、sides监控处出现一个想当然的问题,见代码解释
    5、动画的循环是通过resetInterval中的定时器实现的,动画的渐变是监控了stats的变化。

    render函数

    第一个例子
    Hello world!
    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:'' } })
    完整例子
    hello world
    //创建一个查找并递归子文本的函数 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)
    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要自己实现相应逻辑
    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






    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:'

    '
    相当于:
    render:function(createElement){
    return createElement('div',this.$slots.foo)
    }

      - 作用域slot,子组件
    

    template:'

    '
    相当于:
    render:function(createElement){
    return createElement('div',[
    this.$scopedSlots.default({
    text:this.msg
    })])
    }

      - 作用域slot,父组件
    

    template:''
    相当于:
    render:function(createElement){
    return createElement('child',{
    scopedSlots:{
    default:function(props){
    return createElement('span',props.text)
    }
    }
    })
    }

    
    学到此处,我默默回头复习了一下组件内slot部分
    - JSX
    为了把render内的语句写的更接近模板一点,可以用JSX语法,安装babel的插件实现
    - 函数化组件
    (存疑)例子有问题
    - 模板编译:vue的模板实际是编译成了render函数
    
    ####自定义指令
    - 第一个简单例子,同样需要补充完整:
    

    111
    Vue.directive('focus',{
    inserted:function(el){
    el.focus()
    }
    })
    var app01=new Vue({
    el:'#app-01',
    //写在实例作用域内
    /*
    directives:{
    focus:{
    inserted:function(el){
    el.focus()
    }
    }
    }
    */
    })

    类似于组件的写法,两种写法,一种是全局自定义指令,另一种是定义在实例作用域内部
    - 钩子函数和函数参数
    


    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
    }

    - 对象字面量
    之前的例子都是传入对象名称,比如:
    

    111

    var app02=new Vue({
    el:'#app-02',
    data:{
    message:"hello",
    color:'#123456'
    },
    directives:{
    swatch:function(el,binding,v){
    el.style.backgroundColor=binding.value
    }
    }
    })

    也可以直接传入一个js对象字面量,比如:
    

    111

    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实例,在其内部使用
    


    //创建+注册
    Vue.component("mycom",{
    我是组件构造器的内容
    })
    //利用其它vue实例实现挂载
    var app01=new Vue({
    el:'#app-01'
    })

      - 不注册也存在:不注册组件,只创建,通过实例化,后台可以看到console语句
    

    mycom=Vue.extend({
    我是组件构造器的内容
    })
    new mycom()

    
     >回到开始的例子,首先定义了一个混合对象myMixinm,然后定义了一个组件构造器,命名为Component,然后用这个构造器创建了一个实例,由于没有挂载,也没注册组件,所以只能后台看到它确实存在
    - 选项合并和优先性:
     - 同名钩子函数将被合并为一个数组,混合对象的钩子优先调用
    

    var myMixin = {
    template:'

    2222

    ',
    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:'

    222

    ',
    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

    你可能感兴趣的:(Vue.js学习笔记-进阶部分+完整实现代码)