给我一首歌的时间,教你如何解决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 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder6 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727576 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架