Vue学习笔记二(P16-P30)

226 阅读17分钟

7-6、列表渲染指令v-for

Vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组循环渲染一个列表结构。

  • 作用:基于数据 循环 ,多次渲染整个元素
  • 对象:数组、对象、数字······
  • 语法:v-for="(item,index) in 数组”

v-for 指令需要使用 (item, index) in arr 形式的特殊语法,其中:

  • item 是数组中的每一项
  • index 是每一项的索引(下标),不需要可以省略:v-for="item in 数组"
  • arr 是被遍历的数组

此语法也可以遍历对象和数字

//遍历对象
<div v-for="(value, key, index) in object">{{value}}</div>
value:对象中的值
key:对象中的键
index:遍历索引从0开始

//遍历数字
<p v-for="item in 10">{{item}}</p>
item从1 开始

7-案例1、小案例-小黑的书架

需求:

1.根据左侧数据渲染出右侧列表(v-for)

2.点击删除按钮时,应该把当前行从列表中删除(获取当前行的id,利用filter进行过滤)

1681896632672.png

准备代码:

<div id="app">
    <h3>小黑的书架</h3>
    <ul>
      <li v-for="(item, index) in booksList" :key="item.id">
        <span>{{ item.name }}</span>
        <span>{{ item.author }}</span>
        <!-- 注册点击事件 →  通过 id 进行删除数组中的 对应项 -->
        <button @click="del(item.id)">删除</button>
      </li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        booksList: [
          { id: 1, name: '《红楼梦》', author: '曹雪芹' },
          { id: 2, name: '《西游记》', author: '吴承恩' },
          { id: 3, name: '《水浒传》', author: '施耐庵' },
          { id: 4, name: '《三国演义》', author: '罗贯中' }
        ]
      },
      methods: {
        del (id) {
          // console.log('删除', id)
          // 通过 id 进行删除数组中的 对应项 → filter(不会改变原数组)
          // filter: 根据条件,保留满足条件的对应项,得到一个新数组。
          // console.log(this.booksList.filter(item => item.id !== id))
          this.booksList = this.booksList.filter(item => item.id !== id)
        }
      }
    })
  </script>

7-6-1、v-for中的key

语法: key="唯一值"

作用:给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用

为什么加key:v-for的默认行为会尝试原地修改元素就地复用

实例代码:

<ul>
  <li v-for="(item, index) in booksList" :key="item.id">
    <span>{{ item.name }}</span>
    <span>{{ item.author }}</span>
    <button @click="del(item.id)">删除</button>
  </li>
</ul>

注意:

  1. key 的值只能是字符串数字类型
  2. key 的值必须具有唯一性
  3. 推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(会变化,不对应)

7-8、双向绑定指令v-model

  • v-model 可以让数据和视图,形成双向数据绑定
    (1) 数据变化,视图自动更新
    (2) 视图变化,数据自动更新
    可以快速 [获取] 或 [设置] 表单元素的内容

作用:表单元素(input、radio、select)使用,双向绑定数据,可以快速 获取设置 表单元素内容

语法:v-model="变量"

需求:使用双向绑定实现以下需求

  1. 点击登录按钮获取表单中的内容
  2. 点击重置按钮清空表单中的内容

1681913125738.png

<div id="app">
    账户:<input type="text" v-model="username"> <br><br>
    密码:<input type="password" v-model="password"> <br><br>
    <button @click="login">登录</button>
    <button @click="reset">重置</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        password: ''
      },
      methods: {
        login () {
          console.log(this.username, this.password)
        },
        reset () {
          this.username = ''
          this.password = ''
        }
      }
    })
  </script>

7-案例2、综合案例-小黑记事本

1681914565816.png

功能需求:

  1. 列表渲染 v-for key的设置 {{}}插值表达式
  2. 删除功能 v-on调用传参 filter过滤 覆盖修改原数组
  3. 添加功能 v-model绑定 unshift修改原数组添加
  4. 底部统计 和 清空 数组.length累计长度 覆盖数组清空列表 v-show控制隐藏
  • CSS部分
html,
body {
 margin: 0;
 padding: 0;
}
body {
 background: #fff;
}
button {
 margin: 0;
 padding: 0;
 border: 0;
 background: none;
 font-size: 100%;
 vertical-align: baseline;
 font-family: inherit;
 font-weight: inherit;
 color: inherit;
 -webkit-appearance: none;
 appearance: none;
 -webkit-font-smoothing: antialiased;
 -moz-osx-font-smoothing: grayscale;
}

body {
 font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
 line-height: 1.4em;
 background: #f5f5f5;
 color: #4d4d4d;
 min-width: 230px;
 max-width: 550px;
 margin: 0 auto;
 -webkit-font-smoothing: antialiased;
 -moz-osx-font-smoothing: grayscale;
 font-weight: 300;
}

:focus {
 outline: 0;
}

.hidden {
 display: none;
}

#app {
 background: #fff;
 margin: 180px 0 40px 0;
 padding: 15px;
 position: relative;
 box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
#app .header input {
 border: 2px solid rgba(175, 47, 47, 0.8);
 border-radius: 10px;
}
#app .add {
 position: absolute;
 right: 15px;
 top: 15px;
 height: 68px;
 width: 140px;
 text-align: center;
 background-color: rgba(175, 47, 47, 0.8);
 color: #fff;
 cursor: pointer;
 font-size: 18px;
 border-radius: 0 10px 10px 0;
}

#app input::-webkit-input-placeholder {
 font-style: italic;
 font-weight: 300;
 color: #e6e6e6;
}

#app input::-moz-placeholder {
 font-style: italic;
 font-weight: 300;
 color: #e6e6e6;
}

#app input::input-placeholder {
 font-style: italic;
 font-weight: 300;
 color: gray;
}

#app h1 {
 position: absolute;
 top: -120px;
 width: 100%;
 left: 50%;
 transform: translateX(-50%);
 font-size: 60px;
 font-weight: 100;
 text-align: center;
 color: rgba(175, 47, 47, 0.8);
 -webkit-text-rendering: optimizeLegibility;
 -moz-text-rendering: optimizeLegibility;
 text-rendering: optimizeLegibility;
}

.new-todo,
.edit {
 position: relative;
 margin: 0;
 width: 100%;
 font-size: 24px;
 font-family: inherit;
 font-weight: inherit;
 line-height: 1.4em;
 border: 0;
 color: inherit;
 padding: 6px;
 box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
 box-sizing: border-box;
 -webkit-font-smoothing: antialiased;
 -moz-osx-font-smoothing: grayscale;
}

.new-todo {
 padding: 16px;
 border: none;
 background: rgba(0, 0, 0, 0.003);
 box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
}

.main {
 position: relative;
 z-index: 2;
}

.todo-list {
 margin: 0;
 padding: 0;
 list-style: none;
 overflow: hidden;
}

.todo-list li {
 position: relative;
 font-size: 24px;
 height: 60px;
 box-sizing: border-box;
 border-bottom: 1px solid #e6e6e6;
}

.todo-list li:last-child {
 border-bottom: none;
}

.todo-list .view .index {
 position: absolute;
 color: gray;
 left: 10px;
 top: 20px;
 font-size: 22px;
}

.todo-list li .toggle {
 text-align: center;
 width: 40px;
 /* auto, since non-WebKit browsers doesn't support input styling */
 height: auto;
 position: absolute;
 top: 0;
 bottom: 0;
 margin: auto 0;
 border: none; /* Mobile Safari */
 -webkit-appearance: none;
 appearance: none;
}

.todo-list li .toggle {
 opacity: 0;
}

.todo-list li .toggle + label {
 /*
   	Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
   	IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
   */
 background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
 background-repeat: no-repeat;
 background-position: center left;
}

.todo-list li .toggle:checked + label {
 background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}

.todo-list li label {
 word-break: break-all;
 padding: 15px 15px 15px 60px;
 display: block;
 line-height: 1.2;
 transition: color 0.4s;
}

.todo-list li.completed label {
 color: #d9d9d9;
 text-decoration: line-through;
}

.todo-list li .destroy {
 display: none;
 position: absolute;
 top: 0;
 right: 10px;
 bottom: 0;
 width: 40px;
 height: 40px;
 margin: auto 0;
 font-size: 30px;
 color: #cc9a9a;
 margin-bottom: 11px;
 transition: color 0.2s ease-out;
}

.todo-list li .destroy:hover {
 color: #af5b5e;
}

.todo-list li .destroy:after {
 content: '×';
}

.todo-list li:hover .destroy {
 display: block;
}

.todo-list li .edit {
 display: none;
}

.todo-list li.editing:last-child {
 margin-bottom: -1px;
}

.footer {
 color: #777;
 padding: 10px 15px;
 height: 20px;
 text-align: center;
 border-top: 1px solid #e6e6e6;
}

.footer:before {
 content: '';
 position: absolute;
 right: 0;
 bottom: 0;
 left: 0;
 height: 50px;
 overflow: hidden;
 box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
   0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
   0 17px 2px -6px rgba(0, 0, 0, 0.2);
}

.todo-count {
 float: left;
 text-align: left;
}

.todo-count strong {
 font-weight: 300;
}

.filters {
 margin: 0;
 padding: 0;
 list-style: none;
 position: absolute;
 right: 0;
 left: 0;
}

.filters li {
 display: inline;
}

.filters li a {
 color: inherit;
 margin: 3px;
 padding: 3px 7px;
 text-decoration: none;
 border: 1px solid transparent;
 border-radius: 3px;
}

.filters li a:hover {
 border-color: rgba(175, 47, 47, 0.1);
}

.filters li a.selected {
 border-color: rgba(175, 47, 47, 0.2);
}

.clear-completed,
html .clear-completed:active {
 float: right;
 position: relative;
 line-height: 20px;
 text-decoration: none;
 cursor: pointer;
}

.clear-completed:hover {
 text-decoration: underline;
}

.info {
 margin: 50px auto 0;
 color: #bfbfbf;
 font-size: 15px;
 text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
 text-align: center;
}

.info p {
 line-height: 1;
}

.info a {
 color: inherit;
 text-decoration: none;
 font-weight: 400;
}

.info a:hover {
 text-decoration: underline;
}

/*
   Hack to remove background from Mobile Safari.
   Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio: 0) {
 .toggle-all,
 .todo-list li .toggle {
   background: none;
 }

 .todo-list li .toggle {
   height: 40px;
 }
}

@media (max-width: 430px) {
 .footer {
   height: 50px;
 }

 .filters {
   bottom: 10px;
 }
}

  • vue部分
<!-- 主体区域 -->
<section id="app">
  <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input v-model="todoName"  placeholder="请输入任务" class="new-todo" />
    <button @click="add" class="add">添加任务</button>
  </header>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <li class="todo" v-for="(item, index) in list" :key="item.id">
        <div class="view">
          <span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label>
          <button @click="del(item.id)" class="destroy"></button>
        </div>
      </li>
    </ul>
  </section>
  <!-- 统计和清空 -->
  <footer class="footer" v-show="list.length > 0">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> {{ list.length }} </strong></span>
    <!-- 清空 -->
    <button @click="clear" class="clear-completed">
      清空任务
    </button>
  </footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  // 添加功能
  // 1. 通过 v-model 绑定 输入框 → 实时获取表单元素的内容
  // 2. 点击按钮,进行新增,往数组最前面加 unshift
  const app = new Vue({
    el: '#app',
    data: {
      todoName: '',
      list: [
        { id: 1, name: '跑步一公里' },
        { id: 3, name: '游泳100米' },
      ]
    },
    methods: {
      del (id) {
        // console.log(id) => filter 保留所有不等于该 id 的项
        this.list = this.list.filter(item => item.id !== id)
      },
      add () {
        if (this.todoName.trim() === '') {
          alert('请输入任务名称')
          return
        }
        this.list.unshift({
          id: +new Date(),
          name: this.todoName
        })
        this.todoName = ''
      },
      clear () {
        this.list = []
      }
    }
  })
  
</script>

第二天 P23

一、学习目标

1.指令补充

  1. 指令修饰符
  2. v-bind对样式增强的操作
  3. v-model应用于其他表单元素

2.computed计算属性

  1. 基础语法
  2. 计算属性vs方法
  3. 计算属性的完整写法
  4. 成绩案例

3.watch侦听器

  1. 基础写法
  2. 完整写法

4.综合案例 (演示)

  1. 渲染 / 删除 / 修改数量 / 全选 / 反选 / 统计总价 / 持久化

二、指令修饰符

1.什么是指令修饰符?

所谓指令修饰符就是通过“.”指明一些指令后缀 不同的后缀封装了不同的处理操作 —> 简化代码

2.按键修饰符

  • @keyup.enter —>当点击enter键的时候才触发

代码演示:

  <div id="app">
    <h3>@keyup.enter  →  监听键盘回车事件</h3>
    <input @keyup.enter="fn" v-model="username" type="text">
    <!-- 按键修饰符@keyup.enter  —>当点击enter键的时候才触发 -->
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: ''
      },
      methods: {
        fn (e) {
          /*if (e.key === 'Enter') {
            console.log('键盘回车的时候触发', this.username)
          }*/
          //按键修饰符@keyup.enter中,修饰符.enter就是在使用指令的时候,帮助我们进行分装,简化代码。此修饰符便可以省略以上代码判断

          console.log('键盘回车的时候触发', this.username)
        }
      }
    })
  </script>

3.v-model修饰符

  • v-model.trim —>去除首位空格
  • v-model.number —>转数字

4.事件修饰符

  • @事件名.stop —> 阻止冒泡
  • @事件名.prevent —>阻止默认行为
  • @事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为
 <style>
    .father {
      width: 200px;
      height: 200px;
      background-color: pink;
      margin-top: 20px;
    }
    .son {
      width: 100px;
      height: 100px;
      background-color: skyblue;
    }
  </style>

 <div id="app">
    <h3>v-model修饰符 .trim .number</h3>
    姓名:<input v-model.trim="username" type="text"><br>
    年纪:<input v-model.number="age" type="text"><br>

    
    <h3>@事件名.stop     →  阻止冒泡</h3>
    <div @click="fatherFn" class="father">
      <div @click.stop="sonFn" class="son">儿子</div>
    </div>

    <h3>@事件名.prevent  →  阻止默认行为</h3>
    <a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        age: '',
      },
      methods: {
        fatherFn () {
          alert('老父亲被点击了')
        },
        sonFn (e) {
          // e.stopPropagation()
          alert('儿子被点击了')
        }
      }
    })
  </script>

三、v-bind对样式控制的增强-操作class

为了方便开发者进行样式控制, Vue 扩展了 v-bind 的语法,可以针对 class 类名style 行内样式 进行控制 。

1.语法:

  • :class = "对象/数组"
<div> :class = "对象/数组">这是一个div</div>

1-1.对象语法

当class动态绑定的是对象时,键就是类名,值就是布尔值,如果值是true,就有这个类,否则没有这个类

<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
  • 适用场景:一个类名,来回切换

1-2.数组语法

当class动态绑定的是数组时 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表

<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>
  • 使用场景:批量添加或删除类

2.代码练习

<!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">
  <title>Document</title>
  <style>
    .box {
      width: 200px;
      height: 200px;
      border: 3px solid #000;
      font-size: 30px;
      margin-top: 10px;
    }
    .pink {
      background-color: pink;
    }
    .big {
      width: 300px;
      height: 300px;
    }
  </style>
</head>
<body>
  <div id="app">
    <!-- <div class="box pink big">黑马程序员</div> -->
    <!-- 传对象 -->
    <div class="box" :class="{ pink: true, big: true }">黑马程序员</div>
    <!-- 传数组 -->
    <div class="box" :class="['pink', 'big']">黑马程序员</div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>
</html>

3、案例:京东秒杀-tab栏切换导航高亮

1.需求:

当我们点击哪个tab页签时,哪个tab页签就高亮

2.思路:

1.基于数据,动态渲染tab → v-for

2.准备一个下标 记录高亮的是哪一个 tab → activeIndex

3.基于下标,动态切换class的类名 → v-bind:class

3.准备代码:

 <style>
    * {
      margin: 0;
      padding: 0;
    }
    ul {
      display: flex;
      border-bottom: 2px solid #e01222;
      padding: 0 10px;
    }
    li {
      width: 100px;
      height: 50px;
      line-height: 50px;
      list-style: none;
      text-align: center;
    }
    li a {
      display: block;
      text-decoration: none;
      font-weight: bold;
      color: #333333;
    }
    li a.active {
      background-color: #e01222;
      color: #fff;
    }

  </style>

<div id="app">
    <ul>
      <li v-for="(item, index) in list" :key="item.id" @click="activeIndex = index">
        <a :class="{ active: index === activeIndex }" href="#">{{ item.name }}</a>
      </li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        activeIndex: 2, // 记录高亮
        list: [
          { id: 1, name: '京东秒杀' },
          { id: 2, name: '每日特价' },
          { id: 3, name: '品类秒杀' }
        ]
      }
    })
  </script>

四、v-bind对有样式控制的增强-操作style

1.作用:可以对单个属性进行控制

2.语法

  • 语法 :style="样式对象"
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>

3.适用场景:某个具体属性的动态设置

4.代码练习

 <style>
    .box {
      width: 200px;
      height: 200px;
      background-color: rgb(187, 150, 156);
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></div>
     <!-- JS对象里不支持带“-”的写法,→①将带-的特殊属性名用""引起来 ②驼峰写法 -->

  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>

5.案例:进度条案例

 <style>
    .progress {
      height: 25px;
      width: 400px;
      border-radius: 15px;
      background-color: #272425;
      border: 3px solid #272425;
      box-sizing: border-box;
      margin-bottom: 30px;
    }
    .inner {
      width: 50%;
      height: 20px;
      border-radius: 10px;
      text-align: right;
      position: relative;
      background-color: #409eff;
      background-size: 20px 20px;
      box-sizing: border-box;
      transition: all 1s;//动态演示
    }
    .inner span {
      position: absolute;
      right: -20px;
      bottom: -25px;
    }
  </style>

<div id="app">
    <!-- 外层盒子底色 (黑色) -->
    <div class="progress">
      <!-- 内层盒子 - 进度(蓝色) -->
      <div class="inner" :style="{ width: percent + '%' }">
        <!-- JS中值要用''引起来' -->
        <span>{{ percent }}%</span>
      </div>
    </div>
    <button @click="percent = 25">设置25%</button>
    <button @click="percent = 50">设置50%</button>
    <button @click="percent = 75">设置75%</button>
    <button @click="percent = 100">设置100%</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        percent: 30
      }
    })
  </script>

五、v-model在其他表单元素的使用

1.讲解内容:

常见的表单元素都可以用 v-model 绑定关联 → 快速 获取设置 表单元素的值

它会根据 控件类型 自动选取 正确的方法 来更新元素

输入框  input:text   ——> value
文本域  textarea	 ——> value
复选框  input:checkbox  ——> checked
单选框  input:radio   ——> checked
下拉菜单 select    ——> value
...

2.代码准备

 <style>
    textarea {
      display: block;
      width: 240px;
      height: 100px;
      margin: 10px 0;
    }
  </style>
  
<div id="app">
    <h3>小黑学习网</h3>

    姓名:
      <input type="text" v-model="username"> 
      <br><br>

    是否单身:
      <input type="checkbox" v-model="isSingle"> 
      <br><br>

    <!-- 
      前置理解:
        1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥
        2. value: 给单选框加上 value 属性,用于提交给后台的数据
      结合 Vue 使用 → v-model
    -->
    性别: 
      <input v-model="gender" type="radio" name="gender" value="1"><input v-model="gender" type="radio" name="gender" value="2"><br><br>

    <!-- 
      前置理解:
        1. option 需要设置 value 值,提交给后台
        2. select 的 value 值,关联了选中的 option 的 value 值
      结合 Vue 使用 → v-model
    -->
    所在城市:
      <select v-model="cityId">
        <option value="101">北京</option>
        <option value="102">上海</option>
        <option value="103">成都</option>
        <option value="104">南京</option>
      </select>
      <br><br>

    自我描述:
      <textarea v-model="desc"></textarea> 

    <button>立即注册</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        isSingle: false,
        gender: "2",
        cityId: '102',
        desc: ""
      }
    })
  </script>

六、computed计算属性

1.概念

基于现有的数据,计算出来的新属性依赖的数据变化,自动重新计算。

2.语法

  1. 声明在 computed 配置项中,一个计算属性对应一个函数
  2. 使用起来和普通属性一样使用 {{ 计算属性名}}

3.注意

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

4.案例

比如我们可以使用计算属性实现下面这个业务场景

1682039327858.png

5.代码准备

<style>
    table {
      border: 1px solid #000;
      text-align: center;
      width: 240px;
    }
    th,td {
      border: 1px solid #000;
    }
    h3 {
      position: relative;
    }
  </style>

<div id="app">
    <h3>小黑的礼物清单</h3>
    <table>
      <tr>
        <th>名字</th>
        <th>数量</th>
      </tr>
      <tr v-for="(item, index) in list" :key="item.id">
        <td>{{ item.name }}</td>
        <td>{{ item.num }}个</td>
      </tr>
    </table>

    <!-- 目标:统计求和,求得礼物总数 -->
    <p>礼物总数:{{ totalCount }} 个</p>
  </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, name: '篮球', num: 1 },
          { id: 2, name: '玩具', num: 2 },
          { id: 3, name: '铅笔', num: 5 },
        ]
      },
      computed: {
        totalCount () {
          // 基于现有的数据,编写求值逻辑
          // 计算属性函数内部,可以直接通过 this 访问到 app 实例
          // console.log(this.list)

          // 需求:对 this.list 数组里面的 num 进行求和 → reduce
          let total = this.list.reduce((sum, item) => sum + item.num, 0)
          return total
        }
      }
    })
  </script>

七、computed计算属性 VS methods方法

1.computed计算属性

  • 作用:封装了一段对于数据的处理,求得一个结果

  • 语法:

  1. 写在computed配置项中
  2. 作为属性,直接使用
    • js中使用计算属性: this.计算属性
    • 模板中使用计算属性:{{计算属性}}

2.methods方法

作用:给Vue实例提供一个方法调用处理业务逻辑

语法:

  1. 写在methods配置项中
  2. 作为方法调用
    • js中调用:this.方法名()
    • 模板中调用 {{方法名()}} 或者 @事件名=“方法名”

3.computed计算属性的优势

  1. 缓存特性(提升性能)

    计算属性会对计算出来的结果缓存,再次使用直接读取缓存,

    依赖项变化了,会自动重新计算 → 并再次缓存

  2. methods没有缓存特性

  3. 通过代码比较

<style>
    table {
      border: 1px solid #000;
      text-align: center;
      width: 300px;
    }
    th,td {
      border: 1px solid #000;
    }
    h3 {
      position: relative;
    }
    span {
      position: absolute;
      left: 145px;
      top: -4px;
      width: 16px;
      height: 16px;
      color: white;
      font-size: 12px;
      text-align: center;
      border-radius: 50%;
      background-color: #e63f32;
    }
  </style>

<div id="app">
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <table>
      <tr>
        <th>名字</th>
        <th>数量</th>
      </tr>
      <tr v-for="(item, index) in list" :key="item.id">
        <td>{{ item.name }}</td>
        <td>{{ item.num }}个</td>
      </tr>
    </table>

    <p>礼物总数:{{ totalCountFn() }} 个</p>
  </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, name: '篮球', num: 3 },
          { id: 2, name: '玩具', num: 2 },
          { id: 3, name: '铅笔', num: 5 },
        ]
      },

      methods: {
        totalCountFn () {
          console.log('methods方法执行了')
          let total = this.list.reduce((sum, item) => sum + item.num, 0)
          return total
        }
      },

      computed: {
        // 计算属性:有缓存的,一旦计算出来结果,就会立刻缓存
        // 下一次读取 → 直接读缓存就行 → 性能特别高
        // totalCount () {
        //   console.log('计算属性执行了')
        //   let total = this.list.reduce((sum, item) => sum + item.num, 0)
        //   return total
        // }
      }
    })
  </script>

4.总结

1.computed有缓存特性,methods没有缓存

2.当一个结果依赖其他多个值时,推荐使用计算属性

3.当处理业务逻辑时,推荐使用methods方法,比如事件的处理函数

八、计算属性的完整写法

既然计算属性也是属性,能访问,应该也能修改了?

  1. 计算属性默认的简写,只能读取访问,不能 "修改"
  2. 如果要 "修改" → 需要写计算属性的完整写法

1682041822963.png

完整写法代码演示

  <style>
    input {
      width: 30px;
    }
  </style>
  
 <div id="app">
    姓:<input type="text" v-model="firstName"> +
    名:<input type="text" v-model="lastName"> =
    <span>{{ fullName }}</span><br><br>
    
    <button @click="changeName">改名卡</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        firstName: '刘',
        lastName: '备',
      },
      methods: {
        changeName () {
          this.fullName = '黄忠'
        }
      },
      computed: {
        // 简写 → 获取,没有配置设置的逻辑
        // fullName () {
        //   return this.firstName + this.lastName
        // }

        // 完整写法 → 获取 + 设置
        fullName: {
          // (1) 当fullName计算属性,被获取求值时,执行get(有缓存,优先读缓存)
          //     会将返回值作为,求值的结果
          get () {
            return this.firstName + this.lastName
          },
          // (2) 当fullName计算属性,被修改赋值时,执行set
          //     修改的值,传递给set方法的形参
          set (value) {
            // console.log(value.slice(0, 1))          
            // console.log(value.slice(1))         
            this.firstName = value.slice(0, 1)
            this.lastName = value.slice(1)
          }
        }
      }
    })
  </script>

九、综合案例-成绩案例

1682042489319.png

  • 功能描述:

1.渲染功能 2.删除功能 3.添加功能 4.统计总分,求平均分

  • 思路分析:

1.渲染功能 v-if v-else v-for :key v-bind:动态绑定class的样式

2.删除功能 点击传参 filter过滤覆盖原数组 v-on绑定事件, 阻止a标签的默认行为.prevent

3.添加功能 v-model的修饰符 (.trim、 .number)、
unshift修改数组更新视图 判断数据是否为空后 再添加、添加后清空文本框的数据

4.统计总分,求平均分 使用计算属性computed 计算总分和平均分的值

.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;
}
<!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 v-if="list.length > 0">
            <tr v-for="(item, index) in list" :key="item.id">
              <td>{{ index + 1 }}</td>
              <td>{{ item.subject }}</td>
              <!-- 需求:不及格的标红, < 60 分, 加上 red 类 -->
              <td :class="{ red: item.score < 60 }">{{ item.score }}</td>
              <td><a @click.prevent="del(item.id)" href="http://www.baidu.com">删除</a></td>
            </tr>
          </tbody>

          <tbody v-else>
            <tr>
              <td colspan="5">
                <span class="none">暂无数据</span>
              </td>
            </tr>
          </tbody>

          <tfoot>
            <tr>
              <td colspan="5">
                <span>总分:{{ totalScore }}</span>
                <span style="margin-left: 50px">平均分:{{ averageScore }}</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"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label">分数:</div>
          <div class="input">
            <input
              type="text"
              placeholder="请输入分数"
              v-model.number="score"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label"></div>
          <div class="input">
            <button @click="add" class="submit" >添加</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: 62 },
            { id: 7, subject: '数学', score: 89 },
            { id: 12, subject: '英语', score: 70 },
          ],
          subject: '',
          score: ''
        },
        computed: {
          totalScore() {
            return this.list.reduce((sum, item) => sum + item.score, 0)
          },
          averageScore () {
            if (this.list.length === 0) {
              return 0
            }
            return (this.totalScore / this.list.length).toFixed(2)
          }
        },
        methods: {
          del (id) {
            // console.log(id)
            this.list = this.list.filter(item => item.id !== id)
          },
          add () {
            if (!this.subject) {
              alert('请输入科目')
              return
            }
            if (typeof this.score !== 'number') {
              alert('请输入正确的成绩')
              return
            }
            this.list.unshift({
              id: +new Date(),
              subject: this.subject,
              score: this.score
            })

            this.subject = ''
            this.score = ''
          }
        }
      })
    </script>
  </body>
</html>