目录

插值表达式的不足

计算属性介绍

案例

计算属性与方法的区别

完成需求

使用侦听器

计算属性的不足

侦听器介绍

侦听单个数据

侦听器异步操作

Computed 和 Watch 的区别

使用事件处理页面交互

v-on指令

内联事件处理

方法事件处理

v-on指令简写

事件修饰符

如何阻止冒泡

事件修饰符

按键与系统修饰符


插值表达式的不足

之前的内容请见:Java 程序员的 Vue 指南 - Vue 万字速览(01)-CSDN博客

现在又有全新的需求:渲染的数据需要按照性别获取男生与女生。

如果我们需要使用插值表达式,就会有许多不足。

计算属性介绍

计算属性形式上是属性,本质上是方法(函数)

该怎么理解这句话呢?看看下面的代码:

// 定义计算属性
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

// 使用方式 - 像访问属性一样
console.log(this.fullName) // "张三"
<!-- 像属性一样使用,不需要括号 -->
<p>{{ fullName }}</p>

形式上是属性:使用方式像属性,且在模板中作为属性使用。

computed: {
  // 这实际上是一个getter函数
  reversedMessage() {
    return this.message.split('').reverse().join('')
  },
  
  // 完整写法更清楚地显示其函数本质
  fullName: {
    get() {
      return this.firstName + ' ' + this.lastName
    },
    set(value) {
      const names = value.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

本质上是方法(函数):定义形式是函数。

案例

第一个方法:

第二个方法,这么做的缺点是很复杂。

最后使用插值表达式,页面能看到计算结果,而且vue的实例上也能看到计算属性。

最后值得注意的是,计算属性的值不可以被修改,因为计算属性默认无法写入数据。

<!DOCTYPE html>
<html lang="ch">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>demo1</title>
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
</head>
<body>
    <div id="root">
        <h1>姓名:{{name}}</h1>
        <h1>语文:{{score_chinese}}</h1>
        <h1>数学:{{score_math}}</h1>
        <h1>平均分:{{score_avg}}</h1>
    </div>
    <script>
        const app = Vue.createApp({
            data() {
                return {
                    // 第一个方法:传统办法
                    name: '张飞',
                    score_chinese: 90,
                    score_math: 80,
                    // 页面显示NaN,因为this获取成绩,是无法在data当中使用的
                    // 因为平均成绩的计算和语文、数学成绩的编译是同时进行的
                    // 所以无法获取到语文成绩和数学成绩
                    // score_avg: (this.score_chinese + this.score_math) / 2
                }
            },
            // 第二个方法:methods,方法可以被插入到插值表达式中
            // methods: {
            //     calAvg() {
            //         return ((this.score_chinese + this.score_math)/2).toFixed(2)
            //     }
            // },
            // 第三个方法:计算属性
            computed:{
                score_avg() {
                    return ((this.score_chinese + this.score_math)/2).toFixed(2)
                }
            }
        });
        const vm = app.mount("#root");
    </script>
</body>
</html>

计算属性与方法的区别

  • 计算属性和方法的区别

计算属性 方法
有无缓存 有缓存 无缓存
何时执行 依赖变化,则执行 页面重新渲染,则执行
一句话总结 “跟我有关系的人动,我才动” “任何人动,我就动”

想要验证区别,只需要在下面的案例中证明即可:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算属性与方法的区别</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
    <div id="root">
        <h1>姓名: {{ name }}</h1>
        <h1>方法-平均分: {{ getAvg() }}</h1>
        <h1>计算属性-平均分: {{ score_avg }}</h1>
    </div>

    <script>
        const { createApp } = Vue;
        
        const app = createApp({
            data() {
                return {
                    name: '张飞',
                    score_chinese: 68,
                    score_math: 88,
                }
            },
            methods: {
                getAvg() {
                    console.log('方法被调用');
                    return ((this.score_chinese + this.score_math) / 2).toFixed(2)
                    // 添加时间戳来对比两者区别
                        + '-' + new Date().getTime()
                }
            },
            computed: {
                score_avg() {
                    console.log('计算属性被调用');
                    return ((this.score_chinese + this.score_math) / 2).toFixed(2)
                    + '-' + new Date().getTime()
                }
            }
        });
        const vm = app.mount("#root");
    </script>
</body>
</html>

完成需求

最后再来完成开头提到的计算属性的需求:

<!DOCTYPE html>
<html lang="ch">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>利用计算属性筛选出男生和女生数据</title>
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
</head>

<body>
    <div id="root">
        <h1>男生</h1>
        <ul>
            <!-- v-if 与 v-for 不能出现在同一个标签里,因为 v-if 有同样的优先级 -->
            <!-- 所以需要把 v-for 移动到 <template> -->
            <!-- <li v-for="stu in students" v-if="stu.sex === '男'"> -->
            <li v-for="stu in stus_m">{{stu.name}}</li>
        </ul>
        <h1>女生</h1>
        <ul>
            <li v-for="stu in stus_f">{{stu.name}}</li>
        </ul>
    </div>

    <script>
        const app = Vue.createApp({
            data() {
                return {
                    students: [
                        {
                            name: "刘备",
                            sex: "男",
                        },
                        {
                            name: "张飞",
                            sex: "男",
                        },
                        {
                            name: "小乔",
                            sex: "女",
                        },
                        {
                            name: "郭鲜",
                            sex: "女",
                        },
                    ],
                };
            },
            computed: {
                // 获取男生的数据源
                stus_m() {
                    return this.students.filter(i => {
                        return i.sex === '男';
                    });
                },
                // 获取女生的数据源
                stus_f() {
                    return this.students.filter(i => {
                        return i.sex === '女';
                    });
                }
            }
        });
        const vm = app.mount("#root");
    </script>
</body>

</html>

使用侦听器

计算属性的不足

侦听器介绍

例子代码如下,当 count 值发生变化时,handler 函数代码将被执行:

<!DOCTYPE html>
<html lang="ch">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>案例1:监听单个数据</title>
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
</head>
<body>
    <div id="root">
        <h1> 当前计数:{{count}} </h1>
    </div>

    <script>
        const app = Vue.createApp({
            data(){
                return {
                    count: 1
                }
            },
            watch: {
                count: {
                    handler() {
                        console.log('count 发生了变化');
                    }
                }
            }
        });
        const vm = app.mount('#root');
    </script>
</body>
</html>

侦听单个数据

handler(newValue, oldValue) {
    console.log(`count从${oldValue}变成了${newValue}`);
}

如果只有一个参数会报错:

侦听器异步操作

handler() {
setTimeout(() => {
    console.log('count 发生了变化');
}, 3000)

<!DOCTYPE html>
<html lang="ch">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>案例2:侦听对象</title>
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
</head>
<body>
    <div id="root">
        <h1>对象信息:{{ student }}</h1>
    </div>

    <script>
        const app = Vue.createApp({
            data() {
                return {
                    student: {
                        name: '张三',
                        score: 18
                    }
                }
            },
            watch: {
                student: {
                    handler() {
                        console.log("student发生了变化");
                    }
                }
            }
        });
        const vm = app.mount('#root');
    </script>
</body>
</html>

把学生的整个对象数据做了改变,student被重新赋值了一个对象,地址就会变,回调函数被执行,student发生了变化:

任务:侦听学生体重变化

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>任务:侦听学生体重变化</title>
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
  </head>
  <body>
    <div id="root">
      <h1>姓名:{{student.name}}</h1>
      <h1>体重:{{student.weight}}</h1>
      <h1>变化:{{message}}</h1>
    </div>

    <script>
      const app = Vue.createApp({
        data() {
          return {
            message: "无",
            student: {
              name: "张三",
              weight: 100,
            },
          };
        },
        // 配置监听器
        watch: {
            // 回调函数
            'student.weight'(newWeight, oldWeight) {
                // 获取体重变化的差值
                let changeValue = newWeight - oldWeight;
                if (changeValue > 0) {
                    // 胖了
                    this.message = "胖子";
                    if (changeValue > 5) {
                        this.message += ",该减肥了"
                    }
                } else if (changeValue < 0) {
                    // 瘦了
                    this.message = "瘦子";
                    // 50 - 60 = -10
                    if (changeValue < -5) {
                        this.message += ",该增重了"
                    }
                } else {
                    // 没变
                    this.message = "无";
                }
                console.log(this.message);
            }
        }
      });
      const vm = app.mount("#root");
    </script>
  </body>
</html>

Computed 和 Watch 的区别

Computed 是基于依赖关系进行缓存的计算属性,只有当依赖的响应式数据发生变化时才会重新计算,适合进行数据转换和组合。

Watch 是监听器,用于观察和响应数据的变化,无缓存且支持异步操作,适合在数据变化时执行副作用操作。

Computed(计算属性):

  • 像自动计算的表格单元格:你设置好公式(依赖关系),数据一变结果自动更新

  • 有记忆功能:同样的输入不会重复计算,直接给出上次的结果

  • 同步计算:像数学计算一样,立即出结果,不适合做网络请求

  • 适合场景:全名拼接、价格计算、数据过滤等需要基于现有数据计算新值的场景

Watch(监听器):

  • 像保安盯监控:数据一有变化就立即报告,然后你可以采取行动

  • 没有记忆:每次变化都会重新执行

  • 支持异步:可以等用户停止输入后再搜索,或者做网络请求

  • 适合场景:搜索框输入、表单验证、数据保存等需要在数据变化时执行特定操作的场景

简单比喻:

  • Computed 像智能计算器:输入变了,结果自动变,但不会重复算同样的题

  • Watch 像报警器:监测到变化就响铃,然后你可以去处理事情

使用选择:

  • 需要基于其他值计算新值 → 用 Computed

  • 需要在数据变化时执行操作(特别是异步操作) → 用 Watch

  • 需要缓存优化性能 → 用 Computed

  • 需要深度监听对象内部变化 → 用 Watch

使用事件处理页面交互

v-on指令

内联事件处理

<!DOCTYPE html>
<html lang="ch">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>内联事件处理器</title>
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
</head>
<body>
    <div id="root">
        <!-- 购物车小模块 -->
        <button type="button" v-on:click="num++">+</button>
        <input type="text" :value="num">
        <button type="button" v-on:click="num--">-</button>
    </div>

    <script>
        Vue.createApp({
            data() {
                return {
                    num: 0,
                }
            }
        }).mount('#root')
    </script>
</body>
</html>

方法事件处理

<div id="root">
        <!-- 购物车小模块 -->
        <button type="button" v-on:click="add">+</button>
        <input type="text" :value="num">
        <button type="button" v-on:click="reduce">-</button>
    </div>

    <script>
        Vue.createApp({
            data() {
                return {
                    num: 0,
                }
            },
            methods: {
                add() {
                    this.num++;
                },
                reduce() {
                    if (this.num > 0) {
                        this.num--;
                    } else {
                        alert('商品数量不能小于1')
                    }
                }
            }
        }).mount('#root')
    </script>

<!DOCTYPE html>
<html lang="ch">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>内联事件处理器</title>
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
</head>
<body>
    <div id="root">
        <!-- 购物车小模块 -->
        <!-- 为方法设置参数,在调用方法时传递参数 -->
        <button type="button" v-on:click="add(2)">+</button>
        <input type="text" :value="num">
        <button type="button" v-on:click="reduce(2)">-</button>
    </div>

    <script>
        Vue.createApp({
            data() {
                return {
                    num: 0,
                }
            },
            methods: {
                add(v) {
                    this.num += v;
                },
                reduce(v) {
                    if (this.num > 0) {
                        this.num-=v;
                    } else {
                        alert('商品数量不能小于1')
                    }
                }
            }
        }).mount('#root')
    </script>
</body>
</html>

vue获取鼠标坐标:

<!DOCTYPE html>
<html lang="ch">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
    <title>获取鼠标的坐标</title>
    <style>
      .mouseplace {
        width: 500px;
        height: 300px;
        background-color: #f0f0f0;
        border: 1px solid #ccc;
        margin-top: 20px;
      }
    </style>
  </head>
  <body>
    <div id="root">
      <h1>鼠标的坐标:({{x}}, {{y}})</h1>
      <!-- 注册事件mousemove -->
      <!-- 此处v-on省略为了@ -->
      <div class="mouseplace" @mousemove="mousehandler"></div>
    </div>
    <script>
      Vue.createApp({
        data() {
          // 鼠标的坐标
          return {
            x: 0,
            y: 0,
          }
        },
        methods: {
          // 事件mousemove的方法
          // 使用event获取事件对象
            mousehandler(event) {
                this.x = event.clientX;
                this.y = event.clientY;
                // console.log(this.x, this.y)
            }
        }
      }).mount('#root');
    </script>
  </body>
</html>

v-on指令简写

事件修饰符
如何阻止冒泡

当你点击一个嵌套在多层 HTML 元素中的子元素(比如一个按钮)时,该点击事件不仅会在按钮上触发,还会依次向上传播到它的父元素、祖父元素,直到 document 根节点。这种从最内层目标元素向外逐级传播的过程,就叫做事件冒泡。

在 Vue 中,可以使用如下的方式阻止事件冒泡:

<!DOCTYPE html>
<html lang="ch">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
    <title>事件修饰符</title>
    <style>
      .div_big {
        width: 200px;
        height: 200px;
        background-color: lightgray;
        border: 1px solid #333;
      }
      
      .div_small {
        width: 100px;
        height: 100px;
        background-color: white;
        border: 1px solid #333;
        margin: 50px;
      }
    </style>
  </head>
  <body>
    <div id="root">
      <div class="div_big" @click="bighandler">
      <!-- vue阻止事件冒泡 .stop -->
        <div class="div_small" @click.stop="smallhandler"></div>
      </div>
    </div>
    <script>
      Vue.createApp({
        // 为两个事件注册事件
        methods: {
          bighandler(event) {
            console.log("大方块被点击了!");
            event.currentTarget.style.backgroundColor = "red";
          },
          smallhandler(event) {
            console.log("小方块被点击了!");
            event.currentTarget.style.backgroundColor = "blue";
            // 点击小方块时,大方块也会变颜色,就是事件冒泡
            // js阻止事件冒泡
            // event.stopPropagation();
          },
        },
      }).mount("#root");
    </script>
  </body>
</html>
事件修饰符

按键与系统修饰符

<!DOCTYPE html>
<html lang="ch">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.staticfile.net/vue/3.3.4/vue.global.min.js"></script>
    <title>按键与系统修饰符</title>
    <style>
        .input2 {
            margin: 20px;
            padding: 10px;
            font-size: 16px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div id="root">
        <!-- 注册事件keyup -->
        <!-- 先按ctrl与enter才能触发事件 -->
        <input class="input2" type="text" @keyup.ctrl.enter="keyhandler">
    </div>
    <script>
        Vue.createApp({
            methods: {
                keyhandler(event) {
                    // 打印文本框的值
                    console.log(event.target.value);
                }
            }
        }).mount("#root");
    </script>
</body>
</html>
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐