GoView潜伏的重大隐患: 组件配置项修改后, 报vnode为null错误, 如何解决它?

877 阅读3分钟

前言

最近, 基于GoView开发的低代码大屏项目逐渐处于交付阶段, 但棘手的问题来了, 原来搭建好的模板页面, 由于组件的配置项修改, 导致原模板页面进入到编辑状态时, 会报http.XXX问题, 且预览页面也无法显示, 直接抛出如下的异常错误

Cannot read properties of null (reading 'shapeFlag')

特意查看了下vue3shapeFlag, 它主要用来区分不同的组件的类型, 在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方法中的vnodenull导致, 该方法内使用vnode.shapeFlag, 由于vnodenull, 故抛出该异常

那么, 为何会出现vnodenull呢? 如何解决该问题呢?

正文

问题排查

vnodenull的可能原因分析如下:

  • 注册时机:确保组件在使用之前已正确注册。

  • 名称一致:检查注册和使用的组件名称是否完全一致。

  • 异步加载:如果使用异步加载,确保组件加载完成后再渲染。

    可以在组件加载完成后,再进行模板渲染或通过条件渲染控制组件的显示

    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'].componentapp.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里的数据, 是之前存储在数据库里的各个组件的属性数据, 通过接口请求获得.

当我们修改部分组件的配置项后, 比如新加某个文字颜色属性, 由于数据库存储的是之前组件的配置数据, 在传递这些老属性数据时, 这些组件由于新增了一些属性设置, 而传递的老属性数据缺少这些新属性的初始值, 从而导致组件的加载出现问题, 报vnodenull的错误

解决方式

读取组件的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)
        ...
      })

重新进入到模板页面, 发现页面可以正常显示, 且无报错信息.

非常完美地解决了看似不可能解决的问题, 若对大家有所帮助, 欢迎在下方留言, 或点赞关注~~