给我一首歌的时间,教你如何解决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,
});
相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万5 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋5 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁5 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95275 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大5 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师5 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学5 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端