Vue基础语法

113 阅读9分钟

Vue3基础语法

创建自己的Vscode代码片段

网址:snippet-generator.app/

Mustache语法

    <div id="app"></div>
    <template id="my-app">
      <!-- 1.mustache的基本使用 -->
      <!-- 可用“-”连接多个-->
      <h2>{{message}} - {{message}}</h2>
      <!-- 2.是一个js表达式 -->
      <h2>{{counter * 10}}</h2>
      <h2>{{message.split(' ').reverse().join(' ')}}</h2>
      <!-- 3.也可以调用函数 -->
      <h2>{{getReverseMessage()}}</h2>
      <!-- 4.三元表达式-->
      <h2>{{isShow ? '哈哈哈哈' : '呵呵呵呵'}}</h2>
      <button @click="click">切换</button>
​
      
      <!-- 错误用法 -->
      <!-- ar name = "abc" -> 赋值语句,不是表达式 -->
      <h2>{{var name = "abc"}}</h2>
      <!-- if语句不是表达式,错误-->
      <h2>{{ if (isShow) { return "哈哈哈" } }}</h2>
    </template>
​
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            message: "Hello World",
            counter: 100,
            isShow: true,
          };
        },
        methods: {
          getReverseMessage() {
            return this.message.split(" ").reverse().join(" ");
          },
          click() {
            this.isShow = !this.isShow;
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-once

v-once用于制定元素或者组件只渲染一次:

  • 当数据发生变化时,元素或者组件以及所有的子元素将视为静态内容并且跳过;
  • 该指令可以用于性能优化;
 <h2>{{counter}}</h2>
 <h2 v-once>{{counter}}</h2> // 只会被渲染一次,后续永不会改变
 <button @click="increment">+1</button>

v-text指令

用于更新元素的 textContent:

<h2>{{message}}</h2>
<!-- 等价于下面代码,但上面更加灵活 -->
<h2 v-text="message"></h2>

v-html

默认情况下,如果我们展示的内容本身是 html 的,那么 vue 并不会对其进行特殊的解析。

若我们希望这个内容被vue解析,可使用v-html

    <div id="app"></div>
    <template id="my-app">
      <div>{{msg}}</div>
     <!--利用下面代码可实现html样式解析 -->  
      <div v-html="msg"></div>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            msg: "<span style='color:red; background: blue'>哈哈哈</span>",
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-pre

v-pre用于跳过元素和他的子元素的编译过程,显示原始的Mustache标签:

跳过不需要编译的节点,加快编译的速度;

<template id="my-app">
      <h2 v-pre>{{message}}</h2> 
</template>

v-cloak

这个指令可以隐藏未编译的Mustache标签直到组件实例准备完毕。

 <template id="my-app">
      <h2 v-cloak>{{message}}</h2>
 </template>

v-bind

前端讲的一系列指令,主要是将值插入到模板内容中。

但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定。

  • 比如动态绑定a元素的href属性;
  • 比如动态绑定img元素的src属性;

绑定属性我们使用v-bind:

  • 缩写::
  • 预期:any(with argument)|Object(without argument)
  • 参数:attrOrProp(optional)
  • 修饰符:
  • 用法:动态绑定一个或多个ttribute,或一个组件prop到表达式。

绑定class介绍:

在开发中,有时候我们的元素class也是动态的,比如:

  • 当数据为某个状态时,字体显示红色。
  • 当数据另一个状态时,字体显示黑色。

绑定class有两种方式:

  • 对象语法;
    <div id="app"></div>
    <template id="my-app">
      <!-- 对象语法:{} 可以有多个键值对-->
      <div :class="{active: isActive, title:isTitle}">哈哈哈哈</div>
      <button @click="click">切换</button>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            isActive: false,
            isTitle: true,
          };
        },
        methods: {
          click() {
            this.isActive = !this.isActive;
            this.isTitle = !this.isTitle;
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>
  • 数组语法
    <div id="app"></div>
    <template id="my-app">
      <!-- 数组语法:可以加三元运算符亦或者数组里可以嵌套对象-->
      <div :class="['abc',{active: isActive, title:isTitle}]">哈哈哈哈</div>
      <button @click="click">切换</button>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            isActive: false,
            isTitle: true,
          };
        },
        methods: {
          click() {
            this.isActive = !this.isActive;
            this.isTitle = !this.isTitle;
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

绑定style介绍:

可以利用v-bind:style来绑定一些CSS内联样式:

  • 某些样式我们需要根据数据动态来决定;
  • 如某些文字的颜色,大小等;

CSS property 名可以用驼峰式或短横线分隔来命名;

绑定class有两种方式:

  • 对象语法
    <div id="app"></div>
    <template id="my-app">
      <div :style="{color: finalColor, fontSize: finalFontSize}">
        哈哈哈哈哈
      </div>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            finalColor: "red",
            finalFontSize: "50px",
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>
  • 数组语法
    <div id="app"></div>
    <template id="my-app">
      <div :style="[style1Obj, style2Obj]">哈哈哈哈哈</div>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            style1Obj: {
              color: "red",
              fontSize: "30px",
            },
            style2Obj: {
              textDecoration: "underline",
            },
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

动态绑定属性:

    <div id="app"></div>
    <template id="my-app">
      <div :[name]="value">hhhhh</div>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            name: "abc",
            value: "kobe",
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

绑定一个对象:

如果我们希望将一个对象的所有属性,绑定到元素上的所有属性,可以直接使用v-bind绑定一个对象;

    <div id="app"></div>
    <template id="my-app">
      <div v-bind="info">哈哈哈哈哈</div>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            info: {
              name: "why",
              age: 18,
              height: 1.88,
            },
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-on绑定事件

v-on的使用:

    <div id="app"></div>
    <template id="my-app">
      <!-- 完整写法: v-on:监听的事件 -->
      <button v-on:click="btn1click">按钮1</button>
      <div class="area" v-on:mousemove="mouseMove">div</div>

      <!-- 语法糖 -->
      <button @click="btn1click">按钮1</button>
      <!-- 绑定一个表达式 -->
      <button @click="counter++">{{counter}}</button>
      <!-- 绑定多个对象 -->
      <div class="area" v-on="{click: btn1click, mousemove: mouseMove}">
        div
      </div>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            message: "Hello World",
            counter: 100,
          };
        },
        methods: {
          btn1click() {
            console.log("按钮1发生了点击");
          },
          mouseMove() {
            console.log("鼠标移动");
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-on参数传递

    <div id="app"></div>
    <template id="my-app">
        <!-- 默认传入event,可在方法中获取 -->
      <button @click="btn1Click">按钮1</button>

      <!-- $event可以获取到事件发生时的事件对象 -->
      <button @click="btn2Click($event, 'coderwhy', 18)">按钮2</button>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            message: "Hello World",
          };
        },
        methods: {
          btn1Click(event) {
            console.log(event);
          },
          btn2Click(event, name, age) {
            console.log(name, age, event);
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-on的修饰符

v-on支持修饰符,修饰符相当于对事件进行了一些特殊处理:

  • .stop - 调用event.stopPropagation()
  • .prevent - 调用event.preventDefault()
  • .capture - 添加事件侦听器时使用Capture模式。
  • .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
  • .{keyAlias} - 仅当事件是从特定键触发时才触发回调。
  • .once - 只触发一次回调。
  • .left - 只当点击鼠标左键时触发。
  • .left - 只当点击鼠标右键时触发。
  • .middle - 只当点击鼠标中键时触发。
  • .passive - {passive: ture}模式添加侦听器。
    <div id="app"></div>
    <template id="my-app">
      <div @click="divClick">
        <!-- 加上.stop可以阻止冒泡 -->
        <button @click.stop="btnClick">按钮</button>
      </div>
      <!-- 指定触发监听事件的键盘文字 -->
      <input type="text" @keydown.i="iKeyup" />
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            message: "Hello World",
          };
        },
        methods: {
          divClick() {
            console.log("divClick");
          },
          btnClick() {
            console.log("btnClick");
          },
          iKeyup() {
            console.log("i");
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

条件渲染

  • 在某些情况下,我们需要根据当前的条件决定某些元素或组件是否渲染,这个时候我们就需要进行条件判断了。

  • Vue 提供了下面的指令来进行条件判断:

    • v-if
    • v-else
    • v-else-if

上述三种指令用于根据条件来渲染某一块的内容:

  • 这些内容只有在条件为 true 时,才会被渲染出来;
  • 这三个指令与JavaScript的条件语句 if、else、else if 类似;
    <div id="app"></div>
    <template id="my-app">
      <input type="text" v-model="score" />
      <h2 v-if="score > 90">优秀</h2> 
      <h2 v-else-if="score > 60">良好</h2>
      <h2 v-else>不及格</h2>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            score: 90, // 分数默认为90;
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-if 的渲染原理:

  • v-if 是惰性的;
  • 当条件为 false 时,其判断内容完全不会被渲染或者会被销毁;
  • 当条件为 true 时,才会真正渲染条件块中的内容;

v-iftemplate 结合使用可避免性能浪费

    <div id="app"></div>
    <template id="my-app">
      <!-- 添加两个 div 虽然可以达到目的,但会造成性能的浪费    -->
      <!-- <div v-if="isShowHa">
        <h2>哈哈哈</h2>
        <h2>哈哈哈</h2>
        <h2>哈哈哈</h2>
        </div>
​
      <div v-else>
        <h2>呵呵呵</h2>
        <h2>呵呵呵</h2>
        <h2>呵呵呵</h2>
      </div> -->
​
      <!-- 可利用 template 模板达到这种效果 -->
      <template v-if="isShowHa">
        <h2>哈哈哈</h2>
        <h2>哈哈哈</h2>
        <h2>哈哈哈</h2>
      </template>
​
      <template v-else>
        <h2>呵呵呵</h2>
        <h2>呵呵呵</h2>
        <h2>呵呵呵</h2>
      </template>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            isShowHa: true,
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>
  • v-show
    <div id="app"></div>
    <template id="my-app">
      <h2 v-show="isShow">哈哈哈哈</h2>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            isShow: true,
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-showv-if 的区别:

  • 用法上的区别:

    • v-show 是不支持 template
    • v-show 不可以和 v-else 一起使用;
  • 本质的区别:

    • v-show 元素无论是否需要显示到浏览器上,它的 DOM 实际都是有渲染的,只是通过 CSS 的 display 属性来进行切换;
    • v-if 当条件为 false 时,其对应的元素直接不会被渲染到 DOM 中;
  • 开发中的选择

    • 如果元素需要在显示和隐藏之间频繁切换,使用 v-show;
    • 否则使用 v-if

列表渲染

v-for 的基本使用:

    <div id="app"></div>
    <template id="my-app">
      <h2>电影列表</h2>
      <ul>
        <!-- 遍历数组 -->
        <li v-for="(movie, index) in movies">{{index + 1}}.{{movie}}</li>
      </ul>
​
      <h2>个人信息</h2>
      <ul>
        <!-- 遍历对象 -->
        <li v-for="(value, key, index) in info">
          {{value}}-{{key}}-{{index + 1}}
        </li>
      </ul>
      <!-- 遍历数字 -->
      <ul>
        <li v-for="(num, index) in 10">{{num}}-{{index}}</li>
      </ul>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            movies: ["星际穿越", "盗梦空间", "大话西游", "教父"],
            info: {
              name: "why",
              age: 18,
              height: 1.88,
            },
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-fortemplate 结合使用:

    <div id="app"></div>
    <template id="my-app">
      <ul>
        <template v-for="(value, key) in info">
          <li>{{value}}</li>
          <li>{{key}}</li>
        </template>
      </ul>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            info: {
              name: "why",
              age: 18,
              height: 1.88,
            },
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

数组更新检测和修改方法

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹的方法包括:

  • push(): 添加元素
  • pop(): 删除最后一个元素
  • shift(): 删除第一个元素
  • unshift(): 将一个或多个元素添加到数组开头
  • splice(): 删除或替换现有元素或原地添加新的元素
  • sort() : 对数组进行排序
  • reverse(): 翻转数组

替换数组的方法

上面方法会直接修改运来的数组,但是某些方法不会替换原来的数组,而是会生成新的数组,比如 filter() (筛选数组)、concat() (合并两个或多个数组) 和 slice() (对原来数组的拷贝)。

    <div id="app"></div>
    <template id="my-app">
      <ul>
        <li v-for="(movie, index) in movies">{{index + 1}}.{{movie}}</li>
      </ul>
      <input type="text" v-model="newMovie" />
      <button @click="addMovie">添加电影</button>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            newMovie: "",
            movies: ["星际穿越", "盗梦空间", "大话西游", "教父", "默"],
          };
        },
        methods: {
          addMovie() {
            this.movies.push(this.newMovie);
            this.newMovie = "";
​
            this.movies = this.movies.filter((item) => item.length > 2); // 将数组中字符长     度大于2的元素保留下来
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

v-for 中 key 的作用

在使用 v-for 进行列表渲染时,我们通常会给元素或者组件绑定一个 key 属性。

具体作用为(官方解释):

  • key 属性主要作用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes
  • 如果不适用 key ,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;
  • 而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除/销毁 key 不存在的元素;

认识VNode (Virtual Node, 虚拟节点)

HTML元素 VNode 概念:

  • 事实上,无论是组件还是元素,它们最终在 Vue 中表示出来的都是一个个 VNode
  • VNode 的本质是一个 JavaScript 对象;
    <template>
      <div class="title" style="font-size: 30px; color: red">哈哈哈</div>
    </template>
    // 模板中的代码产生的 VNode 如下
    <script>
      const vnode = {
        type: "div",
        props: { class: "title", style: { "font-size": "30px", color: "red" } },
        children: "哈哈哈",
      };
    </script>

虚拟 DOM (VDom)

若存在的元素比较复杂,它们就会形成一个 VNode Tree,即为虚拟 DOM。

插入 f 案例

  <div id="app"></div>
    <template id="my-app">
      <ul>
        <li v-for="item of letters" :key="item">{{item}}</li> <!-- 使用 key 绑定,性能更高 -->
      </ul>
      <button @click="insertF">插入f元素</button>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            letters: ["a", "b", "c", "d"],
          };
        },
        methods: {
          insertF() {
            this.letters.splice(2, 0, "f");
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

\

复杂 data的处理方式

  • 在模板中可以直接通过插值语法显示一些 data 中的数据

  • 但在某些情况下,需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示;

    • 需要对多个 data 数据进行运算、三元运算符来决定结果、数据进行某种转化后显示;
    • 在模板中使用表达式,可以非常方便的实现,但设计初衷是用于简单运算
    • 在模板中放入太多逻辑会让模板过重和难以维护
    • 如果很多地方都使用到,会有大量重复代码;
  • 将逻辑抽离的方法:

    • 将逻辑抽离到一个 method 中,放到 methodsoptions 中,但弊端是所有的 data 使用过程都会变成方法的调用
    • 使用计算属性 computed

认识计算属性 computed

计算属性的含义:

对于任何包含响应式数据的复杂逻辑,都应使用计算属性,它将被混入到组件实例中。所有 gettersetterthis 上下文自动地绑定为组件实例;

计算属性的用法:

  • 选型:computed;
  • 类型:{[key: string]: Function | {get: Function, set: Function}}

模板语法对案例的实现

    <div id="app"></div>
    <template id="my-app">
      <h2>{{firstName + ' ' + lastName}}</h2>
      <h2>{{score >= 60 ? '及格' : '不及格'}}</h2>
      <h2>{{message.split(' ').reverse().join(' ')}}</h2>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            firstName: "Kobe",
            lastName: "Bryant",
            score: 80,
            message: "Hello World",
          };
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

存在的缺点:

  • 模板中存在大量复杂逻辑,不便于维护;
  • 当有多次一样逻辑时,存在代码重复;
  • 多次使用时,运算也需多次执行,没有缓存;

methods 方法对案例的实现

    <div id="app"></div>
    <template id="my-app">
      <h2>{{getFullName()}}</h2>
      <h2>{{getResult()}}</h2>
      <h2>{{getReverseMessage()}}</h2>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            firstName: "Kobe",
            lastName: "Bryant",
            score: 80,
            message: "Hello World",
          };
        },
        methods: {
          getFullName() {
            return this.firstName + " " + this.lastName;
          },
          getResult() {
            return this.score >= 60 ? "及格" : "不及格";
          },
          getReverseMessage() {
            return this.message.split(" ").reverse().join(" ");
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

存在的缺点:

  • 显示调用结果不清晰,每次都是调用方法;
  • 多次运算时,没有缓存;

计算属性 (computed) 对案例的实现

    <div id="app"></div>
    <template id="my-app">
      <h2>{{fullName}}</h2>
      <h2>{{result}}</h2>
      <h2>{{reverseMessage}}</h2>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            firstName: "Kobe",
            lastName: "Bryant",
            score: 80,
            message: "Hello World",
          };
        },
        computed: {
          // 定义了一个计算属性fullName
          fullName() {
            return this.firstName + " " + this.lastName; // 计算结果fullName会存入缓存中等待调用,不需要多次执行
          },
          result() {
            return this.score >= 60 ? "及格" : "不及格";
          },
          reverseMessage() {
            return this.message.split(" ").reverse().join(" ");
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

特点:

  • 计算属性看起来像一个函数,但在我们使用时不需要加 ''()'';
  • 计算属性有缓存;

计算属性的 settergetter

  • 计算属性在大多数情况下,只需要一个 getter 方法即可,所以我们会将计算属性直接写成一个函数

  • 设置计算属性的值:

    • 可以给计算属性设置一个 setter方法;

认识侦听器 watch

侦听器的用法:

  • 选项:watch
  • 类型:{[key: string]: string | Function | Object | Array}

侦听器的意义:

  • data 返回的对象中定义数据,通过插值语法等方式绑定到 template 中;
  • 当数据变化时,template 会自动进行更新来显示最新的数据;
  • 某些情况下,我们希望在代码逻辑中监听某个数据的变化,需要使用侦听器 watch 来完成;
    <div id="app"></div>
    <template id="my-app">
      您的问题: <input type="text" v-model="question" />
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            // 侦听到question的变化时,进行一些逻辑的处理
            question: "Hello World",
          };
        },
        watch: {
          // question侦听的data中的属性的名称
          // newValue变化后的新值
          // oldValue变化前的旧值
          question(newValue, oldValue) {
            console.log("新值:", newValue, "旧值:", oldValue);
            this.queryAnswer();
          },
        },
        methods: {
          queryAnswer() {
            console.log(`你的问题${this.question}的答案是哈哈哈`);
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

侦听器的配置选项

    <div id="app"></div>
    <template id="my-app">
      <h2>{{info.name}}</h2>
      <!-- <button @click="changeInfo">改变info</button> -->
      <button @click="changeInfoName">改变info.name</button>
      <button @click="changeInfoNbaName">改变info.nba.name</button>
    </template>
    <script src="../js/Vue.js"></script>
    <script>
      const App = {
        template: "#my-app",
        data() {
          return {
            info: { name: "why", age: 18, nba: { name: "kobe" } },
          };
        },
        watch: {
          // 默认情况下我们的侦听器只会针对侦听的数据本身的改变(内部发生的改变是不能侦听的)
          //  info(newInfo, oldInfo) {
          //   console.log('newValue:', newInfo,'oldValue:', oldInfo);
          // }
​
          // 深度侦听/立即执行(一定会执行一次)
          info: {
            handler: function (newInfo, oldInfo) {
              console.log("newValue:", newInfo, "oldValue:", oldInfo);
            },
            deep: true, // 深度侦听
            immediate: true, // 立即执行
          },
        },
        methods: {
        //  changeInfo() {
        //   this.info = { name: "kobe" };
        //  },
          changeInfoName() {
            this.info.name = "kobe";
          },
          changeInfoNbaName() {
            this.info.nba.name = "james";
          },
        },
      };
      Vue.createApp(App).mount("#app");
    </script>

综合案例(书籍购物车)

HTML 部分

  <body>
    <div id="app"></div>
    <template id="my-app">
      <template v-if="books.length > 0">
        <table>
          <thead>
            <th>序号</th>
            <th>书籍名称</th>
            <th>出版日期</th>
            <th>价格</th>
            <th>购买数量</th>
            <th>操作</th>
          </thead>
          <tbody>
            <tr v-for="(book, index) of books">
              <td>{{index + 1}}</td>
              <td>{{book.name}}</td>
              <td>{{book.date}}</td>
              <td>{{formatPrice(book.price)}}</td>
              <td>
                <button :disabled="book.count <= 1" @click="decrement(index)">
                  -1
                </button>
                <span class="counter">{{book.count}}</span>
                <button @click="increment(index)">+1</button>
              </td>
              <td><button @click="removeBook(index)">移除</button></td>
            </tr>
          </tbody>
        </table>
        <h2>总价格: {{formatPrice(totalPrice)}}</h2>
      </template>
      <template v-else>
        <h2>购物车为空</h2>
      </template>
    </template>
    <script src="../js/Vue.js"></script>
    <script src="index.js"></script>
  </body>

CSS 部分

table {
  border: 1px solid #e9e9e9;
  border-collapse: collapse;
  border-spacing: 0;
}
th,
td {
  padding: 8px 16px;
  border: 1px solid #e9e9e9;
  text-align: left;
}
th {
  background-color: #f7f7f7;
  color: #5c6b77;
  font-weight: 600;
}
.counter {
  margin: 0 5px;
}
​

JS 部分

const App = {
  template: "#my-app",
  data() {
    return {
      books: [
        {
          id: 1,
          name: "《算法导论》",
          date: "2006-9",
          price: 85.0,
          count: 1,
        },
        {
          id: 2,
          name: "《UNIX编程艺术》",
          date: "2006-2",
          price: 59.0,
          count: 1,
        },
        {
          id: 3,
          name: "《编程珠玑》",
          date: "2008-10",
          price: 39.0,
          count: 1,
        },
        {
          id: 4,
          name: "《代码大全》",
          date: "2006-3",
          price: 128.0,
          count: 1,
        },
      ],
    };
  },
  computed: {
    totalPrice() {
      let finalPrice = 0;
      for (let book of this.books) {
        finalPrice += book.count * book.price;
      }
      return finalPrice;
    },
    // Vue3不支持过滤器了,推荐两种方法: 使用计算属性/使用全局的方法
    // 计算属性方法如下:
    // filterBooks() {
    //   return this.books.map((item) => {
    //     item.price = "¥" + item.price;
    //     return item;
    //   });
    // },
  },
​
  methods: {
    increment(index) {
      // 通过索引值获取到对象
      this.books[index].count++;
    },
    decrement(index) {
      this.books[index].count--;
    },
    removeBook(index) {
      this.books.splice(index, 1);
    },
    formatPrice(price) {
      return "¥" + price;
    },
  },
};
Vue.createApp(App).mount("#app");

\