Vue 项目开发常见 bug 整理与解决方案(三)

304 阅读2分钟

Vue 开发项目的一个特点就是组件化开发。具有相同功能的模块可以封装成一个组件,在需要的地方作为子组件导入使用即可。这样开发确实提高了效率,但同时也增加了出现 bug 的概率。这篇文章主要是为了记录我在开发项目时遇到的问题以及自己解决问题的思路和方法。 问题说明: 项目需求是一个实验室管理的网页,功能要实现物料新增和编辑的功能。因为新增和编辑的内容是一样的,只是初始值不一样。所以共用组件 ItemEditForm,区别是新增是页面,编辑是弹框。进入新增页面希望编辑框内容是默认数据。而点击编辑按钮弹出的编辑框内容是已经新增的物料信息。一开始我觉得这个功能挺简单的,但是后来实现发现在入库记录列表中点击某条记录编辑按钮的时候,始终没有接收到父组件传入的值,要再点击另外一条记录的编辑按钮才出现上一条记录的内容。大概就是数据延迟。于是我就想到一个办法就是在组件渲染时就对数据进行初始化,定义一个初始化函数 initData, 在 created 中调用,代码如下:

 created() {
    this.$store.dispatch('admin/getItemTree');
    this.$store.dispatch('adlab/getImagesList');
    this.initData();
  },
  mounted() {},
  methods: {
    /**
     * 数据初始化
     */
    initData() {
      if (this.dialogVisible) {
        this.initEditForm();
        this.getFeatruesData(this.editForm.class_id);
      } else {
        this.getFeatruesData(this.editForm.class_id);
         if (this.locationTreeData[0]) {
            const arr = this.$helper.getLastTree(this.locationTreeData[0].children, {});
          if (arr) {
            this.editForm.location_id = arr[1];
          }
        }
      }
    }

初始化的过程中,要注意一点,就是因为有两个地方引用了这个组件,一个是入库,一个是编辑,编辑的内容是弹框。所以我就用参数 dialogVisible 来判断是入库还是编辑。如果是编辑就初始化就是该条记录的数据,如果是入库初始化数据就为默认值。

新增页面效果图如下:

09.png

入库库编辑框效果图如下:

12.png

这个问题算是解决了,但是后来又出现了另外一个问题,就是除了入库页面中用到了这个组件,物料库列表也会用到这个组件。当在物料库列表中点击编辑按钮,弹出弹框,出现相对应的数据,如图所示:

10.png

然后关闭弹框,再回到入库新增页面,就会出现上一次编辑存留的数据。效果图如下:

11.png

这个并不是我想要看到的。于是我就想是不是可以在关闭弹框的时候清空数据,这确实是一个办法,但是如果用户习惯不是点击取消按钮,而是直接点击 el-dialog 右上角的关闭按钮,这样一来就没有办法清空数据。而且即使清空数据,这个问题仍然存在,因为在入库页面中,ItemEditForm 组件已经创建并渲染数据,没有办法重新渲染内容。所以进入入库新增页面数据仍然存在。这样一来这个方法还是行不通,只能另求出路,其实有一个最简单粗暴的方法,就是在物料库列表中不引用 ItemEditForm 组件,这样一来数据就不会复用。但是这样就会增加许多重复的代码,代码不够优化。于是我就在想有什么办法可以让 ItemEditForm 组件在入库页面中重新渲染呢?于是我就把目光投向了点击入库页签这个事件,是否可以在点击这个页签的时候做一些什么事情。入库页面代码如下:

<div v-if="activeName === 'finish'" style="margin: 20px">
      <ItemTable
        :item-data="itemStockData.list"
        :count="itemStockData.count"
        show-more="admin"
        :page="page"
        @limit="changeLimit"
        @page="changePage"
      >
        <template slot="button" slot-scope="item">
          <div>
            <el-button
              type="primary"
              size="mini"
              class="project-btn"
              :disabled="item.item.row.checkout"
              @click="editItem(item.item.row)"
            >{{ $t('lab.item.Edit') }}</el-button>
          </div>
          <div>
            <el-button
              type="success"
              plain
              size="mini"
              class="project-btn"
              @click="downloadFile(item.item.row.file)"
            >{{ $t('lab.item.Material') }}</el-button>
          </div>
        </template>
      </ItemTable>
    </div>
    <div v-else>
      <ItemEditForm :dialog-visible="dialogVisible" @changeStatus="changeStatus" />
    </div>

从代码中可以看到,当 activeName 这个参数等于 'finish' 时,渲染的是 ItemTable 这个组件,否则渲染的是 ItemEditForm 这个组件,因为在进入这个页面是,activeName 的值是默认为 'doing' 的,所以在点击物料库中的编辑按钮后,回到入库页面时,activeName 的值没有变,仍然是 'doing'。所以这就是为什么编辑框中的数据仍然保留上一次记录。于是我就想到在点击入库页签时发生事件,将 activeName 的值改为 'finish',然后再改为 'doing',相当于刷新一下入库页面。 点击页签事件是发生在它的父组件中,于是截取部分父组件代码如下:

  watch: {
    itemDetailData: function() {
      this.flag = true;
    }
  },
  created() {},
  mounted() {},
  methods: {
    /*
     ** 切换页签
     */
    handleClick(val) {
      this.activeName = val.name;
      if (val.name === 'stock') {
        if (this.status === 'doing' && this.flag) {
           this.status = 'finish';
           this.$nextTick(() => {
              this.status = 'doing';
          });
        }
      }
    },
    /*
     ** 子组件返回父组件数据
     */
    changeStatus(val) {
      this.status = val;
    }
  }

flag 是通过监测物料库中的编辑框有没有数据变化,有变化 flag 赋值为 true,否则为 false。status 是子组件入库 ItemStock 返回的状态,也就是 activeName 的值,如果 status 是 'doing' 并且 flag 为 true 时就进行一次刷新页面。这样一来,ItemEditForm 组件就可以重新初始化数据并渲染了。这个方法是我目前想到的方案,纯属是偷懒的办法,在不改变代码结构的情况下。如果后续发现更优的办法,我再补充进来。