Vue.js进阶

作者: 其心 | 来源:发表于2017-05-04 19:55 被阅读270次

    Vue笔记系列
    1、Vue.js入门
    2、Vue.js渐进


    深入响应式的原理

    • 追踪变化
      把一个普通 Javascript 对象传给 Vue 实例的 data选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty(中文版) 把这些属性全部转为 getter/setter。
      Object.defineProperty 是仅 ES5 支持,且无法 shim(什么是shim?) 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。

    • 变化检测
      Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
      解决方法就是之前提到的,可以使用 Vue.set(object, key, value)方法将响应属性添加到嵌套的对象上。还有就是之前提到的要注意,对象不能是 Vue 实例,或者 Vue 实例的根数据对象

    • 声明响应式属性  解决变化检测问题的另一个方法
      由于 Vue 不允许动态添加根级响应式属性,干脆在一开始就就把该加的属性加上,所以你必须在初始化实例前声明根级响应式属性,哪怕只是一个空值

    • 异步更新队列
      参考Vue.js渐进中的API和实例方法中的关于nextTick的介绍。

    过渡

    Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:

    • 在 CSS 过渡和动画中自动应用 class

    • 可以配合使用第三方 CSS 动画库,如 Animate.css

    • 在过渡钩子函数中使用 JavaScript 直接操作 DOM

    • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

    • (1)单元素、组件的过渡
      Vue 提供了** transition 的封装组件,在下列情形中,可以给任何元素和组件添加 entering/leaving 过渡**
       条件渲染 (使用 v-if)
       条件展示 (使用 v-show)
       动态组件
       组件根节点

    元素封装成过渡组件之后,在遇到插入或删除时,Vue 将
    1、自动嗅探目标元素是否有 CSS 过渡或动画,并在合适时添加/删除 CSS 类名。
    2、如果过渡组件设置了过渡的 JavaScript 钩子函数,会在相应的阶段调用钩子函数。
    3、如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作(插入/删除)在下一帧中立即执行。

    ** 过渡的-css-类名**
    会有 4 个(CSS)类名在 enter/leave 的过渡中切换
    v-enter: 定义进入过渡的开始状态。在元素被插入时生效,在下一个帧移除。
    v-enter-active: 定义进入过渡的结束状态。在元素被插入时生效,在 transition/animation 完成之后移除。
    v-leave: 定义离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除。
    v-leave-active: 定义离开过渡的结束状态。在离开过渡被触发时生效,在 transition/animation 完成之后移除。
    图示:

    css过渡类名各阶段展示

    过渡的-css-类名 例子 ,注意里面星号部分

    <!--css-->
    .fade-enter-active, .fade-leave-active {
      transition: opacity .5s
    }
    .fade-enter, .fade-leave-active {
      opacity: 0
    }
    <!--html-->
    <div id="demo">
      <button v-on:click="show = !show">
        Toggle
      </button>
      <transition name="fade">
        <p v-if="show">hello</p>
      </transition>
    </div>
    <!--js-->
    new Vue({
      el: '#demo',
      data: {
        show: true
      }
    })
    
    如果<transition name="my-transition">中 name 没有设置,对于这些在 enter/leave 过渡中切换的类名,v- 是这些类名的默认前缀。
    <!--css-->
    .v-enter-active, .v-leave-active {
      transition: opacity .5s
    }
    .v-enter, .v-leave-active {
      opacity: 0
    }
    <!--html-->
    ...
    <transition>
        <p v-if="show">hello</p>
      </transition>
    

    CSS 动画
    CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。


    对于 Vue 的过渡系统和其他第三方 CSS 动画库,如过想跟 Animate.css 结合使用的话,Vue也准备了自定义过渡类名来控制,他们的优先级高于普通的类名。
    enter-class
    enter-active-class
    leave-class
    leave-active-class

    javaScript钩子
    可以在属性中声明 JavaScript 钩子

    <transition
      v-on:before-enter="beforeEnter"
      v-on:enter="enter"
      v-on:after-enter="afterEnter"
      v-on:enter-cancelled="enterCancelled"
      v-on:before-leave="beforeLeave"
      v-on:leave="leave"
      v-on:after-leave="afterLeave"
      v-on:leave-cancelled="leaveCancelled"
    >
      <!-- ... -->
    </transition>
    
    // ...
    methods: {
      // --------
      // 进入中
      // --------
      beforeEnter: function (el) {
        // ...
      },
      // 此回调函数是可选项的设置
      // 与 CSS 结合时使用
      enter: function (el, done) {
        // ...
        done() //当只用 JavaScript 过渡的时候, 在 enter 和 leave 中,回调函数 done 是必须的 。 否则,它们会被同步调用,过渡会立即完成。
      },
      afterEnter: function (el) {
        // ...
      },
      enterCancelled: function (el) {
        // ...
      },
      // --------
      // 离开时
      // --------
      beforeLeave: function (el) {
        // ...
      },
      // 此回调函数是可选项的设置
      // 与 CSS 结合时使用
      leave: function (el, done) {
        // ...
        done() //当只用 JavaScript 过渡的时候, 在 enter 和 leave 中,回调函数 done 是必须的 。 否则,它们会被同步调用,过渡会立即完成。
      },
      afterLeave: function (el) {
        // ...
      },
      // leaveCancelled 只用于 v-show 中
      leaveCancelled: function (el) {
        // ...
      }
    }
    

    JavaScript 钩子的例子,注意里面星号部分

    <!-- html -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
    <div id="example-4">
      <button @click="show = !show">
        Toggle
      </button>
      <transition
        v-on:before-enter="beforeEnter"
        v-on:enter="enter"
        v-on:leave="leave"
        v-bind:css="false" ******推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
      >
        <p v-if="show">
          Demo
        </p>
      </transition>
    </div>
    
    //javascript
    new Vue({
      el: '#example-4',
      data: {
        show: false
      },
      methods: {
        beforeEnter: function (el) {
          el.style.opacity = 0
          el.style.transformOrigin = 'left'
        },
        enter: function (el, done) {
          Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
          Velocity(el, { fontSize: '1em' }, { complete: done })
        },
        leave: function (el, done) {
          Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
          Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
          Velocity(el, {
            rotateZ: '45deg',
            translateY: '30px',
            translateX: '30px',
            opacity: 0
          }, { complete: done })
        }
      }
    })
    ******下面说一下,当只用 JavaScript 过渡的时候, 在 enter 和 leave 中,回调函数 done 是必须的 。 
    否则,它们会被同步调用,过渡会立即完成。
     enter: function (el) {
          Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
          Velocity(el, { fontSize: '1em' })
        },
        leave: function (el) {
          Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
          Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
          Velocity(el, {
            rotateZ: '45deg',
            translateY: '30px',
            translateX: '30px',
            opacity: 0
          })
    ******可以看到done不只是在Velocity的options中去掉了,而且参数中的done也去掉了。这是因为我尝试只去掉options中的done不管用,还是正常动画,只有当参数中的done也去掉,元素离开的时候才是立即完成的,但是进入的过渡还是存在。
    
    • (2)多个元素的过渡
      对于原生标签可以使用 v-if/v-else 。注意: 当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。
      例如:
    <transition>
      <button v-if="isEditing" key="save">
        Save
      </button>
      <button v-else key="edit">
        Edit
      </button>
    </transition>
    
    • (3)过渡模式
      默认是进入和离开的过渡同时生效。
      in-out: 新元素先进行过渡,完成之后当前元素过渡离开。
      out-in: 当前元素先进行过渡,完成之后新元素过渡进入。

    • (4)多个组件的过渡
      需要使用动态组件

    多个组件过渡的例子

    <!--CSS-->
    <style type="text/css">
            #app div{
                position: absolute;
                top: 40px;
                left: 0px;
                margin-left: 20px;
            }
            #app .component-fade-enter,#app .component-fade-leave-active {
                opacity: 0;
            }
            #app .component-fade-enter {
                left: -30px;
            }
            #app .component-fade-enter-active {
                transition: all .5s;
            }
            #app .component-fade-leave-active {
                left: 30px;
                transition: all .5s;
            }
        </style>
    <!--html-->
        <div id="app">
            <button type="button" @click="change">改变</button>
            <transition name="component-fade" mode="out-in">
              <component v-bind:is="view"></component>
            </transition>
        </div>
    <!--javascript-->
        <script>
            new Vue({
                el: '#app',
                data: {
                    view: 'v-a'
                },
                  components: {
                    'v-a': {
                      template: '<div>Component A</div>'
                    },
                    'v-b': {
                      template: '<div>Component B</div>'
                    }
                  },
                  methods : {
                    change : function (){
                        this.view == 'v-a'?this.view = 'v-b':this.view = 'v-a'
                    }
                  }
            })
        </script>
    
    • (4)列表过渡
      使用 <transition-group> 组件渲染列表。除了 mode,其他特性和 <transition> 相同。
      <transition-group><transition>的不同点:
      1、不同于 <transition>, `<transition-group>会以一个真实元素呈现:默认为一个 <span>。你也可以通过 tag 特性更换为其他元素。
      2、内部元素 总是需要 提供唯一的 key 属性值
    <div id="list-demo" class="demo">
      <button v-on:click="add">Add</button>
      <button v-on:click="remove">Remove</button>
      <transition-group name="list" tag="p"> <!--元素是p,下面的key是数组的值-->
        <span v-for="item in items" v-bind:key="item" class="list-item">
          {{ item }}
        </span>
      </transition-group>
    </div>
    

    列表的位移过渡
    <transition-group> 支持通过 CSS transform 过渡移动。当一个子节点被更新,从屏幕上的位置发生变化,它将会获取应用 CSS 移动类(通过 name 属性或配置 move-class 属性自动生成)。

    <div id="flip-list-demo" class="demo">
      <button v-on:click="shuffle">Shuffle</button>
      <transition-group name="flip-list" tag="ul">
        <li v-for="item in items" v-bind:key="item">
          {{ item }}
        </li>
      </transition-group>
    </div>
    <!--没有move-class,像之前的类名一样,可以通过 name 属性来自定义前缀-->
    .flip-list-move {
      transition: transform 1s;
    }
     
    <transition-group name="flip-list" tag="ul" move-class="start">
        ···
      </transition-group>
    <!--通过 move-class 属性手动设置-->
    .start {
      transition: transform 1s;
    }
    也可以这么写:
    <transition-group name="flip-list" tag="ul">
        <li v-for="item in items" v-bind:key="item" class="start">
          {{ item }}
        </li>
      </transition-group>
    <!--通过子元素的class 属性手动设置-->
    .start {
      transition: transform 1s;
    }
    

    Vue 使用了一个叫 FLIP 简单的动画队列使用 transforms 将元素从之前的位置平滑过渡新的位置。
    需要注意的是使用 FLIP 过渡的元素不能设置为 display: inline 。作为替代方案,可以设置为 display: inline-block 或者放置于 flex 中

    过渡状态

    Vue 的过渡系统提供了非常多简单的方法设置进入、离开和列表的动效(上面提到的那些)。对于数据元素本身的动效,比如:
    数字和运算
    颜色的显示
    SVG 节点的位置
    元素的大小和其他的属性

    所有的原始数字都被事先存储起来,可以直接转换到数字。可以结合 Vue 的响应式和组件系统,使用第三方库来实现切换元素的过渡状态。官方文档中使用的是Tween.js,tween.js是一款可生成平滑动画效果的js动画库,关于它的中文介绍可看一下这篇文章tween.js可生成平滑动画效果的js动画库

    这里面涉及到watch
    watch一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。

    var vm = new Vue({
      data: {
        a: 1,
        b: 2,
        c: 3
      },
      watch: {
        a: function (val, oldVal) {
          console.log('new: %s, old: %s', val, oldVal)
        },
        // 方法名
        b: 'someMethod',
        // 深度 watcher
        c: {
          handler: function (val, oldVal) { /* ... */ },
          deep: true
        }
      }
    })
    vm.a = 2 // -> new: 2, old: 1
    

    顺便再说一下实例方法vm.$watch( expOrFn, callback, [options] )
    观察 Vue 实例变化的一个表达式或计算属性函数。回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。
    注意:在变异(不是替换)对象或数组时,旧值将与新值相同,因为它们的引用指向同一个对象/数组。Vue 不会保留变异之前值的副本。(试了一下,没有进回调函数)
    例子:

    // 监督的键路径
    vm.$watch('a.b.c', function (newVal, oldVal) {
      // 做点什么
    })
    // 更复杂的表达式==>函数
    vm.$watch(
      function () {
        return this.a + this.b
      },
      function (newVal, oldVal) {
        // 做点什么
      }
    )
    

    vm.$watch 返回一个取消观察函数,用来停止触发回调:

    var unwatch = vm.$watch('a', cb)
    // 之后取消观察
    unwatch()
    

    选项:deep
    为了发现对象内部值的变化,可以在选项参数中指定 deep: true 。注意监听数组的变动不需要这么做。

    vm.$watch('someObject', callback, {
      deep: true
    })
    vm.someObject.nestedValue = 123
    // callback is fired???
    

    选项:immediate
    在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调:

    vm.$watch('a', callback, {
      immediate: true
    })
    // 立即以 `a` 的当前值触发回调
    
    Render函数

    Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML。在某些情况需要用到比template 更接近编译器的render函数

    render 函数接收一个 createElement 方法作为第一个参数用来创建 VNode。
    如果组件是一个函数组件,Render 函数还会接收一个额外的 context 参数,为没有实例的函数组件提供上下文信息。

    createElement接受的参数:

    // @returns {VNode}
    createElement(
      // {String | Object | Function}
      // 一个 HTML 标签字符串,组件选项对象,或者一个返回值类型为String/Object的函数,必要参数
      'div',
      // {Object}
      // 一个包含模板相关属性的数据对象
      // 这样,您可以在 template 中使用这些属性.可选参数.
      {
      },
      // {String | Array}
      // 子节点(VNodes),可以是一个字符串或者一个数组. 可选参数.
      [
        createElement('h1', 'hello world'),
        createElement(MyComponent, {
          props: {
            someProp: 'foo'
          }
        }),
        'bar'
      ]
    )
    

    createElement的第二个属性data Object参数详解

    {
      // 和`v-bind:class`一样的 API
      'class': {
        foo: true,
        bar: false
      },
      // 和`v-bind:style`一样的 API
      style: {
        color: 'red',
        fontSize: '14px'
      },
      // 正常的 HTML 特性
      attrs: {
        id: 'foo'
      },
      // 组件 props
      props: {
        myProp: 'bar'
      },
      // DOM 属性
      domProps: {
        innerHTML: 'baz'
      },
      // 事件监听器基于 "on"
      // 所以不再支持如 v-on:keyup.enter 修饰器
      // 需要手动匹配 keyCode。
      on: {
        click: this.clickHandler
      },
      // 仅对于组件,用于监听原生事件,而不是组件内部使用 vm.$emit 触发的事件。
      nativeOn: {
        click: this.nativeClickHandler
      },
      // 自定义指令. 注意事项:不能对绑定的旧值设值
      // Vue 会为您持续追踪
      directives: [
        {
          name: 'my-custom-directive',
          value: '2'
          expression: '1 + 1',
          arg: 'foo',
          modifiers: {
            bar: true
          }
        }
      ],
      // Scoped slots in the form of
      // { name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: props => h('span', props.text)
      },
      // 如果组件是其他组件的子组件,需为slot指定名称
      slot: 'name-of-slot'
      // 其他特殊顶层属性
      key: 'myKey',
      ref: 'myRef'
    }
    

    函数化组件
    标记组件为 functional, 组件无状态(没有 data),无实例(没有 this 上下文)。

    Vue.component('my-component', {
      functional: true,
      // 为了弥补缺少的实例
      // 提供第二个参数作为上下文
      render: function (createElement, context) {
        // ...
      },
      // Props 可选
      props: {
        // ...
      }
    })
    

    组件需要的一切都是通过上下文传递,包括:
    props: 提供props 的对象
    children: VNode 子节点的数组
    slots: slots 对象
    data: 传递给组件的 data 对象
    parent: 对父组件的引用

    注意:slots() 和 children 对比

    <my-functional-component>
      <p slot="foo">
        first
      </p>
      <p>second</p>
    </my-functional-component>
    

    对于这个组件,children 会给你两个段落标签,而 slots().default 只会传递第二个匿名段落标签,slots().foo 会传递第一个具名段落标签。同时拥有 children 和 slots() ,因此你可以选择让组件通过 slot() 系统分发或者简单的通过 children 接收,让其他组件去处理。

    自定义指令

    Vue.directive( id, [definition] )
    注册或获取全局指令。

    // 注册
    Vue.directive('my-directive', {
      bind: function () {},
      inserted: function () {},
      update: function () {},
      componentUpdated: function () {},
      unbind: function () {}
    })
    // 注册(传入一个简单的指令函数)
    Vue.directive('my-directive', function () {
      // 这里将会被 `bind` 和 `update` 调用
    })
    // getter,返回已注册的指令
    var myDirective = Vue.directive('my-directive')
    

    钩子函数
    指令定义函数提供了几个钩子函数(可选):

    • **bind: **只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
    • **inserted: **被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
    • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。
    • **componentUpdated: **被绑定元素所在模板完成一次更新周期时调用。
    • unbind: 只调用一次, 指令与元素解绑时调用。

    钩子函数参数
    钩子函数被赋予了以下参数:

    • el: 指令所绑定的元素,可以用来直接操作 DOM 。
    • binding: 一个对象,包含以下属性:
      • name: 指令名,不包括 v-前缀。
      • value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2。
      • oldValue: 指令绑定的前一个值,仅在 update和 componentUpdated钩子中可用。无论值是否改变都可用。
      • expression: 绑定值的字符串形式。 例如 v-my-directive="1 + 1", expression 的值是 "1 + 1"。
      • arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"。
      • modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
    • vnode: Vue 编译生成的虚拟节点,查阅 VNode API 了解更多详情。
    • oldVnode: 上一个虚拟节点,仅在 update和 componentUpdated钩子中可用。

    注意:除了 el之外,其它参数都应该是只读的,尽量不要修改他们。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

    例子:

    html
    <div id="hook-arguments-example" v-demo:hello.a.b="message"></div>
    
    javascript
    Vue.directive('demo', {
      bind: function (el, binding, vnode) {
        var s = JSON.stringify
        el.innerHTML =
          'name: '       + s(binding.name) + '<br>' +
          'value: '      + s(binding.value) + '<br>' +
          'expression: ' + s(binding.expression) + '<br>' +
          'argument: '   + s(binding.arg) + '<br>' +
          'modifiers: '  + s(binding.modifiers) + '<br>' +
          'vnode keys: ' + Object.keys(vnode).join(', ')
      }
    })
    new Vue({
      el: '#hook-arguments-example',
      data: {
        message: 'hello!'
      }
    })
    


    用Vue实现一个demo(包含增加,删除,选中)

    这个demo放到了github上,有时间会把它整理到我的github主页的一个小栏目里,那样就是方便看了。

    从github上用各种方法下载到本地,找到index.html文件,双击就能看到效果

    推荐一下微信公众号:《web前端教程》的一篇vue实战案例,大家可以关注一下这个公众号,他的教程都是挺新的,而且很基础很基础,我的这个demo就是在根据他的例子(特别是样式),增加了计数,增加了本地存储。

    相关文章

      网友评论

        本文标题:Vue.js进阶

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