资讯专栏INFORMATION COLUMN

Vue2 transition源码分析

Genng / 2007人阅读

摘要:至此算是找到了源码位置。至此进入过渡的部分完毕。在动画结束后,调用了由组件生命周期传入的方法,把这个元素的副本移出了文档流。这篇并没有去分析相关的内容,推荐一篇讲非常不错的文章,对构造函数如何来的感兴趣的同学可以看这里

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标签的状态如下

xxx

为vue的占位符,当元素通过v-if隐藏后,会在原来位置留下占位符。那就说明,当leave方法被触发时,原本的真实dom元素已经隐藏掉了(从vnode中被移除),而正在显示的元素,只是一个真实dom的副本。

leave方法关键代码其实和enter基本一致,只不过是将startClass换为了leaveClass等,还有处理一些动画生命周期的勾子函数。在动画结束后,调用了由组件生命周期remove传入的rm方法,把这个dom元素的副本移出了文档流。

如有错误,欢迎指正。

这篇并没有去分析Vue core相关的内容,推荐一篇讲Vue core非常不错的文章,对Vue构造函数如何来的感兴趣的同学可以看这里

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83949.html

相关文章

  • Vue2.0仿今日头条

    摘要:基于仿照今日头条的移动端项目源码地址预览地址前言先占个坑位。项目中还有许多可以完善的地方,不足之处希望小伙伴们可以,我会在这里更新。目前还没有全面地测试该项目,有问题提问,大家一起学习。 toutiao 基于Vue2.0仿照今日头条的移动端项目 源码地址:toutiao_Vue2.0 预览地址:toutiao_Vue2.0 前言 先占个坑位。 之前打算做个东西熟悉vue的使用,由于...

    pepperwang 评论0 收藏0
  • 前方来报,八月最新资讯--关于vue2&3的最佳文章推荐

    摘要:哪吒别人的看法都是狗屁,你是谁只有你自己说了才算,这是爹教我的道理。哪吒去他个鸟命我命由我,不由天是魔是仙,我自己决定哪吒白白搭上一条人命,你傻不傻敖丙不傻谁和你做朋友太乙真人人是否能够改变命运,我不晓得。我只晓得,不认命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出处 查看github最新的Vue...

    izhuhaodev 评论0 收藏0
  • 慕课网 饿了么 vue2.0 项目

    饿了么 vue 项目总结 项目效果预览 ele效果预览项目源码地址 ele源码跟着慕课网黄轶老师 敲饿了么 vue 项目 作者项目源代码地址 项目完成之后 npm run build 这本来是写在最后面一段的,我现在把他写在了最前面,方便我们事先知道,整个项目做完之后是什么样子的 项目完成之后在 根目录 下 npm run build (就是 npm run dev 的那个目录) 会在根目录...

    xuexiangjys 评论0 收藏0
  • Vue.js资源分享

    摘要:中文官网英文官网组织发出一个问题之后,不要暂时的离开电脑,如果没有把握先不要提问。珍惜每一次提问,感恩每一次反馈,每个人工作还是业余之外抽出的时间有限,充分准备好应有的资源之后再发问,有利于问题能够高效质量地得到解决。 Vue.js资源分享 更多资源请Star:https://github.com/maidishike... 文章转自:https://github.com/maid...

    vpants 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<