Error in beforeDestroy hook: “Error: [ElementForm]unpected width “

发布于:2025-05-25 ⋅ 阅读:(20) ⋅ 点赞:(0)

使用 element 的 form 时候报错:

vue.runtime.esm.js:3065  Error: [ElementForm]unpected width 
    at VueComponent.getLabelWidthIndex (element-ui.common.js:23268:1)
    at VueComponent.deregisterLabelWidth (element-ui.common.js:23281:1)
    at VueComponent.updateLabelWidth (element-ui.common.js:23483:1)
    at VueComponent.beforeDestroy (element-ui.common.js:23510:1)

原因:el-form-item 的 label 组件有个 beforeDestroy 生命同期,el-form 组件的 display: none 获取labelWidth 有问题导致的

解决办法:el-form 上加 v-if,用 el-dialog 的 :visible.sync="dialogVisible" 的话,也加 v-if="dialogVisible"

由于 el-form 标签 label-width 设为 "auto" ,而 el-form-item 会继承这个属性,在某种情况下页面销毁时获取这个 el-form-item 的 label 的 width 为 'auto',然后用 parseFloat 转换后为 NaN 就报错了。

这个方法是在 element-ui 共通方法里写的 node_modules\element-ui\lib\element-ui.common.js

或是

computedWidth 为 “auto" 或 "" 都会返回 NaN

网上说是因为代码中使用了 v-show 以及 el-form 标签中使用了label-width="auto",导致离开页面后产生报错

解决方法一:label-width 换成固定值

解决方法二: v-show 换成  v-if

但我的原因是因为使用了 keep-alive 用下面代码可以再现出来

src\router\index.js

import Vue from 'vue';
import Router from 'vue-router';
import Home from '../views/Home.vue';
import Layout from '../views/management/Layout.vue';

Vue.use(Router);

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/management',
      component: Layout,
      children: [
        {
          path: '/management/audit',
          component: () => import("@/views/management/audit/index.vue"),
          name: "memberAudit",
        },
        {
          path: '/management/detail',
          component: () => import("@/views/management/detail/index.vue"),
          name: "memberAudit",
        }
      ]
    }
  ]
});

export default router;

src\views\management\Layout.vue

<template>
  <div class="main">
    <el-scrollbar>
      <div class="left">
        <el-menu
          default-active="$route.path"
          background-color="#545c64"
          text-color="#fff"
          router
        >
          <el-menu-item :router="true" index="/management/audit">
            <i class="el-icon-menu"></i>
            <span slot="title">审核</span>
          </el-menu-item>
          <el-menu-item :router="true" index="/management/detail?label=menu">
            <i class="el-icon-menu"></i>
            <span slot="title">详情</span>
          </el-menu-item>
          <el-menu-item :router="true" index="/management/about">
            <i class="el-icon-menu"></i>
            <span slot="title">About</span>
          </el-menu-item>
        </el-menu>
      </div>
      <div class="right">
        <keep-alive>
          <router-view />
        </keep-alive>
      </div>
    </el-scrollbar>
  </div>
</template>

<style lang="scss">
.left {
  float: left;
  width: 200px;
}
.right {
  float: left;
  width: 80%;
}
</style>

src\views\management\audit\index.vue

<template>
  <div class="hello1" style="margin-top: 100px">
    audit
    <span @click="detail">click</span>
  </div>
</template>

<script>
export default {
  methods: {
    detail() {
      this.$router.push("/management/detail?label=测试");
    },
  },
};
</script>

src\views\management\detail\index.vue

<template>
  <div>
    <el-form
      :model="ruleForm"
      status-icon
      ref="ruleForm"
      label-width="auto"
      class="demo-ruleForm"
    >
      <el-form-item label="年龄" prop="age">
        <el-input v-model.number="ruleForm.age"></el-input>
      </el-form-item>
      <el-form-item :label="$route.query.label" prop="name">
        <el-input v-model.number="ruleForm.name"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button @click="resetForm()">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  name: "Home",
  components: {},
  data() {
    return {
      ruleForm: {
        age: "",
        name: "",
      },
    };
  },
  methods: {
    resetForm() {
      this.$router.push("/management/audit");
    },
  },
};
</script>

点击详情

点击重置

再点击 about

报错了,这里注意报错的主要写法

1、src\views\management\Layout.vue

使用 keep-alive

2、src\router\index.js

这里我没设置 about 页面路由,可能这样会触发 VueComponent.beforeDestroy

3、src\views\management\detail\index.vue

el-form-item 必须两个以上,并且其中一个的 label 使用动态值 :label="$route.query.label"

我的这种情况即使设置 label-width 为固定值也不行,因为最后页面消失,获取  label-width 为空

parseFloat(label-width) 为 NaN 就报错了

去掉 keep-alive 就不会报错

去掉后就不会走 'update' 分支,因为页面切换时因为有个 label 是动态赋值的,所以即使页面销毁因为是 keep-alive 且 label 动态值变没了,导致 el-form 认为是 label 值 update ,走的这个方法,而这个时候页面找不到元素, label-width 就是空,所以报错了。

其实是因为 el-form-item 的 label 设为响应式的了,而且在页面销毁时响应式值还更新,可以换一种响应式使它在页面销毁时不更新值

<el-form-item :label="qLabel" prop="name">

  created() {

    this.qLabel = this.$route.query.label;

  },

<template>
  <div>
    <el-form
      :model="ruleForm"
      status-icon
      ref="ruleForm"
      label-width="auto"
      class="demo-ruleForm"
    >
      <el-form-item label="年龄" prop="age">
        <el-input v-model.number="ruleForm.age"></el-input>
      </el-form-item>
      <el-form-item :label="qLabel" prop="name">
        <el-input v-model.number="ruleForm.name"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button @click="resetForm()">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  name: "Home",
  components: {},
  created() {
    this.qLabel = this.$route.query.label;
  },
  data() {
    return {
      qLabel: "",
      ruleForm: {
        age: "",
        name: "",
      },
    };
  },
  methods: {
    resetForm() {
      this.$router.push("/management/audit");
    },
  },
};
</script>

这么写不会报错,算是完美避开了这个 bug

但是公司的是另一个问题,是用 el-dialog 组件包裹 el-form 也出这个 bug了

<el-dialog
      title="提示"
      :visible.sync="dialogVisible"
      width="30%"
      :before-close="handleClose"
      :destroy-on-close="true"
    >
      <el-form
        :model="ruleForm"
        status-icon
        ref="ruleForm"
        label-width="auto"
        class="demo-ruleForm"
      >
        <el-form-item :span="24" label="年龄" prop="age">
          <el-input v-model.number="ruleForm.age"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button @click="resetForm()">重置</el-button>
        </el-form-item>
      </el-form>
    </el-dialog>


    resetForm() {
      this.dialogVisible = false;
      this.$router.push("/management/detail");
    },

查看源码仔细分析发现,虽然报错在 element-ui.common.js 但其实报错是在源码里 label-wrap.vue

 

<script>

export default {
  props: {
    isAutoWidth: Boolean,
    updateAll: Boolean
  },

  inject: ['elForm', 'elFormItem'],

  render() {
    const slots = this.$slots.default;
    if (!slots) return null;
    if (this.isAutoWidth) {
      const autoLabelWidth = this.elForm.autoLabelWidth;
      const style = {};
      if (autoLabelWidth && autoLabelWidth !== 'auto') {
        const marginLeft = parseInt(autoLabelWidth, 10) - this.computedWidth;
        if (marginLeft) {
          style.marginLeft = marginLeft + 'px';
        }
      }
      return (<div class="el-form-item__label-wrap" style={style}>
        { slots }
      </div>);
    } else {
      return slots[0];
    }
  },

  methods: {
    getLabelWidth() {
      if (this.$el && this.$el.firstElementChild) {
        const computedWidth = window.getComputedStyle(this.$el.firstElementChild).width;
        return Math.ceil(parseFloat(computedWidth));
      } else {
        return 0;
      }
    },
    updateLabelWidth(action = 'update') {
      if (this.$slots.default && this.isAutoWidth && this.$el.firstElementChild) {
        if (action === 'update') {
          this.computedWidth = this.getLabelWidth();
        } else if (action === 'remove') {
          this.elForm.deregisterLabelWidth(this.computedWidth);
        }
      }
    }
  },

  watch: {
    computedWidth(val, oldVal) {
      if (this.updateAll) {
        this.elForm.registerLabelWidth(val, oldVal);
        this.elFormItem.updateComputedLabelWidth(val);
      }
    }
  },

  data() {
    return {
      computedWidth: 0
    };
  },

  mounted() {
    this.updateLabelWidth('update');
  },

  updated() {
    this.updateLabelWidth('update');
  },

  beforeDestroy() {
    this.updateLabelWidth('remove');
  }
};
</script>

因为 form-item 组件用了 label 组件,而 label 组件是用上面的 label-wrap 包裹的,所以 el-dialog 关闭并跳转到新页面时就调用了 label-wrap beforeDestroy生命周期函数,这个时候因为 el-dialog 是用的 v-show 所以 display 为 none,这个时候获取 label 的 width 

if (this.$el && this.$el.firstElementChild) 这个判断条件是获取 label 标签因为用了 <keep-alive>,所以还是能找到的,但是window.getComputedStyle(this.$el.firstElementChild).width 获取的值就是 空或者 ’auto'

解决办法 v-show 改为 v-if 就是让 this.$el.firstElementChild 根本查询不到 label 标签,就不会走这个条件分支了,就不会报错了,对功能也没影响。

所以可以在 el-dialog 上加 v-if 和 visible 用一个变量即可,或是在 el-form 上加,或是 el-form-item上加 v-if 但是这种情况得所有 el-form-item 都加上

测试解决了


网站公告

今日签到

点亮在社区的每一天
去签到