深入理解Vue transition源码分析


Posted in Javascript onJuly 30, 2017

这两天学习了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元素的副本移出了文档流。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery动态地获取系统时间实现代码
May 24 Javascript
js获取电脑分辨率的思路及操作
Nov 22 Javascript
js实现checkbox全选、不选与反选的方法
Feb 09 Javascript
JS返回iframe中frameBorder属性值的方法
Apr 01 Javascript
js实现滑动触屏事件监听的方法
May 05 Javascript
使用javascript提交form表单方法汇总
Jun 25 Javascript
jQuery实现商品活动倒计时
Oct 16 Javascript
微信小程序 实战小程序实例
Oct 08 Javascript
原生js实现addclass,removeclass,toggleclasss实例
Nov 24 Javascript
为Jquery EasyUI 组件加上清除功能的方法(详解)
Apr 13 jQuery
详解使用Node.js 将txt文件转为Excel文件
Jul 05 Javascript
详解angularjs popup-table 弹出框表格指令
Sep 20 Javascript
使用travis-ci如何持续部署node.js应用详解
Jul 30 #Javascript
Vue框架中正确引入JS库的方法介绍
Jul 30 #Javascript
webpack配置sass模块的加载的方法
Jul 30 #Javascript
Angular.js中$resource高大上的数据交互详解
Jul 30 #Javascript
Vue的Flux框架之Vuex状态管理器
Jul 30 #Javascript
webpack实现热加载自动刷新的方法
Jul 30 #Javascript
利用JavaScript如何查询某个值是否数组内
Jul 30 #Javascript
You might like
基于Windows下Apache PHP5.3.1安装教程
2010/01/08 PHP
两个开源的Php输出Excel文件类
2010/02/08 PHP
PHP中copy on write写时复制机制介绍
2014/05/13 PHP
ThinkPHP CURD方法之where方法详解
2014/06/18 PHP
php7 安装yar 生成docker镜像
2017/05/09 PHP
PHP发送邮件确认验证注册功能示例【修改别人邮件类】
2019/11/09 PHP
JavaScript中Array 对象相关的几个方法
2006/12/22 Javascript
jquery UI 1.72 之datepicker
2009/12/29 Javascript
jQuery下通过$.browser来判断浏览器.
2011/04/05 Javascript
jquery获得页面元素的坐标值实现思路及代码
2013/04/15 Javascript
用js一次改变多个input的readonly属性值的方法
2014/06/11 Javascript
利用JS提交表单的几种方法和验证(必看篇)
2016/09/17 Javascript
基于jQuery的select下拉框选择触发事件实例分析
2016/11/18 Javascript
JS实现图片高斯模糊切换效果的焦点图实例
2017/01/21 Javascript
Vue渲染函数详解
2017/09/15 Javascript
nodejs Assert中equal(),strictEqual(),deepEqual(),strictDeepEqual()比较
2017/09/18 NodeJs
学习JS中的DOM节点以及操作
2018/04/30 Javascript
layerUI下的绑定事件实例代码
2018/08/17 Javascript
使用微信小程序开发弹出框应用实例详解
2018/10/18 Javascript
webpack打包多页面的方法
2018/11/30 Javascript
React性能优化系列之减少props改变的实现方法
2019/01/17 Javascript
jQuery 隐藏/显示效果函数用法实例分析
2020/05/20 jQuery
[03:22]DAC最前线(第二期)—DOTA2亚洲邀请赛主赛场周边及线路探访
2015/01/24 DOTA
[59:35]DOTA2上海特级锦标赛主赛事日 - 3 败者组第三轮#1COL VS Alliance第二局
2016/03/04 DOTA
Python中装饰器兼容加括号和不加括号的写法详解
2017/07/05 Python
Python 实现平台类游戏添加跳跃功能
2020/03/27 Python
Levi’s美国官网:美国著名的牛仔裤品牌
2016/08/19 全球购物
桥梁与隧道工程专业本科生求职信
2013/10/08 职场文书
报考公务员诚信承诺书
2014/08/29 职场文书
党员检讨书
2014/10/13 职场文书
2014年房地产工作总结范文
2014/11/19 职场文书
开国大典观后感
2015/06/04 职场文书
教师实习自我鉴定总结
2019/08/20 职场文书
祝福语集锦:朋友新店开业祝福语
2019/12/10 职场文书
试了下Golang实现try catch的方法
2021/07/01 Golang
教你在 Java 中实现 Dijkstra 最短路算法的方法
2022/04/08 Java/Android