Vue2 transition源码剖析

Vue transition源码剖析

底本盘算本身造一个transition的轮子,所以决议先看看源码,理清思绪。Vue的transition组件供应了一系列钩子函数,而且具有优越可扩展性。

相识构建历程

既然要看源码,就先让Vue在开辟环境跑起来,首先从GitHub clone下来全部项目,在文件./github/CONTRIBUTING.md中看到了以下备注,须要强调一下的是,npm run dev构建的是runtime + compiler版本的Vue。

# watch and auto re-build dist/vue.js
$ npm run dev

紧接着在package.json中找到dev对应的shell语句,就是下面这句

"scripts": {
    "dev": "rollup -w -c build/config.js --environment TARGET:web-full-dev",
    ...
}

Vue2运用rollup打包,-c 背面跟的是打包的设置文件(build/config.js),实行的同时传入了一个TARGET参数,web-full-dev。翻开设置文件继承往里找。

...
const builds = {
  ...
  'web-full-dev': {
      entry: resolve('web/entry-runtime-with-compiler.js'),
      dest: resolve('dist/vue.js'),
      format: 'umd',
      env: 'development',
      alias: { he: './entity-decoder' },
      banner
  },
  ...
}

从上面的构建设置中,找到构建进口为web/entry-runtime-with-compiler.js,它也就是umd版本vue的进口了。
我们发如今Vue的根目次下并没有web这个文件夹,实际上是由于Vue给path.resolve这个要领加了个alias, alias的设置在/build/alias.js中

module.exports = {
  vue: path.resolve(__dirname, '../src/platforms/web/entry-runtime-with-compiler'),
  compiler: path.resolve(__dirname, '../src/compiler'),
  core: path.resolve(__dirname, '../src/core'),
  shared: path.resolve(__dirname, '../src/shared'),
  web: path.resolve(__dirname, '../src/platforms/web'),
  weex: path.resolve(__dirname, '../src/platforms/weex'),
  server: path.resolve(__dirname, '../src/server'),
  entries: path.resolve(__dirname, '../src/entries'),
  sfc: path.resolve(__dirname, '../src/sfc')
}

web对应的目次为’../src/platforms/web’,也就是src/platforms/web,顺着这个文件继承往下找。检察src/platforms/web/entry-runtime-with-compiler.js的代码,这里主如果处置惩罚将Vue实例挂载到实在dom时的一些异常操纵提醒,
,比方不要把vue实例挂载在body或html标签上等。然则关于要找的transition,这些都不主要,主要的是

import Vue from './runtime/index'

Vue对象是从当前目次的runtime文件夹引入的。翻开./runtime/index.js,先检察引入了哪些模块, 发明Vue是从src/core/index引入的,并看到platformDirectives和platformComponents,官方的指令和组件八九不离十就在这了。

import Vue from 'core/index'
...
...
import platformDirectives from './directives/index'
import platformComponents from './components/index'

...
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

在platformComponents中发明transtion.js,它export了一个对象,这个对象有name,props和rander要领,一个规范的Vue组件。至此算是找到了源码位置。

export default {
  name: 'transition',
  props: transitionProps,
  abstract: true,

  render (h: Function) {
    ...
  }
}

transition完成剖析

从上一节的代码中,能够看到directives和components是保存在Vue.options内里的, 还须要注意一下背面的Vue.prototype.__patch__,由于transtion并不单单是以一个组件来完成的,还须要在Vue组织函数上打一些patch。

rander当中的参数h要领,就是Vue用来建立假造DOM的createElement要领,但在此组件中,并没有发明处置惩罚过分动画相干的逻辑,主如果集合处置惩罚props和假造DOM参数。由于transtion并不单单是以一个组件来完成的,它须要操纵实在dom(未插进去文档流)和假造dom,所以只能在Vue的组织函数上打一些patch了。

往回看了下代码,之前有一句Vue.prototype.__patch__ = inBrowser ? patch : noop,在patch相干的代码中找到了transition相干的完成。modules/transtion.js

这就是过渡动画结果相干的patch的源码位置。

export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
  ...
}
export function leave (vnode: VNodeWithData, rm: Function) {
  ...
}

export default inBrowser ? {
  create: _enter,
  activate: _enter,
  remove (vnode: VNode, rm: Function) {
    /* istanbul ignore else */
    if (vnode.data.show !== true) {
      leave(vnode, rm)
    } else {
      rm()
    }
  }
} : {}

这个模块默许export的对象包含了三个性命周期函数create,activate,remove,这应该是Vue没有对外暴露的性命周期函数,create和activate直接运转的就是上面的enter要领,而remove实行了leave要领。

继承看最主要的是两个要领,enter和leave。经由过程在这两个要领上打断点得知,实行这两个要领的之前,vnode已建立了实在dom, 并挂载到了vnode.elm上。个中这段代码比较症结

// el就是实在dom节点
beforeEnterHook && beforeEnterHook(el)
if (expectsCSS) {
  addTransitionClass(el, startClass)
  addTransitionClass(el, activeClass)
  nextFrame(() => {
    addTransitionClass(el, toClass)
    removeTransitionClass(el, startClass)
    if (!cb.cancelled && !userWantsControl) {
      if (isValidDuration(explicitEnterDuration)) {
        setTimeout(cb, explicitEnterDuration)
      } else {
        whenTransitionEnds(el, type, cb)
      }
    }
  })
}

首先给el添加了startClass和activeClass, 此时dom节点还未插进去到文档流,推想应该是在create或activate勾子实行完今后,该节点被插进去文档流的。nextFrame要领的完成以下, 如requestAnimationFrame不存在,用setTimeout替代

const raf = inBrowser && window.requestAnimationFrame
  ? window.requestAnimationFrame.bind(window)
  : setTimeout

export function nextFrame (fn: Function) {
  raf(() => {
    raf(fn)
  })
}

这类体式格局的nextFrame完成,正如官方文档中所说的鄙人一帧添加了toClass,并remove掉startClass,末了在过渡结果终了今后,remove掉了一切的过渡相干class。至此‘进入过渡’的部份终了。

再来看‘脱离过渡’的要领leave,在leave要领中打断点,发明html标签的状况以下

<p>xxx</p>
<!---->

<!—-> 为vue的占位符,当元素经由过程v-if隐蔽后,会在本来位置留下占位符。那就申明,当leave要领被触发时,底本的实在dom元素已隐蔽掉了(从vnode中被移除),而正在显现的元素,只是一个实在dom的副本。

leave要领症结代码实在和enter基础一致,只不过是将startClass换为了leaveClass等,另有处置惩罚一些动画性命周期的勾子函数。在动画终了后,调用了由组件性命周期remove传入的rm要领,把这个dom元素的副本移出了文档流。

若有毛病,迎接斧正。

这篇并没有去剖析Vue core相干的内容,引荐一篇讲Vue core异常不错的文章,对Vue组织函数如何来的感兴趣的同砚能够看这里

    原文作者:GeoffZhu
    原文地址: https://segmentfault.com/a/1190000010121812
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞