Vue 函数定义、事件绑定与列表渲染精讲

Vue 函数定义、事件绑定与列表渲染精讲

一、函数定义的两种写法对比

这是一个折叠面板的完整示例,它包含了数据控制、事件绑定和条件渲染:

html 复制代码
<template>
  <div id="app">
    <h3>案例:折叠面板</h3>
    <div>
      <div class="title">
        <h4>芙蓉楼送辛渐</h4>
        <span class="btn" @click="togglePanel">
          {{ isExpanded ? '收起' : '展开' }}
        </span>
      </div>
      <div class="container" v-show="isExpanded">
        <p>寒雨连江夜入吴,</p>
        <p>平明送客楚山孤。</p>
        <p>洛阳亲友如相问,</p>
        <p>一片冰心在玉壶。</p>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

// 控制面板展开/收起
const isExpanded = ref(true)

const togglePanel = () => {
  isExpanded.value = !isExpanded.value
}
</script>

<style scoped>
body {
  background-color: #ccc;
}
#app {
  width: 400px;
  margin: 20px auto;
  background-color: #fff;
  border: 4px solid blueviolet;
  border-radius: 1em;
  box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
  padding: 1em 2em 2em;
}
#app h3 {
  text-align: center;
}
.title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border: 1px solid #ccc;
  padding: 0 1em;
}
.title h4 {
  line-height: 2;
  margin: 0;
}
.container {
  border: 1px solid #ccc;
  padding: 0 1em;
}
.btn {
  cursor: pointer;
}
</style>

在这个例子里,我们用 togglePanel 函数来切换面板的展开状态。定义这个函数时,我可以选择两种写法。

1. 箭头函数表达式

js 复制代码
const togglePanel = () => {
  isExpanded.value = !isExpanded.value
}

2. 普通函数声明

js 复制代码
function togglePanel() {
  isExpanded.value = !isExpanded.value
}

它们都能完成同样的工作,但在 Vue 3 的 <script setup> 环境下,有一些关键区别,我给大家整理成了一张表格:

特性 箭头函数 const fn = () => {} 普通函数 function fn() {}
提升(Hoisting) 不会被提升,必须先定义后使用 会被提升,可以在定义前调用
this 绑定 词法绑定(继承外层作用域的 this 动态绑定(取决于调用方式)
能否作为构造函数 不能使用 new 可以使用 new
arguments 对象 没有
简洁性 单语句时可省略 {}return 需要完整函数体
在 Vue <script setup> 中的推荐度 推荐(风格一致,避免 this 陷阱) 也可用,但需注意提升导致隐式依赖

我的建议是 :在组合式 API 中我们根本不使用 this,所以箭头函数更简洁、更安全。除非你需要利用函数提升(比如两个函数互相递归调用),或者需要动态 this,否则一律使用箭头函数。记住口诀:箭头优先,除非需要提升或 this


二、事件绑定中括号 () 的用法对比

html 复制代码
<span class="btn" @click="togglePanel">
  {{ isExpanded ? '收起' : '展开' }}
</span>

这里 @click="togglePanel" 没有加括号。但有时候你也会看到 @click="togglePanel()" 的写法。它们有什么区别?

1. 不加括号:@click="togglePanel"

  • 执行时机:点击事件触发时才会调用 togglePanel
  • 传参:Vue 会自动把原生事件对象 $event 作为第一个参数传进去(如果函数需要的话)。
  • 典型用途:处理函数不需要额外参数时,直接写函数名即可。

2. 加括号:@click="togglePanel()"

  • 执行时机:模板渲染时就会立即执行一次 togglePanel(),然后把它的返回值(如果返回值是函数)作为事件处理函数。这种用法非常少见,通常是错误的。
  • 传参:不会自动传递 $event。如果你想传参,必须手动写成 @click="togglePanel($event)"@click="togglePanel('自定义参数')"
  • 典型用途:需要传递自定义参数时,例如 @click="deleteItem(item.id)"。这时我们必须加括号,Vue 会正确地在点击时调用函数并传入参数。

一句话总结

  • 无参 → 不加括号,Vue 自动调用并传入事件对象。
  • 有参 → 加括号,手动传参;如果需要事件对象,用 $event

口诀:无参不加括号,有参加括号且传参。


三、列表渲染与删除实战

来看一个更复杂的案例:品牌列表的展示与删除。下面是完整的代码:

html 复制代码
<template>
  <div id="app">
    <table class="tb">
      <thead>
        <tr>
          <th>编号</th>
          <th>品牌名称</th>
          <th>创立时间</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in list" :key="item.id">
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.time }}</td>
          <td>
            <button @click="deleteItem(item.id)">删除</button>
          </td>
        </tr>
        <tr v-if="list.length === 0">
          <td colspan="4">没有数据咯~</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

interface BrandRecord {
  id: number
  name: string
  time: string
}

const list = ref<BrandRecord[]>([
  { id: 1, name: '奔驰', time: '2020-08-01' },
  { id: 2, name: '宝马', time: '2020-08-02' },
  { id: 3, name: '奥迪', time: '2020-08-03' },
])

// 删除方法
const deleteItem = (id: number) => {
  list.value = list.value.filter((item) => item.id !== id)
}
</script>

<style scoped>
#app {
  width: 600px;
  margin: 10px auto;
}
.tb {
  border-collapse: collapse;
  width: 100%;
}
.tb th {
  background-color: #0094ff;
  color: white;
}
.tb td,
.tb th {
  padding: 5px;
  border: 1px solid black;
  text-align: center;
}
.add {
  padding: 5px;
  border: 1px solid black;
  margin-bottom: 10px;
}
</style>

下面逐步拆解这段代码的逻辑。

1. 模板部分:数据的展示与交互

html 复制代码
<tbody>
  <tr v-for="item in list" :key="item.id">
    <td>{{ item.id }}</td>
    <td>{{ item.name }}</td>
    <td>{{ item.time }}</td>
    <td>
      <button @click="deleteItem(item.id)">删除</button>
    </td>
  </tr>
  <tr v-if="list.length === 0">
    <td colspan="4">没有数据咯~</td>
  </tr>
</tbody>
  • v-for="item in list"

    循环渲染 list 数组中的每一个对象,item 就是当前迭代的品牌对象。

  • :key="item.id"

    key 是 Vue 用来标识每一个循环项的特殊属性,必须唯一且稳定。这里我们用品牌 id,它永远不会重复,能帮助 Vue 高效地更新 DOM。例如删除第二行时,Vue 能精确移除对应的 <tr>,而不是重新渲染整个表格。

  • 插值表达式

    {``{ item.id }}{``{ item.name }}{``{ item.time }} 用来在表格中显示对应数据。

  • 事件绑定

    @click="deleteItem(item.id)" 给删除按钮绑定了点击事件,并传入当前项的 id。注意,因为我们传了参数,所以必须加括号。

  • 条件渲染

    v-if="list.length === 0" 会在列表为空时显示"没有数据咯~"这一行,colspan="4" 让它合并四列占满整行。

2. 脚本部分:数据定义与删除逻辑

ts 复制代码
interface BrandRecord {
  id: number
  name: string
  time: string
}

const list = ref<BrandRecord[]>([
  { id: 1, name: '奔驰', time: '2020-08-01' },
  { id: 2, name: '宝马', time: '2020-08-02' },
  { id: 3, name: '奥迪', time: '2020-08-03' },
])

const deleteItem = (id: number) => {
  list.value = list.value.filter((item) => item.id !== id)
}
  • interface BrandRecord

    这是 TypeScript 的接口,定义了品牌对象必须包含 idnametime 三个属性。这样我们在写代码时就能得到类型检查和智能提示。

  • ref<BrandRecord[]>

    我们声明了一个响应式数组,并通过泛型约束了数组元素的结构。

  • deleteItem 函数

    它接收一个 id 参数,然后用 filter 方法返回一个新数组,其中包含了所有 id 不等于传入 id 的项。相当于把要删除的那一项过滤掉。

    js 复制代码
    list.value.filter((item) => item.id !== id)

    这里 (item) => item.id !== id 是一个箭头函数,它判断每一个元素的 id 是否不等于要删除的 id。如果条件为 true,该元素就被保留;为 false 就被过滤掉。最后我们把新数组重新赋值给 list.value,触发 Vue 的响应式更新,页面就会自动移除那一行。

3. 整体执行流程

  1. 用户点击"宝马"行的删除按钮。
  2. 触发 deleteItem(2)(因为宝马的 id 是 2)。
  3. filter 遍历数组:
    • { id:1 }1 !== 2true → 保留
    • { id:2 }2 !== 2false → 移除
    • { id:3 }3 !== 2true → 保留
  4. list.value 更新为只包含奔驰和奥迪的新数组。
  5. Vue 检测到数据变化,重新渲染表格,宝马行消失。如果所有数据都被删除,会显示"没有数据咯~"。

四、简单列表中的 key 策略:什么时候可以用 index

最后看一个生成随机数并支持删除的简单列表:

html 复制代码
<template>
  <div>
    <ul>
      <li v-for="person in personList">{{ person }}</li>
    </ul>
    <button @click="walk">走一走</button>
    <hr />
    <div>
      <ul>
        <li v-for="(item, index) in numList" :key="index">
          {{ item }} <button @click="del(index)">删除</button>
        </li>
      </ul>
      <button @click="add">生成</button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const personList = ref<string[]>(['帅哥', '美女', '程序员'])
const walk = () => {
  const first = personList.value[0]
  if (first) {
    personList.value = [...personList.value.slice(1), first]
  }
}

let numList = ref<number[]>([])

const add = () => {
  const num = Math.floor(Math.random() * 20 + 1)
  numList.value.push(num)
}
const del = (index: number) => {
  numList.value.splice(index, 1)
}
</script>

注意,这里在 v-for 中使用了 (item, index) in numList,并且 :key="index"

在特定场景下,使用 index 是可以接受的。 原因如下:

  • 列表没有复杂状态 :每个项只是显示一个数字和一个删除按钮,没有 <input><checkbox> 等需要维持内部状态的元素。即使 Vue 因为 index 复用策略导致 DOM 元素错乱,也不会产生可见的 bug。
  • 数据量很小:用户生成几个随机数后删除,重新渲染的开销可以忽略不计。
  • 没有更好的唯一键 :数字可能重复,不能直接当 key;如果强行改造成 { id, value } 结构反而会增加复杂度。

但要清楚,一旦列表变复杂(比如加入了输入框、动画或可排序功能),就必须使用唯一的业务 id。用 index 时删除非末尾项会导致后面所有元素的 key 变化,Vue 会错误地复用 DOM,表现为输入框内容串位、勾选状态错乱等。

总结

  • 简单列表(纯展示 + 删除)且数据量小时,用 index 没问题,可以节省代码。
  • 正式项目或列表可能演变为复杂交互时,请从一开始就设计唯一 id 作为 key。

五、总结

  1. 函数定义 :在 Vue 3 的 <script setup> 中,优先使用箭头函数;除非需要提升或动态 this
  2. 事件绑定括号:无参不加括号,Vue 自动传事件对象;有参必须加括号,手动传参。
  3. 列表渲染与删除 :掌握 v-for:keyv-if,用 filter 实现不可变删除。
  4. key 的选择:唯一 id 是最佳实践;简单场景下 index 也可以应急,但要知道它的局限性。

以上是本人学习过程中遇到的问题,边学变搜索的,如不满意,不喜勿喷哈

相关推荐
神秘代码行者1 小时前
pnpm zip命令详解
前端·npm·pnpm
Xpower 171 小时前
Codex 桌面端更新后 Chrome 插件和 Computer Use 不可用,怎么排查和修复
前端·人工智能·chrome·python·学习
lolo大魔王2 小时前
Gin 框架响应格式与 HTML 模板渲染完整实战教程
前端·html·gin
llz_1124 小时前
web-第二次课后作业
前端·后端·web
vipbic9 小时前
别再把“做个H5”挂嘴边了:这个词,官方压根就没有定义过
前端
ZC跨境爬虫11 小时前
跟着 MDN 学CSS day_39:(Flexbox 弹性盒子核心机制)
前端·css·ui·html·tensorflow
小陈同学呦11 小时前
前端如何处理订单状态导航的数据竞态问题
前端·javascript
开发者每周简报11 小时前
网海三部曲·无名宗师传
javascript·人工智能
喵个咪11 小时前
GoWind Toolkit 前端代码生成|Vue3(ElementPlus/Vben)、React(AntDesign)全自动一键生成教程
前端·vue.js·react.js