给我一首歌的时间,教你如何解决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,
});
相关推荐
何以解忧,唯有..1 分钟前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
软件聚导航1 小时前
「聚小软 AI 助手」技术升级:从数据库检索到 RAG 知识库问答
前端·数据库·mysql·微信小程序·小程序·ai编程·rag
恋猫de小郭1 小时前
看懂大模型架构术语,帮助你理解目前常见的大模型开源架构
前端·人工智能·ai编程
yma161 小时前
通过提示词实现GitHub Pages 和 Nginx 双部署竟然这么简单?
前端
前端 贾公子2 小时前
第09章:上下文与记忆 (2)
java·服务器·前端
可乐鸡翅yeah_2 小时前
第三方接口对接 M3U8 流媒体排坑实战,解决外部服务商流兼容难题
前端·javascript·python·django·html·m3u8·m3u8在线
GoppViper2 小时前
RDF资源描述框架深度解析:语义Web的数据基石与实战逻辑
前端·数据库
郑州光合科技余经理2 小时前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php
IT_陈寒2 小时前
Vue的嵌套组件竟然吃掉了我的事件?
前端·人工智能·后端
默_笙2 小时前
🏠 「LLM Notes」:我用 Next.js + Redis 给自己造了个笔记博客
前端·javascript