VueJS2-高级教程-五-

81 阅读28分钟

VueJS2 高级教程(五)

原文:Pro Vue.js 2

协议:CC BY-NC-SA 4.0

十二、使用基本指令

指令是将 Vue.js 功能应用于组件模板中 HTML 元素的特殊属性。在这一章中,我将解释如何使用 Vue.js 提供的基本内置指令,这些指令提供了一些 web 应用开发中最常用的特性。在第十三章–15 章中,我描述了更复杂的指令,在第二十六章中,我解释了当内置指令不提供您需要的特性时,如何创建自定义指令。表 12-1 将内置指令放在上下文中。

表 12-1

将内置指令放在上下文中

|

问题

|

回答

| | --- | --- | | 它们是什么? | 内置指令提供了 web 应用开发中通常需要的特性。我在本章中描述的过滤器用于管理元素的文本或 HTML 内容,决定元素是否对用户可见,以及管理元素的属性。还有用于响应用户交互、重复内容和管理表单元素的指令,将在后面的章节中介绍。 | | 它们为什么有用? | 指令使得将组件的script元素中的数据和代码与其template中的内容联系起来变得容易。 | | 它们是如何使用的? | 指令作为名称以v-开头的特殊属性应用于 HTML 元素,例如v-textv-bind。 | | 有什么陷阱或限制吗? | 有些内置指令很难使用,可能会产生意想不到的结果。对于后面章节中描述的指令来说,情况更是如此。 | | 还有其他选择吗? | 不。指令是连接组件的 HTML 元素和 JavaScript 代码的 Vue.js 构建块。 |

表 12-2 总结了本章内容。

表 12-2

章节总结

|

问题

|

解决办法

|

列表

| | --- | --- | --- | | 设置元素的文本内容 | 使用文本插值绑定或v-text指令 | three | | 显示原始 HTML | 使用v-html指令 | 4–5 | | 选择性显示元素 | 使用v-ifv-elsev-show指令 | 6, 9–13 | | 选择性显示对等元素 | 将指令应用于一个template元素 | 7–8 | | 设置属性和特性 | 使用v-bind指令 | 14–19 |

为本章做准备

在本章中,我继续使用在第十一章中创建的templatesanddata项目。为了准备本章,我简化了应用的根组件,如清单 12-1 所示。

小费

你可以从 https://github.com/Apress/pro-vue-js-2 下载本章以及本书其他章节的示例项目。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3>Product: {{ name }}</h3>
        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket"
            }
        },
        methods: {
            handleClick() {
                // do nothing
            }
        }
    }
</script>

Listing 12-1Simplifying the Content of the App.vue File in the src Folder

我已经使用了文本插值绑定来显示名为namedata属性的值,如第十一章所述。我还添加了一个button元素,并对其应用了v-on指令,如下所示:

...
<button v-on:click="handleClick" class="btn btn-primary">
...

正如我在第十四章中详细描述的那样,v-on指令用于处理事件。我在本章中描述的一些指令有一些有用的特性,这些特性只有在应用的状态改变时才能看到,我需要一种机制来触发这些改变。该指令被配置为通过调用一个名为handleClick的方法来响应click事件,该事件在用户单击button元素时被触发。我已经在组件的script元素的methods部分定义了这个方法,但是目前它不包含任何语句。在本章的后面,我将使用handleClick方法来演示一些有用的指令特性。保存对App.vue文件的更改,并运行templatesanddata文件夹中清单 12-2 所示的命令,启动 Vue.js 开发工具。

npm run serve

Listing 12-2Starting the Development Tools

打开一个新的浏览器窗口并导航至http://localhost:8080以查看图 12-1 所示的内容。

img/465686_1_En_12_Fig1_HTML.jpg

图 12-1

运行示例应用

设置元素的文本内容

一个好的起点是最基本的指令,它执行您已经熟悉的任务:设置元素的文本内容。在清单 12-3 中,我用一个指令替换了组件模板中的文本插值绑定。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3>Product: <span v-text="name"></span></h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket"
            }
        },
        methods: {
            handleClick() {
                // do nothing
            }
        }
    }
</script>

Listing 12-3Using a Directive in the App.vue File in the src Folder

这是v-text指令,以用于将指令应用于 HTML 元素的属性命名。图 12-2 显示了该指令及其应用的元素的剖析。

img/465686_1_En_12_Fig2_HTML.jpg

图 12-2

v-text 指令的剖析

使用一个v-text属性来应用该指令。该属性的值是一个表达式,Vue.js 对其求值以获得应该向用户显示的内容。这与我在第十一章中描述的文本插值绑定使用的表达式类型相同,这个绑定的结果是显示名为namedata属性的值。

与文本插值绑定不同,v-text指令完全替换了它所应用的元素的内容,这就是为什么我在清单 12-3 的模板中添加了一个span元素。

当检测到变化时,Vue.js 重新评估指令的表达式,这可以通过使用 Vue Devtools 在浏览器中改变name属性的值来测试,如图 12-3 所示。

img/465686_1_En_12_Fig3_HTML.jpg

图 12-3

值的变化对指令的影响

显示原始 HTML

文本插值绑定和v-text指令自动清理它们显示的内容,删除浏览器可能解释为 HTML 文档结构一部分的任何字符。净化数据值有助于防止跨站点脚本(XSS)攻击,在这种攻击中,浏览器将数据值解释为 HTML,并允许攻击者在浏览器中插入内容或代码。(你可以在 https://en.wikipedia.org/wiki/Cross-site_scripting 了解更多关于 XSS 攻击的工作原理。)为了演示,我添加了一个data属性,其内容是一个script元素,如清单 12-4 所示。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3>Product: <span v-text="name"></span></h3>
            <span v-text="fragment"></span>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                fragment: `<div class="form-group">

                             Password

                             <input class="form-control" />

                           </div>`

            }
        },
        methods: {
            handleClick() {
                // do nothing
            }
        }
    }
</script>

Listing 12-4Adding a Data Property in the App.vue File in the src Folder

新的data属性被称为fragment,它的值是一组包含一个input元素的 HTML 元素。在组件的模板中,我添加了一个span元素并应用了v-text绑定,该绑定将用fragment值替换元素的内容。当您保存更改时,应用将被更新,您将看到 HTML 的片段已经变得安全,如图 12-4 所示。

img/465686_1_En_12_Fig4_HTML.jpg

图 12-4

净化数据值

净化数据值是一个好主意,并且在处理用户提供的数据时非常重要。但是,当您处理可信任的内容时,有时您可能希望将其视为 HTML,而清理会阻止数据正确显示。对于这些情况,Vue.js 提供了v-html指令,我在清单 12-5 中使用了这个指令。

警告

除非您信任所显示数据的来源,否则不要使用此功能。

...
<template>
    <div class="bg-primary text-white text-center m-2 p-3">
        <h3 v-text="name" >Product:<span v-text="name"></span></h3>
        <span v-html="fragment"></span>

    </div>
    <button v-on:click="handleClick" class="btn btn-primary">
        Press Me
    </button>
</template>
...

Listing 12-5Displaying HTML Content in the App.vue File in the src Folder

v-html指令的应用方式与v-text相同,但显示的数据值未经净化,如图 12-5 所示。如果没有净化,浏览器会将数据值解释为 HTML 元素,并向用户呈现一个input元素。

img/465686_1_En_12_Fig5_HTML.jpg

图 12-5

显示 HTML 数据值

选择性显示元素

组件显示的元素集经常需要更改,以适应组件状态的变化。Vue.js 包括一组指令,这些指令根据数据绑定表达式的计算结果来更改应用它们的 HTML 元素的可见性。在清单 12-6 中,我使用了v-if指令来控制 HTML 元素的可见性。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3>Product: <span v-text="name"></span></h3>
            <h4 v-if="showElements">{{ price }}</h4>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                price: 275,

                showElements: true

            }
        },
        methods: {
            handleClick() {
                this.showElements = !this.showElements;

            }
        }
    }
</script>

Listing 12-6Selectively Displaying Content in the App.vue File in the src Folder

在这个例子中,我将v-if指令应用于一个h4元素,如下所示:

...
<h4 v-if="showElements">{{ price }}</h4>
...

该指令将评估其表达式,并使用结果来控制h4元素的可见性。如果表达式结果为真,则元素可见,否则隐藏(参见 JavaScript 真值的“理解真值和假值”侧栏)。结果是,当名为showElements的数据属性为true时,h4元素将可见,当其为false时,元素将隐藏。

我在handleClick方法中添加了一条语句,当点击按钮时,该语句切换showElements值,这演示了当表达式结果改变时,v-if指令改变了它所应用的元素的可见性,如图 12-6 所示。

小费

为了控制可见性,v-if指令销毁并重新创建元素及其内容,或者,如果元素是相同的类型,重用单个元素来显示不同的内容。这意味着只有可见的元素才是 DOM 的一部分。使用本章稍后描述的v-show指令在 DOM 中保留一个元素,并使用 CSS 属性管理其可见性。

img/465686_1_En_12_Fig6_HTML.jpg

图 12-6

控制元素的可见性

理解真理和谬误

v-if这样的指令评估它们的表达式,以确定它们是真还是假,这是一个奇怪的 JavaScript 特性,经常导致混乱,并为粗心的人提供了一个陷阱。以下结果总是假的:

  • –值false ( boolean)

  • 0(数字)值

  • –空字符串("")

  • null

  • undefined

  • NaN(特殊数值)

所有其他值都是真实的,这可能会令人困惑。例如,"false"(内容为单词false的字符串)为 truthy。避免混淆的最好方法是只使用评估为booleantruefalse的表达式。

选择性地显示相邻的对等元素

使用v-if指令的标准方式是将它直接应用于可见性被管理的顶层元素。如果在同一个层次上有几个元素的可见性由同一个表达式控制,那么这种方法就变得很笨拙,如清单 12-7 所示。

...
<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3>Product: <span v-text="name"></span></h3>
            <ul class="text-left">

                <li>List item</li>

                <li v-if="showElements">{{name}}</li>

                <li v-if="showElements">{{price}}</li>

                <li>Other list item</li>

            </ul>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>
...

Listing 12-7Applying the Same Directive to Peer Elements in the App.vue File in the src Folder

我想基于相同的数据绑定表达式来控制列表中四个li元素中的两个的可见性。重复应用指令是重复且容易出错的,最好避免。对于某些元素,这个问题可以通过添加一个中性元素作为公共父元素来解决,比如一个divspan元素,但这在这里不起作用,因为结果将是非法的 HTML ( ul元素不允许包含divspan元素)。

在这些情况下,template元素可以被用作公共父元素,如清单 12-8 所示。

...
<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3>Product: <span v-text="name"></span></h3>
            <ul class="text-left">
                <li>List item</li>
                <template v-if="showElements">

                    <li>{{name}}</li>

                    <li>{{price}}</li>

                </template>

                <li>Other list item</li>
            </ul>
        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>
...

Listing 12-8Using a Template Element in the App.vue File in the src Folder

该指令应用于template元素,该元素在编译过程中被删除,不会导致非法的 HTML。结果是使用v-if指令的单个实例来管理相邻的li元素,结果如图 12-7 所示。

小费

您可以使用v-for指令和一个 computed 属性对不相邻的元素实现类似的效果。第十三章中描述了v-for指令。

img/465686_1_En_12_Fig7_HTML.jpg

图 12-7

使用模板元素将对等元素分组

在内容部分之间选择

如果你想显示基于数据值的替代内容,那么你可以重复v-if指令并否定其中一个表达式,如清单 12-9 所示。

...
<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-if="showElements">Product: {{name}}</h3>

            <h3 v-if="!showElements">Price: {{price}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>
...

Listing 12-9Choosing Content to Display in the App.vue File in the src Folder

这种方法可行,但是很笨拙,当显示元素的标准改变时,您必须记住更新两个表达式。当表达式比检查值是否为true更复杂时,这种方法也会变得复杂。为了避免这种表达式,Vue.js 提供了v-else指令,它与v-if一起工作,不需要自己的表达式,如清单 12-10 所示。

...
<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-if="showElements">Product: {{name}}</h3>
            <h3 v-else>Price: {{price}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>
...

Listing 12-10Simplifying Content Selection in the App.vue File in the src Folder

v-if之后立即应用v-else指令,与v-if指令相反,它会自动改变它所应用到的元素的可见性,如图 12-8 所示。

img/465686_1_En_12_Fig8_HTML.jpg

图 12-8

在内容部分之间选择

执行更复杂的选择

如果一个基本的 if/else 方法不够,那么您还可以使用v-else-if指令,它与v-ifv-else结合使用来选择元素,并且有自己的表达式。如果对v-if表达式求值的结果是false,则检查v-else-if指令的表达式,看其元素是否应该显示,如果不是,则显示v-else元素。可以使用v-else-if指令的多个实例来管理复杂的选择,如清单 12-11 所示。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-if="counter % 3 == 0">Product: {{name}}</h3>

            <h3 v-else-if="counter % 3 == 1">Price: {{price}}</h3>

            <h3 v-else>Category: {{category}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                price: 275,
                category: "Watersports",

                counter: 0

            }
        },
        methods: {
            handleClick() {
                this.counter++;

            }
        }
    }
</script>

Listing 12-11Performing a Complex Content Selection in the App.vue File in the src Folder

v-ifv-else-if指令的表达式依赖于一个counter属性,该属性的值在按钮被单击时递增。还有一个v-else指令,当其他两个指令表达式都为假时,将显示其元素,产生如图 12-9 所示的结果。

img/465686_1_En_12_Fig9_HTML.jpg

图 12-9

执行更复杂的选择

使用 CSS 有选择地显示元素

v-ifv-else-ifv-else指令隐藏元素,将它们从文档对象模型(DOM)中移除,并再次重新创建它们以使它们可见。结果是 DOM 只包含用户可见的元素,这可能是一个问题,尤其是在使用基于元素在父元素中的位置来选择元素的 CSS 样式时。清单 12-12 展示了可能出现的问题类型。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-if="counter % 2 == 0">Product: {{name}}</h3>

            <h3 v-else>Price: {{price}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                price: 275,
                counter: 0
            }
        },
        methods: {
            handleClick() {
                this.counter++;
            }
        }
    }
</script>

<style>

    h3:first-child { background-color: aquamarine; padding: 10px; color: black; }

</style>

Listing 12-12Adding Position-Specific CSS in the App.vue File in the src Folder

我已经简化了模板,所以只有两个 header 元素,我对它们应用了v-ifv-else指令。我还添加了一个style元素,它包含一个带有h3:first-child选择器的样式,该样式匹配作为其父元素的第一个子元素的h3元素。

当您保存更改并单击“按我”按钮时,您可以看到出现的问题。这些指令确保只有一个h3元素是可见的,但是由于不可见的元素被从 DOM 中移除,可见的元素是其父元素的第一个也是唯一的子元素,并且总是被 CSS 选择器匹配,如图 12-10 所示。

小费

您可能需要重新加载浏览器才能看到style元素的效果。

img/465686_1_En_12_Fig10_HTML.jpg

图 12-10

由位置 CSS 选择器匹配的元素

当 CSS 的意图是只改变显示产品名称的元素的样式属性时,这种行为是一个问题。这些样式不会影响 price 元素,因为它是模板中其父元素的第二个子元素,但是从 DOM 中移除不可见元素的方式会导致意外的结果。

在清单 12-12 中,我可以通过改变我的 CSS 选择器来解决这个问题,但是当处理应用于整个应用的全局样式或者使用第三方 CSS 框架比如 Bootstrap 时,这并不总是可能的。在这些情况下,v-show指令是一个合适的选择,因为它具有与v-if相同的效果,但是不会从 DOM 中删除不可见的元素。在清单 12-13 中,我已经对组件应用了v-show指令。

...
<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-show="counter % 2 == 0">Product: {{name}}</h3>

            <h3 v-show="counter % 2 != 0">Price: {{price}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>
...

Listing 12-13Leaving Invisible Elements in the DOM in the App.vue File in the src Folder

v-show指令的应用方式与v-if相同,可用作直接替换。保存更改并点击按我按钮来增加计数器并使v-show指令显示和隐藏它们的内容。通过使用浏览器的 F12 工具在 DOM 中检查元素,您可以看到v-show是如何隐藏元素的,这将显示元素保留在 DOM 中,并且它们的 display 属性设置为 none,如下所示:

...
<h3 style="display: none;">Price: 275</h3>
...

由于元素只是被隐藏,而不是被删除,组件的样式被应用,如图 12-11 所示。

img/465686_1_En_12_Fig11_HTML.jpg

图 12-11

隐藏元素

设置display属性可能比从 DOM 中移除一个元素更有效,但是v-show指令不能与template元素一起使用,也没有与v-else-ifv-else指令等价的指令,这就是为什么我不得不将v-show指令应用于两个h3元素。

设置元素的属性和特性

v-bind指令用于设置元素的属性或特性。我从关注属性开始这一部分,之后我将解释为什么属性是不同的。在清单 12-14 中,我使用了v-bind指令将元素分配给对应于引导 CSS 样式的类,这是v-bind指令最常见的用法。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-bind:class="elemClasses">Product: {{name}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                highlight: false

            }
        },
        computed: {

            elemClasses() {

                return this.highlight

                    ? ["bg-light", "text-dark", "display-4"]

                    : ["bg-dark", "text-light", "p-2"];

            }

        },

        methods: {
            handleClick() {
                this.highlight = !this.highlight;

            }
        }
    }
</script>

Listing 12-14Assigning Elements to Classes in the App.vue File in the src Folder

这个例子看起来比实际更复杂。起点是指令,我将它应用于h3元素,如下所示:

...
<h3 v-bind:class="elemClasses">Product: {{name}}</h3>
...

v-bind指令配置了一个参数和一个表达式,如图 12-12 所示。参数指定指令将配置的元素属性,对表达式求值将提供该属性的值。

img/465686_1_En_12_Fig12_HTML.jpg

图 12-12

v-bind 指令的剖析

在清单 12-14 中,指令的表达式获取一个计算属性的值,该属性返回一个样式数组,该数组的内容由名为highlightdata属性的值决定。这看起来像是一种间接的设置样式的方式,但是它展示了一种灵活的方式,可以将 Vue.js 特性结合起来管理呈现给用户的内容。要查看效果,保存对App.vue文件的更改并点击按我按钮切换highlight属性的值,产生如图 12-13 所示的结果。

img/465686_1_En_12_Fig13_HTML.jpg

图 12-13

设置元素的 class 属性

单击该按钮可以在两组类之间更改主体元素的成员资格。当highlight属性为true时,宿主元素是bg-lighttext-darkdisplay-4类的成员(浅背景色、深色文本和大字体)。当highlight属性为false时,宿主元素是bg-darktext-lightp-2类的成员(深色背景色、浅色文本和额外填充)。

使用指令速记

v-bind指令有两种形式。我在前面的例子中使用的是手写形式,它结合了指令名、冒号和要配置的属性名。简写形式省略了指令名,这样v-bind:class也可以表示为:class。这意味着像这样的指令:

...
<h3 v-bind:class="elemClasses">Product: {{name}}</h3>
...

也可以这样应用:

...
<h3 :class="elemClasses">Product: {{name}}</h3>
...

在长格式和简写格式之间的选择是个人喜好,不会改变指令的行为方式。

使用对象配置类

如果有多个输入来决定主机元素应该属于哪一组类,那么我在上一节中使用的数组语法可能会变得难以管理。指令v-bind也可以使用一个对象,其属性名对应于类,其值决定了主机元素的类成员资格,如清单 12-15 所示。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-bind:class="elemClasses" class="display-4">Product: {{name}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
        <button v-on:click="handleOtherClick" class="btn btn-primary">

            Or Press Me

        </button>

    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                highlight1: false,
                highlight2: false

            }
        },
        computed: {
            elemClasses() {
                return {

                    "text-dark": this.highlight1,

                    "bg-light": this.highlight2

                }

            }
        },
        methods: {
            handleClick() {

                this.highlight1 = !this.highlight1;

            },

            handleOtherClick() {

                this.highlight2 = !this.highlight2;

            }

        }
    }
</script>

Listing 12-15Using an Object to Control Class Membership with the App.vue File in the src Folder

我添加了另一个button元素和一个切换data属性值的方法。elemClasses计算属性返回这样一个对象:

...
return {
    "text-dark": this.highlight1,
    "bg-light": this.highlight2
}
...

对象属性名根据两个数据属性的值控制主机元素的bg-lighttext-dark类的成员资格。v-bind指令将其主机元素添加到那些对应于值为true的属性的类中,并从其他类中移除该元素。

小费

我将清单 12-15 中的计算属性返回的对象中的属性名放在引号中。Bootstrap CSS 框架使用的类名包含连字符,只有用引号括起来时,才允许在对象属性名中使用连字符。

我仍然能够在模板中为那些类使用class属性,主机元素应该总是这些类的成员。

...
<h3 v-bind:class="elemClasses" class="display-4">Product: {{name}}</h3>
...

结果是h3元素将始终是display-4类的成员,并根据按钮切换的值属于bg-lighttext-dark类,如图 12-14 所示。

img/465686_1_En_12_Fig14_HTML.jpg

图 12-14

使用对象配置类成员资格

设置个人风格

v-bind属性为设置style属性提供了与类相同的特性,这意味着可以管理单个 CSS 样式属性。在清单 12-16 中,我使用了该指令来控制不同样式属性的值。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-bind:style="elemStyles" class="display-4">Product: {{name}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                highlight: false,

            }
        },
        computed: {
            elemStyles() {

                return {

                    "border": "5px solid red",

                    "background-color": this.highlight ? "coral": ""

                }

            }

        },
        methods: {
            handleClick() {
                this.highlight = !this.highlight;

            }
        }
    }
</script>

Listing 12-16Managing the Style Attribute in the App.vue File in the src Folder

elemStyles computed 属性返回一个属性名为 CSS 属性名的对象。border属性是一个常量值,但是background-color属性是由highlighted属性的值决定的,该值在单击按钮时会改变。随着数据属性的改变,h3元素上的background-color属性的值也会改变,如图 12-15 所示。

img/465686_1_En_12_Fig15_HTML.jpg

图 12-15

设置单个样式属性

设置其他属性

v-bind指令可以用来设置任何属性的值,尽管没有对允许使用对象和数组的classstyle属性的特殊支持。在清单 12-17 中,我使用了v-bind指令来设置与style元素中定义的选择器相匹配的自定义属性的值。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-bind:data-size="size" class="display-4">Product: {{name}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                highlight: false,
            }
        },
        computed: {
            size() {

                return this.highlight ? "big" : "small";

            }

        },
        methods: {
            handleClick() {
                this.highlight = !this.highlight;
            }
        }
    }
</script>

<style>

    [data-size=big] { font-size: 40pt; }

    [data-size=small] { font-size: 20pt; }

</style>

Listing 12-17Setting a Custom Attribute in the App.vue File in the src Folder

HTML 规范允许名称以data-开头的定制属性应用于任何元素。当使用 Vue.js 时,您不需要在自定义属性前加上前缀data-,但是当容易地识别特定于我的应用的属性很重要时,这是我遵循的惯例。

在这个例子中,我使用了v-bind指令来管理一个自定义data-size属性的值,该属性的值取自一个名为size的计算属性,该属性返回bigsmall。这些值对应于style元素中的选择器,它改变字体大小,产生如图 12-16 所示的结果。

小费

您可能需要重新加载浏览器才能看到清单 12-17 中style元素的效果。

img/465686_1_En_12_Fig16_HTML.jpg

图 12-16

设置自定义属性

设置多个属性

单个v-bind指令可以设置多个属性。将指令应用于其宿主元素时,不使用任何参数。相反,表达式必须产生一个对象,其属性名代表要配置的属性,如清单 12-18 所示。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-bind="attrValues">Product: {{name}}</h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                highlight: false,
            }
        },
        computed: {
            attrValues() {

                return {

                    class: this.highlight ? ["bg-light", "text-dark"] : [],

                    style: {

                        border: this.highlight ? "5px solid red": ""

                    },

                    "data-size": this.highlight ? "big" : "small"

                }

            }

        },
        methods: {
            handleClick() {
                this.highlight = !this.highlight;
            }
        }
    }
</script>

<style>
    [data-size=big] { font-size: 40pt; }
    [data-size=small] { font-size: 20pt; }
</style>

Listing 12-18Setting Multiple Attributes in the App.vue File in the src Folder

attrValues computed 属性返回的对象定义了classstyledata-size属性,这些属性的值由名为highlight的数据属性的值决定,单击按钮即可切换。当highlight值为false时,指令所应用的h3元素配置如下:

...
<h3 class="" style="" data-size="small">Product: Lifejacket</h3>
...

highlight值为true时,v-bind指令修改元素如下:

...
<h3 class="bg-light text-dark" style="border: 5px solid red;" data-size="big">
    Product: Lifejacket
</h3>
...

一个绑定管理多个属性,产生如图 12-17 所示的结果。

img/465686_1_En_12_Fig17_HTML.jpg

图 12-17

用单个绑定管理多个属性

设置 HTMLElement 属性

默认情况下,v-bind指令配置主机元素的属性,如前面的示例所示。也可以使用v-bind指令来设置文档对象模型中表示元素的对象的属性值。

当浏览器处理 HTML 文档时,它创建文档对象模型并用表示 HTML 元素的对象填充它。这些对象定义了与 HTML 元素支持的属性不对应的属性,或者是因为它们提供了专门的特性,或者是因为 HTML 和 DOM 规范中的一些奇怪之处,这些并没有得到很好的管理。

在大多数项目中,你并不需要这个特性,但是如果你发现设置一个属性并不能得到需要的结果,那么你可以在v-bind指令中使用prop修饰符,如清单 12-19 所示。

<template>
    <div class="container-fluid text-center">
        <div class="bg-primary text-white m-2 p-3">
            <h3 v-bind:text-content.prop="textContent"></h3>

        </div>
        <button v-on:click="handleClick" class="btn btn-primary">
            Press Me
        </button>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket",
                highlight: false,
            }
        },
        computed: {
            textContent() {

                return this.highlight ? "Highlight!" : `Product: ${this.name}`;

            }

        },
        methods: {
            handleClick() {
                this.highlight = !this.highlight;
            }
        }
    }
</script>

Listing 12-19Setting an Element Property in the App.vue File in the src Folder

将指示词套用至元素时,修饰词会用在属性名称之后,以句点分隔,如下所示:

...
<h3 v-bind:text-content.prop="textContent">Product: {{name}}</h3>
...

这个配置告诉v-bind指令管理名为text-content的对象属性的值。text-content属性提供了对元素文本内容的访问,本例设置了h3元素的内容,如图 12-18 所示。

了解元素属性

Mozilla Foundation 为所有用于在 DOM 中表示 HTML 元素的对象提供了一个有用的参考。对于每个元素,Mozilla 提供了可用属性的摘要以及每个属性的用途。从HTMLElement ( developer.mozilla.org/en-US/docs/Web/API/HTMLElement)开始,它提供了所有元素共有的功能。然后,您可以分支到特定元素的对象中,比如用于表示input元素的HTMLInputElement

img/465686_1_En_12_Fig18_HTML.jpg

图 12-18

设置元素属性

摘要

在这一章中,我描述了 Vue.js 为处理 HTML 元素提供的一些内置指令。我向您展示了如何使用v-textv-html指令管理元素的内容,如何使用v-ifv-show指令选择性地显示内容,以及如何使用v-bind指令设置元素的属性。在下一章中,我将描述用于为数组中的每一项重复内容的指令。

十三、使用重复器指令

在本章中,我将继续描述内置的 Vue.js 指令,并将重点放在v-for指令上,它通常用于填充列表并为表格和网格布局生成行。表 13-1 将v-for指令置于上下文中。

表 13-1

将 v-for 指令放在上下文中

|

问题

|

回答

| | --- | --- | | 这是什么? | v-for 指令用于为数组中的每个项目或对象定义的每个属性复制一组 HTML 元素。 | | 为什么有用? | v-for 指令定义了一个变量,该变量提供对正在处理的对象的访问,可以在数据绑定中使用该变量来自定义重复的 HTML 元素。 | | 如何使用? | v-for 指令应用于要复制的顶级元素,它的表达式指定了对象的源和变量名,通过它们可以在数据绑定中引用每个对象。 | | 有什么陷阱或限制吗? | v-for 指令不支持 Set 和 Map 等 JavaScript 集合,必须注意对象定义的属性的枚举顺序。 | | 还有其他选择吗? | 你可以编写一个自定义指令,如第二十六章所述,来执行类似的任务,但是 v-for 指令包含了许多优化,使得它在处理大型数据集时更有效。 |

表 13-2 总结了本章内容。

表 13-2

章节总结

|

问题

|

解决办法

|

列表

| | --- | --- | --- | | 对数组中的每个对象或对象定义的每个属性重复相同的元素集 | 使用v-for指令 | 3, 13, 15–16 | | 引用复制的元素集中的当前对象 | 使用v-for指令的别名功能 | four | | 将 HTML 元素与特定对象相关联 | 使用v-bind指令定义一个key属性 | 5–7 | | 引用当前对象在数组中的位置 | 使用v-for指令的索引功能 | eight | | 确保检测到对数组索引的更改 | 使用Vue.set方法 | 9–11 | | 没有数据源的重复元素 | 在v-for指令的表达式中使用一个数字值代替数据源 | Fourteen |

为本章做准备

在这一章中,我继续使用第十二章中的templatesanddata项目。为了准备本章,我简化了应用的根组件,如清单 13-1 所示。

小费

你可以从 https://github.com/Apress/pro-vue-js-2 下载本章以及本书其他章节的示例项目。

<template>
    <div class="container-fluid">
        <div class="bg-primary text-white m-2 p-3 text-center">
            <h3>Product: {{ name }}</h3>
        </div>
        <div class="text-center">
            <button v-on:click="handleClick" class="btn btn-primary">
                Press Me
            </button>
        </div>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                name: "Lifejacket"
            }
        },
        methods: {
            handleClick() {
                // do nothing
            }
        }
    }
</script>

Listing 13-1Simplifying the Content of the App.vue File in the src Folder

保存对App.vue文件的修改,运行templatesanddata文件夹中清单 13-2 所示的命令,启动 Vue.js 开发工具。

npm run serve

Listing 13-2Starting the Development Tools

打开一个新的浏览器窗口并导航至http://localhost:8080以查看如图 13-1 所示的内容。

img/465686_1_En_13_Fig1_HTML.jpg

图 13-1

运行示例应用

枚举数组

大多数应用处理必须呈现给用户的相关对象的数组,通常是为了在表格或网格布局中创建行。指令用于为数组中的每个对象重复一组 HTML 元素。在清单 13-3 中,我使用了v-for指令来枚举对象数组以填充一个表。

注意

对于本节中的示例,您将看到一些 linter 警告。当我介绍由v-for指令提供的特性时,这些警告将被处理。

<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>

        <table class="table table-sm table-bordered table-striped text-left">

            <tr><th>Name</th><th>Price</th></tr>

            <tbody>

                <tr v-for="p in products">

                    <td>Name</td>

                    <td>Category</td>

                </tr>

            </tbody>

        </table>

        <div class="text-center">
            <button v-on:click="handleClick" class="btn btn-primary">
                Press Me
            </button>
        </div>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                products: [

                    { name: "Kayak", price: 275 },

                    { name: "Lifejacket", price: 48.95 },

                    { name: "Soccer Ball", price: 19.50 }]

            }
        },
        methods: {
            handleClick() {
                // do nothing
            }
        }
    }
</script>

Listing 13-3Enumerating an Array in the App.vue File in the src Folder

名为productsdata属性的值是一个对象数组,v-for指令处理数组中的每个对象以产生如图 13-2 所示的内容。这还不是一个特别有用的结果,但是v-for指令会引起混淆,需要仔细解释。

img/465686_1_En_13_Fig2_HTML.jpg

图 13-2

枚举对象以创建表格行

我在模板中应用了v-for指令,如下所示:

...
<tr v-for="p in products">
    <td>Name</td>
    <td>Category</td>
</tr>
...

v-for指令的表达式必须是特定的形式: <别名>在<源> 中。 source 项是要被处理的对象的源,并且在本例中指定了名为products的数据属性。in关键字将数组与别名分开,后者是一个临时变量,在处理时分配给数组中的每个对象。

在示例中,别名是p,源是products。应用该指令告诉v-for为 products 数组中的每个对象复制tr元素和它包含的两个td元素,如下所示:

...
<tbody>
    <tr><td>Name</td><td>Category</td></tr>
    <tr><td>Name</td> <td>Category</td></tr>
    <tr><td>Name</td> <td>Category</td></tr>
</tbody>
...

product数组包含三个对象,因此v-for指令复制了三次trtd元素。

使用别名

前面的示例为源中的每个对象重复了相同的内容。大多数应用需要定制为每个对象生成的内容,这就是别名的作用,别名是数组中的每个对象在被v-for指令处理时被赋予的变量。别名很有用,因为它可以用在由v-for指令重复的元素内的数据绑定中,如清单 13-4 所示。

<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>
        <table class="table table-sm table-bordered table-striped text-left">
            <tr><th>Name</th><th>Price</th></tr>
            <tbody>
                <tr v-for="p in products">

                    <td>{{ p.name }}</td>

                    <td>{{ p.price | currency }}</td>

                </tr>

            </tbody>
        </table>
        <div class="text-center">
            <button v-on:click="handleClick" class="btn btn-primary">
                Press Me
            </button>
        </div>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                products: [
                    { name: "Kayak", price: 275 },
                    { name: "Lifejacket", price: 48.95 },
                    { name: "Soccer Ball", price: 19.50 }]
            }
        },
        filters: {

            currency(value) {

                return new Intl.NumberFormat("en-US",

                    { style: "currency", currency: "USD", }).format(value);

            },

        },

        methods: {
            handleClick() {
                // do nothing
            }
        }
    }
</script>

Listing 13-4Using the v-for Alias in the App.vue File in the src Folder

本例中别名变量的名称是p,我使用了文本插值绑定,通过别名显示每个产品的nameprice属性的值,绑定表达式为p.namep.price

...
<tr v-for="p in products">
    <td>{{ p.name }}</td>
    <td>{{ p.price | currency }}</td>
</tr>
...

我在第十一章中描述的所有文本插值特性都可以与一个v-for别名一起使用,我恢复了currency过滤器并使用它来格式化价格属性,产生了如图 13-3 所示的结果。

img/465686_1_En_13_Fig3_HTML.jpg

图 13-3

使用 v-for 指令别名

在数据绑定中包含分配给别名的对象的能力使得v-for指令非常有用,允许为每个被处理的对象生成不同的内容。

识别钥匙

如果您查看开发工具的输出——无论是在命令行上还是在浏览器的 JavaScript 控制台上——您将会看到 linter 正在报告一个警告。

...
error: Elements in iteration expect to have 'v-bind:key' directives (vue/require-v-for-key)
   5 |             <tr><th>Name</th><th>Price</th></tr>
   6 |             <tbody>
>  7 |                 <tr v-for="p in products">
     |                 ^
   8 |                     <td>{{ p.name }}</td>
   9 |                     <td>{{ p.price | currency }}</td>
  10 |                 </tr>
...

这个警告与v-for指令处理对象顺序变化的方式有关。默认情况下,v-for指令通过更新它所创建的每个元素显示的内容来响应它所处理的对象顺序的变化。在本例中,这意味着重新访问已经创建的每个tr元素,并更新包含在td元素中的文本。

要了解这些变化是如何进行的,需要做一些工作。在清单 13-5 中,我在handleClick方法中添加了一条语句,从数组中移除第一项,并在末尾再次插入它。我还向组件添加了一个style元素,其样式选择了一个idtagged的元素,并设置了它的背景色。

...
<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                products: [
                    { name: "Kayak", price: 275 },
                    { name: "Lifejacket", price: 48.95 },
                    { name: "Soccer Ball", price: 19.50 }]
            }
        },
        filters: {
            currency(value) {
                return new Intl.NumberFormat("en-US",
                    { style: "currency", currency: "USD", }).format(value);
            },
        },
        methods: {
            handleClick() {
                this.products.push(this.products.shift());

            }
        }
    }
</script>

<style>

    #tagged { background-color: coral; }

</style>

...

Listing 13-5Adding a Style in the App.vue File in the src Folder

保存更改,应用更新后,打开浏览器的 F12 开发工具,切换到控制台面板,执行清单 13-6 中所示的语句。

document.querySelector("tbody > tr").id = "tagged"

Listing 13-6Marking an Element

该语句使用 DOM API 选择第一个tr元素,它是tbody元素的子元素,并将其 ID 设置为tagged,这对应于清单 13-6 中样式的选择器。该命令一执行,表体的第一行就以不同的背景色显示。

点击按我按钮,查看v-for指令处理对象顺序变化的默认方式。每次handleClick方法改变数组中对象的顺序,v-for指令就会更新它所创建的元素的内容,如图 13-4 所示。

img/465686_1_En_13_Fig4_HTML.jpg

图 13-4

就地更新元素的内容

v-for指令必须更新它创建的所有元素,因为它不知道如何将它们与对象关联起来,因为数组已经被修改了。

给指令提供一个关于哪个对象与哪个元素相关的提示意味着选择一个属性并把它作为唯一键,然后使用v-bind指令来识别它,如清单 13-7 所示。

...
<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>
        <table class="table table-sm table-bordered table-striped text-left">
            <tr><th>Name</th><th>Price</th></tr>
            <tbody>
                <tr v-for="p in products" v-bind:key="p.name">

                    <td>{{ p.name }}</td>
                    <td>{{ p.price | currency }}</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <button v-on:click="handleClick" class="btn btn-primary">
                Press Me
            </button>
        </div>
    </div>
</template>
...

Listing 13-7Selecting a Key in the App.vue File in the src Folder

v-bind指令用于设置一个名为key的属性,其值使用在v-for指令表达式中定义的别名来表示。在本例中,我使用了p作为v-for别名,并且我想使用name属性作为数据对象的键,所以我使用了p.name作为v-bind表达式。

保存对组件的更改,一旦应用更新,再次执行清单 13-6 中所示的语句来标记table主体中的第一个tr元素。背景颜色改变后,点击按钮改变products数组中对象的顺序。既然v-for指令知道如何计算出哪个对象与每组元素相关联,它就能够通过移动元素来响应数组顺序的变化,如图 13-5 所示。

小费

如果您想要默认行为,可以禁用需要键的 linter 规则,这对于少量对象来说会更快。参见清单 13-14 中的示例。

img/465686_1_En_13_Fig5_HTML.jpg

图 13-5

移动元素以响应更改

获取项目索引

v-for指令支持一个附加变量,该变量被分配给数组中当前对象的索引,并可用于多种用途,如在表格中显示行号或用样式定位元素。在清单 13-8 中,我向显示索引的表格中添加了一个新列,并使用v-bind指令在我应用了样式的表格行上设置了一个属性。

<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>
        <table class="table table-sm table-bordered  text-left">
            <tr><th>Index</th><th>Name</th><th>Price</th></tr>

            <tbody>
                <tr v-for="(p, i) in products"

                        v-bind:key="p.name" v-bind:odd="i % 2 == 0">

                    <td>{{ i + 1 }}</td>

                    <td>{{ p.name }}</td>
                    <td>{{ p.price | currency }}</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <button v-on:click="handleClick" class="btn btn-primary">
                Press Me
            </button>
        </div>
    </div>
</template>

<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                products: [

                    { name: "Kayak", price: 275 },

                    { name: "Lifejacket", price: 48.95 },

                    { name: "Soccer Ball", price: 19.50 },

                    { name: "Corner Flags", price: 39.95 },

                    { name: "Stadium", price: 79500 },

                    { name: "Thinking Cap", price: 16 }

                ]

            }

        },
        filters: {
            currency(value) {
                return new Intl.NumberFormat("en-US",
                    { style: "currency", currency: "USD", }).format(value);
            },
        },
        methods: {
            handleClick() {
                this.products.push(this.products.shift());
            }
        }
    }
</script>

<style>

    [odd]{ background-color: lightblue; }

</style>

Listing 13-8Using a v-for Index in the App.vue File in the src Folder

要使用索引功能,在应用v-for表达式时定义一个附加变量,如下所示:

...
<tr v-for="(p, i) in products" v-bind:key="p.name" v-bind:odd="i % 2 == 0">
...

使用逗号(,字符)将索引变量与别名分开,两个变量都用括号括起来。当v-for指令枚举对象时,它将当前对象分配给p,并将i设置为当前对象在数组中的位置,从零开始。index 变量可以在数据绑定中使用,就像任何其他数据值一样。在清单中,我使用文本插值绑定来设置一个新表列的内容,使用一个表达式向用户呈现一个以 1 而不是零开始的序列:

...
<td>{{ i + 1 }}</td>
...

我还使用索引在创建的每个tr元素上设置一个名为odd的自定义属性,并使用模运算符来指示一行是否为奇数。

...
<tr v-for="(p, i) in products" v-bind:key="p.name" v-bind:odd="i % 2 == 0">
...

其效果是奇数行成为odd类的成员,该类匹配style元素中的选择器,并对表中的行进行条带化,如图 13-6 所示。我在清单 13-8 的products数组中添加了更多的项目来强调结果。

V-FOR 指令的扩展形式

看起来很奇怪,我能够使用一个应用于与v-for相同元素的v-bind指令来访问清单 13-8 中的索引变量。这之所以有效,是因为我使用了简洁形式的v-for指令,它应用于应该为每个数据对象复制的最外层元素。这相当于使用一个template项目,就像这样:

...
<tbody>
    <template v-for="(p, i) in products" >

        <tr v-bind:key="p.name"  v-bind:odd="i % 2 == 0">

            <td>{{ i + 1 }}</td>
            <td>{{ p.name }}</td>
            <td>{{ p.price | currency }}</td>
        </tr>
    </template>
</tbody>
...

这等同于清单 13-8 中应用的指令,但更清楚地显示了指令及其内容交互的方式。除非需要为每个数据对象复制多个对等元素,否则不需要将template元素与v-for指令一起使用。如果您确实使用了一个template元素,请记住,key属性的v-bind指令必须应用于复制的元素,而不是模板。

img/465686_1_En_13_Fig6_HTML.jpg

图 13-6

使用 v-for 指令的索引功能

使用 CSS 对表格行进行条带化

在表格或网格布局中交替颜色是一种常见的需求,但是有一种比依赖于由v-for指令提供的索引特性更简单的方法。大多数 CSS 框架,包括 Bootstrap,都实现了表分条,但是如果你依赖于自定义样式,那么你可以在你的组件的style元素中使用 CSS 选择器:

...
<style>
    tbody > tr:nth-child(even) { background-color: coral; }
    tbody > tr:nth-child(odd) { background-color: lightblue; }
</style>
...

这些样式的选择器匹配odd甚至是tr元素,它们是tbody元素的子元素。这些选择器的重要部分是:n-child(odd):nth-child(even),可以用来选择任意奇数和偶数元素。

了解数组更改检测

Vue.js 将使用以下方法检测对数组的更改:pushpopshiftunshiftsplicesortreverse。这些被称为可变数组方法,因为它们改变数组的内容,这允许 Vue.js 保持对数组对象的引用并观察变化。这是我在清单 13-8 中所做的更改类型,以展示关键特性:

...
handleClick() {
    this.products.push(this.products.shift());
}
...

我使用shift方法从数组中移除一个对象,然后使用push方法再次将它添加回来。尽管数组的内容发生了变化,但是相同的数组对象仍然被分配给名为productsdata属性。

其他操作会生成一个新数组,反映它们所做的更改。例如,filterslice方法都返回新的数组,这导致一个新的数组对象被分配给data属性,如清单 13-9 所示。

...
handleClick() {
    this.products = this.products.filter(p => p.price > 20);

}
...

Listing 13-9Creating a New Array in the App.vue File in the src Folder

我用一个使用filter方法创建新数组的语句替换了handleClick方法中的语句,该数组只包含那些price值大于 20 的对象。我将filter方法返回的新数组赋给了products属性,但这是 Vue.js 设计来应对的操作,如果旧数组和新数组中的对象有重叠,现有内容将被重用以提高效率。但是,不管有多少重叠,用一个新的对象替换数组将会更新呈现给用户的内容,如图 13-7 所示。

img/465686_1_En_13_Fig7_HTML.jpg

图 13-7

分配新数组

了解更新问题

有两种类型的数组变化 Vue.js 不能检测到,也不会响应。第一种情况是数组中的一个项目被替换,如清单 13-10 所示。

...
handleClick() {
    this.products[1] = { name: "Running Shoes", price: 100 };

}
...

Listing 13-10Replacing an Array Item in the App.vue File in the src Folder

方法使用数组索引表示法将新对象分配给数组中的位置 1。Vue.js 不会检测到更改,对新对象属性值的任何更改也不会被检测到。

为了解决这一限制,Vue.js 提供了一种替换数组中的对象并触发变化检测过程的方法。这个方法必须从它的模块中导入才能使用,如清单 13-11 所示。

...
<script>
    import Vue from "vue";

    export default {
        name: "MyComponent",
        data: function () {
            return {
                products: [
                    { name: "Kayak", price: 275 },
                    { name: "Lifejacket", price: 48.95 },
                    { name: "Soccer Ball", price: 19.50 },
                    { name: "Corner Flags", price: 39.95 },
                    { name: "Stadium", price: 79500 },
                    { name: "Thinking Cap", price: 16 }]
            }
        },
        filters: {
            currency(value) {
                return new Intl.NumberFormat("en-US",
                    { style: "currency", currency: "USD", }).format(value);
            },
        },
        methods: {
            handleClick() {
                Vue.set(this.products, 1, { name: "Running Shoes", price: 100 });

            }
        }
    }
</script>
...

Listing 13-11Safely Replacing an Array Item in the App.vue File in the src Folder

Vue.set方法接受三个参数:要修改的数组、要替换的对象的索引和新对象。其效果是执行更新并触发变化检测过程,从而更新呈现给用户的内容,如图 13-8 所示。

img/465686_1_En_13_Fig8_HTML.jpg

图 13-8

安全替换数组中的对象

小费

在组件中,Vue.set方法也可以作为this.$set来访问。我更喜欢使用Vue.set,因为不是所有的数组都在组件中执行,我喜欢保持更新的一致性。

Vue.js 无法检测到的对数组的另一个更改是通过更改length属性的值来缩短数组。通过使用 array splice方法以 Vue.js 可以看到的方式移除不需要的元素,可以避免这个问题。

枚举对象属性

虽然v-for指令最常见的用途是枚举数组的内容,但它也可以用于枚举对象的属性,这比它第一次出现时更有用。在清单 13-12 中,我用一个对象替换了对象数组,这个对象的属性名和值包含了我需要的信息。

<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>
        <table class="table table-sm table-bordered  text-left">
            <tr><th>Index</th><th>Name</th><th>Price</th></tr>
            <tbody>
                <tr v-for="(p, key, i) in products" v-bind:key="p.name">

                    <td>{{ i + 1 }}</td>
                    <td>{{ p.name }}</td>
                    <td>{{ p. price | currency }}</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <button v-on:click="handleClick" class="btn btn-primary">
                Press Me
            </button>
        </div>
    </div>
</template>

<script>
    import Vue from "vue";

    export default {
        name: "MyComponent",
        data: function () {
            return {
                products: {
                    1: { name: "Kayak", price: 275 },

                    2: { name: "Lifejacket", price: 48.95 },

                    3: { name: "Soccer Ball", price: 19.50 },

                    4: { name: "Corner Flags", price: 39.95 }

                }
            }
        },
        filters: {
            currency(value) {
                return new Intl.NumberFormat("en-US",
                    { style: "currency", currency: "USD", }).format(value);
            },
        },
        methods: {
            handleClick() {
                Vue.set(this.products, 5, { name: "Running Shoes", price: 100});

            }
        }
    }
</script>

Listing 13-12Using an Object for Enumeration in the App.vue File in the src Folder

在这个清单中,我修改了products属性,使它返回一个对象。这个对象定义了一些属性,这些属性的值本身就是带有nameprice属性的对象。为了枚举products对象的属性,我像这样应用了v-for指令:

...
<tr v-for="(p, key, i) in products" v-bind:key="p.name">
...

处理对象时,指令提供别名、包含键的新变量和索引变量。Vue.js 可以检测属性何时被修改,但不能判断新属性何时被添加到对象中,这就是为什么我在handleClick方法中使用了Vue.set方法,如下所示:

...

Vue.set(this.products, 5, { name: "Running Shoes", price: 100});
...

结果是v-for指令将枚举对象的属性并为每个属性复制内容,如图 13-9 所示。

img/465686_1_En_13_Fig9_HTML.jpg

图 13-9

枚举对象的属性

了解对象属性排序

属性按照 JavaScript Object.keys方法返回的顺序进行枚举,该方法通常对属性进行如下排序:

  1. 具有整数值的键,包括可以解析为整数的值,以升序排列

  2. 具有字符串值的键,按照它们被定义的顺序

  3. 所有其他键,按照它们被定义的顺序

警告

这是您通常会遇到的顺序,但是 JavaScript 实现之间可能会有差异。使用计算属性对对象进行排序,以确保一致性,如下一节所示。

为了演示属性排序的方式,我更改了products对象的键,并在 HTML 表中添加了一列来显示每个键值,如清单 13-13 所示。

<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>
        <table class="table table-sm table-bordered  text-left">
            <tr><th>Index</th><th>Key</th><th>Name</th><th>Price</th></tr>

            <tbody>
                <tr v-for="(p, key, i) in products" v-bind:key="p.name">
                    <td>{{ i + 1 }}</td>
                    <td>{{ key }}</td>

                    <td>{{ p.name }}</td>
                    <td>{{ p. price | currency }}</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <button v-on:click="handleClick" class="btn btn-primary">
                Press Me
            </button>
        </div>
    </div>
</template>

<script>
    import Vue from "vue";

    export default {
        name: "MyComponent",
        data: function () {
            return {
                products: {
                    "kayak": { name: "Kayak", price: 275 },

                    22: { name: "Lifejacket", price: 48.95 },

                    3: { name: "Soccer Ball", price: 19.50 },

                    "4": { name: "Corner Flags", price: 39.95 }

                }
            }
        },
        filters: {
            currency(value) {
                return new Intl.NumberFormat("en-US",
                    { style: "currency", currency: "USD", }).format(value);
            },
        },
        methods: {
            handleClick() {
                Vue.set(this.products, 5, { name: "Running Shoes", price: 100 });
            }
        }
    }
</script>

Listing 13-13Working with Object Keys in the App.vue File in the src Folder

v-for指令枚举products对象的属性时,将按以下顺序处理:3422kayak。点击按钮,使用5作为键向对象添加一个新的属性,在422键之间会显示一个新的表格行,如图 13-10 所示。

img/465686_1_En_13_Fig10_HTML.jpg

图 13-10

对象属性排序的效果

没有数据源的重复 HTML 元素

指令可以用来复制 HTML 元素特定的次数,而不需要使用数据数组或对象作为数据源。这个特性对于创建与特定数据项无关的内容很有用,比如分页按钮,如清单 13-14 所示。

...
<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>
        <table class="table table-sm table-bordered  text-left">
            <tr><th>Index</th><th>Key</th><th>Name</th><th>Price</th></tr>
            <tbody>
                <tr v-for="(p, key, i) in products" v-bind:key="p.name">
                    <td>{{ i + 1 }}</td>
                    <td>{{ key }}</td>
                    <td>{{ p.name }}</td>
                    <td>{{ p. price | currency }}</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <!-- eslint-disable-next-line vue/require-v-for-key -->

            <button v-for="i in 5" v-on:click="handleClick(i)"

                    class="btn btn-primary m-1">

                {{ i }}

            </button>

        </div>
    </div>
</template>
...

Listing 13-14Repeating Content in the App.vue File in the src Folder

v-for表达式中的源是一个整数值时,该指令将 HTML 元素重复指定的次数,并将当前值赋给别名。在这个清单中,当将v-for指令应用于按钮元素时,我指定了数字 5,并使用别名作为元素的内容。分配给别名的第一个值是 1,产生如图 13-11 所示的结果。

小费

请注意,我禁用了要求标识密钥的 linter 规则。因为值的序列是由指令生成的,所以不需要担心处理数组项顺序变化的策略。

img/465686_1_En_13_Fig11_HTML.jpg

图 13-11

没有数据源的重复元素

将计算属性与 v-for 指令一起使用

到目前为止,所有的例子都使用了data属性,但是v-for指令也将使用计算的属性和方法,这使得使用 JavaScript 来过滤或排序内容被复制的对象成为可能。在接下来的几节中,我将展示管理由v-for指令处理的数据的不同方法。

分页数据

大多数应用需要向用户呈现可用数据的子集,这通常是通过呈现数据页面来完成的。将计算出的属性与无数据源重复内容的特性结合起来,可以很容易地实现分页,如清单 13-15 所示。

小费

在本书的第一部分,您可以在 SportsStore 应用中看到一个更复杂的分页示例。

<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>
        <table class="table table-sm table-bordered  text-left">
            <tr><th>Name</th><th>Price</th></tr>

            <tbody>
                <tr v-for="p in pageItems" v-bind:key="p.name">

                    <td>{{ p.name }}</td>
                    <td>{{ p. price | currency }}</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <!-- eslint-disable-next-line vue/require-v-for-key -->
            <button v-for="i in pageCount" v-on:click="selectPage(i)"

                    class="btn btn-secondary m-1"

                    v-bind:class="{'bg-primary': currentPage == i}">

                {{ i }}

            </button>

        </div>
    </div>
</template>
<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                pageSize: 3,

                currentPage: 1,

                products: [

                    { name: "Kayak", price: 275 },

                    { name: "Lifejacket", price: 48.95 },

                    { name: "Soccer Ball", price: 19.50 },

                    { name: "Corner Flags", price: 39.95 },

                    { name: "Stadium", price: 79500 },

                    { name: "Thinking Cap", price: 16 },

                    { name: "Unsteady Chair", price: 29.95 },

                    { name: "Human Chess Board", price: 75 },

                    { name: "Bling Bling King", price: 1200 }

                ]

            }
        },
        computed: {

            pageCount() {

                return Math.ceil(this.products.length / this.pageSize);

            },

            pageItems() {

                let start = (this.currentPage - 1) * this.pageSize;

                return this.products.slice(start, start + this.pageSize);

            }

        },

        filters: {
            currency(value) {
                return new Intl.NumberFormat("en-US",
                    { style: "currency", currency: "USD", }).format(value);
            },
        },
        methods: {
            selectPage(page) {

                this.currentPage = page;

            }

        }
    }
</script>

Listing 13-15Paging Data in the App.vue File in the src Folder

在这个例子中,我又使用了一个数组,并添加了一些对象,这样就有更多的数据可以处理了。有两个新的data属性:pageSize属性指定每页的项数,而currentPage属性用于跟踪向用户显示的页面。还有两个新的计算属性:pageCount属性返回应用需要的页数,而pageItems属性只返回当前页面需要的数据对象。

为了生成分页按钮,我在v-for表达式中使用了pageItems属性,这允许我为用户可以选择的每个页面重复使用button元素。我想突出显示代表当前页面的按钮,这是通过使用v-bind指令将元素分配给基于v-for别名值的类来实现的。最后,我使用v-on指令——我在第十四章中描述了该指令——在用户点击按钮时调用一个名为selectPage的方法,该方法允许我更改currentPage值,允许用户在页面间导航,如图 13-12 所示。

img/465686_1_En_13_Fig12_HTML.jpg

图 13-12

分页数据

过滤和排序数据

计算出的属性也可用于在数据被v-for指令接收之前对其进行过滤和排序。在清单 13-16 中,我添加了select元素,它们的值用于改变显示给用户的数据。

<template>
    <div class="container-fluid">
        <h2 class="bg-primary text-white text-center p-3">Products</h2>
        <table class="table table-sm table-bordered  text-left">
            <tr><th>Name</th><th>Price</th></tr>
            <tbody>
                <tr v-for="p in pageItems" v-bind:key="p.name">
                    <td>{{ p.name }}</td>
                    <td>{{ p. price | currency }}</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <button class="btn btn-secondary m-1" v-on:click="toggleSort"

                    v-bind:class="{'bg-primary': sort}">

                Toggle Sort

            </button>

            <button class="btn btn-secondary m-1" v-on:click="toggleFilter"

                    v-bind:class="{'bg-primary': filter}">

                Toggle Filter

            </button>

            <!-- eslint-disable-next-line vue/require-v-for-key -->
            <button v-for="i in pageCount" v-on:click="selectPage(i)"
                    class="btn btn-secondary m-1"
                    v-bind:class="{'bg-primary': currentPage == i}">
                {{ i }}
            </button>
        </div>
    </div>
</template>
<script>
    export default {
        name: "MyComponent",
        data: function () {
            return {
                pageSize: 3,
                currentPage: 1,
                filter: false,

                sort: false,

                products: [
                    { name: "Kayak", price: 275 },
                    { name: "Lifejacket", price: 48.95 },
                    { name: "Soccer Ball", price: 19.50 },
                    { name: "Corner Flags", price: 39.95 },
                    { name: "Stadium", price: 79500 },
                    { name: "Thinking Cap", price: 16 },
                    { name: "Unsteady Chair", price: 29.95 },
                    { name: "Human Chess Board", price: 75 },
                    { name: "Bling Bling King", price: 1200 }
                ]
            }
        },
        computed: {
            pageCount() {
                return Math.ceil(this.dataItems.length / this.pageSize);

            },
            pageItems() {
                let start = (this.currentPage - 1) * this.pageSize;
                return this.dataItems.slice(start, start + this.pageSize);

            },
            dataItems() {

                let data = this.filter

                    ? this.products.filter(p => p.price > 100) : this.products;

                return this.sort

                    ? data.concat().sort((p1, p2) => p2.price - p1.price) : data;

            }

        },
        filters: {
            currency(value) {
                return new Intl.NumberFormat("en-US",
                    { style: "currency", currency: "USD", }).format(value);
            },
        },
        methods: {
            selectPage(page) {
                this.currentPage = page;
            },
            toggleFilter() {

                this.filter = !this.filter

                this.currentPage = 1;

            },

            toggleSort() {

                this.sort = !this.sort;

                this.currentPage = 1;

            }

        }
    }
</script>

Listing 13-16Filtering and Sorting Data in the App.vue File in the src Folder

这个例子使用了一个名为dataItems的新计算属性,根据名为sortfilterdata属性来准备要显示的数据。通过点击新的button元素来切换data属性值,分页按钮的数量根据用户的选择进行更新,如图 13-13 所示。

img/465686_1_En_13_Fig13_HTML.jpg

图 13-13

分页和排序数据

摘要

在这一章中,我解释了v-for指令的用法,并演示了它如何枚举数组、对象的属性和数字序列。我向您展示了如何使用别名,如何使用键使更新更有效,以及如何获取正在处理的项目的索引。我还演示了如何将v-for指令用于计算属性,以便对数据进行分页、排序和过滤。在下一章,我将向您展示如何使用 Vue.js 指令处理事件。