前端Web前端之路让前端飞

Vue.js进阶

2017-05-04  本文已影响270人  其心

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


深入响应式的原理

过渡

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

元素封装成过渡组件之后,在遇到插入或删除时,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也去掉,元素离开的时候才是立即完成的,但是进入的过渡还是存在。
<transition>
  <button v-if="isEditing" key="save">
    Save
  </button>
  <button v-else key="edit">
    Edit
  </button>
</transition>

多个组件过渡的例子

<!--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>
<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')

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

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

注意:除了 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就是在根据他的例子(特别是样式),增加了计数,增加了本地存储。

上一篇 下一篇

猜你喜欢

热点阅读