新手关于v-for的疑惑,望各位前辈帮帮我

v-for 动态生成表格,单元格内容和背景色随机生成。
然而遇到一个很不解的地方,代码以及页面效果如下

   <div id="container">
      <div class="ipt">
         <label >input: </label>
         <input type="number" v-model.number="rows"> x 
         <input type="number" v-model.number="cols"> 
      </div>
      <p><b>Background Color :</b> {{bgColor}}</p>
      <table class="tab">
        <tr v-for="r in rows" :key="r">
          <td v-for="c in cols" 
          :key="c" 
          :style="{ backgroundColor: color() }"
          @click="getColor">{{ random() }}
          </td>
       </tr>
   </table>
</div>
<script>
  export default{
    name: 'autoTable',
    data () {
      return {
        rows: null,
        cols: null,
        bgColor: null,
      }
    },
    methods: {
      random () {
        console.log("random() has been called")
        return Math.ceil(Math.random() * this.rows * this.cols)
      },
      color () {
        console.log("color() has been called")
        const red = Math.floor(Math.random() * 256)
        const green = Math.floor(Math.random() * 256)
        const blue = Math.floor(Math.random() * 256)
        return `rgb(${red},${green},${blue})`
      },
      getColor (e) {
        this.bgColor = e.target.style.backgroundColor
      },
    },
  }
</script>

为什么当我点击表格时,v-for 会重新渲染表格,甚至当我点击输入框都会触发页面重新渲染,不是应该数据驱动视图吗,但是数据明明没有变啊,只是触发了一个点击事件而已。
希望各位能帮我解惑。。。

确实很奇怪。在英文版发了个贴子,等待高人解答。

非常感谢你:grinning:

在那个帖子里看了你们的讨论,第一位大佬说只有要响应式依赖发生更新,整个组件就会重新渲染,可能就是这个原因吧。
第二位大佬说,应该使用计算属性而不是方法,但是比如像我这例子这样的场景,如果使用计算属性如何实现随机生成不同的内容?计算属性只有当响应式依赖发生改变时才会重新计算,而我这里的 rowscols 不会改变,那计算属性每次的结果不就都一样了?

测试了一下,是 v-model.number的原因;
本来如果单就 v-model 是没有变化的,但是加了 number 之后,
鼠标离开输入框 会再将 string 在转化为number 类型,
这里可能触发了数据的 set,就出现数据的再次渲染;

你是说鼠标进入输入框的时候,rows 是 string,离开输入框的时候,rows 转化为 number,但是我在devtools里看 rows 的状态一直都是 number,并没有发生转换啊

整个事情是这样的:

在 Vue 2.x,当有响应式依赖发生变化时,组件的整个 VDOM 树都会被重新渲染。接下来的 diff、patch 等过程会减少最终附加到 DOM 树时的性能损耗,但模板(或者说 render 函数)本身是经过重新计算的。此时,由于 Vue 无法感知 methods 里的依赖,为了保证更新结果的准确,Vue 会再次执行写到模板里的函数调用。这就是你的代码里背景颜色总是在变化的原因(因为你在模板里调用了一个函数)。

计算属性是可以被 Vue 收集依赖的,用计算属性代替函数调用可以避免这个问题。

或者,考虑到你的代码里单元格的背景颜色只需要一次随机生成,把 color() 的逻辑放到模板之外进行就好了。

<tr v-for="r in rows" :key="r">
  <td
    v-for="c in cols"
    :key="c"
    :style="{ backgroundColor: colors[(r - 1) * rows + (c - 1)] }"
    @click="getColor">
    {{ random() }}
  </td>
</tr>
{
  computed: {
    colors() {
      return new Array(this.rows * this.cols).fill(0).map(_ => this.color())
    }
  },
}

这样可以保证 rowscols 未变化时颜色是稳定的。单元格内容的逻辑可同法改造。

2 Likes

也就是说,由于模板已经计算过了,如果在模板里有 method 调用,而这时候你改变了某个响应式依赖的值,由于 Vue 无法感知已经计算过的模板里的 method 内的依赖,所以不管这个 method 里的依赖是不是你更改的那个依赖,为了保证结果的准确,Vue 都会重新调用模板里 method,是这样吗?

那模板里的事件注册属于这种这种情况吗

非常感谢,我是新手,一个人学,有时候真的连问的人都没有,最近才发现这个论坛,希望以后能多多和你交流!:grinning:

「事件注册」是指 @click="getColor" 这种?这个不是函数调用,它实际上是函数定义,最终变成了类似于这个样子的东西:element.onclick = function () { vm.getColor() }

1 Like

懂了,谢谢,我学的太浅了,很多实质都没搞清楚 :sweat_smile:

因为版本的原因,有点搞错了 光是改 number 的方法在2.4以后版本是可行的,
demo:http://en.jsrun.net/dyqKp;
而我这个例子在2.2版本以下是不可行的,还是得添加依赖才可行;

楼主的问题是,在 rowscols 已经确定的情况下,如何阻止单击单元格导致单元格背景色和内容自动变化。

看错了…
不过一般来说不是纯函数,没有存入某个地方,肯定会变化,我还以为都知道,我就解释了一下点击输入框为什么会变化.

源码我看不太懂 :sweat_smile:, 但是我觉得你说得有道理,由于使用 v-model.number,鼠标移入移出输入框肯定是导致了某个依赖 (rowscols) 发生了变化,但是这个改变没有在外部表现出来,而 Vue 是能感知到的,所以如 @silentdepth 所说的,Vue会重新计算模板里的method,所以导致了表格内容发生了变化