Vue 条件渲染问题

如图,在我的vue应用中有一个element-ui表格组件,某一行最后一列的内容是对应该行的一个按钮:


某一行的按钮在按下后,这一行的按钮应当变成另一个有着不同的事件的按钮:

为此我采用条件渲染来实现:

<el-table-column
                        header-align="center"
                        align="center"
                        fixed="right"
                        label=""
                        width="100px">
                        <template slot-scope="operationButtonScope">
                            <el-button
                                    v-if="flag"
                                    type="success"
                                    style="font-size: small"
                                    @click="handleSelect(operationButtonScope.$index)">
                                            选取</el-button>
                            <el-button
                                    v-else
                                    type="danger"
                                    style="font-size: small"
                                    @click="handleCancelSelect(operationButtonScope.$index)">
                                            退选</el-button>
                        </template>
</el-table-column>

在这个vue组件data()返回的对象中定义flag如下:

data() {
  return {
    flag: true,
    // ...
  }
}

methods定义如下:

methods: {
            handleSelect(index) {
                console.log('select index ' + index);
                this.flag = false;
            },
            handleCancelSelect(index) {
                console.log('cancel select index ' + index);
                this.flag = true;
            }
        }

至此,选择性地渲染按钮至少是实现出来了,而且切换非常流畅,点击按钮到按钮切换没有明显的时延:

当然这样会让所有行的按钮都跟着切换。于是我使用一个布尔数组存放每一行的flag:

data() {
  return {
    flagArray: [true, true],
    // ...
  }
}

相应地修改了条件渲染的表达式:

<el-table-column
                        header-align="center"
                        align="center"
                        fixed="right"
                        label=""
                        width="100px">
                        <template slot-scope="operationButtonScope">
                            <el-button
                                    v-if="flagArray[operationButtonScope.$index]"
                                    type="success"
                                    style="font-size: small"
                                    @click="handleSelect(operationButtonScope.$index)">
                                            选取</el-button>
                            <el-button
                                    v-else
                                    type="danger"
                                    style="font-size: small"
                                    @click="handleCancelSelect(operationButtonScope.$index)">
                                            退选</el-button>
                        </template>
</el-table-column>

methods的改动:

methods: {
            handleSelect(index) {
                this.flagArray[index] = false;
                console.log('flag ' + index + ' has turned to ' + this.flagArray[index]);
                console.log(this.flagArray);
            },
            handleCancelSelect(index) {
                this.flagArray[index] = true;
                console.log('flag ' + index + ' has turned to ' + this.flagArray[index]);
                console.log(this.flagArray);
            }
        }

现在问题来了:


当我点击第一行的按钮时,控制台输出了预想的内容,说明数组中的内容确实被改变了,但页面看起来却并没有任何响应,按钮没有被重新渲染,当我关闭控制台后,按钮才被正确地“替换”:

多次试验发现,无论点击按钮多少次,无论等待多久,按钮都不会被替换,除非我打开或关闭控制台。
vue的文档中指出频繁的切换应该使用v-show做条件渲染,但是改用v-show命令后问题没有任何改变。
请问问题出在哪?如果用我的写法无法规避这个问题,按钮的“替换”是否有更好的实现方法?
谢谢耐心看到这里的各位。

除非在 data() 里面将 flagArray 中可能用到的成员全部显式声明一遍,如 flagArray: [true, true] ,不然对未声明的数组成员进行操作是无响应性的,因为未经过 observable() 处理。

所以针对 flagArray 中一些未被 observable() 的数组成员执行 this.flagArray[index] = true 不会通知视图重渲。

解决问题的方法:


this.flagArray[index] = true;
改为
this.$set(flagArray, index, true);

这样 $set() 会通知视图重渲。

你的写法存在安全隐患,由于 flagArray 是和 rowIndex(行索引) 有着强关联性的,意味着如果删除掉第一行或其他行,flagArray 若不进行同步变更的话其成员所对应的行也就错误了。对于此问题,最简单的解决办法就是让 flagArray 储存 id 而不是 index

还有另一种更传统的写法,可以直接取出选中的结果数组和数量,例:

<template>
  <div>
    选中数量: {{ selected.length }}<br />
    选中结果: {{ selected }}<br />
    <div v-for="row of rows" :key="row.id">
      {{ row.text }}

      <!-- 操作 对象 -->
      <button v-if="isSelected(row)" @click="onCancel(row)">取消</button>
      <button v-else @click="onSelect(row)">选中</button>

      <!-- 操作 对象id -->
      <!-- <button v-if="isSelected(row.id)" @click="onCancel(row.id)">取消</button>
      <button v-else @click="onSelect(row.id)">选中</button> -->
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      rows: [
        {
          id: 1,
          text: "aaa",
        },
        {
          id: 2,
          text: "bbb",
        },
        {
          id: 3,
          text: "ccc",
        },
      ],
      selected: [], // selected 存储 row.id 或者 row 对象
    };
  },
  methods: {
    isSelected(row) {
      return this.selected.includes(row);
    },
    onSelect(row) {
      this.selected.push(row);
    },
    onCancel(row) {
      const i = this.selected.indexOf(row);
      if (i !== -1) this.selected.splice(i, 1);
    },
  },
};
</script>

受教了,感谢!另外我想知道,打开或关闭控制台时,页面或者说vue内部发生了什么,让这时候的按钮可以被正确地渲染?

打开关闭控制台,页面部分重新绘制了