vue事件机制、表单、watch、computed

本文详细介绍了Vue中的事件机制,包括事件绑定、参数传递、事件修饰符、表单v-model以及watch与computed的区别。通过实例演示了如何利用v-on和v-model简化DOM操作,以及如何利用事件修饰符处理常见事件行为。

事件机制

概述 在dom阶段,我们已经讲述了事件机制的特点:

  1. 事件三要素

  2. 事件绑定

  3. 事件流

  4. 事件对象

  5. 事件代理

  6. 事件类型 这些概念在vue中依旧存在,但是vue中的事件机制要更加的简单便捷一

1.事件绑定 可以用 v-on 指令监听DOM 事件,并在触发时运行一些 JavaScript 代码。v-on 还可以接收一个需要调用的方法名称。 <button v-on:click="handler">good</button> methods: { handler: function (event) { if (event) { alert(event.target.tagName) } // event 是原生 DOM 事件 } } 除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法,通过$event传递原生事件对象: <button v-on:click="say('hi',$event)">Say hi</button> methods: { say: function (message,event) { alert(message) } } 由于事件绑定在vue中使用概率较大,所以这里提供了简写形式 <button @click="say('hi',$event)">Say hi</button>

事件参数
在事件调用时,可以进行参数的传递 :
### html代码
<div id="app">
        <el-button @click="toAdd" type="primary" size="small">新增</el-button>
        <div>
            <el-table type="index" size="small" :data="tableData" style="width: 50%">
                <el-table-column  prop="date" label="日期" width="180">
                </el-table-column>
                <el-table-column prop="name" label="姓名" width="180">
                </el-table-column>
                <el-table-column prop="address" label="地址">
                </el-table-column>
                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-button size="small" @click="toEdit(scope.row)" type="primary">编辑</el-button>
                        <el-button size="small" type="danger" @click="toDelete(scope.row.id)">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>
        </div>
        <el-dialog :title="title" :visible.sync="dialogFormVisible">
            <el-form :model="form">
                <el-form-item  label="时间" :label-width="formLabelWidth">
                    <el-input v-model="form.date" autocomplete="off"></el-input>
                </el-form-item>
                <el-form-item label="姓名" :label-width="formLabelWidth">
                    <el-input v-model="form.name" autocomplete="off"></el-input>
                </el-form-item>
                <el-form-item label="地址" :label-width="formLabelWidth">
                    <el-input v-model="form.address" autocomplete="off"></el-input>
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="dialogFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="dialogFormVisible = false">确 定</el-button>
            </div>
        </el-dialog>
    </div>
### js代码
<script>
        new Vue({
            el: "#app",
            data: {
                dialogFormVisible: false,
                formLabelWidth: "120px",
                form: {},
                title: '',
                tableData: [{
                    id: 1,
                    date: '2016-05-02',
                    name: '王小虎',
                    address: '上海市普陀区金沙江路 1518 弄'
                }, {
                    id: 2,
                    date: '2016-05-04',
                    name: '王小虎',
                    address: '上海市普陀区金沙江路 1517 弄'
                }, {
                    id: 3,
                    date: '2016-05-01',
                    name: '王小虎',
                    address: '上海市普陀区金沙江路 1519 弄'
                }, {
                    id: 4,
                    date: '2016-05-03',
                    name: '王小虎',
                    address: '上海市普陀区金沙江路 1516 弄'
                }]
            },
            methods: {
                toAdd() {
                    this.dialogFormVisible = true;
                    this.form = {};
                    this.title = '新增数据'
                },
                toEdit(row) {
                    this.form = { ...row };
                    this.dialogFormVisible = true;
                    this.title = '修改数据';
                },
                toDelete(id) {
                    this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                        confirmButtonText: '确定',
                        cancelButtonText: '取消',
                        type: 'warning'
                    }).then(() => {
                        this.tableData.splice(id,1)
                        this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                    }).catch(() => {
                        this.$message({
                            type: 'info',
                            message: '已取消删除'
                        });
                    });
                }
            }
        })
    </script>

进行事件绑定时,可以将v-on:事件名缩写为@事件名,此方式经常使用 :

事件修饰符

事件修饰符 在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。Vue提供了更好的方式:事件处理函数只有纯粹的数据逻辑,而不是去处理 DOM 事件细节,通过事件修饰符来完成这些细节。

<button v-on:click.prevent="handler">点我点我</button>

常见修饰符如下 .stop 停止事件冒泡 .prevent 阻止事件默认行为 .capture 在事件捕获阶段执行事件处理函数 .self 只当在 event.target 是当前元素自身时触发处理函数 .once 事件处理函数执行一次后解绑 .passive 滚动事件的默认行为 (即滚动行为) 将会立即触发 ,一般与scroll连用,能够提升移动端的性能 按键修饰符 一般与keyup事件类型配合使用 .enter、.tab、.delete、.esc、.space、.up、.down、.left、.right .ctrl、.alt、.shift、.meta 鼠标修饰符mouseup事件 .left、.right、.middle

### js代码
new Vue({
      el: "#app",
      data: {
        msg: '事件修饰符'
      },
      methods: {
        keyupHandle() {
          console.log('按下某些特殊键');
        },
        toJump() {
          console.log('跳转');
          alert(1);
        },
        outer(e) {
          // e.target是触发事件的源头元素,目标元素
          // e.currentTarget 当前执行事件处理程序的元素,当前目标元素
          // console.log('outer', e.target, e.currentTarget);
          console.log('outer')
          for (let i = 0; i < 100; i++) {
            console.log(i);
          }
        },
        inner(e) {
          // e.stopPropagation();
          // console.log('inner', e.target, e.currentTarget);
          console.log('inner');
        }
      }
    })
### html代码
<div id="app">
    <!-- <input type="text" @keyup.enter="keyupHandle"> -->
    <input type="text" @keyup.13="keyupHandle">
    <!-- <input type="text" @mouseup.left="keyupHandle"> -->
    {{msg}}
    <a @click.prevent="toJump" href="http://www.baidu.com">百度一下</a>
    
    <!-- 点击inner   event.target   -->
    <!-- <div class="outer" @click.self.once="outer"> -->
    <!-- <div class="outer" @click.self="outer"> -->
    <!-- <div class="outer" @click.capture="outer"> -->
    <div class="outer" @scroll.passive="outer">
      outer
      <div class="inner" @click="inner">
        <!-- <div class="inner" @click.stop="inner"> -->
        inner
      </div>
    </div>
  </div>

表单

可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。 它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。使用v-model绑定了值, 那么name属性就可以不用写了。

### html代码
<div id="app">
    {{msg}}
    <br>
    {{stu}}
    <br>
    <!-- 用户名:<input type="text" v-model.lazy="stu.username"> -->
    用户名:<input type="text" v-model.trim="stu.username">
    <br>
    <!-- .number修饰符,可以将采集到的数据转为number类型,然后再存储到数据模型中 -->
    年龄:<input type="text" v-model.number="stu.age">
    <br>
    <!-- 性别 -->
    性别:<input type="radio" value="male" v-model="stu.gender">男
    <input type="radio" value="female" v-model="stu.gender">女
    <br>
    <!-- 爱好 -->
    爱好:<input type="checkbox" value="basketball" v-model="stu.hobby">篮球
    <input type="checkbox" value="swimming" v-model="stu.hobby">游泳
    <input type="checkbox" value="dancing" v-model="stu.hobby">跳舞
    <br>
    <!-- 城市 -->
    城市:
    <!-- <select multiple v-model="stu.city"> -->
    <select v-model="stu.city">
      <option value="shanghai">上海</option>
      <option value="beijing">北京</option>
      <option value="guangzhou">广州</option>
    </select>
    <br>
    <!-- 简介 -->
    简介:<textarea v-model="stu.info" cols="30" rows="10"></textarea>
  </div>
### js代码
    new Vue({
      el: '#app',
      data: {
        msg: 'hello',
        stu: {
          // 复选框
          hobby: []
        }
      },
      methods: {}
    })

watch(监听器 监听属性)

当需要在数据变化时执行异步或开销较大的操作时,使用监听器是最有用的

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的 。

### html代码
<div id="app">
    {{msg}}
    <br>
    a:<input type="text" v-model.number="a">
    <br>
    +
    <br>
    b:<input type="text" v-model.number="b">
    <br>
    =
    <br>
    <output>{{total}}</output>
  </div>
### js代码
 new Vue({
      el: '#app',
      data: {
        msg: 'hello',
        a: 0,
        b: 0,
        total: 0
      },
      methods: {},
      // 监听  侦听
      watch: {
        a(newValue, oldValue) {
          this.total = this.a + this.b;
        },
        b(newValue, oldValue) {
          this.total = this.b + this.a;
        }
      }
    })

### 深度监听
### js代码
 new Vue({
      el: '#app',
      data: {
        msg: 'hello',
        a: 1,
        obj: {
          name: 'zhangsan',
          age: 12
        },
      },
      watch: {
        a(newValue, oldValue) {
          console.log('a数据发生变化...');
        },
        /* obj(newValue, oldValue) {
          console.log('obj数据发生变化...');
        } */
        // 深度监听
        obj: {
          handler(newValue, oldValue) {
            console.log('obj数据发生变化...');
          },
          // 深度监听
          deep: true
        }
      },
      methods: {
        changeObj() {
          // 更改内部数据
          this.obj.name = 'lisi';
        }
      }
    })
### html代码
 <div id="app">
    {{msg}}
    <br>
    {{obj}}
    <button @click="changeObj">更改obj</button>
  </div>

computed(计算属性)

计算属性 有依赖关系的数据

我们希望一个变量是经过某种计算然后输出而不是直接输出的时候可以使用到计算属性 计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。而每次调用函数都会导致函数的重新执行。

### html代码
<div id="app">
    {{msg}}
    <br>
    a:<input type="text" v-model.number="a">
    <br>
    +
    <br>
    b:<input type="text" v-model.number="b">
    <br>
    =
    <br>
    {{total}}
  </div>    
### js代码
    new Vue({
      el: '#app',
      data: {
        msg: 'hello',
        a: 0,
        b: 0,
        // total: 0
      },
      // 计算属性
      computed: {
        total(){
            console.log('计算属性');
            // return 111
            return this.a+this.b
        }
      },
      methods: {}
    })
面试题
*** watch和computed的区别 
  computed
    1. 具有缓存性,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数
    2.计算属性计算某一个属性得变化,如果某一个值改变了,计算属性会见监听到进行返回
  watch
    1. 监听值的变化,执行异步操作【axios请求】
    ’$route.query‘:{
        this.loadArticle()
    }
    2. 无缓存性,只有当当前值发生变化时才会执行/响应
​

<think>好的,用户之前问了Vue的父子组件传参方法,现在又转向了computedwatch,说明他们可能在学习Vue的核心概念。用户可能是刚入门Vue的开发者,想要理解这两个特性的区别和使用场景。 首先,我需要确认用户对Vue的基础知识掌握程度。他们已经了解了组件通信,现在想深入响应式系统。computedwatch都是处理数据变化的,但用途不同。用户可能在实际编码中遇到了何时使用哪个的问题,比如数据依赖计算还是监听变化执行异步操作。 接下来,我需要区分两者的核心差异。computed适用于基于依赖数据计算新值,有缓存,而watch用于监听数据变化执行副作用。需要举例说明,比如全名计算用computed表单验证用watch。 还要考虑用户可能遇到的常见问题,比如computed的缓存机制如何工作,watch的deep和immediate选项的使用场景。此外,用户可能对Vue 3的Composition API中的用法感兴趣,是否需要提及? 另外,用户可能希望知道最佳实践,避免滥用watch,导致代码难以维护。需要强调优先使用computed,当需要执行异步或复杂操作时才用watch。同时,对比表格能帮助用户快速理解两者的区别。 最后,确保回答结构清晰,分点说明,提供代码示例和实际应用场景,帮助用户将理论应用于实践。可能还需要提醒一些注意事项,比如避免在computed中修改数据,或者watch中处理旧值和新值的情况。</think>### Vue 计算属性 vs 监听器:核心差异与最佳实践指南 --- #### **1. 核心概念对比** ```mermaid graph TD A[Computed] -->|依赖追踪| B[自动缓存] C[Watch] -->|精确监听| D[手动处理] E[响应式系统] --> A E --> C ``` --- #### **2. 计算属性Computed)** ##### **2.1 核心特性** - **自动缓存**:依赖值未变化时直接返回缓存结果 - **声明式编程**:聚焦"应该显示什么" - **同步计算**:适合纯计算场景 ##### **2.2 典型应用场景** ```vue <script> export default { data() { return { firstName: '张', lastName: '三' } }, computed: { // 全名拼接 fullName() { return `${this.firstName} ${this.lastName}` }, // 带 setter 的计算属性 formattedPrice: { get() { return '¥' + (this.price * this.quantity).toFixed(2) }, set(newValue) { const num = parseFloat(newValue.replace('¥', '')) this.price = num / this.quantity } } } } </script> ``` --- #### **3. 监听器(Watch)** ##### **3.1 核心特性** - **主动观察**:精确监听特定数据变化 - **命令式编程**:聚焦"当变化发生时做什么" - **异步支持**:适合副作用操作 ##### **3.2 典型应用场景** ```vue <script> export default { data() { return { searchQuery: '', apiResults: [] } }, watch: { // 基础监听 searchQuery(newVal, oldVal) { this.debounceSearch(newVal) }, // 深度监听对象 userInfo: { handler(newVal) { this.saveToLocalStorage(newVal) }, deep: true, immediate: true }, // 监听多个数据源 '$route.params.id'(newId) { this.fetchData(newId) } } } </script> ``` --- #### **4. 核心差异对比表** | **特性** | Computed | Watch | |-----------------|-------------------|-------------------| | **返回值** | 必须返回计算结果 | 无返回值 | | **缓存机制** | 自动缓存 | 无缓存 | | **执行时机** | 依赖变化时 | 监听值变化时 | | **异步支持** | 不支持 | 支持 | | **适用场景** | 数据派生/格式化 | 副作用操作 | | **代码风格** | 声明式 | 命令式 | --- #### **5. 最佳实践指南** ##### **5.1 选择策略** - **优先使用 Computed**:当需要基于现有状态计算新值时 ```javascript // Good computed: { total() { return this.items.reduce((sum, item) => sum + item.price, 0) } } // Bad(应使用 computed) data() { return { total: 0 } }, watch: { items: { handler() { this.total = this.items.reduce(...) }, deep: true } } ``` - **合理使用 Watch**:当需要执行异步操作或复杂副作用时 ```javascript // 合法使用场景 watch: { userId(newVal) { this.fetchUserData(newVal) } } ``` ##### **5.2 性能优化** - **避免深度监听滥用**:使用精确路径代替 `deep: true` ```javascript // 优化前 watch: { obj: { handler() { /*...*/ }, deep: true } } // 优化后 watch: { 'obj.targetKey'() { /*...*/ } } ``` - **合理设置 immediate** ```javascript // 需要立即执行的初始化逻辑 watch: { filterParams: { handler() { this.loadData() }, immediate: true } } ``` --- #### **6. 进阶用法(Vue 3 Composition API)** ```vue <script setup> import { ref, computed, watch } from 'vue' const count = ref(0) const double = computed(() => count.value * 2) // 监听多个源 watch([count, double], ([newCount, newDouble]) => { console.log(`Count: ${newCount}, Double: ${newDouble}`) }) // 条件监听 const stop = watch(count, (newVal) => { if (newVal > 10) { stop() // 停止监听 } }) </script> ``` --- #### **7. 常见问题排查** **Q1 计算属性不更新** ✅ 检查依赖数据是否响应式 ✅ 确认没有在计算属性中修改依赖值 **Q2 监听器多次触发** ✅ 使用 `flush: 'post'` 确保 DOM 更新后执行 ✅ 对数组操作使用 `{ deep: true }` **Q3 内存泄漏** ✅ 组件卸载时自动停止 Composition API 的 watch ✅ Options API 中使用 `this.$watch()` 返回取消函数: ```javascript const unwatch = this.$watch('value', handler) beforeUnmount() { unwatch() } ``` --- #### **8. 性能对比示例** ```javascript // 计算属性方案(高效) computed: { filteredList() { return this.list.filter(item => item.name.includes(this.searchKeyword) ) } } // 监听器方案(低效) data() { return { filteredList: [] } }, watch: { searchKeyword: { handler(newVal) { this.filteredList = this.list.filter(item => item.name.includes(newVal) ) }, immediate: true } } ``` --- #### **9. 决策流程图** ```mermaid graph TD Start[需要派生新数据?] -->|是| UseComputed Start -->|否| NeedSideEffect{需要副作用?} NeedSideEffect -->|是| UseWatch NeedSideEffect -->|否| MaybeWrong[可能设计不合理] UseComputed -->|同步计算| Done[完成] UseWatch -->|异步操作| Done ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值