给我一首歌的时间,教你如何解决el-cascader级联选择器导致的页面崩溃问题

问题现象

使用 element-ui 的 Cascader 级联选择器的动态加载功能,在连续多次点击选项加载下级时,会导致页面崩溃,CPU占用100%。

原因分析

原因在于 Cascader 级联选择器在开发的时候使用了 aria-owns 属性。

据 MDN 解释aria-owns 属性用于标识一个或多个元素,以便在 DOM 层次结构不能用于表示关系时定义父元素与其子元素之间的视觉、功能或上下文关系。在某些情况下,由于 JavaScript 能够更改内容以及 CSS 能够更改布局,屏幕上显示的布局可能与底层 DOM 结构不同,在这种情况下,该 aria-owns 属性可用于为使用 DOM 的辅助技术重新创建有意义的关系。

MDN 提示不要将aria-owns用作 DOM 层次结构的替代品。如果关系在 DOM 中表示,请勿使用 aria-owns。默认情况下,子元素由其 DOM 父元素拥有,在这种情况下,aria-owns不应使用。避免使用该aria-owns属性将现有子元素重新排列为不同的顺序。

注意:aria-owns仅当无法从 DOM 确定父/子关系时才应使用该属性。

处理方式

既然问题是由于 Cascader 使用了aria-owns属性来表示父/子关系,那么可以考虑从该属性入手解决问题。既然MDN给出的解释是仅当无法从 DOM 确定父/子关系时才应使用 aria-owns属性,所以可以尝试移除aria-owns属性。

通过审查元素可知,aria-owns 属性是挂载在 .el-cascader-node 元素上的,所以需要当.el-cascader-node 元素生成后才可以移除。

什么时候会生成.el-cascader-node 元素?可能会有几个场景:

  • 下拉框出现
  • 当选中节点变化
  • 当展开节点变化

那么就需要在这几个场景触发时的回调函数中获取.el-cascader-node 元素并移除aria-owns属性。

代码如下:

js 复制代码
<template>
    <el-cascader
        :props="props"
        :show-all-levels="false"
        v-model="alertType"
        :options="searchList"
        filterable
        clearable
        @change="removeCascaderAriaOwns"
        @visible-change="removeCascaderAriaOwns"
        @expand-change="removeCascaderAriaOwns"
        >
   </el-cascader>
</template>

<script>
export default {
    methods() {
        removeCascaderAriaOwns() {
            this.$nextTick(() => {
                    const $el = document.querySelectorAll(
                            '.el-cascader-panel .el-cascader-node[aria-owns]'
                    );
                    Array.from($el).map(item => item.removeAttribute('aria-owns'));
            });
        },
    },
};
</script>

经多次测试,通过移除aria-owns属性的方法是可以解决此bug的。但此时会存在一个问题:动态加载下级节点时,由于接口请求数据较慢,会导致在执行querySelectorAll的时候,aria-owns属性还没有添加到 .el-cascader-node元素上,此时就会获取不到aria-owns属性的元素,那么就无法移除该属性,这时候如果再去点击选项,还是会导致页面崩溃。

解决方式:在加载下级节点完成并且resolve之后,再移除aria-owns属性。

完整代码如下:

js 复制代码
<template>
    <el-cascader
        :props="props"
        :show-all-levels="false"
        v-model="alertType"
        :options="searchList"
        filterable
        clearable
        @change="removeCascaderAriaOwns"
        @visible-change="removeCascaderAriaOwns"
        @expand-change="removeCascaderAriaOwns"
        >
    </el-cascader>
</template>

<script>
export default {
    data() {
        const that = this;
        return {
            props: {
                lazy: true,
                checkStrictly: true,
                async lazyLoad(node, resolve) {
                    const data = await that.loadNode(node);
                    resolve(data);
                    this.removeCascaderAriaOwns();
                },
            },
        }
    },
    methods() {
        removeCascaderAriaOwns() {
            this.$nextTick(() => {
                    const $el = document.querySelectorAll(
                            '.el-cascader-panel .el-cascader-node[aria-owns]'
                    );
                    Array.from($el).map(item => item.removeAttribute('aria-owns'));
            });
        },
    },
};
</script>

以上方法需要在每个用到 Cascader 选择器的地方都写一次,过于麻烦。那有没有一劳永逸的方法?

嘿嘿~还真有!

可以通过 MutationObserver 全局监视 DOM 的变化,由于 Cascader 选择器在下拉框出现或者展开下级等操作的时候,都会改变 DOM,那么就可以在MutationObserver 的回调函数中移除aria-owns属性,做到一劳永逸的效果,建议将此方法放在 main.js 文件中执行或者其他初始化项目的地方执行。

js 复制代码
const mutationObserver = new MutationObserver(mutations => {
  mutations.forEach(mutation => {
    mutation.addedNodes.forEach(node => {
      if (node.nodeType === 1) {
        const $el = node.querySelectorAll(
          '.el-cascader-panel .el-cascader-node[aria-owns]'
        );
        Array.from($el).map(item => item.removeAttribute('aria-owns'));
        // 防止懒加载数据太慢上面获取不到$el,直接移除node的aria-owns属性
        if (node.hasAttribute('aria-owns')) {
          node.removeAttribute('aria-owns');
        }
      }
    });
  });
});
mutationObserver.observe(document.body, {
  characterData: false,
  childList: true,
  attributes: false,
  subtree: true,
});
相关推荐
清汤饺子10 小时前
OpenClaw 本地部署教程 - 从 0 到 1 跑通你的第一只龙虾
前端·javascript·vibecoding
爱吃的小肥羊13 小时前
比 Claude Code 便宜一半!Codex 国内部署使用教程,三种方法任选一!
前端
IT_陈寒14 小时前
SpringBoot项目启动慢?5个技巧让你的应用秒级响应!
前端·人工智能·后端
树上有只程序猿15 小时前
2026低代码选型指南,主流低代码开发平台排名出炉
前端·后端
橙某人15 小时前
LogicFlow 小地图性能优化:从「实时克隆」到「占位缩略块」!🚀
前端·javascript·vue.js
高端章鱼哥15 小时前
为什么说用OpenClaw对打工人来说“不划算”
前端·后端
大脸怪15 小时前
告别 F12!前端开发者必备:一键管理 localStorage / Cookie / SessionStorage 神器
前端·后端·浏览器
Mr_Mao15 小时前
我受够了混乱的 API 代码,所以我写了个框架
前端·api
小徐_233315 小时前
向日葵 x AI:把远程控制封装成 MCP,让 AI 替我远程控制设备
前端·人工智能
冴羽15 小时前
来自顶级大佬 TypeScript 之父的 7 个启示
前端·typescript