千里不留行 发布的文章



引言

在 Vue 开发中,列表渲染是我们经常使用的功能,而 v-forkey 的配合使用则是优化渲染性能的关键。然而,许多开发者在处理 <template v-for> 时,常常会遇到关于 key 放置位置的 ESLint 警告,甚至出现看似矛盾的规则冲突。本文将带你理清这些规则背后的逻辑,明确不同 Vue 版本下的最佳实践。

问题背景

你可能遇到过以下两种 ESLint 警告:

  1. 旧规则警告
    '<template v-for>' cannot be keyed. Place the key on real elements instead
    (vue/no-v-for-template-key)
  2. 新规则警告
    '<template v-for>' key should be placed on the '<template>' tag
    (vue/no-v-for-template-key-on-child)

这两种看似矛盾的警告实际上反映了 Vue 社区对 v-for 使用方式的规则演变。

规则演变解析

Vue 2.x 时代的实践

在 Vue 2 时期,官方推荐将 key 放在实际渲染的子元素上:

<!-- Vue 2 推荐写法 -->
<template v-for="item in items">
  <div :key="item.id">{{ item.name }}</div>
</template>

原因

  • <template> 标签不会被渲染到 DOM 中
  • key 需要直接作用于实际 DOM 元素以帮助 Vue 的虚拟 DOM 算法识别节点

Vue 3.x 的新规范

随着 Vue 3 的发布,官方调整了最佳实践,推荐将 key 放在 <template> 标签上:

<!-- Vue 3 推荐写法 -->
<template v-for="item in items" :key="item.id">
  <div>{{ item.name }}</div>
</template>

变化原因

  1. 逻辑一致性:整个 <template> 块被视为一个重复单元
  2. 减少遗漏:避免开发者忘记在子元素上加 key
  3. 支持 Fragments:更好地配合 Vue 3 的多根节点特性
  4. 简化代码:集中管理 key 更清晰

解决规则冲突

如果你同时看到两种警告,可以按照以下步骤解决:

1. 更新 ESLint 配置

// .eslintrc.js
module.exports = {
  rules: {
    'vue/no-v-for-template-key': 'off', // 禁用旧规则
    'vue/no-v-for-template-key-on-child': 'error' // 启用新规则
  }
}

2. 升级相关依赖

确保你使用的是最新版本的 ESLint Vue 插件:

npm install eslint-plugin-vue@latest --save-dev

3. 检查 Vue 版本

  • Vue 2 项目:建议遵循旧规则(key 在子元素)
  • Vue 3 项目:遵循新规则(key 在 <template>

特殊场景处理

多根节点情况(Fragments)

在 Vue 3 中,当 <template> 内有多个根节点时:

<template v-for="item in items" :key="item.id">
  <div>{{ item.name }}</div>
  <div>{{ item.description }}</div>
  <div>{{ item.price }}</div>
</template>

仍然只需要在 <template> 上加一个 key 即可,不需要为每个子元素加 key

组件列表渲染

渲染组件列表时同样适用:

<template v-for="item in items" :key="item.id">
  <ListItem :item="item" />
</template>

为什么 key 如此重要?

无论 key 放在哪里,它的核心作用始终不变:

  1. 高效更新:帮助 Vue 识别哪些元素被更改、添加或删除
  2. 状态保持:确保元素在重新渲染时保持正确的状态
  3. 性能优化:减少不必要的 DOM 操作

最佳实践总结

场景Vue 2 写法Vue 3 写法
基本列表渲染<template v-for><div :key><template v-for :key><div>
多根节点不支持<template v-for :key><div><div>
组件列表<template v-for><Comp :key><template v-for :key><Comp>
ESLint 规则vue/no-v-for-template-keyvue/no-v-for-template-key-on-child

结语

Vue 3 对 <template v-for>key 放置规则的调整,反映了框架设计理念的演进。作为开发者,理解这些变化背后的原因,比记住规则本身更重要。无论你使用哪个版本,关键是要:

  1. 保持一致性:在项目中统一 key 的放置位置
  2. 使用有意义的 key:优先使用唯一 ID 而非数组索引
  3. 关注工具更新:及时调整 ESLint 配置以适应新规范

希望本文能帮助你彻底解决关于 v-forkey 的困惑,写出更高效、更符合规范的 Vue 代码!

只需要通过 View Transitions API 切换不同的类名,即可实现流畅的切换动画

2025-03-16T07:07:52.png

命名 view-transition-name
在 View Transitions 动画执行的过程中,默认会在页面根节点下自动创建一组伪元素:

::view-transition  // 视图过渡根元素,包含所有视图过渡组,且位于其他页面内容的顶部
├─ ::view-transition-group(root)  // 默认视图过渡组 (root)
    └─ ::view-transition-image-pair(root)  // 承载一个过渡中旧视图状态和新视图状态的容器
        ├─ ::view-transition-old(root)  // 旧视图状态
        └─ ::view-transition-new(root)  // 新视图状态

通过调用 API,让浏览器为新旧两种不同视图分别捕获并建立了快照 (即 ::view-transition-old 旧快照 和 ::view-transition-new 新快照),而后新旧两快照在 ::view-transition-image-pair 容器中完成转场动画的过渡。动画结束后则删除其相关伪元素 (快照和容器)。
动画执行的基本过程如下图所示:
2025-03-16T07:15:01.png

  • 开发者通过 document.startViewTransition(callback) 启动转场动画,其中 callback
    函数是用来更新 DOM 状态 (即更新为新视图状态)
  • 捕获当前状态为旧视图状态
  • 暂停 DOM 树渲染
  • 回调函数 callback 被调用,用来更新文档状态 (可以是异步函数,返回 Promise)
  • 回调函数 callback 成功后,transition.updateCallbackDone 被执行 (即 promise is
    resolved)
  • 恢复 DOM 树渲染,而后捕获当前状态为新视图状态
  • 创建过渡伪元素 (即 ::view-transition-old::view-transition-new ...等)
  • 渲染未暂停,显示过渡伪元素
  • transition.ready 被执行 (即 promise is resolved)
  • 伪元素开始动画,直至动画完成
  • 删除了过渡伪元素
  • transition.finished 被执行 (即 promise is resolved)

若需要使某个元素执行过渡动画,需要给每个元素添加一个自定义属性:view-transition-name,且每个元素的 view-transition-name 必须唯一,即同一个页面上渲染的元素(display 非 none) view-transition-name 不同重复。

- 阅读剩余部分 -

特性方面,箭头函数和普通函数存在诸多不同。

特性箭头函数普通函数
this 绑定不会创建自身的this,而是从定义位置的上下文中继承this值,即遵循词法作用域,其this指向外层最近的非箭头函数的this在调用时动态决定this的指向,取决于调用方式,如直接调用时指向全局对象,作为对象方法调用时指向该对象,作为构造函数调用时指向新实例。
使用 new 调用不能作为构造函数使用,若使用new调用会抛出错误。可以作为构造函数使用,在实例化新对象时,this指向该新实例。
arguments 对象的处理没有arguments对象,需使用 rest 参数(...args)来获取参数数组。内部有自己的arguments对象,可通过它访问传入的所有参数。
隐式返回对于单行表达式可以省略大括号和return关键字,直接返回结果,简化了写法。必须显式地使用return关键字返回结果。
作为对象方法使用不适合直接作为对象方法,因为它没有自己的thisthis会指向定义位置的外部上下文,而非调用它的对象。更适合作为对象方法,其this会指向调用者,即该对象。
性能在特定情况下会略微节省内存,尤其是在较少嵌套的环境中,因为无需管理thisarguments。但这种差异在绝大多数情况下微乎其微。在某些优化场景下性能可能更好,尤其是在函数调用频繁且需要频繁重新绑定this的情况下。
绑定上下文无法通过bind()call()apply()改变this的指向,其this永远指向定义时的上下文。可以使用bind()call()apply()显式改变this指向,动态绑定不同的上下文。
函数声明提升不支持函数声明提升。支持函数声明提升。

- 阅读剩余部分 -