Vue 源码解析之一:transition

1837 查看

最近公司的项目选型使用了vue,所以用vue开发了一个项目,期间在处理一些动画的时候,发现vue-transition虽然用起来简单,但是局限性很大,比如无法处理一个组件中父子元素的联动动画。这就极大得限制了我们的发挥,你只能进行单层次的动画,类似modal这种两种动画联动的就很难做,你很可能需要分成两个组件来做,这就不符合组件化的原则。

所以打算研读一下源码,然后研究一下如何解决这个问题。

既然是看transition,那么首先就得狙击transition指令,然后transition指令代码如此简单:

// (/src/directives/internal/transition.js)

export default {

  priority: TRANSITION,

  update (id, oldId) {
    var el = this.el
    // resolve on owner vm
    var hooks = resolveAsset(this.vm.$options, 'transitions', id)
    id = id || 'v'
    // apply on closest vm
    el.__v_trans = new Transition(el, id, hooks, this.el.__vue__ || this.vm)
    if (oldId) {
      removeClass(el, oldId + '-transition')
    }
    addClass(el, id + '-transition')
  }
}

在这里我们指令在el上加上了一个__v_trans属性,这个属性是一个Transition的实例,所以我们去看看这个实例干了什么

// (/src/transition/transition.js)

export default function Transition (el, id, hooks, vm) {
    ...
}

这就是一个保存这个过渡动画应该包含的一些属性和钩子,我们还是不知道什么时候通过什么方法调用了动画

这时候我回头去看了vue的文档,在transition的介绍里有这么一句:

transition 特性可以与下面资源一起用:

v-if
v-show
v-for (只为插入和删除触发)
动态组件 (介绍见组件)
在组件的根节点上,并且被 Vue 实例 DOM 方法(如 vm.$appendTo(el))触发。

这时候我就怀疑只有特定的场景才能触发transition,而触发的关键就在这些指令里面,于是我就去看v-if指令,然后在他插入节点的时候发现了这一句代码:

this.frag = this.factory.create(this._host, this._scope, this._frag)
this.frag.before(this.anchor)

这个factory是什么?,接着找

this.elseFactory = new FragmentFactory(this.vm, next)

他是一个FragmentFactory,在这个FragmentFactory里面我看到了这一个方法:

function singleBefore (target, withTransition) {
  this.inserted = true
  var method = withTransition !== false
    ? beforeWithTransition
    : before
  method(this.node, target, this.vm)
  if (inDoc(this.node)) {
    this.callHook(attach)
  }
}

貌似看到了一些曙光,我们终于看到跟transition有关的东西,这里我们继续找到beforeWithTransition这个方法,代码如下:

export function beforeWithTransition (el, target, vm, cb) {
  applyTransition(el, 1, function () {
    before(el, target)
  }, vm, cb)
}

这里调用了applyTransition,代码如下:

export function applyTransition (el, direction, op, vm, cb) {
  var transition = el.__v_trans
  if (
    !transition ||
    // skip if there are no js hooks and CSS transition is
    // not supported
    (!transition.hooks && !transitionEndEvent) ||
    // skip transitions for initial compile
    !vm._isCompiled ||
    // if the vm is being manipulated by a parent directive
    // during the parent's compilation phase, skip the
    // animation.
    (vm.$parent && !vm.$parent._isCompiled)
  ) {
    op()
    if (cb) cb()
    return
  }
  var action = direction > 0 ? 'enter' : 'leave'
  transition[action](op, cb)
}

在这里我们终于看到了el.__v_trans这个属性,那么这个回路基本就算走完了。

然后我去看了一下v-show指令,于是发现,还有更简单的方法。。。

apply (el, value) {
    if (inDoc(el)) {
      applyTransition(el, value ? 1 : -1, toggle, this.vm)
    } else {
      toggle()
    }
    function toggle () {
      el.style.display = value ? '' : 'none'
    }
}

直接applyTransition就可以调用了

现在我们知道为什么v-transition是只能单点的了,因为本质上他只能作用于特定指令所影响的特定元素,他的实现方式并不是类似于react的TransitionGroup这样通过给子组件增加生命周期在通过子组件在生命周期自定义动画的方式,vue的局限性表露无遗,让我觉得很奇怪作者为什么选择这种方式,难道真的只是为了降低使用难度?

问题提出来了,就得想办法了

我得想想。。。