Vue2:基础入门2

news2024/11/21 1:43:36

Vue2:基础入门2

Date: July 29, 2023
Sum: Computed计算属性、watch侦听器、水果车


计算属性

基础及案例:

概念

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

计算属性本质上就是一个 function 函数,它可以实时监听 data 中数据的变化,并 return 一个计算后的新值,供组件渲染 DOM 时使用。

应用场景

表单中的数据变化,会导致结果也跟着变化

Untitled

语法:先声明后使用

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

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

案例: 小黑的礼物清单

需求:礼物总数与表单中的数量之和同步

Untitled

  • Code:

    <!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>
        table {
          border: 1px solid #000;
          text-align: center;
          width: 240px;
        }
        th,td {
          border: 1px solid #000;
        }
        h3 {
          position: relative;
        }
      </style>
    </head>
    <body>
    
      <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>礼物总数:{{ plus }} 个</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: {
            plus() {
              let total = this.list.reduce((sum, item) => sum += item.num, 0)
              return total
            }
          }
        })
      </script>
    </body>
    </html>
    

reduce参考:https://blog.csdn.net/qq_38970408/article/details/121018660

注意:计算属性侧重于得到一个计算的结果,因此计算属性中必须有 return 返回值!

使用注意点:

① 计算属性必须定义在 computed 节点中

② 计算属性必须是一个 function 函数

③ 计算属性必须有返回值

④ 计算属性必须当做普通属性使用

比如 {{plus}}, 而非是这样用:{{plus()}}



计算属性 vs 方法

1-computed计算属性

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

语法:

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

2-methods计算属性

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

语法:

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

计算属性的优势

1-缓存特性:(提升性能)计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 → 并再次缓存

2-methods没有缓存特性

案例:

理解:计算属性会对计算的结果缓存,下一次直接提缓存结果即可,如图计算属性就仅计算了一次,而methods是调用几次就计算几次。很明显,前者资源开销更低。

Untitled

  • Code:

    <template>
      <div>
        <!-- 通过v-model进行双向数据绑定。 .number指自动将用户的输入值转为数值类型 -->
        <input type="text" v-model.number="count" />
        <p>{{ count }} 乘以2的值为: {{ plus }}</p>
        <p>{{ count }} 乘以2的值为: {{ plus }}</p>
        <p>{{ count }} 乘以2的值为: {{ plus }}</p>
        <hr/>
        <p>{{ count }} 乘以2的值为: {{ m_plus() }}</p>
        <p>{{ count }} 乘以2的值为: {{ m_plus() }}</p>
        <p>{{ count }} 乘以2的值为: {{ m_plus() }}</p>
      </div>
    </template>
    
    <script>
    export default {
      name: 'MyCounter',
      data() {
        return {
          count: 1,
        }
      },
      computed: {
        // 计算属性:监听data中count值的变化,自动计算出count*2之后的新值
        plus() {
          console.log("计算属性被执行了");
          return this.count * 2
        }
      },
      methods: {
        m_plus() {
          console.log("方法被执行了");
          return this.count * 2
        }
      }
    }
    </script>
    

注意:

计算属性的结果会被缓存,性能好

方法的计算结果无法被缓存,性能低



计算属性完整写法:

计算属性也是属性,能访问,也应能修改

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

注:建议结合以下例子理解

Untitled

computed: {
	计算属性名: {
		get(): {
			代码逻辑
			return res
		},
		set(修改的值): {
			代码逻辑
		}
	}
}

案例:通过改名卡,能够修改姓和名

Untitled

  • Code:

    <!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>
        input {
          width: 30px;
        }
      </style>
    </head>
    <body>
    
      <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>
    </body>
    </html>
    

计算属性案例:

案例需求,使用计算属性动态计算:

① 已勾选的商品总个数 ② 已勾选的商品总价 ③ 结算按钮的禁用状态

Untitled

  • Code:

    computed: {
        //动态计算出勾选水果的总数量
        total() {
          let t = 0
          this.fruitlist.forEach(x => {
            if(x.state) {
              t += x.count
            }
          })
          return t
        },
        amount() {
          let a = 0
          this.fruitlist
            .filter(x => x.state)
            .forEach(x => {
              a += x.price * x.count
            })
          return a
        },
        isDisabled() {
          this.total === 0
        }
      },
    

效果:

注意:

这里的this指的是当前组件实例

computed: {
	isDisabled() {
		this.toal === 0
	}
}

个人总结:

计算属性

概念:计算属性本质上就是一个 function 函数,它可以实时监听 data 中数据的变化,并 return 一个计算后的新值,供组件渲染 DOM 时使用。

export default {
  name: 'MyCounter',
  data() {
    return {
      count: 1,
    }
  },
  computed: {
    // 计算属性:监听data中count值的变化,自动计算出count*2之后的新值
    plus() {
      return this.count * 2
    }
  }
}



watch 侦听器

概念及使用:

概念

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

应用场景

1-监视用户名的变化并发起请求,判断用户名是否可用。

2-实时翻译

Untitled

语法

在 watch 节点下,定义自己的侦听器。

export default {
  data() {
    return {
      username: ''
    }
  },
  watch: {
		// 监听 username 的值的变化
		// 形参列表中,第一个值是“变化后的新值”,第二个值是“变化之前的旧值”
		// 注:oldVal 可以不传
   username(newVal, oldVal) {
      console.log(newVal, oldVal);
    }
  }
}

进一步:如果你想要监视某个复杂数据类型中的子属性

const app = new Vue({
  el: '#app',
  data: {
    // words: ''
    obj: {
      words: ''
    }
  },
  watch: {
    'obj.words' (newValue) {
      console.log('变化了', newValue)
    }
  }

案例:实时翻译

Untitled

  • Code:

    <!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>
          * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-size: 18px;
          }
          #app {
            padding: 10px 20px;
          }
          .query {
            margin: 10px 0;
          }
          .box {
            display: flex;
          }
          textarea {
            width: 300px;
            height: 160px;
            font-size: 18px;
            border: 1px solid #dedede;
            outline: none;
            resize: none;
            padding: 10px;
          }
          textarea:hover {
            border: 1px solid #1589f5;
          }
          .transbox {
            width: 300px;
            height: 160px;
            background-color: #f0f0f0;
            padding: 10px;
            border: none;
          }
          .tip-box {
            width: 300px;
            height: 25px;
            line-height: 25px;
            display: flex;
          }
          .tip-box span {
            flex: 1;
            text-align: center;
          }
          .query span {
            font-size: 18px;
          }
    
          .input-wrap {
            position: relative;
          }
          .input-wrap span {
            position: absolute;
            right: 15px;
            bottom: 15px;
            font-size: 12px;
          }
          .input-wrap i {
            font-size: 20px;
            font-style: normal;
          }
        </style>
      </head>
      <body>
        <div id="app">
          <!-- 条件选择框 -->
          <div class="query">
            <span>翻译成的语言:</span>
            <select>
              <option value="italy">意大利</option>
              <option value="english">英语</option>
              <option value="german">德语</option>
            </select>
          </div>
    
          <!-- 翻译框 -->
          <div class="box">
            <div class="input-wrap">
              <textarea v-model="obj.words"></textarea>
              <span><i>⌨️</i>文档翻译</span>
            </div>
            <div class="output-wrap">
              <div class="transbox">mela</div>
            </div>
          </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        <script>
          // 接口地址:https://applet-base-api-t.itheima.net/api/translate
          // 请求方式:get
          // 请求参数:
          // (1)words:需要被翻译的文本(必传)
          // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
          // -----------------------------------------------
          
          const app = new Vue({
            el: '#app',
            data: {
              // words: ''
              obj: {
                words: ''
              }
            },
            // 具体讲解:(1) watch语法 (2) 具体业务实现
            watch: {
              // 该方法会在数据变化时调用执行
              // newValue新值, oldValue老值(一般不用)
              // words (newValue) {
              //   console.log('变化了', newValue)
              // }
    
              'obj.words' (newValue) {
                console.log('变化了', newValue)
              }
            }
          })
        </script>
      </body>
    </html>
    

补充案例:使用 watch 检测用户名是否可用

  • 特点:采用解构获取数据

    监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用:

    Untitled

    复习并理解:

    返回promise对象:

    const res = axios.get('http://www.escook.cn/api/finduser/' + newVal)
    

    Untitled

    若返回promise对象,我们可以通过await/async进行简化,返回的则是一个数据对象,

    async username(newVal, oldVal) {
          console.log(newVal, oldVal);
          const res = await axios.get('http://www.escook.cn/api/finduser/' + newVal)
          console.log(res);
        }
    

    Untitled



防抖优化处理:

案例:实时翻译

需求:防止一直输入,一直及时翻译,而是等输入完,再翻译,以提高性能

Untitled

  • Code:

    <!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>
          * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-size: 18px;
          }
          #app {
            padding: 10px 20px;
          }
          .query {
            margin: 10px 0;
          }
          .box {
            display: flex;
          }
          textarea {
            width: 300px;
            height: 160px;
            font-size: 18px;
            border: 1px solid #dedede;
            outline: none;
            resize: none;
            padding: 10px;
          }
          textarea:hover {
            border: 1px solid #1589f5;
          }
          .transbox {
            width: 300px;
            height: 160px;
            background-color: #f0f0f0;
            padding: 10px;
            border: none;
          }
          .tip-box {
            width: 300px;
            height: 25px;
            line-height: 25px;
            display: flex;
          }
          .tip-box span {
            flex: 1;
            text-align: center;
          }
          .query span {
            font-size: 18px;
          }
    
          .input-wrap {
            position: relative;
          }
          .input-wrap span {
            position: absolute;
            right: 15px;
            bottom: 15px;
            font-size: 12px;
          }
          .input-wrap i {
            font-size: 20px;
            font-style: normal;
          }
        </style>
      </head>
      <body>
        <div id="app">
          <!-- 条件选择框 -->
          <div class="query">
            <span>翻译成的语言:</span>
            <select>
              <option value="italy">意大利</option>
              <option value="english">英语</option>
              <option value="german">德语</option>
            </select>
          </div>
    
          <!-- 翻译框 -->
          <div class="box">
            <div class="input-wrap">
              <textarea v-model="obj.words"></textarea>
              <span><i>⌨️</i>文档翻译</span>
            </div>
            <div class="output-wrap">
              <div class="transbox">{{ result }}</div>
            </div>
          </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        <script>
          // 接口地址:https://applet-base-api-t.itheima.net/api/translate
          // 请求方式:get
          // 请求参数:
          // (1)words:需要被翻译的文本(必传)
          // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
          // -----------------------------------------------
          
          const app = new Vue({
            el: '#app',
            data: {
              // words: ''
              obj: {
                words: ''
              },
              result: '', // 翻译结果
              // timer: null // 延时器id
            },
            // 具体讲解:(1) watch语法 (2) 具体业务实现
            watch: {
              // 该方法会在数据变化时调用执行
              // newValue新值, oldValue老值(一般不用)
              // words (newValue) {
              //   console.log('变化了', newValue)
              // }
    
              'obj.words' (newValue) {
                // console.log('变化了', newValue)
                // 防抖: 延迟执行 → 干啥事先等一等,延迟一会,一段时间内没有再次触发,才执行
                clearTimeout(this.timer)
    						// timer存取延时器id
                this.timer = setTimeout(async () => {
                  const res = await axios({
                    url: 'https://applet-base-api-t.itheima.net/api/translate',
                    params: {
                      words: newValue
                    }
                  })
                  this.result = res.data.data
                  console.log(res.data.data)
                }, 300)
              }
            }
          })
        </script>
      </body>
    </html>
    

注意:如果你想使用一些非响应式的数据,你可以通过this把它们挂载到对象身上,而非存到data中



watch侦听器完整写法

如果结果需要受到多个条件来查询控制,我们通常会把它放入到一个obj对象中进行控制。

比如下面:翻译的结果受到 语言 与 被翻译的内容 两方面控制

Untitled

问题:如果结果会受到多方面的控制,该怎么办?

解决方案:添加额外配置项

(1) deep: true 对复杂类型进行深度件事

(2) immediate: true 初始化立刻执行一次handler方法

采用deep:翻译结果会受到 语言与被翻译内容 二者影响

watch:  {
  // 该方法会在数据变化时调用执行
  // newValue新值, oldValue老值(一般不用)
  // words (newValue) {
  //   console.log('变化了', newValue)
  // }
  obj: {
    deep: true,
    async handler(newValue) { // newValue指obj
      clearTimeout(this.timer)
      // 你可以把上面data中的timer清除,下面依然可以执行
      // 因为this会把timer挂载到obj身上
      this.timer = setTimeout(async() => {
        const res = await axios({
        url: 'https://applet-base-api-t.itheima.net/api/translate',
        params: newValue
      })
        this.result = res.data.data
        console.log(res.data.data);
      }, 200) 
    }
  },
}

注:以上我直接把obj作为对象以参数的方式传入,axios会把obj转成参数(用&拼接对象属性)

Untitled

采用immediate: 文本框中预留内容,页面一加载就会被自动翻译。原理上讲,就是一进页面就会执行一次下面的 handler 方法

watch: {
  obj: {
    deep: true, // 深度监视
    immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
    handler (newValue) {
      clearTimeout(this.timer)
      this.timer = setTimeout(async () => {
        const res = await axios({
          url: 'https://applet-base-api-t.itheima.net/api/translate',
          params: newValue
        })
        this.result = res.data.data
        console.log(res.data.data)
      }, 300)
    }
  }
}

结合以上二者的案例效果

Untitled

  • Code:

    <!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>
          * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-size: 18px;
          }
          #app {
            padding: 10px 20px;
          }
          .query {
            margin: 10px 0;
          }
          .box {
            display: flex;
          }
          textarea {
            width: 300px;
            height: 160px;
            font-size: 18px;
            border: 1px solid #dedede;
            outline: none;
            resize: none;
            padding: 10px;
          }
          textarea:hover {
            border: 1px solid #1589f5;
          }
          .transbox {
            width: 300px;
            height: 160px;
            background-color: #f0f0f0;
            padding: 10px;
            border: none;
          }
          .tip-box {
            width: 300px;
            height: 25px;
            line-height: 25px;
            display: flex;
          }
          .tip-box span {
            flex: 1;
            text-align: center;
          }
          .query span {
            font-size: 18px;
          }
    
          .input-wrap {
            position: relative;
          }
          .input-wrap span {
            position: absolute;
            right: 15px;
            bottom: 15px;
            font-size: 12px;
          }
          .input-wrap i {
            font-size: 20px;
            font-style: normal;
          }
        </style>
      </head>
      <body>
        <div id="app">
          <!-- 条件选择框 -->
          <div class="query">
            <span>翻译成的语言:</span>
            <select v-model="obj.lang">
              <option value="italy">意大利</option>
              <option value="english">英语</option>
              <option value="german">德语</option>
            </select>
          </div>
    
          <!-- 翻译框 -->
          <div class="box">
            <div class="input-wrap">
              <textarea v-model="obj.words"></textarea>
              <span><i>⌨️</i>文档翻译</span>
            </div>
            <div class="output-wrap">
              <div class="transbox">{{ result }}</div>
            </div>
          </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        <script>
          // 需求:输入内容,修改语言,都实时翻译
    
          // 接口地址:https://applet-base-api-t.itheima.net/api/translate
          // 请求方式:get
          // 请求参数:
          // (1)words:需要被翻译的文本(必传)
          // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
          // -----------------------------------------------
       
          const app = new Vue({
            el: '#app',
            data: {
              obj: {
                words: '小黑',
                lang: 'italy'
              },
              result: '', // 翻译结果
            },
            watch: {
              obj: {
                deep: true, // 深度监视
                immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
                handler (newValue) {
                  clearTimeout(this.timer)
                  this.timer = setTimeout(async () => {
                    const res = await axios({
                      url: 'https://applet-base-api-t.itheima.net/api/translate',
                      params: newValue
                    })
                    this.result = res.data.data
                    console.log(res.data.data)
                  }, 300)
                }
              }
    
              // 'obj.words' (newValue) {
              //   clearTimeout(this.timer)
              //   this.timer = setTimeout(async () => {
              //     const res = await axios({
              //       url: 'https://applet-base-api-t.itheima.net/api/translate',
              //       params: {
              //         words: newValue
              //       }
              //     })
              //     this.result = res.data.data
              //     console.log(res.data.data)
              //   }, 300)
              // }
            }
          })
        </script>
      </body>
    </html>
    
  1. immediate 选项

默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。实例代码如下:

Untitled

  1. deep 选项

当 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项,代码示例如下:

data() {
    return {
      username: 'admin',
      info: {
        username: 'zs' //info中包含 username 属性
      }
    }
  },
  watch: {
    info: { //直接监听 info 对象的变化
      async handler(newVal) {
        const { data: res } = await axios.get('https://www.escook.cn/api/finduser' + newVal.username)
        console.log(res);
      },
      deep: true // 需要使用 deep 选项, 否则 username 值的变化无法被监听到
    }
  }
  1. 监听对象单个属性的变化

如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:

Untitled

  1. 计算属性 vs 侦听器

计算属性和侦听器侧重的应用场景不同:

计算属性侧重于监听多个值的变化,最终计算并返回一个新值

计算属性侧重于得到最后的一个结果

侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值

侦听器侧重于去执行某个业务逻辑,业务逻辑执行完,侦听器的目的就达到了



个人总结:

watch 侦听器:

概念:watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作

简单写法:

watch: {
	数据属性名(newVal, oldVal) {
	  业务逻辑 或 异步操作  
	}
	‘对象.属性名’(newVal, oldVal) {
	  业务逻辑 或 异步操作  
	}
}

完整写法:

watch: {
	数据属性名:{
		deep: true, // 深度件事
		immediate: true, // 是否立刻执行一次 handler
		handler (newValue) {
			业务逻辑 或 异步操作 
		}
	}
}

注意:

组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。

当 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选项




综合案例-水果车

需求与思路:

需求说明

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

Untitled

实现思路

1.基本渲染: v-for遍历、:class动态绑定样式

2.删除功能 : v-on 绑定事件,获取当前行的id

3.修改个数 : v-on绑定事件,获取当前行的id,进行筛选出对应的项然后增加或减少

4.全选反选

4-1 必须所有的小选框都选中,全选按钮才选中 → every

4-2 如果全选按钮选中,则所有小选框都选中

4-3 如果全选取消,则所有小选框都取消选中

声明计算属性,判断数组中的每一个checked属性的值,看是否需要全部选

5.统计 选中的 总价 和 总数量 :通过计算属性来计算选中的总价和总数量

6.持久化到本地: 在数据变化时都要更新下本地存储 watch

参考:day2


心得:

实现渲染功能

对于表单数据,一般采用v-mode处理。

比如处理勾选框

Untitled

div class="td"><input type="checkbox" v-model="item.isChecked" /></div>

对于处理图片地址,一般用 v-bind ,而不能这样用:

<div class="td"><img src={{item.icon}} alt="" /></div>

以上这样会报错

插值表达式一般用于渲染标签内内容,用法如下:

<p>姓名:{{username}}</p>
  • Code: 已实现渲染功能

    <!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="http://autumnfish.cn/static/fruit.jpg" alt="" /></div>
          <!-- 面包屑 -->
          <div class="breadcrumb">
            <span>🏠</span>
            /
            <span>购物车</span>
          </div>
          <!-- 购物车主体 -->
          <div class="main" v-if="fruitList.length > 0">
            <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 class="tbody">
    
                <div class="tr" :class="{ active: item.isChecked}" v-for="(item , index) in fruitList" :key="item.id">
                  <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"> - </button>
                      <span class="my-input__inner">{{ item.num }}</span>
                      <button class="increase"> + </button>
                    </div>
                  </div>
                  <div class="td">{{ item.price * item.num }}</div>
                  <div class="td"><button>删除</button></div>
                </div>
    
              </div>
            </div>
            <!-- 底部 -->
            <div class="bottom">
              <!-- 全选 -->
              <label class="check-all">
                <input type="checkbox" />
                全选
              </label>
              <div class="right-box">
                <!-- 所有商品总价 -->
                <span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">24</span></span>
                <!-- 结算按钮 -->
                <button class="pay">结算( 6 )</button>
              </div>
            </div>
          </div>
          <!-- 空车 -->
          <div class="empty">🛒空空如也</div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        <script>
          const app = new Vue({
            el: '#app',
            data: {
              // 水果列表
              fruitList: [
                {
                  id: 1,
                  icon: 'http://autumnfish.cn/static/火龙果.png',
                  isChecked: true,
                  num: 2,
                  price: 6,
                },
                {
                  id: 2,
                  icon: 'http://autumnfish.cn/static/荔枝.png',
                  isChecked: false,
                  num: 7,
                  price: 20,
                },
                {
                  id: 3,
                  icon: 'http://autumnfish.cn/static/榴莲.png',
                  isChecked: false,
                  num: 3,
                  price: 40,
                },
                {
                  id: 4,
                  icon: 'http://autumnfish.cn/static/鸭梨.png',
                  isChecked: true,
                  num: 10,
                  price: 3,
                },
                {
                  id: 5,
                  icon: 'http://autumnfish.cn/static/樱桃.png',
                  isChecked: false,
                  num: 20,
                  price: 34,
                },
              ],
            },
          })
        </script>
      </body>
    </html>
    

实现删除、修改与全选反选

正反选问题:

采用computed来实现正选:即如果上面都勾上,那么下面的全选也勾上

Untitled

问题是反选,如果我在下面勾上反选,会报计算属性的错误,也就是计算属性没设置全的问题

Untitled

完整的计算属性

computed: {
	计算属性名: {
		get(): {
			代码逻辑
			return res
		},
		set(修改的值): {
			代码逻辑
		}
	}
}

解决方案:

computed: {
  isAll: {
    get() {
      return this.fruitList.every(item => item.isChecked === true)
    },
    set(value) {
	    this.fruitList.forEach(item => item.isChecked = value)
    }
  }
},
  • Code:

    <!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="http://autumnfish.cn/static/fruit.jpg" alt="" /></div>
          <!-- 面包屑 -->
          <div class="breadcrumb">
            <span>🏠</span>
            /
            <span>购物车</span>
          </div>
          <!-- 购物车主体 -->
          <div class="main" v-if="fruitList.length > 0">
            <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 class="tbody">
    
                <div class="tr" :class="{ active: item.isChecked}" v-for="(item , index) in fruitList" :key="item.id">
                  <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="sub(item.id)" :disabled="item.num <= 0"> - </button>
                      <span class="my-input__inner">{{ item.num }}</span>
                      <button class="increase" @click="add(item.id)"> + </button>
                    </div>
                  </div>
                  <div class="td">{{ item.price * item.num }}</div>
                  <div class="td"><button @click="del(item.id)">删除</button></div>
                </div>
    
              </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">24</span></span>
                <!-- 结算按钮 -->
                <button class="pay">结算( 6 )</button>
              </div>
            </div>
          </div>
          <!-- 空车 -->
          <div class="empty">🛒空空如也</div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        <script>
          const app = new Vue({
            el: '#app',
            data: {
              // 水果列表
              fruitList: [
                {
                  id: 1,
                  icon: 'http://autumnfish.cn/static/火龙果.png',
                  isChecked: true,
                  num: 2,
                  price: 6,
                },
                {
                  id: 2,
                  icon: 'http://autumnfish.cn/static/荔枝.png',
                  isChecked: false,
                  num: 7,
                  price: 20,
                },
                {
                  id: 3,
                  icon: 'http://autumnfish.cn/static/榴莲.png',
                  isChecked: false,
                  num: 3,
                  price: 40,
                },
                {
                  id: 4,
                  icon: 'http://autumnfish.cn/static/鸭梨.png',
                  isChecked: true,
                  num: 10,
                  price: 3,
                },
                {
                  id: 5,
                  icon: 'http://autumnfish.cn/static/樱桃.png',
                  isChecked: false,
                  num: 20,
                  price: 34,
                },
              ],
            },
            computed: {
              isAll: {
                get() {
                  return this.fruitList.every(item => item.isChecked === true)
                },
                set(value) {
                  this.fruitList.forEach(item => item.isChecked = value)
                }
              }
            },
            methods: {
              del(id) {
                this.fruitList = this.fruitList.filter(item => item.id !== id)
              },
              add(id) {
                const fruit = this.fruitList.find(item => item.id === id)
                fruit.num++
              },
              sub(id) {
                const fruit = this.fruitList.find(item => item.id === id)
                if(fruit.num === 0){
                  alert('不能再减少了')
                  console.log(12);
                  return
                }
                fruit.num--
              }
            }
          })
        </script>
      </body>
    </html>
    

实现总价

问题:

reduce与箭头的使用方式:如果里面箭头函数{}包含多行代码,记得要return

let total = this.fruitList.reduce((sum, item) => {
  if(item.isChecked === true){
    return sum += item.num
  }else {
    return sum
  }
}, 0)
  • Code:

    
    

持久化到本地

问题:任何数据的变化,都需要监视并存取

策略:通过watch的deep: true以及 localStorage来进行处理

watch: {
  fruitList: {
    deep: true,
    handler(newValue) {
      localStorage.setItem('list', JSON.stringify(newValue))
    }
  }
}

通过浏览器开发者模式查看信息:

Untitled

补充:通过这个键清空本地缓存

Untitled

所有代码

  • Code:

    <!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="http://autumnfish.cn/static/fruit.jpg" alt="" /></div>
          <!-- 面包屑 -->
          <div class="breadcrumb">
            <span>🏠</span>
            /
            <span>购物车</span>
          </div>
          <!-- 购物车主体 -->
          <div class="main" v-if="fruitList.length > 0">
            <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 class="tbody">
    
                <div class="tr" :class="{ active: item.isChecked}" v-for="(item , index) in fruitList" :key="item.id">
                  <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="sub(item.id)" :disabled="item.num <= 0"> - </button>
                      <span class="my-input__inner">{{ item.num }}</span>
                      <button class="increase" @click="add(item.id)"> + </button>
                    </div>
                  </div>
                  <div class="td">{{ item.price * item.num }}</div>
                  <div class="td"><button @click="del(item.id)">删除</button></div>
                </div>
    
              </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">{{ totalPrice }}</span></span>
                <!-- 结算按钮 -->
                <button class="pay">结算({{ totalCount }})</button>
              </div>
            </div>
          </div>
          <!-- 空车 -->
          <div class="empty">🛒空空如也</div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        <script>
          const defaultList = [
            {
              id: 1,
              icon: 'http://autumnfish.cn/static/火龙果.png',
              isChecked: true,
              num: 2,
              price: 6,
            },
            {
              id: 2,
              icon: 'http://autumnfish.cn/static/荔枝.png',
              isChecked: false,
              num: 7,
              price: 20,
            },
            {
              id: 3,
              icon: 'http://autumnfish.cn/static/榴莲.png',
              isChecked: false,
              num: 3,
              price: 40,
            },
            {
              id: 4,
              icon: 'http://autumnfish.cn/static/鸭梨.png',
              isChecked: true,
              num: 10,
              price: 3,
            },
            {
              id: 5,
              icon: 'http://autumnfish.cn/static/樱桃.png',
              isChecked: false,
              num: 20,
              price: 34,
            },
          ]
          const app = new Vue({
            el: '#app',
            data: {
              fruitList: JSON.parse(localStorage.getItem('list')) || defaultList,
            },
            computed: {
              isAll: {
                get() {
                  return this.fruitList.every(item => item.isChecked === true)
                },
                set(value) {
                  this.fruitList.forEach(item => item.isChecked = value)
                }
              },
              totalCount() {
                return this.fruitList.reduce((sum, item) => {
                  if(item.isChecked === true){
                    return sum += item.num
                  }else {
                    return sum
                  }
                }, 0)
              },
              totalPrice() {
                return this.fruitList.reduce((sum, item) => {
                  if(item.isChecked === true) {
                    return sum += (item.price * item.num)
                  }else {
                    return sum
                  }
                }, 0)
              }
            },
            methods: {
              del(id) {
                this.fruitList = this.fruitList.filter(item => item.id !== id)
              },
              add(id) {
                const fruit = this.fruitList.find(item => item.id === id)
                fruit.num++
              },
              sub(id) {
                const fruit = this.fruitList.find(item => item.id === id)
                if(fruit.num === 0){
                  alert('不能再减少了')
                  console.log(12);
                  return
                }
                fruit.num--
              },
            },
            watch: {
              fruitList: {
                deep: true,
                handler(newValue) {
                  localStorage.setItem('list', JSON.stringify(newValue))
                }
              }
            }
          })
        </script>
      </body>
    </html>
    

业务总结

  1. 渲染功能:v-if/v-else v-for :class
  2. 删除功能:点击传参 filter过滤覆盖原数组
  3. 修改个数:点击传参find找对象
  4. 全选反选:计算属性computed 完整写法 get/ set
  5. 统计选中的总价和总数量:计算属性computed reduce条件求和
  6. 持久化到本地:watch监视,localStorage, JSON.stringify, JSON.parse



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/830169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言中的常量

整型常量 常量是指在程序运行期间其数值不发生变化的数据。整型常量通常简称为整数。 整数可以是十进制数、八进制数和十六进制数。例如&#xff0c;十进制的数值3356可以有下列二种不同的表示形式&#xff1a; 八进制数 06434十六进制数 0xd1c Tip:当我们判断十六进制或八进…

【JavaEE初阶】博客系统后端

文章目录 一. 创建项目 引入依赖二. 设计数据库三. 编写数据库代码四. 创建实体类五. 封装数据库的增删查改六. 具体功能书写1. 博客列表页2. 博客详情页3. 博客登录页4. 检测登录状态5. 实现显示用户信息的功能6. 退出登录状态7. 发布博客 一. 创建项目 引入依赖 创建blog_sy…

Qt编写自定义控件:自定义表头实现左右两端上部分圆角

如上图&#xff0c;左上角和右上角凸出来了。设置表格圆角和表头圆角和QHeaderView::section圆角都不管用。解决此问题需要重写QHeaderView的paintSection()函数&#xff1a; class CustomHeaderView : public QHeaderView { public:explicit CustomHeaderView(Qt::Orientati…

使用toad库进行机器学习评分卡全流程

1 加载数据 导入模块 import pandas as pd from sklearn.metrics import roc_auc_score,roc_curve,auc from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression import numpy as np import math import xgboost as xgb …

Layui实现OA会议系统之会议管理模块总合

目录 一、项目背景 二、项目概述 1. 概述 2. 环境搭建 3. 工具类引用 4. 功能设计 4.1 会议发布 4.2 我的会议 4.3 会议审批 4.4 会议通知 4.5 待开会议 4.6 历史会议 4.7 所有会议 5. 性能优点 5.1 兼容性好 5.2 可维护性和可扩展性 5.3 轻量灵活 5.4 模块化设计…

C++ 第六弹 STL

目录 1.什么是stl 2.六大组件-容器-序列式容器-C98 string 3.六大组件-容器-序列式容器-C98 vector 4.六大组件-容器-序列式容器-C98 list 5.六大组件-容器-序列式容器-C98 deque 6.六大组件-容器-序列式容器-C11 array 7.六大组件-容器-序列式容器-C11 forward_list 8…

Kaggle狗图像分类实战

文章目录 Kaggle狗图像分类实战d2l安装问题python语法学习os.path.joind2l 数据加载streamlit Kaggle狗图像分类实战 d2l安装问题 d2l安装失败&#xff0c;报错如上图 去下面的网站下载到该项目文件目录下再pip install即可 Python d2l项目安装包(第三方库)下载详情页面 - …

若依打印sql

官方issue 自动生成的代码&#xff0c;sql日志怎么没有打印 在ruoyi-admin中的application.yml配置如下。 # 日志配置&#xff0c;默认 logging:level:com.ruoyi: debugorg.springframework: warn#添加配置com.ying: debug输出sql

java 分支控制语句

在程序中&#xff0c;程序运行的流程控制决定程序是如何执行的。 顺序控制 介绍&#xff1a; 程序从上到下的逐行的执行&#xff0c;中间没有任何判断和跳转。 使用&#xff1a;java中定义变量时&#xff0c;采用合法的前向引用。如&#xff1a; public class Test{int num…

成都链安:7月区块链安全事件爆发式增长,导致损失超4.11亿美元

7月&#xff0c;各类安全事件数量及造成的损失较6月爆发式增长。7月发生较典型安全事件超36起&#xff0c;各类安全事件造成的损失总金额约4.11亿美元&#xff08;虚拟货币案件涉案金额除外&#xff09;&#xff0c;较6月上涨约321%。Rug Pull导致损失约2065万美元&#xff0c;…

【linux基础(三)】Linux基本指令(下)

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到开通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux基本指令 1. 前言2. 取头…

HTML 是什么?它的全称是什么?

聚沙成塔每天进步一点点 专栏简介HTML是什么&#xff1f;HTML的全称是什么&#xff1f;写在最后 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对We…

VMware ESXI虚拟网络和物理网络的对接

探讨VMware ESXI虚拟网络和物理网络的对接 前提&#xff1a; 在上篇搭建了ESXI 6.7&#xff0c;那和VMware Workstation一样想要创建虚拟机前提就必须先创建网络。没有网络连最基本的通信都没有&#xff0c;那肯定不行。所以我们解析要研究一下ESXI的网络。 物理连接模式&am…

Linux Day05

一、库文件生成与使用 1.1库文件 头文件是方法的声明&#xff0c;不是方法的实现 方法的实现是在库&#xff0c;库是预先编译好的方法的集合即.o文件 Linux上的库分为静态库(libxxx.a)和共享库(libxxx.so) 库文件常存放在/lib或者/usr/lib 库对应的头文件一般放在/usr/inc…

【188】Java8利用AVL树实现Map

AVL树又被叫做平衡二叉搜索树、平衡二叉树。AVL是其发明者的首字母缩写。 这篇文章中&#xff0c;AVLTreeMap 类集成了 java.util.Map 接口&#xff0c;并利用 AVL 树结构实现了 Map 接口的所有方法。本文还给出了测试代码。 为什么要发明AVL树&#xff1f; 当我按照从小到大…

【雕爷学编程】MicroPython动手做(33)——物联网之天气预报3

天气&#xff08;自然现象&#xff09; 是指某一个地区距离地表较近的大气层在短时间内的具体状态。而天气现象则是指发生在大气中的各种自然现象&#xff0c;即某瞬时内大气中各种气象要素&#xff08;如气温、气压、湿度、风、云、雾、雨、闪、雪、霜、雷、雹、霾等&#xff…

将自己的网站免费发布到互联网上【无需公网IP】

将自己的网站免费发布到互联网上【无需公网IP】 文章目录 将自己的网站免费发布到互联网上【无需公网IP】将本地搭建的网站发布到互联网步骤 ↓1. 注册并安装cpolar客户端1.1 windows系统1.2 linux系统&#xff08;支持一键自动安装脚本&#xff09;2. 登录cpolar web UI管理界…

Gradio-YOLOv5-YOLOv7 搭建Web GUI

目录 0 相关资料&#xff1a;1 Gradio介绍2 环境搭建3 GradioYOLOv54 GradioYOLOv75 源码解释 0 相关资料&#xff1a; Gradio-YOLOv5-Det&#xff1a;https://gitee.com/CV_Lab/gradio_yolov5_det 【手把手带你实战YOLOv5-入门篇】YOLOv5 Gradio搭建Web GUI: https://www.bi…

一次某某云上的redis读超时排查经历

性能排查&#xff0c;服务监控方面的知识往往涉及量广且比较零散&#xff0c;如何较为系统化的分析和解决问题&#xff0c;建立其对性能排查&#xff0c;性能优化的思路&#xff0c;我将在这个系列里给出我的答案。 问题背景 最近一两天线上老是偶现的redis读超时报警&#xf…

ChatGPT在工作中的七种用途

1. 用 ChatGPT 替代谷歌搜索引擎 工作时&#xff0c;你一天会访问几次搜索引擎&#xff1f;有了 ChatGPT&#xff0c;使用搜索引擎的频率可能大大下降。 据报道&#xff0c;谷歌这样的搜索引擎巨头&#xff0c;实际上很担心用户最终会把自己的搜索工具换成 ChatGPT。该公司针对…