前言:学习笔记,CV即指CV Vue的官方文档,也指 CV 网上的Vue业务代码😎😎

1.Vue是什么?

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统

2.特点

  • 1.html内容可以是变量 {{}}
  • 2.属性内容也可以是变量 v-bind 缩写: :
  • 3.可以通过指令控制显示隐藏 v-if
  • 4.绑定数组元素来渲染列表 v-for
  • 5.给dom绑定方法v-on 缩写 @ 数据驱动视图改变
  • 6.表单状态和应用状态之间双向绑定 v-model

3.组件化构建应用

组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树

组件使用:

1.注册 Vue.component()

组件数据传输:

1.父组件将数据传输给子组件 props

2.props相当于在模板(子组件)上挂了一个可以进行数据绑定的属性(个人理解)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app-2">
        <ol>
            <!-- 创建一个 todo-item 组件的实例 -->
            <todo-item v-for="item in groceryList" :todo="item" :key="item.id">

            </todo-item>
        </ol>
    </div>
    <script src="./vue.js"></script>
    <script>
        Vue.component('todo-item', {
            props: ['todo'],
            template: '<li>{{ todo.text }}</li>'
        })
        var app2 = new Vue({
            el: '#app-2',
            data: {
                groceryList: [
                    { id: 0, text: '西红柿' },
                    { id: 1, text: '茄子' },
                    { id: 2, text: '西瓜' }
                ]
            }
        })
    </script>
</body>
</html>

个人理解:这个例子就相当于props属性值todo挂在template上作为一个可以进行数据绑定的属性,绑定的数据在template定义的html内容中可以使用。

4.Vue组件化与自定义元素的关系

组件思想参考自定义元素Web组件规范,但是与自定义元素还是有区别:

1.Web Components 规范已经完成并通过,但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。

2.Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。

虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。

5.Vue实例

(1)Vue实例介绍

每个 Vue 应用 都是通过用 Vue 函数 创建一个新的 Vue 实例 开始的。一个 Vue 应用 由一个通过 new Vue 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。当创建一个 Vue 实例 时,你可以传入一个选项对象

(2)Vue实例数据挂载

当一个 Vue 实例被创建时,它将 data 对象 中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  // 我们的数据对象
  var data = { a: 1 }

  // 该对象被加入到一个 Vue 实例中
  var vm = new Vue({
    data: data
  })

  // 获得这个实例上的 property
  // 返回源数据中对应的字段
  vm.a == data.a // => true

  // 设置 property 也会影响到原始数据
  vm.a = 2
  data.a // => 2

  // ……反之亦然
  data.a = 3
  vm.a // => 3

我们是不是可以猜测一下Vue内部是按照下面这样处理的呢:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function A(obj) {
            this.__proto__ = obj.data
        }
        let data = {
            a: 200
        }
        let vm = new A({
            data: data
        })
        console.log(vm);
        console.log(data.a);
        console.log(vm.a);
        console.log(data.a === vm.a);
    </script>
</body>

</html>

当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data 中的 property 才是响应式的。也就是说如果你添加一个新的 property,将不会触发任何视图的更新。

注意:使用 Object.freeze(),这会阻止修改现有的 property,也意味着响应系统无法再追踪变化。

除了数据 property,Vue 实例还暴露了一些有用的实例 property 与方法。它们都有前缀 $,以便与用户定义的 property 区分开来,这些方法可以用来操作实例的属性。

(3)生命周期

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置 数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

6.Vue模板语法

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

如果你熟悉 虚拟 DOM 并且偏爱 JavaScript 的原始力量,你也可以不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。

(1) “Mustache”语法 (双大括号)

1
<span>Message: {{ msg }}</span>

Mustache 标签将会被替代为对应数据对象上 msg property 的值。无论何时,绑定的数据对象上 msg property 发生了改变,插值处的内容都会更新。

通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定:

1
<span v-once>这个将不会改变: {{ msg }}</span>

注意:可以用 v-once 优化更新性能 -> 重要

(2)原始 HTML

双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  <!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app" v-once>
      <p>{{ text }}</p>
      <p><span v-html="text"></span></p>
  </div>
  <script src="./vue.js"></script>
  <script>

      let app = new Vue({
          el: '#app',
          data: {
              text: 'Hello<i>你好</i>'
          }
      })
  </script>
</body>

</html>

结果:

从上面这个例子可以看出双大括号并不能渲染标签内容

注意:这个 span 的内容将会被替换成为 property 值 rawHtml,直接作为 HTML——会忽略解析 property 值中的数据绑定。注意,你不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位。

重要:你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。

(3)Attribute属性绑定

Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind 指令:

1
<div v-bind:id="dynamicId"></div>

如果 attribute 值为null,undefined,false,attribute不会包含在被渲染的dom中。

绑定样式的时候注意,Vue有单独绑定style和class的规则。

(4)使用 JavaScript 表达式

这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。

1
2
3
4
5
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}

<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}

模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。你不应该在模板表达式中试图访问用户定义的全局变量。

(5)指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。回顾我们在介绍中看到的例子:

1
<p v-if="seen">现在你看到我了</p>

(6)参数

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML attribute:

1
<a v-bind:href="url">...</a>

另一个例子是 v-on 指令,它用于监听 DOM 事件:

1
<a v-on:click="doSomething">...</a>

在这里参数是监听的事件名。我们也会更详细地讨论事件处理。

注意:这里提到的v-bind和v-on语法,后面跟的属性是传给它们的参数

(7)动态参数

从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:

1
2
3
4
<!--
注意,参数表达式的写法存在一些约束,如之后的“对动态参数表达式的约束”章节所述。
-->
<a v-bind:[attributeName]="url"> ... </a>

同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:

1
<a v-on:[eventName]="doSomething"> ... </a>

对动态参数的值的约束:动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。

对动态参数表达式的约束:动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。

1
2
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>

变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。

在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:

1
2
3
4
5
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
-->
<a v-bind:[someAttr]="value"> ... </a>

个人理解:动态参数使用得得当的话在写业务的时候作用应该非常大

(8)修饰符

修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():

1
<form v-on:submit.prevent="onSubmit">...</form>

在接下来对 v-on 和 v-for 等功能的探索中,你会看到修饰符的其它例子。

inspiration:程序员还是需要会“偷懒”的,能少些一个字符就少写一个字符,程序简洁也很重要。

(9)缩写

v-bind 缩写: :

v-on 缩写: @

7.计算器和侦听属性

(1)计算属性

什么样的表达式写到Mustache语法中,什么样的表达式需要用到计算属性,注意区分使用。

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

1
2
3
<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。

所以,对于任何复杂逻辑,你都应当使用计算属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example">
        <p>Original message: "{{ message }}"</p>
        <p>Computed reversed message: "{{ reversedMessage }}"</p>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#example',
            data: {
                message: 'Hello'
            },
            computed: {
                // 计算属性的 getter
                reversedMessage() {
                    // `this` 指向 vm 实例
                    return this.message.split('').reverse().join('')
                }
            }
        })
    </script>
</body>

</html>

而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。计算属性也是双向绑定。

(2)计算属性缓存 vs 方法

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。 只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

1
2
3
4
5
6
7
<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

个人理解:使用component计算属性会有缓存,不会每次去调用函数,而使用methods会每次去调用函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example">
        <p>Original message: "{{ message }}"</p>
        <p>Computed reversed message: "{{ reversedMessage }}"</p>
        <p>Computed reversed message: "{{ reversedMessage }}"</p>
        <p>Computed reversed message: "{{ reversedMessage }}"</p>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#example',
            data: {
                message: 'Hello'
            },
            computed: {
                reversedMessage() {
                    console.log('computed');
                    return this.message.split('').reverse().join('')
                }
            }
        })
    </script>
</body>

</html>

这段代码只会打印一次computed。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example">
        <p>Original message: "{{ message }}"</p>
        <p>Computed reversed message: "{{ reversedMessage() }}"</p>
        <p>Computed reversed message: "{{ reversedMessage() }}"</p>
        <p>Computed reversed message: "{{ reversedMessage() }}"</p>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#example',
            data: {
                message: 'Hello'
            },
            methods: {
                reversedMessage: function () {
                    console.log('methods');
                    // `this` 指向 vm 实例
                    return this.message.split('').reverse().join('')
                }
            }
        })
    </script>
</body>

</html>

上面这段代码会打印三次 methods

(3)计算属性 vs 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">{{ fullName }}</div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                firstName: 'Foo',
                lastName: 'Bar'
            },
            computed: {
                fullName: function () {
                    return this.firstName + ' ' + this.lastName
                }
            }
        })
    </script>
</body>

</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">{{ fullName }}</div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                firstName: 'Foo',
                lastName: 'Bar',
                fullName: 'Foo Bar'
            },
            watch: {
                firstName: function (val) {
                    this.fullName = val + ' ' + this.lastName
                },
                lastName: function (val) {
                    this.fullName = this.firstName + ' ' + val
                }
            }
        })
    </script>
</body>

</html>

在这个例子中,上面的computed写法

注意:什么时候使用computed,什么时候使用watch,需要知道区别。

(4)计算属性的 setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">{{ fullName }}</div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                firstName: 'Foo',
                lastName: 'Bar'
            },
            computed: {
                fullName: {
                    get() {
                        return this.firstName + ' ' + this.lastName
                    },
                    set(newValue) {
                        let names = newValue.split(' ')
                        this.firstName = names[0]
                        this.lastName = names[names.length - 1]
                    }
                }
            }
        })
    </script>
</body>

</html>

当我们在控制台通过vm.fullName = ‘xxx xxxx’改变fullName时,firstName和lastName也会改变,如果不写set再去改变fullName控制台就会报错 Computed property “fullName” was assigned to but it has no setter,这个地方得稍加注意。

(5)侦听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的(请使用watch)。

使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。除了 watch 选项之外,您还可以使用命令式的 vm.$watch API。

8.Class与Style绑定

操作元素的 class 列表内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。 红色 表达式结果的类型除了字符串之外,还可以是对象或数组。

(1)绑定class

(1)对象语法

我们可以传给 v-bind:class 一个对象,以动态地切换 class:

1
<div v-bind:class="{ active: isActive }"></div>

上面的语法表示 active 这个 class 存在与否将取决于数据 property isActive 是否为真值。 你可以在对象中传入更多字段来动态切换多个 class。此外,v-bind:class 指令也可以与普通的 class attribute 共存。当有如下模板:

1
<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>

绑定的数据对象不必内联定义在模板里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">
        <div v-bind:class="classObject"></div>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                classObject: {
                    active: true,
                    'text-danger': true
                }
            }
        })
    </script>
</body>

</html>

渲染出来的结果:

1
<div class="active text-danger"></div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">
        <div v-bind:class="classObject"></div>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                isActive: true,
                textExist: true
            },
            computed: {
                classObject: function () {
                    return {
                        active: this.isActive,
                        'text-danger': this.textExist
                    }
                }
            }
        })
    </script>
</body>

</html>

渲染出来的结果:

1
<div class="active text-danger"></div>

以上这两种方式都可以,相对而言,我个人可能更倾向于使用计算属性。不知道对不对 有可能这两种有不同适合的使用环境

(2)数组语法

我们可以把一个数组传给 v-bind:class,以应用一个 class 列表:

1
<div v-bind:class="[activeClass, errorClass]"></div>

如果你也想根据条件切换列表中的 class,可以用三元表达式

1
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法(数组语法嵌套对象语法):

1
<div v-bind:class="[{ active: isActive }, errorClass]"></div>

(3)用在组件上

当在一个自定义组件上使用 class property 时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。

1
2
3
Vue.component('my-component', {
  template: '<p class="foo bar">Hi</p>'
})

然后在使用它的时候添加一些 class:

1
<my-component class="baz boo"></my-component>

HTML 将被渲染为:

1
<p class="foo bar baz boo">Hi</p>

对于带数据绑定 class 也同样适用:

1
<my-component v-bind:class="{ active: isActive }"></my-component>

当 isActive 为 true 时,HTML 将被渲染成为:

1
<p class="foo bar active">Hi</p>

(2)绑定内联样式(style)

(1)对象语法

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">
        <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">
            <span>Hello</span>
        </div>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                activeColor: 'orange',
                fontSize: 30
            }
        })
    </script>
</body>

</html>  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">
        <div v-bind:style="styleObject">
            <span>Hello</span>
        </div>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                styleObject: {
                    color: 'orange',
                    fontSize: '30px'
                }
            }
        })
    </script>
</body>

</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">
        <div v-bind:style="styleObject">
            <span>Hello</span>
        </div>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                color: 'orange',
                fontSize: '30px'
            },
            computed: {
                styleObject() {
                    return {
                        color: this.color,
                        fontSize: this.fontSize,
                    }
                }
            }
        })
    </script>
</body>

</html>

(2)数组语法

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">
        <div v-bind:style="[styleObject,styleObject2]">
            <span>Hello</span>
        </div>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                color: 'orange',
                fontSize: '30px',
                fontWeight: 800
            },
            computed: {
                styleObject() {
                    return {
                        color: this.color,
                        fontSize: this.fontSize,
                    }
                },
                styleObject2() {
                    return {
                        fontWeight: this.fontWeight
                    }
                }
            }
        })
    </script>
</body>

</html>

(3)自动添加前缀

当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会自动侦测并添加相应的前缀。也就是说不需要我们去加兼容不同浏览器的前缀,Vue会自动去邦我们做这件事情。

(4)多重值

从 2.3.0 起你可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex。

9.条件渲染

(1)v-if

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

1
<h1 v-if="awesome">Vue is awesome!</h1>

也可以用 v-else 添加一个“else 块”:

1
2
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

(2)在 template 元素上使用 v-if 条件渲染分组

因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 template 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 template 元素。

1
2
3
4
<template v-if="awesome">
    <h1>Vue is awesome!😋😋</h1>
    <h1>Oh no 😢</h1>
</template>

(3)v-else

你可以使用 v-else 指令来表示 v-if 的“else 块”:

1
2
3
4
5
6
<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>

v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。

(4)v-else-if

v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        <div v-if="type === 'A'">
            A
        </div>
        <div v-else-if="type === 'B'">
            B
        </div>
        <div v-else-if="type === 'C'">
            C
        </div>
        <div v-else>
            Not A/B/C
        </div>

类似于 v-else,v-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后。

(5)用 key 管理可复用的元素

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换

1
2
3
4
5
6
7
8
        <template v-if="loginType === 'username'">
            <label>Username</label>
            <input placeholder="Enter your username">
        </template>
        <template v-else>
            <label>Email</label>
            <input placeholder="Enter your email address">
        </template>

那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素, 不会被替换掉——仅仅是替换了它的 placeholder。

这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key attribute 即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="demo">
        <template v-if="loginType === 'username'">
            <label>Username</label>
            <input placeholder="Enter your username" key="username-input">
        </template>
        <template v-else>
            <label>Email</label>
            <input placeholder="Enter your email address" key="email-input">
        </template>
        <br>
        <button @click="clickData" style="cursor:pointer">切换</button>
    </div>
    <script src="./vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#demo',
            data: {
                loginType: 'username'
            },
            methods: {
                clickData() {
                    this.loginType === 'username' ? this.loginType = '' : this.loginType = 'username'
                }
            }
        })
    </script>
</body>

</html>  

注意,label 元素仍然会被高效地复用,因为它们没有添加 key attribute。

(6)v-show

另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:

1
<h1 v-show="ok">Hello!</h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display。

注意,v-show 不支持 template 元素,也不支持 v-else。

(7)v-if vs v-show

1.v-if 会销毁和重建:v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

2.v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

3.v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

总结:一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。 再次提醒,v-show 不支持 template 元素,也不支持 v-else。

(8)v-if 与 v-for 一起使用

不推荐同时使用 v-if 和 v-for。请查阅 风格指南 以获取更多信息。

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级。请查阅 列表渲染指南 以获取详细信息。

10.列表渲染

(1)v-for

v-for 把一个数组对应为一组元素,我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul id="example-1">
        <li v-for="item in items" :key="item.message">
            {{ item.message }}
        </li>
    </ul>
    <script src="./vue.js"></script>
    <script>
        var example1 = new Vue({
            el: '#example-1',
            data: {
                items: [
                    { message: 'Foo' },
                    { message: 'Bar' }
                ]
            }
        })
    </script>
</body>

</html>

v-for 块中,我们可以访问所有父作用域的 propertyv-for 还支持一个可选的第二个参数,即当前项的索引

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul id="example-2">
        <li v-for="(item, index) in items">
            {{ parentMessage }} - {{ index }} - {{ item.message }}
        </li>
    </ul>
    <script src="./vue.js"></script>
    <script>
        var example2 = new Vue({
            el: '#example-2',
            data: {
                parentMessage: 'Parent',
                items: [
                    { message: 'Foo' },
                    { message: 'Bar' }
                ]
            }
        })
    </script>
</body>

</html>  

你也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:

1
<div v-for="item of items"></div>

(2)在 v-for 里使用对象

你也可以用 v-for 来遍历一个对象的 property。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul id="v-for-object" class="demo">
        <li v-for="value in object">
            {{ value }}
        </li>
    </ul>
    <script src="./vue.js"></script>
    <script>
        new Vue({
            el: '#v-for-object',
            data: {
                object: {
                    title: 'How to do lists in Vue',
                    author: 'Jane Doe',
                    publishedAt: '2016-04-10'
                }
            }
        })
    </script>
</body>

</html>

你也可以提供第二个的参数为 property 名称 (也就是键名)。还可以用第三个参数作为索引。

1
2
3
<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
</div>
1
2
3
<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
</div>

注意:在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。

(3)维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个类似 Vue 1.x 的 track-by="$index"。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        let arr = [10, 12, 13];
        arr.forEach((item, index) => {
            arr.splice(1, 1);
            console.log(index);
            console.log(item);
        });
        // 使用下面这种方法可以忽略删除数组元素对数组长度带来的影响
        [...arr].forEach((item, index) => {
            arr.splice(1, 1);
            console.log(index);
            console.log(item);
        }); 

注意:这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。

需要使用 key 的理由:

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:

1
2
3
<div v-for="item in items" v-bind:key="item.id">
  <!-- 内容 -->
</div>

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

因为它是 Vue 识别节点的一个通用机制,key 并不仅与 v-for 特别关联。后面我们将在指南中看到,它还具有其它用途。

不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。

(4)数组更新检测

(1)变更方法

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

  • 1.push()
  • 2.pop()
  • 3.shift()
  • 4.unshift()
  • 5.splice()
  • 6.sort()
  • 7.reverse()

总结:5个增加删除数据元素的方法,2个对数组进行排序的方法

问题:数组中有9个方法可以改变原数组,除了上面7个,还有copyWithin和fill方法,为什么这两个方法不会触发视图更新。

(3)替换数组

变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:

1
2
3
example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

理解:当上面的7个改变原始数组的方法不满足业务需求时,可以使用其它的方法,把改变后的数组赋给原数组。

注意:由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。深入响应式原理中有相关的讨论。

(5)显示过滤/排序后的结果

有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul id="example-2">
        <li v-for="n in evenNumbers">{{ n }}</li>
    </ul>
    <script src="./vue.js"></script>
    <script>
        var example2 = new Vue({
            el: '#example-2',
            data: {
                numbers: [1, 2, 3, 4, 5]
            },
            computed: {
                evenNumbers: function () {
                    return this.numbers.filter(function (number) {
                        return number % 2 === 0
                    })
                }
            }
        })
    </script>
</body>

</html>  

在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-2">
        <ul v-for="set in sets">
            <li v-for="n in even(set)">{{ n }}</li>
        </ul>
    </div>
    <script src="./vue.js"></script>
    <script>
        var example2 = new Vue({
            el: '#example-2',
            data: {
                sets: [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
            },
            methods: {
                even: function (numbers) {
                    return numbers.filter(function (number) {
                        return number % 2 === 0
                    })
                }
            }
        })
    </script>
</body>

</html>  

注意:因为计算属性改变的数据也是双向绑定的,有时候用的不好会变成死循环,有时候使用得注意。

(6)在 v-for 里使用值范围

v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。

1
2
3
<div>
  <span v-for="n in 10">{{ n }} </span>
</div>

(7)在 template 上使用 v-for

类似于 v-if,你也可以利用带有 v-for 的 template 来循环渲染一段包含多个元素的内容。比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-2">
        <ul>
            <template v-for="(item,index) in items">
                <li class="divider">{{ index }}</li>
                <li>{{ item.msg }}</li>
            </template>
        </ul>
    </div>
    <script src="./vue.js"></script>
    <script>
        var example2 = new Vue({
            el: '#example-2',
            data: {
                items: [
                    { msg: 'Java' },
                    { msg: 'Go' },
                    { msg: 'Php' }
                ]
            }
        })
    </script>
</body>

</html> 

(8)v-for 与 v-if 一同使用

注意我们不推荐在同一元素上使用 v-if 和 v-for。更多细节可查阅风格指南。

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:

1
2
3
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 template ) 上。如:

1
2
3
4
5
6
<ul v-if="todos.length">
  <li v-for="todo in todos">
    {{ todo }}
  </li>
</ul>
<p v-else>No todos left!</p>

(9)在组件上使用 v-for

在自定义组件上,你可以像在任何普通元素上一样使用 v-for。

1
<my-component v-for="item in items" :key="item.id"></my-component>

2.2.0+ 的版本里,当在组件上使用 v-for 时,key 现在是必须的。

然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 prop,不自动将 item 注入到组件里的原因是,这会使得组件与 v-for 的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。

总结:不将数据直接注入组件,是为了让组件可以重复使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="todo-list-example">
        <form v-on:submit.prevent="addNewTodo">
            <label for="new-todo">Add a todo</label>
            <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat">
            <button>Add</button>
        </form>
        <ul>
            <li is="todo-item" v-for="(todo, index) in todos" v-bind:key="todo.id" v-bind:title="todo.title"
                v-on:remove="todos.splice(index, 1)"></li>
        </ul>
    </div>
    <script src="./vue.js"></script>
    <script>
        Vue.component('todo-item', {
            template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  ',
            props: ['title']
        })

        new Vue({
            el: '#todo-list-example',
            data: {
                newTodoText: '',
                todos: [
                    {
                        id: 1,
                        title: 'Do the dishes',
                    },
                    {
                        id: 2,
                        title: 'Take out the trash',
                    },
                    {
                        id: 3,
                        title: 'Mow the lawn'
                    }
                ],
                nextTodoId: 4
            },
            methods: {
                addNewTodo: function () {
                    this.todos.push({
                        id: this.nextTodoId++,
                        title: this.newTodoText
                    })
                    this.newTodoText = ''
                }
            }
        })
    </script>
</body>

</html> 

注意:注意这里的 is=“todo-item” attribute。这种做法在使用 DOM 模板时是十分必要的,因为在 ul 元素内只有 li 元素会被看作有效内容。这样做实现的效果与 todo-item 相同,但是可以避开一些潜在的浏览器解析错误。查看 DOM 模板解析说明 来了解更多信息。

11.事件处理

(1)监听事件

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-1">
        <button v-on:click="counter += 1">Add 1</button>
        <p>The button above has been clicked {{ counter }} times.</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var example1 = new Vue({
            el: '#example-1',
            data: {
                counter: 0
            }
        })
    </script>
</body>

</html> 

tips:v-on 绑定的事件中是可以写JS代码的

(2)事件处理方法

然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-2">
        <!-- `greet` 是在下面定义的方法名 -->
        <button @click="greet">Greet</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var example2 = new Vue({
            el: '#example-2',
            data: {
                name: 'Vue.js'
            },
            // 在 `methods` 对象中定义方法
            methods: {
                greet: function (event) {
                    // `this` 在方法里指向当前 Vue 实例
                    alert('Hello ' + this.name + '!')
                    // `event` 是原生 DOM 事件
                    if (event) {
                        alert(event.target.tagName)
                    }
                }
            }
        })
    </script>
</body>

</html>

(3)内联处理器中的方法

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <button v-on:click="say('hi')">Say hi</button>
        <button v-on:click="say('what')">Say what</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#example-3',
            methods: {
                say: function (message) {
                    alert(message)
                }
            }
        })
    </script>
</body>

</html> 

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <button v-on:click="warn('Form cannot be submitted yet.', $event)">
            Submit
        </button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#example-3',
            methods: {
                warn: function (message, event) {
                    // 现在我们可以访问原生事件对象
                    if (event) {
                        event.preventDefault()
                    }
                    alert(message)
                }
            }
        })
    </script>
</body>

</html>

(4)事件修饰符

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

    1. .stop
    1. .prevent
    1. .capture
    1. .self
    1. .once
    1. .passive
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

注意:使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

2.1.4 新增,使用 .once,点击事件将只会触发一次

1
<a v-on:click.once="doThis"></a>

不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上。

2.30新增:Vue 还对应 addEventListener 中的 passive 选项提供了 .passive 修饰符。

1
2
3
4
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

注意:这个 .passive 修饰符尤其能够提升移动端的性能。

警告:不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。

(5)按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

1
2
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。

1
<input v-on:keyup.page-down="onPageDown">

(6)按键码

keyCode 的事件用法已经被废弃了并可能不会被最新的浏览器支持。

(7)系统修饰键

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

    1. .ctrl
    1. .alt
    1. .shift
    1. .meta

12.表单输入绑定

v-model

13.组件基础

(1)基本示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <button-counter></button-counter>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>

        Vue.component('button-counter', {
            data: function () {
                return {
                    count: 0
                }
            },
            template: '<button @click="count++">You clicked me {{ count }} times</button>'
        })
        // 注意这个挂载得放到注册组件之后,要不然找不到注册的组件
        new Vue({
            el: '#example-3'
        })
    </script>
</body>

</html>  

因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

(2)组件的复用

(1)介绍

你可以将组件进行任意次数的复用:

1
2
3
4
5
6
    <div id="example-3">
        <button-counter></button-counter>
        <button-counter></button-counter>
        <button-counter></button-counter>
        <button-counter></button-counter>
    </div>

注意当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建。

注意每使用一次注册的组件,就会创建一个新的实例,这个地方的设计是否合理,在Vue3中是否优化了这个地方。

(2)data 必须是一个函数

当我们定义这个 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:

1
2
3
data: {
  count: 0
}

取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:

1
2
3
4
5
data: function () {
  return {
    count: 0
  }
}

如果 Vue 没有这条规则,组件内的数据就会被共享,上面那个点击的例子,每一个组件就不再是独立的了。

(3)组件的组织

通常一个应用会以一棵嵌套的组件树的形式来组织:

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 Vue.component 全局注册的:

1
2
3
Vue.component('my-component-name', {
  // ... options ...
})

全局注册的组件可以 用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例 ,也包括其组件树中的所有子组件的模板中。

(4)通过 Prop 向子组件传递数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <h1-label title="Hello"></h1-label>
        <h1-label title="World"></h1-label>
        <h1-label title="你好!"></h1-label>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>

        Vue.component('h1-label', {
            props: ['title'],
            template: '<h1>{{ title }}</h1>'
        })
        // 注意这个挂载得放到注册组件之后,要不然找不到注册的组件
        new Vue({
            el: '#example-3'
        })
    </script>
</body>

</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <h1-label v-for="item in posts" :title="item.title" :key="item.id"></h1-label>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>

        Vue.component('h1-label', {
            props: ['title'],
            template: '<h1>{{ title }}</h1>'
        })
        // 注意这个挂载得放到注册组件之后,要不然找不到注册的组件
        new Vue({
            el: '#example-3',
            data: {
                posts: [
                    { id: 1, title: 'My' },
                    { id: 2, title: 'name' },
                    { id: 3, title: 'XKHM' },
                ]
            }
        })
    </script>
</body>

</html> 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <h1-label v-for="item in posts" :posts="item" :key="item.id"></h1-label>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>

        Vue.component('h1-label', {
            props: ['posts'],
            template: `<div>
                            <h1>{{ posts.title1 }}</h1>
                            <h2>{{ posts.title2 }}</h2>
                            <h3>{{ posts.title3 }}</h3>
                       </div>`
        })
        // 注意这个挂载得放到注册组件之后,要不然找不到注册的组件
        new Vue({
            el: '#example-3',
            data: {
                posts: [
                    { id: 1, title1: 'My1', title2: 'My2', title3: 'My3' },
                    { id: 2, title1: 'name1', title2: 'name2', title3: 'name3' },
                    { id: 3, title1: 'XKHM1', title2: 'XKHM2', title3: 'XKHM3' },
                ]
            }
        })
    </script>
</body>

</html>

(5)监听子组件事件

(1)父子组件通信

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <div :style="{fontSize:postFontSize+'px'}">
            <h1-label v-for="item in posts" @enlarge-text="postFontSize += 1" :posts="item" :key="item.id"></h1-label>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>

        Vue.component('h1-label', {
            props: ['posts'],
            template: `<div>
                            <span>{{ posts.title1 }}</span>
                            <span>{{ posts.title2 }}</span>
                            <span>{{ posts.title3 }}</span>
                            <button v-on:click="$emit('enlarge-text')">放大字体</button>
                       </div>`
        })
        // 注意这个挂载得放到注册组件之后,要不然找不到注册的组件
        new Vue({
            el: '#example-3',
            data: {
                posts: [
                    { id: 1, title1: 'My1', title2: 'My2', title3: 'My3' },
                    { id: 2, title1: 'name1', title2: 'name2', title3: 'name3' },
                    { id: 3, title1: 'XKHM1', title2: 'XKHM2', title3: 'XKHM3' },
                ],
                postFontSize: 30,
            }
        })
    </script>
</body>

</html> 

问题:上面不论点击哪个按钮,三列字体都会放大,那怎么做才能让每列字体分开放大呢。

(6)使用事件抛出一个值

有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 组件决定它的文本要放大多少。这时可以使用 $emit 的第二个参数来提供这个值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <div :style="{fontSize:postFontSize+'px'}">
            <h1-label v-for="item in posts" @enlarge-text="postFontSize += $event" :posts="item" :key="item.id">
            </h1-label>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>

        Vue.component('h1-label', {
            props: ['posts'],
            template: `<div>
                            <span>{{ posts.title1 }}</span>
                            <span>{{ posts.title2 }}</span>
                            <span>{{ posts.title3 }}</span>
                            <button v-on:click="$emit('enlarge-text',20)">放大字体</button>
                       </div>`
        })
        // 注意这个挂载得放到注册组件之后,要不然找不到注册的组件
        new Vue({
            el: '#example-3',
            data: {
                posts: [
                    { id: 1, title1: 'My1', title2: 'My2', title3: 'My3' },
                    { id: 2, title1: 'name1', title2: 'name2', title3: 'name3' },
                    { id: 3, title1: 'XKHM1', title2: 'XKHM2', title3: 'XKHM3' },
                ],
                postFontSize: 30,
            }
        })
    </script>
</body>

</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <div :style="{fontSize:postFontSize+'px'}">
            <h1-label v-for="item in posts" @enlarge-text="onEnlargeText" :posts="item" :key="item.id">
            </h1-label>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>

        Vue.component('h1-label', {
            props: ['posts'],
            template: `<div>
                            <span>{{ posts.title1 }}</span>
                            <span>{{ posts.title2 }}</span>
                            <span>{{ posts.title3 }}</span>
                            <button v-on:click="$emit('enlarge-text',20)">放大字体</button>
                       </div>`
        })
        // 注意这个挂载得放到注册组件之后,要不然找不到注册的组件
        new Vue({
            el: '#example-3',
            data: {
                posts: [
                    { id: 1, title1: 'My1', title2: 'My2', title3: 'My3' },
                    { id: 2, title1: 'name1', title2: 'name2', title3: 'name3' },
                    { id: 3, title1: 'XKHM1', title2: 'XKHM2', title3: 'XKHM3' },
                ],
                postFontSize: 30,
            },
            methods: {
                onEnlargeText: function (enlargeAmount) {
                    this.postFontSize += enlargeAmount
                }
            }
        })
    </script>
</body>

</html>

(7)在组件上使用 v-model

自定义事件也可以用于创建支持 v-model 的自定义输入组件。记住。

自定义事件

(8)通过插槽分发内容(slot)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="example-3">
        <div :style="{fontSize:postFontSize+'px'}">
            <h1-label v-for="item in posts" @enlarge-text="onEnlargeText" :posts="item" :key="item.id">
                <i>你好内容</i>
            </h1-label>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>

        Vue.component('h1-label', {
            props: ['posts'],
            template: `<div>
                            <span>{{ posts.title1 }}</span>
                            <span>{{ posts.title2 }}</span>
                            <span>{{ posts.title3 }}</span>
                            <br>
                            <slot></slot>
                            <button v-on:click="$emit('enlarge-text',20)">放大字体</button>
                       </div>`
        })
        // 注意这个挂载得放到注册组件之后,要不然找不到注册的组件
        new Vue({
            el: '#example-3',
            data: {
                posts: [
                    { id: 1, title1: 'My1', title2: 'My2', title3: 'My3' },
                    { id: 2, title1: 'name1', title2: 'name2', title3: 'name3' },
                    { id: 3, title1: 'XKHM1', title2: 'XKHM2', title3: 'XKHM3' },
                ],
                postFontSize: 30,
            },
            methods: {
                onEnlargeText: function (enlargeAmount) {
                    this.postFontSize += enlargeAmount
                }
            }
        })
    </script>
</body>

</html>

(9)动态组件

有的时候,在不同组件之间进行动态切换是非常有用的,比如导航栏tab切换:

动态组件

(10)解析 DOM 模板时的注意事项

有些 HTML 元素,诸如 ul、ol、table 和 select,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 li、tr 和 option,只能出现在其它某些特定的元素内部。

这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:

1
2
3
<table>
  <blog-post-row></blog-post-row>
</table>

这个自定义组件 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is attribute 给了我们一个变通的办法:

1
2
3
<table>
  <tr is="blog-post-row"></tr>
</table>

需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:

字符串 (例如:template: ‘…') 单文件组件 (.vue)

1
<script type="text/x-template">

14.深入响应式原理