Vue created 中的异步请求的影响分析
背景
最近接手了别人写的前端代码,填了两个星期的坑了。周四填的这个坑比较有意思,是关于 created 钩子和异步请求的问题,反复验证了一个小时,终于弄清楚了同步请求和异步请求对钩子执行顺序的影响。
常规思路写出的代码,还真看不出问题,但是一运行就有问题,还是值得细究的。
功能描述
有一个管理功能的主页面,它被拆解成四个子组件:
- ChartsComp:顶部一些统计图
- SearchComp: 中间条件区域
- BtnComp:中间操作按钮区域
- DataTableComp:底部数据列表,有一列会根据国家名称显示国旗图片
主界面 XXXHome 的 created 钩子里,会初始化其他组件需要的数据:
子组件中 watch 不到数据变化
代码的前任想把所有的数据都在 Home 页面获取到,然后由子组件使用 watch 监听并使用。第一个问题发生在 getChartData
上,子组件的引用和数据使用如下:
因为数据是在父组件中通过异步请求获取的,子组件渲染时可能没有数据,就把绘图方法放在 watch 中,监听数据变化后再触发。
父子组件的 created 执行顺序
父子组件的 created 中添加打印信息,执行顺序如下:
从测试表现来看,父组件的所有异步请求返回后,子组件的 created 才执行。即从父到子,顺次执行。此时子组件的那几个 watch 大多数时候根本监控不到数据变化,因为传入的时候,异步响应已经返回了,数据非空,所以监听不到变化。
但是,如果直接在子组件的 created 调用绘图方法,又会出现数据不完整的问题,且具有偶发性,这一点由于时间有限、没有细探。
最保险的解决方法:在 Home 中添加一个 flag ,所有依赖异步请求数据的子组件都加一个 v-if 标识,等待数据回来后修正为真,再渲染子组件 charts v-if="isDataOk"
。
组件拆解,如果仅仅是为了减少主页面的代码量,而无其他复用时,比如本文这个 ChartsCmop,就可以将数据请求放在自己的 created 中,不需要依赖父组件传入,也就不会有这个问题了。
beforeCreated 和 created 的顺序
昨天碰到一个问题,测试后发现:如果把 beforeCreated 定义为 async
,它的 await 后的代码会在 created 执行完成后才执行。感觉有些颠覆官方的介绍,所以着重研究了一下。
官方说的是先执行 beforeCreate 再执行 created ,那么 beforeCreate 中的 await 会对这个顺序产生什么影响呢?
首先,还是这个功能,它的底部数据列表,需要根据国家名称显示国旗图片。前任是在 beforeCreate 中使用 await 操作先获取到国家和图片的 base64 信息,再在 created 中获取 table 数据,代码是这样写的:
其次,这段代码看起来没问题,笔者也感觉 beforeCreate 里面用了 await 同步,应该会先执行完成,再继续执行 created 方法。结果,运行后的日志信息却是这样:
第三,从执行结果来看, beforeCreated 中的 “beforeCreate finished” 是在 created 的代码执行完成后才打印的。这说明,在它等待请求响应结果期间,created 方法已经被执行了。
这种情况下 this.areas
还是默认的空数组,此时 created 的 getDataTable 方法会获取 DataTableComp 需要的数据,并完成子组件的渲染:
<span :title="scope.row.countryName">
<el-image :src="getCountryImg(scope.row.countryName)" class="flagImg"/>
{{ scope.row.countryName}}
</span>
第四,getCountryImg
使用 countries 时,会引发错误。因为它渲染时,会获取国旗图片,查找 countryName 对应的信息,如果不存在,就展示第一个元素 contries[0]
的图片:
getCountryImg(country) {
for (let i = 0; i < this.contries.length; i += 1) {
const item = this.contries[i];
if (item.nameZh === country) {
return `data:image/jpeg;base64,${item.img64}`;
}
}
return `data:image/jpeg;base64,${this.contries[0].img64}`;
},
最后,由于 DataTableComp 绘制的时候 beforeCreate 方法还没有结束,父组件传递来的 countries 是空数组,this.contries[0]
为 undefined 而引发异常:
解决办法:添加 isAreaOk 标识。初始为否,当异步请求回来后设置为真,列表组件依赖该标识 data-table-comp v-if="isAreasOk"
,子组件推迟到依赖解决后再渲染。
启示录
本文介绍 Vue 的 created 和 beforeCreate 钩子执行的两个知识点:
- 父子组件的 created 是顺次执行的,先父后子,即使父组件的 created 方法中有异步请求,也会等待所有的异步请求结束,才会执行子组件的 created 方法;
- 相同组件的 beforeCreate 方法先于 created 方法执行,如果 beforeCreate 中使用await 返回一个 Promise 对象,主流程不会处理,而是继续执行 created 方法。
关于第二点,就是说,虽然将 beforeCreate 定义为 async ,但是 Vue 调用时应该没有 await beforeCreate()
,本质上还是异步逻辑。所以不会等待它真正执行完成,后面的 created 就会继续执行,它的 await 进入回调函数等待队列中。
关于 Vue 开发的一些知识,笔者有一篇万字长文中有详细介绍,感兴趣的朋友可以看看这篇《五条Vue 开发锦集》。
本文地址:https://blog.csdn.net/wojiushiwo945you/article/details/107539344