2.从零开始学会Vue--{{计算属性与侦听器}}

180 阅读6分钟

内容-2.gif

## 1.computed计算属性

1.基础用法

概念: 依赖现有的数据,计算出来的新属性。 当依赖的数据变化时,自动重新计算

语法:

① 声明在 computed 配置项中,一个计算属性对应一个函数

② 使用起来和普通属性一样使用 {{ 计算属性名 }}

演示案例:

注意:

  1. computed配置项和data配置项是同级
  2. computed中的计算属性虽然是函数的写法,但他依然是个属性
  3. computed中的计算属性不能和data中的属性同名
  4. 使用computed中的计算属性和使用data中的属性是一样的用法
  5. computed中计算属性内部的this依然指向的是Vue实例

2.computed与methods的区别

小黑礼物清单案例中的【礼物总数】为什么一定要使用计算属性而不使用方法来计算,这是因为

computed 计算属性具有 【缓存】特性 ,执行性能比methods方法要高

computed 计算属性具有 【缓存】特性, 多次从计算属性中获取礼物总数时,如果结果相同,那么第一次会将计算的结果缓存起来,从第二次开始就会从缓存中返回结果。

methods方法,每次调用都会重新计算一次后再返回结果,性能较 computed 计算属性要低一些

3.计算属性的完整写法

既然计算属性也是属性,能访问,应该也能修改了?
1上面学习的计算属性写法,其实是计算属性的简写方式,只能读取数据,不能 "修改" 【开发较常用】
2如果要 "修改" → 需要写计算属性的完整写法 【不常用,但需要了解】


计算属性完整写法举例:

4.成绩案例

.score-case {
  width: 1000px;
  margin: 50px auto;
  display: flex;
}
.score-case .table {
  flex: 4;
}
.score-case .table table {
  width: 100%;
  border-spacing: 0;
  border-top: 1px solid #ccc;
  border-left: 1px solid #ccc;
}
.score-case .table table th {
  background: #f5f5f5;
}
.score-case .table table tr:hover td {
  background: #f5f5f5;
}
.score-case .table table td,
.score-case .table table th {
  border-bottom: 1px solid #ccc;
  border-right: 1px solid #ccc;
  text-align: center;
  padding: 10px;
}
.score-case .table table td.red,
.score-case .table table th.red {
  color: red;
}
.score-case .table .none {
  height: 100px;
  line-height: 100px;
  color: #999;
}
.score-case .form {
  flex: 1;
  padding: 20px;
}
.score-case .form .form-item {
  display: flex;
  margin-bottom: 20px;
  align-items: center;
}
.score-case .form .form-item .label {
  width: 60px;
  text-align: right;
  font-size: 14px;
}
.score-case .form .form-item .input {
  flex: 1;
}
.score-case .form .form-item input,
.score-case .form .form-item select {
  appearance: none;
  outline: none;
  border: 1px solid #ccc;
  width: 200px;
  height: 40px;
  box-sizing: border-box;
  padding: 10px;
  color: #666;
}
.score-case .form .form-item input::placeholder {
  color: #666;
}
.score-case .form .form-item .cancel,
.score-case .form .form-item .submit {
  appearance: none;
  outline: none;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 10px;
  margin-right: 10px;
  font-size: 12px;
  background: #ccc;
}
.score-case .form .form-item .submit {
  border-color: #069;
  background: #069;
  color: #fff;
}
<!--
 * @Date: 2025-01-03 16:19:54
 * @LastEditors: zl 1077167261@qq.com
 * @LastEditTime: 2025-01-03 16:28:35
 * @FilePath: \vue\2.watch与computed\6.成绩案例.html
-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="index.css" />
    <title>Document</title>
  </head>
  <body>
    <div id="app" class="score-case">
      <div class="table">
        <table>
          <thead>
            <tr>
              <th>编号</th>
              <th>科目</th>
              <th>成绩</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(item, index) in list" :key="item.id">
              <td>{{index+1}}</td>
              <td>{{item.subject}}</td>
              <td :class="{red:item.score<60}">{{item.score}}</td>
              <td><a href="#" @click="del(index)">删除</a></td>
            </tr>
          </tbody>
          <tbody>
            <tr v-if="list.length === 0">
              <td colspan="5">
                <span class="none">暂无数据</span>
              </td>
            </tr>
          </tbody>

          <tfoot>
            <tr>
              <td colspan="5">
                <span>总分:{{total}}</span>
                <span style="margin-left: 50px">平均分:{{average}}</span>
              </td>
            </tr>
          </tfoot>
        </table>
      </div>
      <div class="form">
        <div class="form-item">
          <div class="label">科目:</div>
          <div class="input">
            <input
              type="text"
              placeholder="请输入科目"
              v-model.trim="subject"
              @keyup.enter="add"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label">分数:</div>
          <div class="input">
            <input
              type="text"
              placeholder="请输入分数"
              v-model.trim.number="score"
              @keyup.enter="add"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label"></div>
          <div class="input">
            <button class="submit" @click="add">添加</button>
          </div>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      const app = new Vue({
        el: '#app',
        data: {
          list: [
            { id: 1, subject: '语文', score: 20 },
            { id: 7, subject: '数学', score: 99 },
            { id: 12, subject: '英语', score: 70 },
          ],
          subject: '',
          score: '',
        },
        computed: {
          total() {
            return this.list.reduce((pre, cur) => pre + cur.score, 0);
          },
          average() {
            return (this.total / this.list.length).toFixed(2);
          },
        },
        methods: {
          add() {
            if (!this.subject || !this.score) {
              alert('请输入科目和分数');
              return;
            }
            this.list.push({
              id: this.list.length + 1,
              subject: this.subject,
              score: this.score,
            });
            this.subject = '';
            this.score = '';
          },
          del(index) {
            this.list.splice(index, 1);
          },
        },
      });
    </script>
  </body>
</html>

2.watch侦听器(监视器)

作用:监视数据变化,执行一些业务逻辑或异步操作

例如:

根据关键字,ajax异步请求获取城市数据 接口地址

表单验证逻辑

语法:

① 简单写法 → 简单类型数据,直接监视

② 完整写法 → 添加额外配置项

1.简单写法

2.完整写法

3.综合案例

购物车.zip

购物车案例

需求说明:
1渲染功能
2删除功能
3修改个数
4全选反选
5统计 选中的 总价 和 总数量
6持久化到本地

实现思路:
1.基本渲染: v-for遍历、:class动态绑定样式
2.删除功能 : v-on 绑定事件,获取当前行的id
3.修改个数 : v-on绑定事件,获取当前行的id,进行筛选出对应的项然后增加或减少
4.全选反选
1必须所有的小选框都选中,全选按钮才选中
2如果全选按钮选中,则所有小选框都选中
3如果全选取消,则所有小选框都取消选中
声明计算属性,判断数组中的每一个checked属性的值,看是否需要全部选
5.统计 选中的 总价 和 总数量 :通过计算属性来计算选中的总价和总数量
6.持久化到本地: 在数据变化时都要更新下本地存储 watch

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./css/inputnumber.css" />
    <link rel="stylesheet" href="./css/index.css" />
    <title>购物车</title>
  </head>

  <body>
    <div class="app-container" id="app">
      <!-- 顶部banner -->
      <div class="banner-box"><img src="./img/fruit.jpg" alt="" /></div>
      <!-- 面包屑 -->
      <div class="breadcrumb">
        <span>🏠</span>
        /
        <span>购物车</span>
      </div>
      <!-- 购物车主体 -->
      <div class="main">
        <div class="table">
          <!-- 头部 -->
          <div class="thead">
            <div class="tr">
              <div class="th">选中</div>
              <div class="th th-pic">图片</div>
              <div class="th">单价</div>
              <div class="th num-th">个数</div>
              <div class="th">小计</div>
              <div class="th">操作</div>
            </div>
          </div>
          <!-- 身体 -->
          <div v-if="fruitList.length>0" class="tbody">
            <div
              v-for="(item,index) in fruitList"
              :key="item.id"
              class="tr active"
            >
              <div class="td">
                <input type="checkbox" v-model="item.isChecked" />
              </div>
              <div class="td"><img :src="item.icon" alt="" /></div>
              <div class="td">{{ item.price }}</div>
              <div class="td">
                <div class="my-input-number">
                  <button class="decrease" @click="item.num--">-</button>
                  <span class="my-input__inner">{{ item.num }}</span>
                  <button class="increase" @click="item.num++">+</button>
                </div>
              </div>
              <div class="td">{{ item.num * item.price }}</div>
              <div class="td">
                <button @click="fruitList.splice(index,1)">删除</button>
              </div>
            </div>
          </div>
          <!-- 空车 -->
          <div v-else class="empty">🛒空空如也</div>
        </div>
        <!-- 底部 -->
        <div class="bottom">
          <!-- 全选 -->
          <label class="check-all">
            <input type="checkbox" v-model="isAll" />
            全选
          </label>
          <div class="right-box">
            <!-- 所有商品总价 -->
            <span class="price-box"
              >总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price"
                >{{ totalAmount }}</span
              ></span
            >
            <!-- 结算按钮 -->
            <button class="pay">结算( {{ totalCount }} )</button>
          </div>
        </div>
      </div>
    </div>
    <script src="../vue.2.7.js"></script>
    <script>
      // 页面加载执行
      const oldList = [
        {
          id: 1,
          icon: './img/火龙果.png',
          isChecked: true,
          num: 2,
          price: 6,
        },
        {
          id: 2,
          icon: './img/荔枝.png',
          isChecked: false,
          num: 7,
          price: 20,
        },
        {
          id: 3,
          icon: './img/榴莲.png',
          isChecked: false,
          num: 3,
          price: 40,
        },
        {
          id: 4,
          icon: './img/鸭梨.png',
          isChecked: true,
          num: 10,
          price: 3,
        },
        {
          id: 5,
          icon: './img/樱桃.png',
          isChecked: false,
          num: 20,
          price: 34,
        },
      ];
      const tmpList = JSON.parse(localStorage.getItem('list'));
      // const list = tmpList.length == 0 ? oldList : tmpList;
      function getList() {
        if (tmpList) {
          return tmpList.length == 0 ? oldList : tmpList;
        } else {
          return oldList;
        }
      }
      const list = getList();
      const app = new Vue({
        el: '#app',
        data: {
          // 水果列表
          fruitList: list,
        },
        watch: {
          fruitList: {
            deep: true,
            immediate: true,
            handler(newValue) {
              // console.log(newValue)
              // 将this.fruitList 保存到本地存储
              localStorage.setItem('list', JSON.stringify(newValue));
            },
          },
        },
        computed: {
          // 计算勾选的总价
          totalAmount() {
            let total = 0;
            this.fruitList.forEach((item) => {
              if (item.isChecked) {
                total += item.num * item.price;
              }
            });
            return total;
          },

          // 计算勾选的总数量
          totalCount() {
            let total = 0;
            this.fruitList.forEach((item) => {
              if (item.isChecked) {
                total += item.num;
              }
            });

            return total;
          },

          isAll: {
            get() {
              // 它的返回值为boolean,来决定全选的复选框是否勾选
              // 什么时候返回true? false
              // 遍历fruitList中的所有数据 isChecked全部为true,返回true,只要有一个为false,返回false
              let isOK = true; // 默认全选
              // 证明它 -》 只要有一个false,即为false
              for (let i = 0; i < this.fruitList.length; i++) {
                if (!this.fruitList[i].isChecked) {
                  isOK = false;
                  break;
                }
              }

              return isOK;
            },
            set(val) {
              // console.log(val);
              //  遍历水果列表,将对象中的isChecked = val
              this.fruitList.forEach((element) => {
                element.isChecked = val;
              });
            },
          },
        },
      });
    </script>
  </body>
</html>

内容-1.gif