本篇又名:Vue中mounted之后就一定能getElementById获取到DOM节点了么?

需求

天钿Daily中的视频详情页面,访问时根据aid从api获取视频信息以及历史数据,如果系统中不存在这个视频则显示“不存在”,否则显示视频信息,以及根据历史数据,绘制图表。

设计1

听上去是个很简单的需求。根据习惯,使用ajax调用API部分放在created钩子里,绘制图表放在mounted钩子里。很快,第一版设计就完成了。

页面结构与变量都进行了简化以方便理解。这里created钩子里模拟了一次耗时1000ms的从API获取videovideoRecords的操作,并且在操作前后使用isLoading变量标注,而mounted钩子里调用renderChart函数绘制图表。HTML部分,使用v-if v-else语法,实现逻辑:当在加载时显示“Loading…”,加载完后判断video是否为空,若为空显示“Fail to load video”,若不为空则显示video的详细信息,以及根据videoRecords绘图。

在Chrome中运行,并没有看到图表渲染出来,控制台报错:Error: Please specify the container for the chart!

这里并没有去关注报错信息,而是突然意识到一个问题:created中,从API获取数据需要时间,而mounted中绘图时videoRecords还没有得到,依旧是null。修改代码,于是有了设计2。

设计2

使用监听器,监听videoRecords变量,当其发生变化时调用renderChart函数绘图。

在Chrome中运行,报了一样的错。很显然,该错误的原因不在此。

分析:即使绘图时数据源videoRecords为null,G2因为缺少源数据无法绘制图表,但是并不会报错。阅读错误提示,才明白错误问题在于:没有找到绘图的容器

分析

观察renderChart函数

参考G2文档了解到,创建chart时,container指定渲染的图表的id,此处通过document.getElementById('c1')获取渲染目标DOM节点。猜测可能是因为此时document.getElementById('c1')无法获取该元素,所以报错。

为了验证该猜测,修改代码,测试此时是否能通过document.getElementById('c1')获取元素,在创建G2对象前加入此句:

运行,输出c1: null。果不其然,获取不到该DOM姐节点。

根据Vue生命周期图示,mounted钩子在挂载完毕之时被触发,此时所有节点应该都已经挂载到<div id=’app’>…</div>中了,为什么c1获取不到呢?

是因为获取到videoRecords之后,还没有完成挂载么?为验证该猜测,在mounted钩子中加入log。

在Chrome中运行,观察控制台,可以发现在document.getElementById('c1')执行之前,mounted钩子就已经执行了。也就是说,绘制图表时已经完成挂载,但是那一时刻,就是没有c1这个节点。

顿悟

那么,这个DOM节点去哪儿了呢?不是已经mounted了么?Chrome里查看源码也确实存在的呀?那到底是什么时候出现的呢?

回头再看看HTML代码,顿时发现了问题。

这里使用了Vue中的条件渲染。文档开头是这样介绍的:

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

也就是说,只有条件满足的时候,这个块中的所有元素才会被渲染。查看此处HTML代码的逻辑,可以发现,<div id=’c1′></div>节点只有在isLoading为false且video不为null的时候才会被渲染。

追踪JS代码中isLoading的值的变化,在created钩子中,代码如下:

第3行,即将开始调用API获取videovideoRecords信息,isLoading设置为true

第6~13行,模拟请求过程完成,更新videovideoRecords数据;

第14行,变量更新完成,isLoading设置为false

此时,达成上述条件,<div id=’c1′></div>节点才会被渲染。设计2中的renderChart函数在videoRecords被赋值时触发,之后寻找该节点渲染图表;而此时由于还没有执行完成第16行修改isLoadingfalse该节点还没有满足渲染条件,进而不会被渲染,也就自然找不到该节点,报一开始的错了。

设计3

现在问题已经很明朗了,解决方法也很简单:只需要保证该节点被渲染后再渲染图表即可。这里我项目中的实现方案是:使用组件封装该图,传入videoRecords作为参数,当该组件被渲染时,即mounted钩子中,对图表进行渲染。封装了的组件如下:

去除原先对videoRecords的watch以及methods中的renderChart函数,原先的div替换为封装好的组件:

最终效果如下:

现在可以回答最开始的问题了。Vue中mounted之后就一定能getElementById获取到DOM节点了么?是的!前提是该节点并没有被别的条件限制导致无法渲染。

本次使用的文件可以在这里找到。


0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注