Vue3 基础语法综合案例

280 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

1. 综合案例——书籍购物车

image-20210909185005310.png

案例说明:

  1. 在界面上以表格的形式,显示一些书籍的数据;
  2. 在底部显示书籍的总价格;
  3. 点击 +- 可以增加或减少书籍数量(如果为 1,那么不能继续 -);
  4. 点击移除按钮,可以将书籍移除(当所有书籍都移除完时,显示:“购物车为空~”);

初步实现如下:

  • 目录结构:

image-20210909185352501.png

  • index.html 中的内容:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <link rel="stylesheet" href="style.css">
    </head>
    <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) in books">
                <td>{{ index + 1 }}</td>
                <td>{{ book.name }}</td>
                <td>{{ book.date }}</td>
                <td>¥{{ book.price }}</td>
                <td>
                  <button :disabled="book.count <= 1" @click='decrement(index)'>-</button>
                  <span class="counter">{{ book.count }}</span>
                  <button @click="increment(index)">+</button>
                </td>
                <td>
                  <button @click="removeBook(index)">移除</button>
                </td>
              </tr>
            </tbody>
          </table>
          <h2>总价:¥{{totalPrice}}</h2>
        </template>
        <template v-else>
          <h2>购物车为空~</h2>
        </template>
      </template>
    
      <script src="../js/vue.js"></script>
      <script src="./index.js"></script>
    </body>
    </html>
    
  • index.js 中的内容:

    Vue.createApp({
      data() {
        return {
          books: [
            {
              id: 1,
              name: '《算法导论》',
              date: '2006-9',
              price: 85.00,
              count: 1
            },
            {
              id: 2,
              name: '《UNIX编程艺术》',
              date: '2006-2',
              price: 59.00,
              count: 1
            },
            {
              id: 3,
              name: '《编程珠玑》',
              date: '2008-10',
              price: 39.00,
              count: 1
            },
            {
              id: 4,
              name: '《代码大全》',
              date: '2006-3',
              price: 128.00,
              count: 1
            },
          ]
        }
      },
      computed: {
        totalPrice() {
          let total = 0;
          for (const book of this.books) {
            total += book.price * book.count;
          }
          return total;
          // 或者使用数组的归并方法 reduce()
          // return this.books.reduce((prev, curr) => prev + curr.price * curr.count, 0);
        }
      },
      methods: {
        decrement(index) {
          // 通过索引值拿到对象
          this.books[index].count--;
        },
        increment(index) {
          this.books[index].count++;
        },
        removeBook(index) {
          this.books.splice(index, 1);
        }
      },
      template: '#my-app'
    }).mount('#app');
    
  • style.css 中的内容:

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

页面效果如下:

综合案例-书籍购物车.gif

现在,我们来思考一个问题:价格前面的人民币符号(¥)直接写在模板中的做法合适吗?

不合适。因为首先,如果模板中有很多地方要显示价格,那么价格前面都要加上这个人民币符号;而且如果有多个界面都要显示价格,那么每个界面上在显示价格时,前面也都要加上人民币符号。这样看来这种直接修改模板的做法就显得有点笨拙了。那有没有更好的办法呢?有,在 Vue2 中,我们可以使用过滤器来给价格添加上人民币符号。但是,从 Vue 3.0 开始,过滤器已移除,且不再支持。取而代之的是,建议用计算属性方法调用来实现。

下面我们就分别用计算属性和方法来实现 Vue2 中过滤器的效果。

1.1 Vue3 中使用计算属性替代过滤器

我们对前面 index.js 文件中的 computed 选项中的内容进行修改:

computed: {
  totalPrice() {
    let total = 0;
    for (const book of this.books) {
      total += book.price * book.count;
    }
    // return total;
    return '¥' + total;
  },
  filterBooks() {
    const filteredBooks = this.books.map(book => {
      // 注意这里是对 books 中的对象做了修改,所以上面计算属性 totalPrice 中的 book.price 取到的会是这里修改后的值(价格前加了¥)
      // 原因是这里 filteredBooks 数组中的对象元素和 books 数组中的对象元素其实是指向的同一个对象的引用
      //(数组中的对象元素其实保存的是对象的引用)
      // book.price = '¥' + book.price;
      // return book;
      // 为了不影响其它地方使用 book.price,我们这里拷贝一份 book 后再使用
      const tempBook = Object.assign({}, book);
      tempBook.price = '¥' + book.price;
      return tempBook;
    });
    return filteredBooks;
  }
}

再将 index.html 文件中对 books 的遍历改为对 filterBooks 的遍历:

<tr v-for="(book, index) in filterBooks">

同时去掉模板中价格前面的人民币符号(¥):

<td>{{ book.price }}</td>
<h2>总价:{{totalPrice}}</h2>

最终页面的效果和之前是一样的。

1.2 Vue3 中使用方法调用替代过滤器

我们也可以通过调用方法来实现同样的效果,在 index.js 文件中的 methods 选项中添加方法:

methods: {
  // ...
  formatPrice(price) {
    return '¥' + price;
  }
}

computed 选项中的计算属性 totalPrice 的返回值前的人民币符号取消掉:

totalPrice() {
  let total = 0;
  for (const book of this.books) {
    total += book.price * book.count;
  }
  return total;
}

再将 index.html 文件中书籍的遍历设置为对 books 的遍历:

<tr v-for="(book, index) in books">

同时在模板中需要展示价格的地方调用我们添加的方法:

<td>{{ formatPrice(book.price) }}</td>
<h2>总价:{{formatPrice(totalPrice)}}</h2>

最终页面的效果和之前是一样的。

当然,本案例中我们是把给价格添加人民币符号的方法定义在当前的 Vue 实例中的,而如果有另外一个页面中也要用到这个方法该怎么办呢?这时为了方便使用,我们就可以通过形如 app.config.globalProperties.$filters 的全局属性把方法定义到全局了,相当于实现了全局过滤器,这样就可以在任何地方直接拿到这个方法了。具体我们后面再讲。