You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
if(初始化){// for each component option object to be hot-reloaded,// you need to create a record for it with a unique id.// do this once on startup.api.createRecord('very-unique-id',myComponentOptions)}
// if a component has only its template or render function changed,// you can force a re-render for all its active instances without// destroying/re-creating them. This keeps all current app state intact.api.rerender('very-unique-id',myComponentOptions)// --- OR ---// if a component has non-template/render options changed,// it needs to be fully reloaded. This will destroy and re-create all its// active instances (and their children).api.reload('very-unique-id',myComponentOptions)
exports.install=function(vue,browserify){// 如果安装过了就不再重复安装if(installed){return}installed=true// 兼容es modules模块Vue=vue.__esModule ? vue.default : vue// 把vue的版本如2.6.3分隔成[2, 6, 3] 这样的数组version=Vue.version.split('.').map(Number)isBrowserify=browserify// compat with < 2.0.0-alpha.7// 兼容2.0.0-alpha.7以下版本if(Vue.config._lifecycleHooks.indexOf('init')>-1){initHookName='init'}// 只有Vue在2.0以上的版本才支持这个库。exports.compatible=version[0]>=2if(!exports.compatible){console.warn('[HMR] You are using a version of vue-hot-reload-api that is '+'only compatible with Vue.js core ^2.0.0.')return}}
/** * Create a record for a hot module, which keeps track of its constructor * and instances * * @param {String} id * @param {Object} options */exports.createRecord=function(id,options){// 如果已经存储过了就returnif(map[id]){return}// 关键流程 下一步解析makeOptionsHot(id,options)// 将记录存储在map中// instances变量应该不难猜出是vue的实例对象。map[id]={options: options,instances: []}}
大家都用过Vue-CLI创建vue应用,在开发的时候我们修改了vue文件,保存了文件,浏览器上就自动更新出我们写的组件内容,非常的顺滑流畅,大大提高了开发效率。想知道这背后是怎么实现的吗,其实代码并不复杂。
这个功能的实现底层用了vue-hot-load-api这个库,得益于vue的良好设计,热更新的实现总共就一个js文件,200行代码,绰绰有余。
而在这个库里涉及到的技巧又非常适合我们去深入了解vue内部的一些机制,所以赶快来和我一起学习吧。
提要
本文单纯的从
vue-hot-load-api
这个库出发,在浏览器的环境运行Vue的热更新示例,主要测试的组件是普通的vue组件而不是functional等特殊组件,以最简单的流程搞懂热更新的原理。在源码解析中贴出的代码会省略掉一些不太相关的流程,更便于理解。
示例
学习一个库当然还是先从示例看起,github页面上的示例结合了webpack的一些机制,有点偏离本文的重点,所以我简化了一个例子,先给大家饱饱眼福,使用起来就是这么简单。
解析
从github仓库示例入手
进入了这个github仓库以后,最先开始看的肯定是Readme的里的示例,在看示例的时候作者给出的注释就非常重要了,他会标注出每一个重要的环节。并且我们要结合自己的一些经验排除掉和这个库无关的代码。(在这个示例中,webpack的相关代码就可以先不去过多关注)
第一步需要调用
install
方法,传入Vue构造函数,根据注释来看,这一步是要知道这个库与Vue版本之间是否兼容。接下来的这段注释告诉我们,每个需要热更新的组件选项对象,我们都需要为它建立一个独一无二的id,并且这段代码需要在初始化的时候完成。
最后就是激动人心的热更新时间了,
根据注释来看,这个库的使用分为两种情况。
rerender
只有template或者render函改变的情况下使用。reload
如果template或者render为改变,则这个函数需要调用reload方法先销毁然后重新创建(包括它的子组件)。从这个简单的示例里面可以看出,这个库的核心流程就是:
api.install
检测兼容性。api.createRecord
为组件对象通过一个独一无二的id建立一个记录。api.rerender
或api.reload
进行组件的热更新。什么,Readme的示例到此就结束了?这个very-unique-id到底是个什么东西,myComponentOptions又是什么样的。
因为这个仓库可能并不是面向广大开发者的,所以它的文档写的非常的简略。其实看完了这个简短的示例,大家肯定还是一脸懵逼的。
在看一个你没有熟练使用的库的源码的时候,其实还有一个很关键的步骤,那就是看测试用例。
探索测试用例
测试用例
上面我们总结出两个关键api
rerender
和reload
之后,就带着目的性的去看测试用例。rerender用例
reload用例
具体流程已经在注释里分析了,果然和示例代码的注释里写的一样,而且现在我们也更清楚这个api到底该怎么用了。
总结一个最简单的可用demo
这个demo(源码)是直接在浏览器可用的,效果如下:
源码分析
源码地址
全局变量
进入js文件的入口,首先定义了一些变量
其实看到window对象的出现,我们就已经可以确定这个api可以在浏览器端调用。
install
可以看出install方法很简单,就是帮你看一下Vue的版本是否在2.0以上,确认一下兼容性,关于初始化生命周期,在这篇文章里我们就不考虑2.0.0-alpha.7以下版本,可以认为这个库的初始化工作就是在beforeCreate这个生命周期进行。
createRecord
这一步在把id和对应的options对象存进map后,就没做啥了,关键步骤肯定在于
makeOptionsHot
这个方法。看完了这几个函数以后,我们对createRecord应该有个清晰的认识了。
比如上面我们的例子中这段代码
options
字段也就是上面传入的组件对象,还有instances
用于记录活动组件的实例,Ctor
用来记录组件的构造函数。beforeCreate执行完了以后的map对象长这样。
其中Ctor我们暂时也不需要去具体关心,因为正常情况下的组件的构造函数都是Vue函数。
接下来进入关键的rerender函数。
rerender
其实这个原函数很长,但是简化以后核心的更改视图的方法就是这些,平常我们在写vue单文件组件的时候都会像下面这样写:
这样的.vue文件,会被vue-loader编译成单个的组件选项对象,template中的部分会被编译成render函数挂到组件上,最终生成的对象是类似于:
而在运行时,组件实例(也就是生命周期或者methods中访问的this对象)会通过$option保存render这个函数,而通过上面的源码我们不难猜出vue在渲染组件的时候也是通过调用$option.render去实现的。我们可以去vue的源码里验证一下我们的猜想。
而在$forceUpdate的时候,vue内部会重新调用_render这个方法去生成vnode,然后patch到界面上,在此之前rerender把$options.render给替换成新的render方法了,这个时候再调用$forceUpdate,不就渲染新传入的render了吗?这个运行时的偷天换日我不得不佩服~
reload
reload的讲解我们基于这样一个示例:
一开始会显示foo的文本,一秒以后会显示成bar。
reload的情况会更加复杂,涉及到很多Vue内部的运行原理,这里只能简单的描述一下。
这段代码关键的点开始于
利用新传入的配置生成了一个新的组件构造函数
然后对record上的Ctor进行了一系列的赋值
注意第一次调用reload时,这里的record.Ctor还是最初传入的Ctor,是由
这个配置对象所生成的构造函数,但是构造函数的options、cid和prototype被替换成了由
这个配置对象所生成的构造函数上的options、cid和prototype,此时的cid肯定是不同的。
也就是说,构造函数的cid变了!,这个点记住后面要考!
继续看源码
此时的instance只有一个,就是在reload之前运行的那个msg为foo的实例,它的$vnode.context是什么呢?
直接在放上控制台打印出来的截图,这个context是一个vue实例,注意这个options里的render函数,是不是非常眼熟,没错,这个vue实例其实就是我们的prepare函数中
返回的vm实例。
那么这个函数的$forceUpdate必然会触发
render: h => h(Comp)
这个函数,看到此时我们似乎还是没看出来这些操作为什么会销毁旧组件,创建新组件。那么此时只能探究一下这个h到底做了什么,这个h就是对应着 $createElement方法。$createElement在创建vnode的时候,最底层会调用一个createComponent方法,
这个方法把Comp对象当做Ctor,然后调用Vue.extend这个api创造出构造函数,
默认情况下第一次h(Comp) 会生成类似于vue-component-${cid}作为组件的tag,
在本例中最开始渲染msg为foo的组件时,tag为vue-component-1,
并且会把这个构造函数缓存在_Ctor这个变量上,这样下次渲染再执行到createComponent的时候就不需要重新生成一次构造函数了,
Vue在选择更新策略时调用一个
sameVnode
方法来决定是要进行打补丁,还是彻底销毁重建,这个
sameVnode
如下:其中很关键的一个对比就是
a.tag === b.tag
但是reload方法偷梁换柱把Ctor的cid换成了2,
生成的vnode的tag是就vue-component-2
后续再调用context.$forceUpdate的时候,会发现两个组件的tag不一样,所以就走了销毁 -> 重新创建的流程。
总结
这个库里面还是能看出很多尤大的编程风格,很适合进行学习,只是reload方法必须要深入了解Vue源码才有可能搞懂生效的原理。
rerender
这个方法相对来说还是比较好理解的,但是reload
方法是怎么生效的就非常让人难以理解了,我一步一步断点调试了大概六七个小时,才渐渐得出结论,只能说好用的api后面潜藏着作者用心良苦的设计啊!想要彻底深入的理解vue的原理,强烈推荐黄轶老师的这门课程:Vue.js源码全方位深入解析 (含Vue3.0源码分析)
The text was updated successfully, but these errors were encountered: