乐趣区

关于javascript:uniappVue3-ucharts-图表-H5-无法渲染

文章已收录到 github,欢送 Watch 和 Star。

简介

从问题定位开始,到给框架(uni-app)提 issue、出解决方案(PR),再到最初的思考,具体记录了整个过程。

前序

当你在业务中可怜踩了开源框架的某些坑,这是你的可怜,但这同时也是你的侥幸,因为这是你给本人简历中减少亮点的绝佳机会。

而给开源社区奉献 PR 是你证实本人技术侧领有 P7 实力的绝佳形式,P7 的评判规范无非是业务和技术,业务上有收益,技术上有深度和广度(他人有的你能做的更好,他人没有的你能有)。

这次整个过程历时 3-4 天,在此之前我也没读过 uni-app 和 ucharts 的源码,所以这里把整个过程分享进去也是给大家一个解决问题的思路。

环境

  • uni-app cli 版本 3.0.0-alpha-3030820220114011
  • hbuilder 版本 3.3.8.20220114-alpha
  • ucharts 版本 uni-modules 2.3.7-20220122

景象

uni-app、vue3 + ucharts 绘制图表,开发环境失常,然而打包上线后,H5 无奈绘制图表,也不报任何谬误。

开发 线上
APP 失常 失常
H5 失常 无奈绘制

问题定位

给 ucharts 的社区提 issue,通过交换,维护者“狐疑“是 uni-app 的 vue3 的 renderjs 有问题,然而他也给不了一个必定的回答,让去 uni-app 的社区提 issue 而且示例中不能用 ucharts。集体对于该答复持狐疑态度,于是决定本人去定位问题。

狐疑是 ucharts 的 bug

  • ucharts 视图局部的要害代码
<view ... 其它属性 :prop="uchartsOpts" :change:prop="rdcharts.ucinit">
  <canvas ... 属性 />
</view>

这里有一个知识点须要补充:当 prop 产生扭转,change:prop 的回调会被调用,这是 uni-app 框架提供的能力,但官网文档没有提及,从源码中能够看到。

  • 看了 ucharts 的源码,绘制图表时的代码执行过程如下:

可是打包后的 H5 线上环境,当执行 this.uchartsOpts = newConfig 之后却没有触发 change:prop 事件,所以这看起来仿佛是 uni-app 的 view 组件有问题

感激 ucharts 官网,在定位问题过程中,和社区进行交换后,ucharts 收费赠送了一个 永恒超级会员,感激 🙏 🙏 !!

view 组件的 prop 和 change:prop

提供如下示例:

<template>
  <view>
    <view :prop="counter" :change:prop="changeProp"></view>
        <view>{{msg}}</view>
  </view>
</template>

<script setup lang="ts">
import {onBeforeUnmount, onMounted, ref} from "vue";

const counter = ref(1)
const msg = ref('hello')

function changeProp() {msg.value = 'hello' + counter.value}

// @ts-ignore
let timer = null
onMounted(() => {timer = setInterval(() => {counter.value += 1}, 1000)
})

onBeforeUnmount(() => {
    // @ts-ignore
    clearInterval(timer)
})
</script>

<style>
</style>
H5 开发环境 H5 打包后
vue2 失常 失常
vue3 失常 change:prop 未执行

因为开发环境没有问题,所以在开发环境中通过在 change:prop 办法中打断点,查看调用栈,找到触发 change:prop 回调的办法,再一步步往上看,终于发现了 uni-app 重写渲染器(render 函数)的中央,在 @dcloudio/uni-h5-vue/dist/vue.runtime.esm.js 中。​

通过浏览 uni-app 的源码,失去如下内容:

响应式数据发生变化,触发 vue 的响应式更新。比方你的响应式数据作为元素的 prop 属性传递,则在 patch 阶段会触发 patchProps 办法,触发该办法后,办法内判断新老 props 是否产生扭转,如果变了,则遍历新的 props 对象,将其中的每个属性、值和老的比照,如果不相等 或者 props 的 key 为 change:xx 则间接调用 patchProp 办法,如果 __UNI_FEATURE_WXS__为真并且 props 的 key 为 change: 结尾,则调用 patchWxs,patchWxs 办法最终会通过 nextTick 调用 change:prop 的回调办法。

以下为上述执行过程的流程图:

最终定位到问题就出在 __UNI_FEATURE_WXS__上,发现开发环境中它是 true,然而打包后就变成了 false。

\_\_UNI_FEATURE_WXS__

__UNI_FEATURE_WXS__是一个全局变量,所以必定是通过 vite 的 define 选项进行设置的。

于是接下来的目标就是须要找到 __UNI_FEATURE_WXS__是在什么中央进行设置的。能够全局搜该变量,而后找到在 @dcloudio/uni-cli-shared 包中找到一个叫 initFeatures 的办法,该办法中申明了一个 features 对象:

const {
  wx,
  wxs,
  // ... 其它变量
} = extend(initManifestFeature(options),
  // ... 其它办法
)

const features = {
  // vue
  __VUE_OPTIONS_API__: vueOptionsApi, // enable/disable Options API support, default: true
  __VUE_PROD_DEVTOOLS__: vueProdDevTools, // enable/disable devtools support in production, default: false
  // uni
  __UNI_FEATURE_WX__: wx, // 是否启用小程序的组件实例 API,如:selectComponent 等(uni-core/src/service/plugin/appConfig)__UNI_FEATURE_WXS__: wxs, // 是否启用 wxs 反对,如:getComponentDescriptor 等(uni-core/src/view/plugin/appConfig)// ... 其它属性
}

看了该对象的设置没什么问题,wxs在开发和生产环境下都是 true。那接下来就须要找到谁调用了 initFeatures 办法,而且可能调用完了当前通过判断以后命令,比方:执行 build 时,将 __UNI_FEATURE_WXS__设置为了 false。

刚开始想正向推导。vite-plugin-uni 是 uni-app 提供给 vite 的一个插件框架,uni-app 中的 vite 配置都来自于这里。

插件当中的 uni 插件提供了 config 选项,config 选项的值是调用 createConfig 办法返回的函数,该函数会返回一个对象,该对象会和 vite 的配置做深度合并;该对象有 define 选项,该选项的值为 createDefine 函数的返回值,该返回值是一个对象,其中调用了 initDefine,再往下看发现不对,而后路 走死了。

发现下面正向推导的形式走不通当前,于是开始反向推导,即全局搜寻,都有哪些地方调用了 initFeatures,而后一步步的往下推,失去如下正确的流程图:

通过最终的调试,发现 启动开发环境和打包时最终的调用门路是:uniH5Plugin -> createConfig -> configDefine -> initFeatures。
而最终的问题也就是出在了 initFeatures 办法调用的 initManifestFeature 办法中。

答案

最终定位到出问题的中央在 @dcloudio/uni-cli-shared/src/vite/features.ts 文件的 initManifestFeature 办法中。有如下比照:

  • github 仓库的最新代码,版本号:3.0.0-alpha-3030820220114011
if (command === 'build') {
    // TODO 须要预编译一遍?// features.wxs = false
    // features.longpress = false
  }
  • 已发版的代码,最高版本号:3.0.0-alpha-3031120220208001
if (command === 'build') {
    // TODO 须要预编译一遍?features.wxs = false;
    features.longpress = false;
}

已发版的版本竟然高于仓库内的最新版本号。查看 npm 上的公布版本信息:

发现版本号产生了回退。这几次回退的版本号都是不符合规范的版本号,而且其中可能携带了 bug,比方下面提到的最高版本。

发版呈现版本号不符合规范的状况是因为我的项目还没有一个标准的发版流程导致的,然而曾经是 alpha 版本了,这种低级谬误还是应该防止的。

更致命的操作是,回退版本号。uni-app 目前每次降级都是降级的最小版本号前面的数值,而业务我的项目的 package.json 都是 "@dcloudio/uni-app": "^xxx" 的模式,这就意味着,你每次从新装包(比方自动化部署时)或者升级包时,都会更新到这个存在 bug 的高版本,这就会导致线上零碎报 bug。

解决方案

所以这里正确的解决形式是从新发一个更高版本的包,而不是回退版本。因为该操作会导致用户线上的零碎出 bug,即以下代码无奈失常执行:

<view :prop="msg" :change:prop="cb"></view>

当失常状况下,当 msg 扭转后,change:prop 的回调会执行。然而这个携带 bug 的高版本包,在打包时(npm run build)将 __UNI_FEATURE_WXS__设置为了 false,导致 change:prop 的回调不会被调用。

总结

代码能够回退,然而版本号不要回退,应该基于以后稳固版本,从新发一版版本号更高的版本。

于是就给官网提了 issue 和 解决方案。

后果

官网已驳回该解决方案,基于以后稳定版从新公布一版版本号更高的版本。

思考

针对 uni-app 这种处于 alpha 版本的框架,我的项目外部也的确不应该持续应用 ^ 符号,还是应该将版本号写死为最新的 tag 版本,因为总追随 alpha 的最新版,的确可能会踩坑。

链接

  • 精通 uni-app 专栏

文章已收录到 github,欢送 Watch 和 Star。

退出移动版