《Vue.js 实战》基础篇(上)

本章内容:认识 Vue.js、数据绑定、计算属性、内置指令

一、初始 Vue.js

1.1 Vue.js 是什么

简单小巧的核心,渐进式技术栈,足以应付任何规模的应用。
简单小巧 指Vue.js 压缩后大小仅 17KB
所谓渐进式(Progressive)就是你可以一步一步、有阶段性地来使用 Vue.js

Vue.js 提供了现代 Web 开发中常见的高级功能,比如:

  • 解耦视图与数据
  • 可复用的组件
  • 前端路由
  • 状态管理
  • 虚拟 DOM(Virtual DOM)
1.1.1 MVVM 模式

Vue.js使用 MVVM (Model-View-ViewModel)模式。MVVM 模式是由经典的软件架构MVC 衍生而来的。当 View(视图层)变化时,会自动更新到 ViewModel(视图模型),反之亦然

MVVM 关系

1.1.2、Vue.js 有什么不同

与传统开发模式相比,Vue.js 通过 MVVM 的模式拆分为 视图数据 两部分,并将其分离,因此,你只需要关系数据即可,DOM的 事情 Vue 会自动帮你搞定。

1.2、如何使用 Vue.js

1.2.1、传统的前端开发模式

前些年称之为“万金油” 的一套技术栈:

jQuery + RequireJS(SeaJS) + artTemplate(doT) + Gullp(Grunt)

这套技术栈 以 jQuery 为核心,RequireJS 或 SeaJS 进行模块开发;加上轻量级的前端模板artTemplate 或 doT;最后使用自动构建工具(如 Gulp)压缩代码

1.2.2、Vue.js 的开发模式

Vue.js 是一个渐进式框架,根据项目需求的不同,你可以选择从不同的维度来使用它。

  • 如果只是想体验 Vue.js 或 开发简单的 HTML5页面 或 小应用,可以直接通过 <script> 加载 CDN 文件

  • 对于一些业务逻辑复杂,对前端工程有要求的项目,可以使用 Vue 单文件的形式配合 webpack 使用,必要时还会用到 vuex 状态管理,vue-router 管理路由。

二、数据绑定 和 第一个Vue应用

学习任何一种框架,都应该从 写 Hello World 应用 开始

<body>
  
  <div id="app">

    <input type="text" v-model="user">

    <h1>Hello {{user}}</h1>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
  
      const app = new Vue({
        el: '#app',
        data: {
          user: ''
        }
      })
  </script>
</body>

在输入框输入的内容会实时展示在 页面的 h1 标签内,虽然这是一段简单的代码,却展示了 Vue.js 最核心的功能:数据的双向绑定

2.1、Vue 实例 与 数据绑定

2.1.1、实例 与 数据

Vue.js 应用的创建很简单,通过 构造函数 Vue 就可以 创建一个 Vue 实例,并启动 Vue 应用。

const app = new Vue({
  // options
})

app 就代表了这个 Vue 实例

实例选项

首先,必不可少的一个选项就是 el。el 用于指定一个页面中已经存在的 DOM
元素来挂载 Vue 实例,他可以是 HTMLElement,也可以是 CSS 选择器。

const app1 = new Vue({
  el: document.getElementById('app')
})

const app2 = new Vue({
  el: '#app'
})

挂载成功后,我们可以通过 app.$el 来访问该元素

data 选项,可以声明应用中需要双向绑定的数据,建议所有会用到的数据都预先在 data 内声明,这样不至于将数据散落在业务逻辑中,难以维护。
Vue 实例本身也代理了 data 对象里的所有属性,所以可以这样访问:

var app = new Vue({
  el: '#app',
  data: {
    a: 1
  }
})

console.log(app.a) // 1

处理显式地声明数据外,也可以指定一个已有的变量,并且他们之间默认建立了双向数据绑定,当修改其中任意一个时,另一个也会一起改变。

2.1.2、生命周期

每个 Vue 实例创建时,都会经历一系列的初始化过程,同时也会调用相应的生命周期钩子,我们可以利用这些钩子,在合适的时机执行我们的业务逻辑。
比较常用的有:

  • created:实例创建完成后调用,此阶段完成了数据的观测等,但尚未挂载,$el 还不可用。需要初始化处理一些数据时比较有用
  • mounted:el 挂载到实例上后调用,一般我们的第一个业务逻辑在这里开始
  • beforeDestroy:实例销毁之前调用。主要解绑一些使用 addEventListener 监听的事件等。
    详细的声明周期函数,会在后面章节讲解
    这些钩子 与 eldata 类似,也是作为选项写入 Vue 实例内,并且 钩子的 this 指向的是调用它的 Vue 实例:
var app = new Vue({
  el: '#app',
  data: {
    a: 2
  },
  created: function() {
    console.log(this.a) // 2
  },
  mouted: function() {
    console.log(this.$el) // <div id="app"></div>
  }
})
2.1.3、差值与表达式

使用 双大括号(Mustache 语法)“{{}}” 是最基本的文本插值方法,它会自动将我们双向绑定的数据实时显示出来。

<body>
  
  <div id="app">
    <h1>{{ book }}</h1>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
  
      const app = new Vue({
        el: '#app',
        data: {
          book: '《云边有个小卖部》'
        }
      })
  </script>
</body>

如果有时候想要输出 HTML,而不是将数据解释后的存文本,可以使用 v-html

<body>
  
  <div id="app">
    <span v-html="bookLink"></span>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
  
      const app = new Vue({
        el: '#app',
        data: {
          bookLink: '<a href="#">《云边有个小卖部》</a>'
        }
      })
  </script>
</body>

这里要注意,如果将用户产生的内容 使用 v-html 输出后,有可能会导致 XSS 攻击,所以要在服务端对用户提交的内容进行处理,一般可将 尖括号 "<>" 转义


如果想显示 {{}} 标签而不进行替换,可以使用 v-pre,即可跳过这个元素和它的子元素的编译过程。

<span v-pre> {{ 这里的内容不会被编译 }} <span>

{{}} 中,处理简单的绑定属性外,还可以使用 JavaScript 表单是进行简单的运算、三元运算等,例如:

<div id="app">
  {{num / 10}}
  {{ isOK ? '确定' : '取消' }}
  {{ text.split(',').reverse().join(',') }}
</div>
<script>
  var app = new Vue({
    el: '#app',
    data: {
      number: 100,
      isOK: false,
      text: '123,456'
    }
  })
</script>

Vue.js 只支持单个表达式,不支持语句和流程控制。另外,在表达式中,不能使用用户自定义的全局变量,只能使用 Vue 白名单内的全局变量,例如 Math 和 Date

2.1.4、过滤器

Vue.js 支持在 {{}} 插值的尾部添加一个 管道符 “ | ” 来对数据进行过滤,经曾用于格式化文本,比如字母全部大写,货币千位使用逗号分割等。过滤的规则是自定义的,通过给 Vue 实例添加 filters 来实现
时间案例

<body>
  
  <div id="app">
    {{ date | formatDate}}
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
  
      const app = new Vue({
        el: '#app',
        data: {
          date: new Date()
        },
        mounted() { // 实例挂载后初始化义时器
          this.timer = setInterval(() => this.date = new Date(), 1000)
        },
        beforeDestroy () { // 实例销毁时 情况定时器
          if (this.timer) clearInterval(this.timer)
        },
        filters: { // 定义过滤器
          formatDate(value) { // value 指需要进行过滤的数据,  管道符前面的数据
            const date = new Date(value)
            const year = date.getFullYear()
            const month = (date.getMonth() + 1).toString().padStart(2, 0)
            const day = date.getDate().toString().padStart(2, 0)
            const hours = date.getDate().toString().padStart(2, 0)
            const min = date.getMinutes().toString().padStart(2, 0)
            const sec = date.getSeconds().toString().padStart(2, 0)

            return `${year}-${month}-${day} ${hours}:${min}:${sec}`
          },
        }
      })
  </script>
</body>

过滤器也可以串联,而且可以接收参数

<!-- 串联 -->
{{ message | filterA | filterB }}

<!-- 接收参数 -->
{{ message | filter('arg1', 'arg2') }}

这里的 arg1 和 arg2 将分别传给过滤器的第二个 和 第三个参数,因为第一个是数据本身。

2.2、指令

指令(Directives)是 Vue.js 模板中最常用的 一项功能,它带有前缀的 v-,Vue,js内置了很多指令,后面会详细讲到,在此之前,你需要先知道 v-bindv-on

v-bind 的基本用途是动态更新 HTML 元素上的属性,比如 id、class 等
例如下面示例:

<body>
  
  <div id="app">
    <a v-bind:href="url">链接</a>
    <img v-bind:src="imgUrl" alt="">
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        url: 'https://www.baidu.com',
        imgUrl: 'https://wallhaven.cc/w/8396gk'
      }
    })
  </script>
</body>

v-on,用来绑定事件监听,这样我们就可以实现一些交互

<body>
  
  <div id="app">
    <p v-if="isShow">The End of the F***ing World</p>
    <button v-on:click="clickHandle">toggle</button>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isShow: true
      },
      methods: {
        clickHandle() {
          this.isShow = !this.isShow
        }
      }
    })
  </script>
</body>

表达式可以是一个方法名,这些方法都写在 Vue 实例的 methods 属性内,并且是函数的形式,函数内的this 指向的是当前 Vue 实例本身,因此可以直接使用 this.xxx 的形式来访问或修改数据

表达式除了方法名,也可以直接是一个内联语句。

  <div id="app">
    <p v-if="isShow">The End of the F***ing World</p>
    <button v-on:click="this.isShow = !this.isShow">toggle</button>
  </div>

如果绑定的事件要处理复杂的业务逻辑,建议还是在 methods 里声明一个方法,这样可读性更强也好维护。

Vue.js 将 methods 里的方法也代理了,所以也可以像 访问 Vue 数据那样来调用方法。在外部 也可以通过 “实例.方法名” 调用内部的方法

2.3、语法糖

语法糖是指在不影响功能的情况下,添加某种方法实现同样的效果,从而方便程序开发。

Vue.js 的 v-bind 和 v-on 指令都提供了语法糖,

v-bind,可以省略 v-bind,直接写一个冒号 “:”

<a :href="url"> 连接 </a>

<img :src="imgUrl" />

v-on 可以直接用 “@” 来缩写

<button @click="handleClick">toggle</button>

三、计算属性

3.1、什么是计算属性

在模板中双向绑定一些数据或表达式,如果过长,或逻辑更为复杂时,就会变得臃肿甚至难以阅读和维护,比如:

 {{ text.split(',').reverse().join(',') }}

当遇到复杂的逻辑是应该使用 计算属性。

<body>
  
  <div id="app">
    {{ reversedText}}
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        text: '123,456'
      },
      computed: {
        reversedText() {
          // 这里的this 指向的是当前啊的 vue 实例
          return this.text.split(',').reverse().join(',')
        }
      }
    })
  </script>
</body>

3.2、计算属性用法

计算属性只要其中任意数据变化,计算属性就会重新执行,视图也会更新。

<body>
  
  <div id="app">
    总价:{{ prices }}
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        package: [
          {
            name: '桔梗',
            price: 10,
            count: 2
          },
          {
            name: '荼蘼',
            price: 11,
            count: 10,
          },
          {
            name: '满天星',
            price: 20,
            count: 15
          }
        ]
      },
      computed: {
        prices() {
          var prices = 0

          for (var i = 0, len = this.package.length; i < len; i++) {
            var item = this.package[i]
            prices += item.price * item.count
          }

          return prices
        }
      }
    })
  </script>
</body>

如上 当 package 中的商品有任何变化时,计算属性 prices 就会自动更新

每一个计算属性都包含一个 getter 和 一个 setter在你需要时,也可以提供一个 setter 函数,当手动修改计算属性的值就像修改一个普通数据那样时,就会触发 setter 函数,执行一些自定义操作。

<body>
  
  <div id="app">
    {{ fullName }}
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        firstName: 'Don',
        lastName: 'Shirley'
      },
      computed: {
        fullName: {
          // getter 用于读取
          get() {
            return this.firstName + ' ' + this.lastName
          },
          // setter 写入时触发
          set(newVal) {
            var names = newVal.split(' ')
            this.firstName = names[0]
            this.lastName = name[names.length -1]
          }
        }
      }
    })
  </script>
</body>

绝大多数情况下,我们只会用默认的 getter 方法来读取一个计算属性,在业务中很少用到 setter,所以在声明一个计算属性时,可以直接使用 默认的写法,不必将 getter 和 setter 都声明。

计算属性还有两个很实用的小技巧容易被忽略:

  • 一是计算属性可以依赖其他计算属性;
  • 而是计算属性不仅可以依赖当前 Vue 实例的数据,还可以依赖其他实例的数据。

3.3、计算属性缓存

调用 methods 里的方法也可以 与 计算属性起到同样的作用。
本小节的第一个例子,使用 methods改写:

<body>
  
  <div id="app">
    <!-- 注意,这里的 reversedText 是方法,所以要带 () -->
    {{ reversedText() }}
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        text: '123,456'
      },
      methods: {
        reversedText() {
          // 这里的this 指向的是当前啊的 vue 实例
          return this.text.split(',').reverse().join(',')
        }
      }
    })
  </script>
</body>

使用方法还可以接受参数,使用起来更灵活,那么为什么还需要计算属性呢?原因就是 计算属性是基于它的依赖缓存的。一个计算属性所依赖的数据发生变化时,它才会重新取值。所以 text 只要不改变,计算属性也就不更新。
使用 计算属性 还是 methods 取决于你是否需要缓存,当遍历大数组和做大量计算时,应该使用计算属性,除非你不希望得到缓存。

四、v-bind 及 class 与 style 绑定

4.1、了解 v-bind 指令

前面已经介绍了 v-bind 指令的基本用法及语法糖,它的主要用处是动态更新 HTML 元素上的属性。在数据绑定中,最常见的两个需求就是 元素的样式名称 class 和 内联样式 style 的动态绑定

4.2、绑定 class 的几种方式

4.2.1、对象语法

给 v-bind:class 设置 一个对象,可以动态地切换 class
例如

<body>
  
  <div id="app">
    <div :class="{'active': isActive}"></div>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isActive: false
      }
    })
  </script>
</body>

对象中也可以传入多个属性,来动态切换 class。:class 与 普通 class 共存

<body>
  
  <div id="app">
    <div :class="{'active': isActive, 'error': isError}"></div>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isActive: false,
        isError: true
      }
    })
  </script>
</body>

:class 内的表达式每项为真时,对应的类名就会加载。

当 :class 的表达式过长 或 逻辑复杂时,还可以绑定一个计算属性,这是一种很友好和常见的用法,一般当条件多于两个时,都可以使用 data 或 computed。
例如使用 计算属性:

<body>
  
  <div id="app">
    <div :class="classes"></div>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isActive: true,
        isError: null
      },
      computed: {
        classes() {
          return {
            active: this.isActive && !this.isError,
            'text-fail': this.isError && this.error.type === 'fail'
          }
        }
      }
    })
  </script>
</body>

除了计算属性,你也可以直接绑定一个 Object 类型的数据,或者使用 雷士计算属性的 methods

4.2.2、数组语法

当需要应用多个 class 时,可以使用数组语法。

<body>
  
  <div id="app">
    <div :class="[activeCls, errorCls]"></div>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        activeCls: 'active',
        errorCls: null
      }
    })
  </script>
</body>

也可以在数组中使用 三元表达式。

  <div :class="[activeCls ? 'active-success' : '', errorCls]"></div>

当 class 有多个条件是,这样写比较烦琐,可以在数组语法中使用对象语法:

<body>
  
  <div id="app">
      <div :class="[{'activeCls': isActive}, errorCls]"></div>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isActive: true,
        errorCls: null
      }
    })
  </script>
</body>

与对象语法一样,也可以使用 data、computed、methods 三种方法。

4.2.3、在组件上使用

如果直接在 自定义组件上使用 class 或 :class,样式规则会直接应用到这个组件的根元素上,例如声明一个简单的组件:

<body>
  
  <div id="app">
      <my-component :class="{'active': isActive}">
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>

    Vue.component('my-component', {
      template: '<p class="article"> some text </p>'
    })
    const app = new Vue({
      el: '#app',
      data: {
        isActive: true,
        errorCls: null
      }
    })
  </script>
</body>

最终组件渲染的结果为:

<p class="article active"> some text </p>

4.3、绑定内联样式

使用 v-bind:style(:style) 可以给元素绑定内联样式,方法与 :class 类似,也有对象语法和数组语法,看起来很像直接在元素上写 CSS:

<body>
  
  <div id="app">
      <div :style="{'color': color, 'fontSize': fontSize + 'px'}">The End of the F***ing World</div>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        color: 'cyan',
        fontSize: 40
      }
    })
  </script>
</body>

大多数情况下,直接写一长串的样式不便于阅读和维护,所以一般 写在 data 或 computed 里。

<body>
  
  <div id="app">
      <div :style="styles">The End of the F***ing World</div>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        styles: {
          color: 'cyan',
          fontSize: '40px'
        }
      }
    })
  </script>
</body>

在实际业务中,:style 的数组语法并不常用,因为往往可以写在一个对象里面;而较为常用的应当时计算属性。
在使用 :style 时, Vue.js 会自动给特殊的 CSS 属性 名称添加前缀(weikit、ms...)。

五、内置指令

5.1、基本指令

5.1.1、v-cloak

v-cloak 不需要表达式,它会在 Vue 实例结束编译是从绑定的 HTML 元素上移除,经常和 CSS 的 display: none; 配合使用。

<head>
  <style>
    [v-cloak] {
      display: none;
    }
  </style>
</head>
<body>
  
  <div id="app">
      <span v-cloak>{{ message }}</span>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: 'some message'
      }
    })
  </script>
</body>

在一般情况下,v-cloak 是一个解决初始化慢导致页面闪动的最佳实践,对于简单的项目很实用。

5.1.2、v-once

v-once 也是一个不需要表达式的指令,作用是定义它的元素或组件只渲染一次,包括元素或组件的所有子节点。首次渲染后,不再随数据的变化重新渲染,将被视为静态内容。

<body>
  
  <div id="app">
      <span v-once>{{ message }}</span>
      <div v-once>
        <span>
          {{ message }}
        </span>
      </div>


      <span>{{ message }}</span>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: 'some message'
      }
    })

    app.message = 'no message'
  </script>
</body>
渲染结果

5.2、条件渲染指令

5.2.1、v-if 、v-else-if、v-else

与 JavaScript 的条件语句 if、else、else if 类似,Vue.js 的条件指令可以根据表达式的值在 DOM 中渲染或销毁元素/组件,例如:

<body>
  
  <div id="app">
     <p v-if="status === 1">当 status 为1时显示 该行</p>
     <p v-else-if="status === 2">当 status 为2时显示 该行</p>
     <p v-else>否则显示该行</p>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        status: 3
      }
    })
  </script>
</body>

v-else-if 要紧跟 v-if, v-else 要跟紧 v-else-if 或 v-if
表达式的值为真时,当前元素/组件及所有子节点被渲染,为假时被移除。

如果一次判断多个元素,可以在 Vue.js 内置的 <template> 元素上使用条件指令,最终渲染的结果不会包含该元素。

<body>
  
  <div id="app">
     <template v-if="status === 1">
       <p>message</p>
       <p>message</p>
       <p>message</p>
     </template>
    
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        status: 1
      }
    })
  </script>
</body>

Vue 在渲染元素是,出于效率考虑,会尽可能地复用已有的元素而非重新渲染。
如下示例:

<body>
  
  <div id="app">
    <template v-if="type === 'name'">
      <label for="username">用户名:</label> 
        <input type="text" id="username" placeholder="用户名" /> 
    </template>
    <template v-else>
        <label for="email">邮箱:</label>  
          <input type="email" id="email" placeholder="邮箱" />
    </template>

    <button @click="handleToggle">切换输入类型</button>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        type: 'name'
      },
      methods: {
        handleToggle() {
          this.type = this.type === 'name' ? 'email' : 'name'
        }
      }
    })
  </script>
</body>

如上代码,输入内容后,点击切换按钮,DOM会发生改变,但是之前在输入框键入的内容并没有改变,只是替换了 placeholder 的内容,说明 <input> 元素被复用了。

切换前

切换后

如果你不希望这样做,可以使用 Vue.js 提供的 key 属性,它可以让你自己决定是否要复用元素,key 的值必须是唯一的,例如:

<!-- ...省略其他代码 -->
<input type="text" id="username" placeholder="用户名" key="username-input" /> 
<!-- ...省略其他代码 -->
<input type="email" id="email" placeholder="邮箱" key="username-email"/>
<!-- ...省略其他代码 -->

给两个 <input> 元素都增加 key 后,就不会复用了,切换类型时键入的内容也会被删除,不过 <label> 元素仍然是被复用的,因为没有添加 key 属性

5.2.2、v-show

v-show 的用法 和 v-if 基本一致。只不过 v-show 指令是通过修改元素的 CSS属性 display。当 v-show 表达式的值为 false 时,元素会隐藏,查看 DOM 结构会看到元素上加载了 内联样式 display: none;

<body>
  
  <div id="app">
    <span v-show="status === 1"> 哔哩哔哩 (゜-゜)つロ 干杯~-bilibil</span>
    <span v-show="status === 2"> xxx </span>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        status: 1
      }
    })
  </script>
</body>

渲染的结果为

<span> 哔哩哔哩 (゜-゜)つロ 干杯~-bilibil</span>
<span style="display: none;"> xxx </span></div>

v-show 不能再 <template> 上使用

5.2.3、v-if 与 v-show 的选择

v-if 和 v-show 具有类似的功能。

  • v-if 真正的条件渲染。若表达式初始值为 false,则一开始元素/组件比不会渲染,只有当条件第一次变为 true 时才开始渲染
  • v-show 只是简单的 CSS 属性切换,无论条件真与否,都会被编译。

相比之下,v-if 更适合条件不经常改变的常见,因为他切换开销相对较大,而 v-show 适合频繁切换条件。

5.3、列表渲染指令 v-for

5.3.1、基本用法

当需要将一个数组遍历或枚举一个对象显示循环时,就会用到列表渲染指令 v-for

<body>
  
  <div id="app">
    <ul>
      <li v-for="item in books"> {{ item.name }}</li>
    </ul>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        books: [
          {name: 'Vue.js 实战'},
          {name: 'JavaScript 高级程序设计'},
          {name: 'jQuery 基础入门'}
        ]
      }
    })
  </script>
</body>

列表渲染也支持用 of 来代替 in 作为分隔符,它更接近 JavaScript 迭代器的语法

v-for 的表达式 支持一个 可选参数 作为当前项的索引。
例如:

    <ul>
      <li v-for="(item, index) in books"> {{ index }} ---- {{ item.name }}</li>
    </ul>

与 v-if 一样,v-for 也可以使用 内置标签 <template> 上,将多个元素进行渲染:

<body>
  
  <div id="app">
    <template v-for="(item, index) in books">
        <p>
          <span>{{ item.name }}</span> --- 
          <span>{{ item.price }}</span> ---
          <span>{{ item.level }}</span>
        </p>
    </template>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        books: [
          {
            name: 'Vue.js 实战',
            price: 50,
            level: '1'
          },
          {
            name: 'ES 6 标准入门',
            price: 70,
            leevl: '2'
          },
          {
            name: 'JavaScript 高级程序设计',
            price: 100,
            level: '3'
          }
        ]
      }
    })
  </script>
</body>

除了数组外,对象的属性也是可以遍历的。遍历对象时,有两个可选参数,分别是键名和索引:

<body>
  <div id="app">
    <p v-for="(value, key, index) in user">value: {{value}} ----- key: {{key}} ------ index: {{index}}</p> 
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        user: {
          name: '了凡',
          age: 1,
          male: true
        }
      }
    })
  </script>
</body>

v-for 还可以迭代整数

<span v-for="n in 10"> {{ n }}</span>
5.3.2、数组更新

Vue 的核心是数据与视图的双向绑定,当我们修改数组时,Vue 会检测到数据变化,所以用 v-for 渲染的视图也会立即更新。Vue包含了 一组观察数组变异的方法,使用他们改变数组会触发视图更新:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

使用以上方法会改变被这些方法调用的原数组。

有些方法不会改变原数组,例如:

  • filter()
  • concat()
  • slice()

它们返回的是一个新数组,在使用这些非变异方法时,可以用新数组来替换原数组

<body>
  <div id="app">
    <template v-for="book in books">
      <p>
        <span>书名:{{ book.name }}</span>
        <span>作者:{{ book.author }}</span>
      </p>
    </template>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        books: [
          {
            name: '《Vue.js 实战》',
            author: '梁灏'
          }, {
            name: 'Professional JavaScript for Web Developers',
            author: 'Nicholas C.Zakas'
          }
        ]
      }
    })

    app.books = app.books.filter(function(item) {
      return item.name.match(/JavaScript/) // 返回 书名中 含有 JavaScript 的书
    })
  </script>
</body>

Vue 在检测到数组变化时,并不是直接重新渲染整个列表,而是最大化地复用 DOM 元素。替换的数组中,含有相同元素的项不会被重写渲染,因此可以大胆地用新数组来替换旧数组,不用担心性能问题。

需要注意的是,以下变动的数组中,Vue 是不能检测的,也不会触发视图更新

  • 通过 索引直接设置项,比如 app.books[3] = {...}
  • 修改数组长度,比如 app.books.length = 1。

解决第一个问题可以用两种方法实现同样的效果,第一种是使用 Vue 内置的 set方法

Vue.set(app.books, 3, {
  name: '《CSS 揭秘》',
  author: 'Lea Verou'
})

如果是在 webpack 中使用 组件化的方式(后面会介绍到),默认是没有Vue的,这是可以使用$set。

this.$set(app.books, 3, {
  name: '《CSS 揭秘》',
  author: 'Lea Verou'
})

这里的this 指向的就是当前组件实例,即 app。在非 webpack 模式下也可以用 $set 方法,例如 app.$set(...)

另一种方法,使用 splice() 方法也可以达到同样的效果:

app.books.aplice(3, 1, {
  name: '《CSS 揭秘》',
  author: 'Lea Verou'
})

第二个问题也可以直接使用 splice() 来解决

app.books.splice(1)
5.3.3、过滤与排序

但你不想改变原数组,想通过一个数组的副本来做过滤或排序的显示时,可以使用 计算属性来返回过滤或排序后的数组。

<body>
  <div id="app">
    <template v-for="book in filterBooks">
      <p>
        <span>书名:{{ book.name }}</span>
        <span>作者:{{ book.author }}</span>
      </p>
    </template>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        books: [
          {
            name: '《Vue.js 实战》',
            author: '梁灏'
          }, {
            name: 'Professional JavaScript for Web Developers',
            author: 'Nicholas C.Zakas'
          }
        ]
      },
      computed: {
        filterBooks: function() {
          return this.books.filter(function(book) {
            return book.name.match(/JavaScript/) // 返回 书名中 含有 JavaScript 的书
          })
        }
      }
    })
  </script>
</body>

类似的 实现排序

  
  sortedBooks() {
    return this.books.sort(function(a, b) {
      return a.name.length - b.name.length
    })
  }
}

5.4、方法和事件

5.4.1、基本用法

事件绑定的表达式 可以直接使用 JavaScript 语句,也可以是一个在 Vue 实例中 methods 选项内的函数名。

调用的方法名后可以不跟括号“()”。此时,如果该方法有参数,默认会将原生对象事件 event 传入,在大部分业务场景中,如果方法不需要传入参数,为了简便可以不写括号

Vue提供一个特殊变量 $event,用于访问原生 DOM 事件。
如下示例:

<body>
  <div id="app">
    <a href="http://www.baidu.com" @click="handleClick('禁止打开', $event)">百度</a>
  </div>

  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      methods: {
        handleClick(msg, event) {
          console.log(msg)
          event.preventDefault() // 阻止跳转
        }
      }
    })
  </script>
</body>
5.4.2、修饰符

在 @ 绑定的事件后面加 小圆点“.”,后跟一个后缀来使用修饰符。
Vue 支持以下修饰符:

  • .stop —— 阻止事件冒泡
  • .prevent —— 阻止默认行为
  • .capture —— 使用事件捕获模式
  • .self —— 只当在 event.target 是当前元素自身时触发处理函数
  • .once —— 事件将只会触发一次
  • .passive —— 不会调用 preventDefault(),仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。

在表单元素上 监听键盘事件时,还可以使用按键修饰符。
比如按下具体某个键时才调用方法

<!-- 按下 keyCode 是 13 时 调用 vm.submit() -->
<input @keyup.13="submit" />

也可以自定义具体按键

Vue.config.keyCode.f1 = 112

这样就可以通过 f1 来访问 keyCode=112 的键了。

除了具体的某个 keyCode 外,Vue 还提供了一些快捷键名称。
以下是全部的别名

  • .enter
  • .tab
  • .delete
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .left
  • .right
    下列按键修饰也可以组合使用,或者和鼠标一起配合使用
  • .ctrl
  • .alt
  • .shift
  • .meta
<!-- shift + s -->
<input @keyup.shift.83="handleSave" />

<!-- Ctrl + click -->
<div @click.ctrl="doSomething" >Do Something</div>

5.5、 发开购物车

基本功能:商品数量增减、移除操作、计算总价

效果图

以下是 实现代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    [v-cloak] {
      display: none;
    }
    table {
      border: 1px solid #e9e9e9;
      border-collapse: collapse;
      border-spacing: 0;
      empty-cells: show;
    }

    td, th {
      padding: 8px 16px;
      border: 1px solid #e9e9e9;
      text-align: left
    }

    th {
      background: #f7f7f7;
      color: #5c6b77;
      font-weight: 600;
      white-space: nowrap
    }
  </style>
</head>
<body>
  <div id="app" v-cloak>
    <template v-if="list.length">
      <table>
        <thead>
          <tr>
            <th></th>
            <th>商品名称</th>
            <th>商品单价</th>
            <th>购买数量</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(item, index) in list">
            <td>{{ index + 1 }}</td>
            <td>{{ item.name }}</td>
            <td>{{ item.price }}</td>
            <td>
              <button @click="handleReduce(index)" :disbaled="item.count === 1">-</button> <!-- 减少按钮 -->
              {{ item.count }}
              <button @click="handleAdd(index)">+</button> <!-- 增加按钮 -->
            </td>
            <td>
              <button @click="handleRemove(index)">移除</button> <!-- 移除按钮 -->
            </td>
          </tr>
        </tbody>
      </table>

      <div>总价:¥ {{ totalPrice }}</div>
    </template>
  </div>
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        list: [ 
          {
            id: 1,
            name: 'iPhone 7',
            price: 6188,
            count: 1
          }, {
            id: 2,
            name: 'iPad Pro',
            price: 5888,
            count: 1
          }, {
            id: 3,
            name: 'MacBook Pro',
            price: 21488,
            count: 1
          }
        ]
      },
      methods: {
        handleReduce(index) { // 减少
          if (this.list[index].count === 1) return

          this.list[index].count--
        },
        handleAdd(index) { // 增加
          this.list[index].count++
        },
        handleRemove(index) { // 移除
          this.list.splice(index, 1)
        }
      },
      computed: {
        totalPrice() { // 计算总价
          var total = 0;
          for (var i = 0, len = this.list.length; i < len; i++) {
            var item = this.list[i]
            total += item.price * item.count
          }
          /*
          \B:匹配非单词边界
          (?=):正向零宽断言
          \d{3}:匹配三个数字字符
          +:与前面的\d{3}结合表示匹配3的整数倍个数字字符
          $:字符串结尾
          所以合起来的意思就是:匹配单词中的某个位置,这个位置之后的字符全部为数字,且出现次数是3的整数倍。
          */
          return total.toString().replace(/\B(?=(\d{3})+$)/g, ',') // 千元分隔符
        }
      }
    })
  </script>
</body>
</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容

  • vue概述 在官方文档中,有一句话对Vue的定位说的很明确:Vue.js 的核心是一个允许采用简洁的模板语法来声明...
    li4065阅读 7,191评论 0 25
  • 主要还是自己看的,所有内容来自官方文档。 介绍 Vue.js 是什么 Vue (读音 /vjuː/,类似于 vie...
    Leonzai阅读 3,332评论 0 25
  • 1. Vue 实例 1.1 创建一个Vue实例 一个 Vue 应用由一个通过 new Vue 创建的根 Vue 实...
    王童孟阅读 1,014评论 0 2
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,204评论 0 6
  • 33、JS中的本地存储 把一些信息存储在当前浏览器指定域下的某一个地方(存储到物理硬盘中)1、不能跨浏览器传输:在...
    萌妹撒阅读 2,077评论 0 2