前言
最近, 基于GoView开发的低代码大屏项目逐渐处于交付阶段, 但棘手的问题来了, 原来搭建好的模板页面, 由于组件的配置项修改, 导致原模板页面进入到编辑状态时, 会报http.XXX问题, 且预览页面也无法显示, 直接抛出如下的异常错误
Cannot read properties of null (reading 'shapeFlag')
特意查看了下vue3的shapeFlag, 它主要用来区分不同的组件的类型, 在patch阶段时, 根据不同的组件类型, 执行不同的处理逻辑
export const enum ShapeFlags {
ELEMENT = 1, // HTML 或 SVG 标签 普通 DOM 元素
FUNCTIONAL_COMPONENT = 1 << 1, // 函数式组件
STATEFUL_COMPONENT = 1 << 2, // 普通有状态组件
TEXT_CHILDREN = 1 << 3, // 子节点为纯文本
ARRAY_CHILDREN = 1 << 4, // 子节点是数组
...
}
进入到抛出异常错误的代码中, 发现是vue3里的setVarsOnVNode方法中的vnode为null导致, 该方法内使用vnode.shapeFlag, 由于vnode为null, 故抛出该异常
那么, 为何会出现vnode为null呢? 如何解决该问题呢?
正文
问题排查
vnode为null的可能原因分析如下:
-
注册时机:确保组件在使用之前已正确注册。
-
名称一致:检查注册和使用的组件名称是否完全一致。
-
异步加载:如果使用异步加载,确保组件加载完成后再渲染。
可以在组件加载完成后,再进行模板渲染或通过条件渲染控制组件的显示
let isComponentLoaded = false; // 异步加载并注册组件 import('./MyDynamicComponent.vue').then(component => { window['$vue'].component('MyDynamicComponent', component.default); isComponentLoaded = true; }); // 确保组件加载完成后再渲染 <template> <component v-if="isComponentLoaded" :is="'MyDynamicComponent'" /> </template> -
Vue 版本兼容:根据 Vue 版本,使用合适的 API 进行组件注册。
若是
vue3, 推荐使用app.component的方式注册组件若是
vue2,window['$vue'].component和app.component两种方式皆可 -
属性传递:确保传递给组件的所有属性和事件都是正确且完整的
经排查, 我非常确信页面的组件绝对在使用之前已全部注册完成, 所以只剩下一个问题, 那便是属性传递
属性传递
无论是预览页面, 还是编辑页面的各个组件, 都是通过如下方式渲染
<div
class="chart-item"
:ref="setBoxRef"
v-for="(item, index) in chartEditStore.componentList"
>
<component
v-if="item.status.show"
v-show="item.isShow"
:is="item.chartConfig.chartKey"
:id="item.id"
:chartConfig="item"
:themeSetting="themeSetting"
:themeColor="themeColor"
:style="{
...getSizeStyle(item.attr),
...getFilterStyle(item.styles)
}"
v-on="useLifeHandler(item, chartEditStore.componentList)"
></component>
</div>
chartConfig是各个组件的属性数据来源, 那么item又是怎样的数据呢?
chartEditStore.componentList里的数据, 是之前存储在数据库里的各个组件的属性数据, 通过接口请求获得.
当我们修改部分组件的配置项后, 比如新加某个文字颜色属性, 由于数据库存储的是之前组件的配置数据, 在传递这些老属性数据时, 这些组件由于新增了一些属性设置, 而传递的老属性数据缺少这些新属性的初始值, 从而导致组件的加载出现问题, 报vnode为null的错误
解决方式
读取组件的config.ts文件, 获取该文件内的option属性配置, 将该新属性与从接口获得的老属性相融合即可.
获取option属性配置数据
const configPropsModules: Record<string, { default: string }> = import.meta.glob('./components/**/config.ts', {
// 在模块加载时立即执行
eager: true
})
/**
* * 获取配置数据
* @param {ConfigType} dropData 配置项
*/
export const fetchConfigPropsComponent = (dropData: ConfigType) => {
const { key } = dropData
//@ts-ignore
return fetchComponent(key, FetchComFlagType.CONFIG_PROPS)?.option
}
融合新老属性数据
localStorageInfo.componentList.forEach(async (e: CreateComponentType | CreateComponentGroupType, index) => {
const options = fetchConfigPropsComponent(e.chartConfig)
const storeOptions = e.option
e.option = Object.assign({}, options, storeOptions)
...
})
重新进入到模板页面, 发现页面可以正常显示, 且无报错信息.
非常完美地解决了看似不可能解决的问题, 若对大家有所帮助, 欢迎在下方留言, 或点赞关注~~