@[TOC](自学前端开发 - VUE 框架 (一) 基础、模板语法、响应式基础、Class 与 Style 的绑定、渲染)
前端有一些比较流行的框架,例如 react.js、angular.js、jQuery 等,Vue.js 也是其中之一。Vue是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助程序员高效地开发用户界面。
vue 和 jQuery 除了完成特效方面稍微重叠,其他方面的定位是不同的:jQuery 主要定位是获取元素,Vue 的定位是方便操作及控制数据。
在学习 vue 之前,有一些准备工作:
vue 支持 npm 引入和文件(CDN)引入。
使用 npm 工具执行命令
npm init vue@latest
这一指令将会安装并执行 create-vue
,它是 Vue 官方的项目脚手架工具。之后将会看到一些诸如 TypeScript 和测试支持之类的可选功能提示。如果不确定是否要开启某个功能,你可以直接按下回车键选择 No。在项目被创建后,通过以下步骤安装依赖并启动开发服务器:
cd <your-project-name>
npm install
npm run dev
现在应该已经运行起来了你的第一个 Vue 项目!请注意,生成的项目中的示例组件使用的是组合式 API 和
可以使用 CDN 引入
<script src="https://unpkg.com/vue@3/dist/vue.global.js">script>
这里使用了 unpkg,也可以使用任何提供 npm 包服务的 CDN,例如 jsdelivr 或 cdnjs。当然,也可以下载此文件并自行提供服务。
通过 CDN 使用 Vue 时,不涉及“构建步骤”。这使得设置更加简单,并且可以用于增强静态的 HTML 或与后端框架集成。但是将无法使用单文件组件 (SFC) 语法。
使用文件引入,也有几种使用版本:
Vue
对象上。<script src="https://unpkg.com/vue@3/dist/vue.global.js">script>
<div id="app">{{ message }}div>
<script>
const { createApp } = Vue
createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app')
script>
,且导入的 CDN 指向的是 ES 模块构建版本<div id="app">{{ message }}div>
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app')
script>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
script>
<div id="app">{{ message }}div>
<script type="module">
import { createApp } from 'vue'
createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app')
script>
vue 可以在 html 中直接使用,或创建为单文件组件使用。在 html 中使用类似于普通的 js 或 jQuery 之类的使用方式。在大多数启用了构建工具的 Vue 项目中,可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为单文件组件。 顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。官方推荐使用这种方法来编写模块化的组件。
在初学阶段,只学习 html 使用方式。
Vue 的组件可以按两种不同的风格书写:选项式API 和组合式API。
使用选项式 API,可以用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。
通过组合式 API,可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,
中的导入和顶层变量/函数都能够在模板中直接使用。
两种 API 风格都能够覆盖大部分的应用场景。它们只是同一个底层系统所提供的两套不同的接口。实际上,选项式 API 是在组合式 API 的基础上实现的!关于 Vue 的基础概念和知识在它们之间都是通用的。
在简单学习阶段,将使用选项式API风格。
官方推荐安装浏览器开发者插件,使我们可以浏览一个 Vue 应用的组件树,查看各个组件的状态,追踪状态管理的事件,还可以进行组件性能分析。
下载并让浏览器加载插件:vuejs-devtools。
和 MVC、MTV 架构类似,vue 采用了 MVVM(Model-View-ViewModel) 架构思想,它是一种基于前端开发的架构模式。
每个 Vue 应用都是通过 createApp
函数创建一个新的 应用实例:
import { createApp } from 'vue'
const app = createApp({
/* 根组件选项 */
})
我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个**“根组件”,其他组件将作为其子组件**。根组件可以使用根组件模板或自定义的单文件组件。使用根组件模板时,Vue 将自动使用容器的 innerHTML
作为模板。
import { createApp } from 'vue'
const app = createApp({
/* 根组件选项 */
data(){
return {
count: 0
}
}
})
data 组件可以使用数据填充模板。
应用实例必须在调用了 .mount() 方法后才会渲染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串:
<div id="app">div>
app.mount('#app')
应用根组件的内容将会被渲染在容器元素里面。容器元素自己将不会被视为应用的一部分。.mount()
方法应该始终在整个应用配置和资源注册完成后被调用。同时请注意,不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。
应用实例会暴露一个 .config
对象允许我们配置一些应用级的选项,例如定义一个应用级的错误处理器,它将捕获所有由子组件上抛而未被处理的错误:
app.config.errorHandler = (err) => {
/* 处理错误 */
}
应用实例并不只限于一个。createApp
API 允许你在同一个页面中创建多个共存的 Vue 应用,而且每个应用都拥有自己的用于配置和全局资源的作用域。
const app1 = createApp({
/* ... */
})
app1.mount('#container-1')
const app2 = createApp({
/* ... */
})
app2.mount('#container-2')
<div id="app">
<p>{{ message }}p>
div>
<script type="module">
import { createApp } from 'vue'
let app = createApp({
data(){
return {
message: 'Hello Vue!'
}
}
});
app.mount('#app');
script>
或者简写为
<div id="app">
<p>{{ message }}p>
div>
<script>
let app = Vue.createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app')
script>
data 组件跟随的是一个处理函数,通常在此使用 ajax 获取具体数据,并 return。vue 会将返回给 data 的数据渲染至页面模板。
Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。
在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。
最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):
<span>Message: {{ msg }}span>
双大括号标签会被替换为相应组件实例中 msg
属性的值。同时每次 msg 属性更改时它也会同步更新。
双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html
指令:
<p>Using text interpolation: {{ rawHtml }}p>
<p>Using v-html directive: <span v-html="rawHtml">span>p>
这里的 v-html
指令的意思是:在当前组件实例上,将此元素的 innerHTML 与 rawHtml 属性保持同步。
指令其实就是标签的一个属性,它由 v-
作为前缀,表明是一个由 vue 提供的特殊属性。它们将为渲染的 DOM 应用特殊的响应式行为。指令属性的期望值大部分为一个 JS 表达式,某些指令会需要一个"参数",在指令后通过冒号 “:
” 隔开做标识。
同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内,即动态参数:
<a v-bind:[attributeName]="url"> ... a>
<a :[attributeName]="url"> ... a>
这里的 attributeName
会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName
,其值为 "href"
,那么这个绑定就等价于 v-bind:href
。相似地,你还可以将一个函数绑定到动态的事件名称上:
<a v-on:[eventName]="doSomething"> ... a>
<a @[eventName]="doSomething">
动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML 属性名称中都是不合法的。如果需要传入一个复杂的动态参数,推荐使用计算属性组件替换复杂的表达式。
另外,当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写。
指令还可以使用修饰符 “.
” ,修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .number
表示此参数类型为数值,.toUpperCase()
表示调用JS方法(大写化)处理等。
完整的指令语法如下:
双大括号不能在标签属性中使用,如果想要响应式的绑定一个属性,应该使用 v-bind
指令:
<div v-bind:id="dynamicId">div>
v-bind
指令指示 Vue 将元素的 id
属性与组件的 dynamicId
属性保持一致。如果绑定的值是 null
或者 undefined
,那么该属性将会从渲染的元素上移除。
也可以简写为
<div :id="dynamicId">div>
对于 v-bind
指令绑定的属性,如果是布尔型属性,例如 disabled、hidden 等,可以这样使用:
<button :disabled="isButtonDisabled">Buttonbutton>
当 isButtonDisabled
为真值或一个空字符串 (即 ) 时,元素会包含这个 disabled 属性。而当其为其他假值时属性将被忽略。
如果 v-bind
指令不带参数,则会绑定其包含的多个属性到元素对象上:
data() {
return {
objectOfAttrs: {
id: 'container',
class: 'wrapper'
}
}
}
<div v-bind="objectOfAttrs">div>
Vue 在所有的数据绑定中都支持完整的 JavaScript 表达式:
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`">div>
在 vue 模板内,JS 表达式可以用于所有的文本插值和 Vue 指令属性的值中。需注意的是,每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return 后面。如果需要使用比较复杂的表达式,可以使用一个在组件中暴露的方法。
Vue 在创建根组件时,会根据 data 来声明组件的响应式状态,将此函数返回的对象用响应式系统进行包装。此对象的所有顶层属性都会被代理到组件实例上。这些实例上的属性仅在实例首次创建时被添加,因此需要确保它们都出现在 data 函数返回的对象上。若所需的值还未准备好,在必要时也可以使用 null、undefined 或者其他一些值占位。虽然也可以不在 data 上定义,直接向组件实例添加新属性,但这个属性将无法触发响应式更新。
另外,Vue 在组件实例上暴露的内置 API 使用 $
作为前缀。它同时也为内部属性保留 _
前缀。因此,应该避免在顶层 data 上使用任何以这些字符作前缀的属性。
之前在创建并挂载应用实例时,使用了两种方法,其效果相同:第一种方法是先创建应用实例并赋值给变量,再将其挂载;第二种是创建时就直接挂载,然后再赋值给变量。式代理。
let app = Vue.createApp({
...
})
app.mount('#app')
let app = Vue.createApp({
...
}).mount('#app')
两者看起来差不多,呈现效果也相同,但是对于变量 app 是完全不一样的。两种写法最大的区别是,第一种方法变量指向的是对象实例,而第二种方法变量指向的是实例的响应式代理。
对于同一实例中的数据,也有原始值和响应式代理之分
let app = Vue.createApp({
data() { return {
someObject: {}
}},
mounted() {
const newObject = {};
this.someObject = newObject;
console.log(newObject === this.someObject) // false
}
}).mount('#app')
从这个例子中可以看出,当挂载实例后再访问 this.someObject,其值为响应式代理,而 newObject 是原始值(不会变为响应式)。可以使用 this 来直接访问响应式代理对象。
在创建实例对象时,除了声明响应式状态外,还增加了一些属性和方法,例如可以使用 app.$data
来获取数据的代理对象,或使用 app.$data.someObject
来访问data内部的数据对象(同 app.someObject
),使用 app.$el
能够获取实例对象的使用范围,使用 app.$el.parentElement
能够获取 实例对象所在的 DOM 元素等。
当更改响应式状态(data 状态)后,DOM 会自动更新。然而,DOM 的更新并不是同步的。相反,Vue 将缓冲它们直到更新周期的 “下个时机” 以确保无论你进行了多少次状态更改,每个组件都只需要更新一次。若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API:
import { nextTick } from 'vue'
export default {
methods: {
increment() {
this.count++
nextTick(() => {
// 访问更新后的 DOM
})
}
}
}
在某些情况下,我们可能需要动态地创建一个方法函数,如果此方法是有状态的(会在运行时维护着一个内部状态),那么当多个组件实例都共享这同一个状态,则会互相影响。要保持每个组件实例的防抖函数都彼此独立,我们可以改为在 created
生命周期钩子中创建这个函数。
数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 class 和 style 都是属性,我们可以和其他属性一样使用 v-bind
将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 class 和 style 的 v-bind
用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
可以给 :class
(v-bind:class
的缩写) 传递一个对象来动态切换 class,例如 active 是否存在取决于数据属性 isActive 的真假值:
<div :class="{ active: isActive }">div>
也可以在对象中写多个字段来操作多个 class。此外,:class
指令也可以和一般的 class 属性共存。举例来说,下面这样的状态和模板:
data() {
return {
isActive: true,
hasError: false
}
}
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
>div>
渲染的结果会是:
<div class="static active">div>
当 isActive
或者 hasError
改变时,class 列表会随之更新。举例来说,如果 hasError
变为 true,class 列表也会变成 "static active text-danger"
。
绑定的对象并不一定需要写成内联字面量的形式,也可以直接绑定一个对象:
data() {
return {
classObject: {
active: true,
'text-danger': false
}
}
}
<div :class="classObject">div>
或绑定一个返回对象的计算属性,渲染结果是一致的:
<div :class="classObject">div>
data() {
return {
isActive: true,
error: null
}
},
computed: {
classObject() {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
我们可以给 :class
绑定一个数组来渲染多个 CSS class:
data() {
return {
activeClass: 'active',
errorClass: 'text-danger'
}
}
<div :class="[activeClass, errorClass]">div>
渲染的结果是:
<div class="active text-danger">div>
如果想在数组中有条件地渲染某个 class,你可以使用三元表达式:
<div :class="[isActive ? activeClass : '', errorClass]">div>
errorClass 会一直存在,但 activeClass 只会在 isActive 为真时才存在。然而,这可能在有多个依赖条件的 class 时会有些冗长。因此也可以在数组中嵌套对象:
<div :class="[{ active: isActive }, errorClass]">div>
:style
支持绑定 JavaScript 对象值,对应的是 HTML 元素的 style 属性:
data() {
return {
activeColor: 'red',
fontSize: 30
}
}
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">div>
<div :style="{ 'color': activeColer, 'font-size': fontSize + 'px' }">div>
直接绑定一个样式对象可以使模板更加简洁:
data() {
return {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
}
<div :style="styleObject">div>
同样的,如果样式对象需要更复杂的逻辑,也可以使用返回样式对象的计算属性。
我们还可以给 :style
绑定一个包含多个样式对象的数组。这些对象会被合并后渲染到同一元素上:
<div :style="[baseStyles, overridingStyles]">div>
当在 :style
中使用了需要浏览器特殊前缀的 CSS 属性时,Vue 会自动为他们加上相应的前缀。Vue 是在运行时检查该属性是否支持在当前浏览器中使用。如果浏览器不支持某个属性,那么将尝试加上各个浏览器特殊前缀,以找到哪一个是被支持的。
也可以对一个样式属性提供多个 (不同前缀的) 值,举例来说:
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">div>
数组仅会渲染浏览器支持的最后一个值。在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display: flex
。
vue 将数据模型渲染到视图中,一般渲染分为条件渲染和列表渲染
条件渲染是对条件进行判断决定是否进行渲染,一般使用 v-if
、v-show
和配套的指令来完成
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。
<h1 v-if="awesome">Vue is awesome!h1>
也可以使用 v-else 为 v-if 添加一个“else 区块”。注意:一个 v-else
元素必须跟在一个 v-if
或者 v-else-if
元素后面,否则它将不会被识别。
<button @click="awesome = !awesome">Togglebutton>
<h1 v-if="awesome">Vue is awesome!h1>
<h1 v-else>Oh no 😢h1>
v-else-if
是相应于 v-if 的“else if 区块”。它可以连续多次重复使用:
<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
元素后面。
上的 v-if
因为 v-if
是一个指令,他必须依附于某个元素。但如果我们想要切换不止一个元素呢?在这种情况下我们可以在一个 元素上使用
v-if
,这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 元素。
v-else
和 v-else-if
也可以在 上使用。
<template v-if="ok">
<h1>Titleh1>
<p>Paragraph 1p>
<p>Paragraph 2p>
template>
另一个可以用来按条件显示一个元素的指令是 v-show
,其用法基本一样。
<h1 v-show="ok">Hello!h1>
不同之处在于 v-show
会在 DOM 渲染中保留该元素;v-show
使用的是切换 css 的 display:none
来实现显示和隐藏。v-show
不支持在 元素上使用,也不能和
v-else
搭配使用。
v-if
是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if
也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下,v-show
简单许多,元素无论初始条件如何,始终会被渲染,只有 display 属性会被切换。
总的来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show
较好;如果在运行时绑定条件很少改变,则 v-if
会更合适。
可以使用 v-for
指令基于一个数组来渲染一个列表。v-for
指令的值需要使用 item in items
形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名:
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
<li v-for="item in items">
{{ item.message }}
li>
在 v-for
块中可以完整地访问父作用域内的属性和变量。v-for
也支持使用可选的第二个参数表示当前项的位置索引。
data() {
return {
parentMessage: 'Parent',
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
li>
v-for
也可以在定义变量别名时使用解构
<li v-for="{ message } in items">
{{ message }}
li>
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
li>
对于多层嵌套的 v-for
,作用域的工作方式和函数的作用域很类似。每个 v-for
作用域都可以访问到父级作用域:
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
span>
li>
也可以使用 of
作为分隔符来替代 in
,这更接近 JavaScript 的迭代器语法:
<div v-for="item of items">div>
可以使用 v-for
来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.keys()
的返回值来决定。
data() {
return {
myObject: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
}
<ul>
<li v-for="value in myObject">
{{ value }}
li>
ul>
可以通过提供第二个参数表示属性名 (例如 key),第三个参数表示位置索引:
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
li>
v-for
可以直接接受一个整数值。在这种用例中,会将该模板基于 1…n 的取值范围重复多次。注意此处 n 的初值是从 1 开始而非 0。
<span v-for="n in 10">{{ n }}span>
上的 v-for
与模板上的 v-if
类似,可以在 标签上使用
v-for
来渲染一个包含多个元素的块。例如:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}li>
<li class="divider" role="presentation">li>
template>
ul>
v-for
与 v-if
同时使用 v-if
和 v-for
是不推荐的,因为这样二者的优先级不明显。当它们同时存在于一个节点上时,v-if
比 v-for
的优先级更高。这意味着 v-if
的条件将无法访问到 v-for
作用域内定义的变量别名:
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
li>
在外新包装一层 再在其上使用
v-for
可以解决这个问题 (这也更加明显易读):
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
li>
template>
Vue 默认按照就地更新的策略来更新通过 v-for
渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
默认模式是高效的,但只适用于列表渲染输出的结果不依赖子组件状态或者临时 DOM 状态 (例如表单输入值) 的情况。
为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,需要为每个元素对应的块提供一个唯一的 key 属性(注意 key 是元素的一个属性,而不是对象的属性名):
<div v-for="item in items" :key="item.id">
div>
当使用 时,key 应该被放置在这个
容器上:
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}li>
template>
Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
相对地,也有一些不可变 (immutable) 方法,例如 filter(),concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的:
this.items = this.items.filter((item) => item.message.match(/Foo/))
这种方法并不会丢弃现有的DOM并重新渲染整个列表,vue 会最大化对 DOM 元素的重用。
有时希望显示数组经过过滤或排序后的内容,而不实际变更或重置原始数据。在这种情况下,可以创建返回已过滤或已排序数组的计算属性。例如
data() {
return {
numbers: [1, 2, 3, 4, 5]
}
},
computed: {
evenNumbers() {
return this.numbers.filter(n => n % 2 === 0)
}
}
<li v-for="n in evenNumbers">{{ n }}li>
在计算属性不可行的情况下 (例如在多层嵌套的 v-for 循环中),可以使用以下方法:
data() {
return {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
}
},
methods: {
even(numbers) {
return numbers.filter(number => number % 2 === 0)
}
}
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}li>
ul>
在计算属性中使用 reverse()
和 sort()
的时候务必小心!这两个方法将变更原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:
return [...numbers].reverse()