Vue 进阶实战:事件修饰符、表单控制、购物车与 Ajax 请求

一、昨日内容回顾

上一节学习了 Vue 的数据绑定、条件渲染、列表渲染、事件监听和 v-model。本节继续使用 Vue 2 示例,重点补充 JavaScript 的箭头函数、循环方式、事件修饰符、表单控件,以及前端通过 Ajax 请求后端接口的方法。

二、ES6 箭头函数

箭头函数是 ES6 提供的函数简写形式,适合编写短小的回调函数。它可以省略 function,只有一个参数时还可以省略参数括号,只有一个表达式时可以省略大括号和 return

js 复制代码
// 普通函数
function add(a, b) {
  return a + b
}

// 箭头函数的几种写法
const sayHello = () => {
  console.log('hello')
}
const square = n => n * n
const sum = (a, b) => a + b

sayHello()
console.log(square(4))
console.log(sum(4, 6))

箭头函数没有自己的 thisargumentsprototype。其中最重要的是 this:箭头函数会捕获定义位置外层作用域的 this,不会因为调用方式改变。Vue 的 methods 建议使用普通方法,让 Vue 正确绑定组件实例;在方法内部作为回调时,箭头函数可以方便地继续使用外层的 this

js 复制代码
new Vue({
  data: { search: '', words: ['vue', 'react', 'angular'] },
  methods: {
    filterWords() {
      // filter 的回调使用箭头函数,可以直接访问外层方法的 this。
      return this.words.filter(word => word.includes(this.search))
    }
  }
})

三、JavaScript 常见循环

不同循环适合不同场景。for 可以控制下标和步长,for...in 遍历对象属性名,for...of 遍历可迭代对象中的值,forEach 适合对数组的每一项执行操作。

js 复制代码
const numbers = [3, 4, 5]

// 传统 for:可以使用下标、break 和 continue。
for (let i = 0; i < numbers.length; i++) {
  console.log(i, numbers[i])
}

// for...in:数组中得到下标,对象中得到属性名。
for (const index in numbers) {
  console.log(index, numbers[index])
}

const user = { name: 'lqz', age: 19 }
for (const key in user) {
  console.log(key, user[key])
}

// for...of:得到数组、字符串等可迭代对象的值。
for (const value of numbers) {
  console.log(value)
}

// forEach:参数依次是 value、index、array。
numbers.forEach((value, index) => {
  console.log(index, value)
})

普通对象不是可迭代对象,不能直接使用 for...of。如果需要以键值对形式遍历,可以使用 Object.entries(user);Vue 模板中的 v-for 则可以直接遍历对象。

js 复制代码
Object.entries(user).forEach(([key, value]) => {
  console.log(key, value)
})

四、事件修饰符

Vue 事件修饰符可以把常见的 DOM 事件处理写在模板中,减少手动调用 event.stopPropagation()event.preventDefault() 的代码。

修饰符 作用
.stop 阻止事件继续冒泡
.self 只有点击当前元素本身时才处理
.prevent 阻止浏览器默认行为
.once 事件只触发一次
.capture 使用捕获阶段监听事件
html 复制代码
<div id="app">
  <div class="panel" @click="parentClick">
    <button @click.stop="buttonClick">点击后不触发父元素</button>
  </div>

  <div class="panel" @click.self="selfClick">
    <button @click="childClick">点击按钮不会触发 selfClick</button>
  </div>

  <a href="https://example.com" @click.prevent="linkClick">暂不跳转</a>
  <button @click.once="pay">只能点击一次</button>
</div>

.self.stop 的含义不同:.stop 是阻止事件向上冒泡,.self 是判断事件目标是否就是当前元素。修饰符只应处理交互行为,权限、支付等关键逻辑仍必须在后端再次校验。

五、按键修饰符

键盘事件可以使用 .enter.esc.tab.delete.up.down.left.right 等修饰符,也可以监听系统组合键。

html 复制代码
<div id="app">
  <input
    v-model="keyword"
    @keyup.enter="search"
    placeholder="输入关键词后按回车"
  >
  <button @click="search">搜索</button>
</div>

<script>
  new Vue({
    el: '#app',
    data: { keyword: '' },
    methods: {
      search() {
        console.log('搜索:', this.keyword)
      }
    }
  })
</script>

相比直接使用数字 keyCode,语义化的 .enter 更容易理解,也不依赖不同浏览器的键码差异。需要读取更多键盘信息时,可以在方法参数中接收原生事件对象。

六、表单控件与 v-model

v-model 会根据控件类型选择合适的属性和事件:文本框通常对应字符串,单个复选框对应布尔值,多个同名复选框对应数组,单选框对应字符串或数字。

html 复制代码
<div id="app">
  <p>用户名:<input v-model.trim="username"></p>
  <p>密码:<input type="password" v-model="password"></p>

  <label><input type="checkbox" v-model="remember"> 记住登录</label>

  <p>爱好:</p>
  <label><input type="checkbox" v-model="hobbies" value="篮球"> 篮球</label>
  <label><input type="checkbox" v-model="hobbies" value="足球"> 足球</label>
  <label><input type="checkbox" v-model="hobbies" value="游泳"> 游泳</label>

  <p>性别:</p>
  <label><input type="radio" v-model="gender" value="男"> 男</label>
  <label><input type="radio" v-model="gender" value="女"> 女</label>

  <button @click="submit">提交</button>
</div>

<script>
  new Vue({
    el: '#app',
    data: {
      username: '',
      password: '',
      remember: false,
      hobbies: [],
      gender: ''
    },
    methods: {
      submit() {
        console.log({
          username: this.username,
          password: this.password,
          remember: this.remember,
          hobbies: this.hobbies,
          gender: this.gender
        })
      }
    }
  })
</script>

v-model 只负责在前端同步数据,不代表表单已经安全。登录、注册等数据仍需要后端校验,密码也不能明文保存。

七、购物车综合案例

购物车同时练习了 v-forv-model、事件、计算总价和全选逻辑。选中商品数组保存的是商品对象,数量变化后总价会根据当前选中项重新计算。

html 复制代码
<div id="app">
  <table>
    <thead>
      <tr>
        <th>商品</th><th>单价</th><th>数量</th><th>选择</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(item, index) in goods" :key="item.id">
        <td>{{ item.name }}</td>
        <td>{{ item.price }} 元</td>
        <td>
          <button @click="decrease(item)">-</button>
          {{ item.number }}
          <button @click="item.number++">+</button>
        </td>
        <td>
          <input type="checkbox" v-model="selectedGoods" :value="item">
        </td>
      </tr>
    </tbody>
  </table>

  <label>
    <input type="checkbox" v-model="checkAll" @change="toggleAll">
    全选
  </label>
  <p>总价:{{ totalPrice }} 元</p>
</div>

<script>
  new Vue({
    el: '#app',
    data: {
      goods: [
        { id: 1, name: '脸盆', price: 9, number: 2 },
        { id: 2, name: '水杯', price: 19, number: 1 },
        { id: 3, name: '电脑', price: 6999, number: 1 }
      ],
      selectedGoods: [],
      checkAll: false
    },
    computed: {
      totalPrice() {
        return this.selectedGoods.reduce(
          (total, item) => total + item.price * item.number,
          0
        )
      }
    },
    watch: {
      selectedGoods: {
        deep: true,
        handler(value) {
          this.checkAll = value.length === this.goods.length
        }
      }
    },
    methods: {
      toggleAll() {
        this.selectedGoods = this.checkAll ? this.goods.slice() : []
      },
      decrease(item) {
        if (item.number > 1) item.number--
      }
    }
  })
</script>

总价属于"由已有数据计算出来的值",因此使用 computed 比每次在模板中调用方法更合适:依赖不变时会缓存结果。全选状态与单个商品选择状态互相影响,使用深度监听可以在选择数组变化后同步更新全选框。

八、v-model 修饰符

v-model 修饰符用于改变同步时机或数据类型:

  • .lazy:从 input 事件改为在 change 后同步。
  • .number:尝试把输入值转换为数字。
  • .trim:去除输入字符串首尾空格。
html 复制代码
<div id="app">
  <input v-model.lazy="text" placeholder="失去焦点后同步">
  <input v-model.number="amount" placeholder="尝试转换数字">
  <input v-model.trim="name" placeholder="去除首尾空格">
  <p>{{ text }} / {{ amount }} / {{ name }}</p>
</div>

.number 不是严格的类型校验,空字符串或无法转换的输入仍可能保留原值。金额、年龄等数据在提交前仍应在后端进行类型和范围校验。

九、Ajax 与接口请求

Ajax 泛指浏览器在页面不刷新时与服务器交换数据的方式。早期可以直接使用 XMLHttpRequest,jQuery 对它做了封装;现代项目更常使用 fetch 或 axios。axios 提供统一的请求配置、拦截器、错误处理和 JSON 转换,适合在 Vue 项目中集中管理接口。

1. axios 请求示例

html 复制代码
<div id="app">
  <button @click="loadUser">加载用户信息</button>
  <p>用户名:{{ user.username }}</p>
  <p>年龄:{{ user.age }}</p>
  <p>性别:{{ user.gender }}</p>
  <p v-if="error" class="error">{{ error }}</p>
</div>

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
  new Vue({
    el: '#app',
    data: {
      user: { username: '', age: '', gender: '' },
      error: ''
    },
    methods: {
      async loadUser() {
        this.error = ''
        try {
          const response = await axios.get('http://127.0.0.1:8888/info')
          // axios 的响应体位于 response.data。
          this.user = response.data
        } catch (error) {
          console.error(error)
          this.error = '用户信息加载失败,请稍后重试'
        }
      }
    }
  })
</script>

使用 async/await 可以让异步流程更接近同步代码;try/catch 用于处理网络失败和非预期响应。实际项目还应处理加载中状态、HTTP 状态码和接口业务错误码。

2. Flask 接口示例

python 复制代码
from flask import Flask, jsonify

app = Flask(__name__)


@app.get('/info')
def info():
    response = jsonify({
        'username': '张三',
        'age': 99,
        'gender': '男',
    })
    # 仅用于演示;生产环境应限制为明确的前端域名。
    response.headers['Access-Control-Allow-Origin'] = 'http://localhost:8080'
    return response


if __name__ == '__main__':
    app.run(port=8888)

当前端和后端的协议、域名或端口不同时,浏览器会执行同源策略检查,服务端需要通过 CORS 响应头明确允许来源。Access-Control-Allow-Origin: * 不适合携带凭证的请求,也不建议在生产环境无限制开放。

十、总结与练习

  1. 箭头函数适合回调,重点理解它没有自己的 this;Vue 的方法本身不要随意改成箭头函数。
  2. for...in 遍历键,for...of 遍历值,数组还可以使用 forEachmapfilter 等方法。
  3. 事件修饰符负责冒泡、默认行为和触发次数;按键修饰符让键盘交互更直观。
  4. 复选框的 v-model 可以绑定布尔值或数组,购物车应使用唯一 key、计算属性和清晰的全选逻辑。
  5. axios 适合 Vue 项目中的接口请求,跨域问题需要由后端 CORS 配置配合解决。
相关推荐
雪芽蓝域zzs2 小时前
(六)打包优化 + Nginx 部署完整配置 + 项目收尾
前端·vue.js
研☆香2 小时前
聊一聊前端的常见字 关键字
开发语言·前端·javascript
东风破_2 小时前
JWT 1:从一个登录请求开始,理解 React 项目里的 API 层与 Mock
前端·后端
东风破_3 小时前
JWT 3:为什么 Token 要放进 Authorization?Axios 拦截器到底解决了什么?
前端·后端
东风破_3 小时前
JWT 5:路由守卫是什么?把整个 JWT 登录鉴权流程串起来
前端·后端
东风破_3 小时前
JWT 2:HTTP 是无状态的,为什么登录成功后还要给 Token?
前端·后端
东风破_3 小时前
JWT 4:Zustand 到底解决了什么?为什么登录状态要放进 Store?
前端·后端
IT_陈寒3 小时前
Vite动态导入差点让我秃头,原来问题出在这
前端·人工智能·后端
白泽_hunter3 小时前
项目里最常见的 5 个 this 指向坑:从规则到实战彻底讲透
前端
宿6743 小时前
vue3-pinia
前端·vue.js