美文网首页VUE框架学习转载的~vue
Vue.js学习笔记-进阶部分+完整实现代码

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

作者: 冥冥2017 | 来源:发表于2017-05-13 16:48 被阅读510次

    深入响应式

    • 追踪变化:

    把普通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

    相关文章

      网友评论

        本文标题:Vue.js学习笔记-进阶部分+完整实现代码

        本文链接:https://www.haomeiwen.com/subject/bshnzttx.html