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: '<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——动画终点
屏幕截图.jpg

(这是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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,046评论 0 29
  • 下载安装搭建环境 可以选npm安装,或者简单下载一个开发版的vue.js文件 浏览器打开加载有vue的文档时,控制...
    冥冥2017阅读 6,033评论 0 42
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,204评论 0 6
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,058评论 1 10
  • 1.安装 可以简单地在页面引入Vue.js作为独立版本,Vue即被注册为全局变量,可以在页面使用了。 如果希望搭建...
    Awey阅读 11,002评论 4 129