safari/vuejs-v-for

更改 safari 输入框内容的颜色(disabled 时)

-webkit-text-fill-color: #222222;
opacity: 1;

vuejs v-for 的渲染及节点复用

场景是这样:
父组件是筛选面板 FilterPanel, 子组件是筛选卡片 FilterPanelCard

筛选面板组件中 filters 里存储了几个筛选卡片, 每个筛选卡片都用一个 filterPanelCard 来渲染.
只有在 filterPanelCard 中点击了确定按钮, 才会把 filterPanelCard 中的筛选条件更新到 filterPanel 的 filters 数组中.

当:
- 勾选了筛选卡片 A 的条件, 但是还没有点击确定按钮 - 然后去删除了 A 前面的筛选卡片 B, 然后 A 卡片已经勾选的条件就被清空了

第一反应是卡片 A 被重新渲染了, 因为对应的筛选条件的数据 filters[indexA] 并没有被子组件更新, 所以重新渲染了一个空的 filterA.

问题变成了: 为什么卡片 A 被重新渲染了, 明明设置了 :key="index". 看看文档怎么说:

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序...这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有的且唯一的 id。
建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

这段话说明: - 如果没有 key 值, dom 是要被复用的 - 除非 DOM 非常简单, 否则都建议提供 key 值 - key 值要唯一, vue 根据 key 来排序 dom

为了解释这一点, 构造一个 demo:

Vue.component('Card', {
  template: '<span>{{ item }}-{{ value }}</span>',
  props: ['item'],
  data () {
    return {
      value: Math.random() * this.item
    }
  },
  created () {
    console.log(`${this.item} created`)
  },
  updated () {
    console.log(`${this.item} updated`)
  },
  destroyed () {
    console.log(`${this.item} destroyed`)
  }
})
const vue = new Vue({
  el: '#app',
  data () {
    return {
      items: ['A', 'B', 'C', 'D', 'E']
    }
  }
})
<div id="app">
  <div v-for="(item, index) of items" :key="index" @click="items.splice(index, 1)">
    <card :item="item"/>
  </div>
</div>

如果没有 key 值, dom 是要被复用的

如果 v-for 不提供 key 值, 当点击字母 C, items 数组中的 'C' 就被删掉了. 但是之前渲染 'C' 的子组件并不会被销毁, vue 采用的策略是: 销毁最后一项 'E', 然后复用'D'和'C', 只不过是把原来'D'组件的数据更新为'E', 把'C'组件的数据更新为'D'.
所以控制台的输出为:

"E destroyed"
"E updated"
"D updated"

除非 DOM 非常简单, 否则都提供 key 值

"DOM 非常简单", 我的理解是: "除了 props 的值, 没有其他状态(如子组件的内部状态或者, 如 html 的 input 之类的状态)"

这个 demo, 如果子组件是没有自己的状态, 那么 vue 的策略应该是没问题的, 更新数据会比移动 dom 节点要高效. 但是如果有子组件有自己的状态, 那么复用就产生了问题: 字母和它之后的数字的对应关系被破坏了.

key 值要唯一

现在提供 key 值为 index, 一开始确实唯一, 但是数组是可变的, 当删除数组项时, dom 和 key 值的对应关系就破坏了, 渲染 'E' 的子组件之前的 key 值是 4, 删除 'C' 之后, 它的 key 值是 3, 从结果看, 3 这个 key 值被两个子组件使用过, 并不唯一 .
之前的5个节点, key 值是 0, 1, 2, 3, 4; 之后是 4 个节点, key 值是 0, 1, 2, 3. 所以虽然删除的是 'C', 但是 destoryed 的还是 'E' 的组件. 控制台输出仍然是:

"E destroyed"
"E updated"
"D updated"

只有维持节点和 key 的对应关系, 才能到达只 destroyed 'C' 组件的目的, 比如把 key 设置为 item. 如果数组中没有任何唯一的数据, 可以迭代数据为它添加一个 id (和数据库的自增 index 类似).