渐进式JavaScript框架:Vue

渐进式JavaScript框架:Vue

前言

Vue (发音为 /vjuː/,类似 view ) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTMLCSSJavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

易学易用 性能出色 灵活多变
基于标准 HTMLCSSJavaScript 构建,提供容易上手的 API 和一流的文档。 经过编译器优化、完全响应式的渲染系统,几乎不需要手动优化。 丰富的、可渐进式集成的生态系统,可以根据应用规模在库和框架间切换自如。

安装

对于制作原型或学习,你可以这样使用最新版本:

js 复制代码
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>

对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏:

js 复制代码
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16"></script>

如果你使用原生 ES Modules ,这里也有一个兼容 ES Module 的构建文件:

js 复制代码
<script type="module">
  import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.esm.browser.js'
</script>

Vue2 已经终止支持且不再维护。

  • NPM

在用 Vue 构建大型应用时推荐使用 NPM 安装。NPM 能很好地和诸如 webpackBrowserify 模块打包器配合使用。同时 Vue 也提供配套工具来开发单文件组件。

js 复制代码
# 最新稳定版
$ npm install vue@^2
  • 命令行工具 (CLI)

Vue 提供了一个官方的 CLI ,为单页面应用 (SPA ) 快速搭建繁杂的脚手架。它为现代前端工作流提供了开箱即用的构建设置。只需要几分钟的时间就可以运行起来并带有热重载、保存时 lint 校验,以及生产环境可用的构建版本。

CLI 工具假定用户对 Node.js 和相关构建工具有一定程度的了解。如果你是新手,我们强烈建议先在不用构建工具的情况下通读指南,在熟悉 Vue 本身之后再使用 CLI

  • 不同构建版本的解释

NPM 包的 dist/ 目录你将会找到很多不同的 Vue.js 构建版本。这里列出了它们之间的差别:

版本 UMD CommonJS ES Module (基于构建工具使用) ES Module (直接用于浏览器)
完整版 vue.js vue.common.js vue.esm.js vue.esm.browser.js
只包含运行时版 vue.runtime.js vue.runtime.common.js vue.runtime.esm.js -
完整版 (生产环境) vue.min.js - - vue.esm.browser.min.js
只包含运行时版 (生产环境) vue.runtime.min.js - - -

(1)完整版:同时包含编译器和运行时的版本。

(2)编译器 :用来将模板字符串编译成为 JavaScript 渲染函数的代码。

(3)运行时 :用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。

(4)UMDUMD 版本可以通过 <script> 标签直接用在浏览器中。默认文件就是运行时 + 编译器UMD 版本 (vue.js)。

(4)CommonJSCommonJS 版本用来配合老的打包工具比如 Browserifywebpack 1 。这些打包工具的默认文件 (pkg.main) 是只包含运行时的 CommonJS 版本 (vue.runtime.common.js)。

(5)ES Module :从 2.6 开始 Vue 会提供两个 ES Modules (ESM) 构建文件:

  1. 为打包工具提供的 ESM :为诸如 webpack 2Rollup 提供的现代打包工具。为这些打包工具提供的默认文件 (pkg.module) 是只有运行时的 ES Module 构建 (vue.runtime.esm.js)。
  2. 为浏览器提供的 ESM (2.6+) :用于在现代浏览器中通过 <script type="module"> 直接导入。
  • 运行时 + 编译器 VS 只包含运行时

如果你需要在客户端编译模板 (比如传入一个字符串给 template 选项,或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板),就将需要加上编译器,即完整版:

js 复制代码
// 需要编译器
new Vue({
  template: '<div>{{ hi }}</div>'
})

// 不需要编译器
new Vue({
  render (h) {
    return h('div', this.hi)
  }
})

当使用 vue-loadervueify 的时候,*.vue 文件内部的模板会在构建时预编译成 JavaScript。你在最终打好的包里实际上是不需要编译器的,所以只用运行时版本即可。

因为运行时版本相比完整版体积要小大约 30%,所以应该尽可能使用这个版本。

  • 开发环境 VS 生产环境模式

对于 UMD 版本来说,开发环境/生产环境模式是硬编码好的:开发环境下用未压缩的代码(会提示具体错误 ),生产环境下使用压缩后的代码(不会提示具体错误),错误代码如下:

javascript 复制代码
  <script>
    var data = { "message": "hello, world" }
    Vue({
      el: "#msg",
      data: data
    })
  </script>

如图所示:

CommonJSES Module 版本是用于打包工具的,因此我们不提供压缩后的版本。你需要自行将最终的包进行压缩。

CommonJSES Module 版本同时保留原始的 process.env.NODE_ENV 检测,以决定它们应该运行在什么模式下。你应该使用适当的打包工具配置来替换这些环境变量以便控制 Vue 所运行的模式。把 process.env.NODE_ENV 替换为字符串字面量同时可以让 UglifyJS 之类的压缩工具完全丢掉仅供开发环境的代码块,以减少最终的文件尺寸。

  • MVVM

MVVMModel-View-ViewModel)是一种软件架构模式,常用于开发用户界面,尤其是与数据绑定密切相关的应用程序。

  1. Model(模型):表示应用程序的数据和业务逻辑,通常从数据库或网络获取数据,或处理应用程序的核心逻辑。它与用户界面无关,通常只包含数据和方法(可以理解为数据)。
  2. View (视图):展示数据给用户,并通过与ViewModel进行绑定来更新界面。视图主要负责用户交互和界面元素的显示。
  3. ViewModel (视图模型):负责将数据从Model 传递给View ,并处理用户输入的逻辑。它通过数据绑定与ViewModel 进行通信,起到连接模型和视图的作用ViewModel 不直接处理用户界面,而是将数据传递给视图并响应用户操作(可以理解new Vue()的实例对象)。

Vue实例中介绍:

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。

基础

实例

每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的:

javascript 复制代码
	// 方法一
	<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
	// 方法二
    <script>
        var vm = new Vue({
			// todo
        });
    </script>

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。

当创建一个 Vue 实例时,你可以传入一个选项对象。一个 Vue 应用由一个通过 new Vue 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。举个例子,一个 todo 应用的组件树可以是这样的:

复制代码
根实例
└─ TodoList
   ├─ TodoItem
   │  ├─ TodoButtonDelete
   │  └─ TodoButtonEdit
   └─ TodoListFooter
      ├─ TodosButtonClear
      └─ TodoListStatistics

不过现在,你只需要明白所有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)。

  • 数据与方法

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

html 复制代码
<body>
  <div id="msg">
    {{message}}
  </div>

  <!-- 引入依赖(Vue 2 + vue-router 3.x,确保版本兼容) -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var data = { "message": "hello, world" }
    var vm = new Vue({
      el: "#msg",
      data: data
    })
  </script>
</body>

HTML 规范id属性为元素指定唯一标识符,Vue 并不禁止使用 class 选择器作为el,但不推荐。如果必须使用,需确保 class 对应的元素在页面中唯一。

当这些数据改变时,视图会进行重渲染(无论修改的是源数据还是Vue对象中的数据都能产生"响应")。

js 复制代码
    // 修改json中源数据的值
    data.message = "hello, vue";
    console.log(vm.message)// 设置原始数据也会影响到vue数据 => hello, vue
    // 修改vm对象中数据
    vm.message = "hello, vue"
    console.log(data.message)// 设置属性也会影响到原始数据 => hello, vue
    // 获得这个实例上的 property
    // 返回源数据中对应的字段
    console.log(vm.a === data.a)// => true

在浏览器的控制台上操作也是如此,如图所示:

只有当实例被创建时就已经存在于 data 中的属性才是响应式的,不存在的属性将不会做出任何改变。

js 复制代码
vm.message2 = "is not exsis property ";

如果你知道你会在晚些时候需要一个属性,但是一开始它为空或不存在,那么你仅需要设置一些初始值。

js 复制代码
  var vm = new Vue({
    el: "#msg",
    data: {
      "message": "hello, world",
      "newTodoText": '',
      "visitCount": 0,
      "hideCompletedTodos": false,
      "todos": [],
      "error": null
    }
  })

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

js 复制代码
  var data = {"message": "hello, world"}
  // 阻止修改现有的属性
  Object.freeze(data)
  var vm = new Vue({
    el: "#msg",
    data: data
  })
  // 修改json中源数据的值
  vm.message = "hello, vue"

错误信息如图所示:


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

js 复制代码
  var data = {"message": "hello, world"}
  var vm = new Vue({
    el: "#msg",
    data: data
  })
  vm.$data.message = "hello, vue";
  console.log(vm.$data === data)// => true
  console.log(vm.$el === document.getElementById('msg')) // => true

另外Vue 还提供了实例方法,可以在API中查看,比如:观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。回调函数得到的参数为新值和旧值。示例代码如下:

js 复制代码
  var data = {"message": "hello, world"}
  var vm = new Vue({
    el: "#msg",
    data: data
  })
  // 命令式-实例方法
  vm.$watch("message", function(newValue, oldValue){
  	// 这个回调将在 `vm.message` 改变后调用
    console.log(newValue, oldValue)
  }) 
  vm.$data.message = "hello, vue";

甚至还可以在挂载的时候进行容器和实例的关联,示例代码如下:

javascript 复制代码
<body>
  <div id="app">这是一个示例,{{name}}</div>
  <script>
    var vm = new Vue({
      data: {
        'name': 'hello, world'
      }
    })
    // 比如延迟加载
    setTimeout(() => {
      // 通过挂载时关联容器
      vm.$mount("#app")
    }, 1000)
  </script>
</body>
  • 自定义选项

自定义选项指的是开发者在组件定义时自行添加的、Vue 内置选项(如 datamethodswatch 等)之外的额外选项。这些选项不是 Vue 框架预设的,而是为了满足特定业务需求由开发者自定义的。

js 复制代码
Vue.component('user-list', {
  // 自定义选项:标记组件所需权限
  auth: {
    require: true,
    roles: ['admin', 'editor']
  },
  created() {
    // 访问自定义选项
    console.log('当前组件需要权限:', this.$options.auth.roles);
  }
});

自定义选项不会像 data 那样自动挂载到组件实例(this)上,需要通过 this.$options.xxx 访问。

  • 实例生命周期钩子

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

我们可以在Vue 提供的API中查看提供了哪些生命周期函数,示例代码如下:

javascript 复制代码
<body>
    <div id="app">{{message}}</div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var data = { "message": "hello, world" }
        var app = new Vue({
            el: "#app",
            data: data,
            created: function () {
                console.log("message is:" + this.message)
            },
            updated: function () {
                console.log("update message:"+ this.message)
            }
        })
        app.message = "hello, Vue"
    </script>
</body>

执行结果如图:


created函数在实例创建完成后被立即同步调用;updated函数在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。除此之外,如 mountedactivateddestroyed等。生命周期钩子的 this 上下文指向调用它的 Vue 实例。

不要在选项属性或回调上使用箭头函数,比如 created: () => console.log(this.a)vm.$watch('a', newValue => this.myMethod())。因为箭头函数并没有 thisthis 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function 之类的错误。

下图展示了实例的生命周期。

模板语法

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

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

  • 插值

数据绑定最常见的形式就是使用"Mustache"语法 (双大括号) 的文本插值:

html 复制代码
<body>
    <!-- 容器 -->
    <div id="app">{{message}}</div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
    	// 实例
        var app = new Vue({
            el: "#app",
            data: { 
                "message": "hello, world" 

            }
        })
    </script>
</body>

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

容器和实例是一一对应的,多个相同容器不能绑定一个实例,多个实例也不能绑定同一个容器,虽然会默认显示第一个内容,但是不可以这样做。

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

html 复制代码
<body>
    <div id="app" v-once>{{message}}</div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                "message": "hello, world" 

            }
        })
    </script>
</body>

执行结果如图所示:

  • 原始 HTML

双大括号会将数据解释为普通文本,而非 HTML 代码。

html 复制代码
<body>
    <div id="app">{{message}}</div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                "message": "<h1>this is a h1 title</h1>" 
            }
        })
    </script>
</body>

执行结果如图:

为了输出真正的 HTML ,你需要使用 v-html 指令:

html 复制代码
<body>
    <div id="app" v-html="message"></div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                "message": "<h1>this is a h1 title</h1>" 
            }
        })
    </script>
</body>

执行结果如图:

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

  • 使用 JavaScript 表达式

迄今为止,在我们的模板中,我们一直都只绑定简单的属性键值。但实际上,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。

html 复制代码
<body>
    <div id="app">
        <p>{{number * 5}}</p>
        <p>{{isOk ? "ok" : "flase"}}</p>
        <p>{{1 === 1 ? "YES" : "NO"}}</p>
        <p>{{message.split('').reverse().join('')}}</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                "number": 10,
                "isOk": false,
                "message": "hello, vue"
            }
        })
    </script>
</body>

执行结果如图:

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。

计算属性和侦听器

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

html 复制代码
<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

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

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

html 复制代码
<body>
  <div id="app">{{ reversedMessage }}</div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var vm = new Vue({
      el: "#app",
      data: {
        "message": "hello, world"
      },
      computed: {
        reversedMessage: function () {
          return this.message.split('').reverse().join('');
        }
      }
    })
  </script>
</body>

这里我们声明了一个计算属性 reversedMessage。我们提供的函数将用作属性vm.reversedMessagegetter 函数,如图所示:

但是你无法修改vm.reversedMessage 的值,它没有setter函数,如图所示:

vm.reversedMessage 的值始终取决于 vm.message 的值。当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。

  • 计算属性缓存 vs 方法

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。

html 复制代码
<body>
  <div id="app">{{ reversedMessage() }}</div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var vm = new Vue({
      el: "#app",
      data: {
        "message": "hello, world"
      },
      methods: {
        reversedMessage: function () {
          return this.message.split('').reverse().join('');
        }
      }
    })
  </script>
</body>

不同的是计算属性是基于它们的响应式依赖进行缓存 的。只在相关响应式依赖发生改变时它们才会重新求值 。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖,因此不会触发计算属性的缓存更新机制,响应式系统通过 getter/setter 监听属性访问 ,浏览器刷新会重新初始化 Vue 实例,首次访问时重新执行Date.now(),导致时间戳更新。但这是页面重置的结果,而非计算属性主动更新:

js 复制代码
computed: {
  now: function () {
    return Date.now()
  }
}

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数

  • 计算属性 vs 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性 (前面有简单介绍命令式-监听属性)。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch。然而,通常更好的做法是使用计算属性 而不是命令式watch 回调(可以称之为对象字面量方式实例方式):

html 复制代码
<body>
  <div id="app">{{ fullName }}</div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var vm = new Vue({
      el: "#app",
      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 复制代码
<body>
  <div id="app">{{ fullName }}</div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var vm = new Vue({
      el: "#app",
      data: {
        firstName: 'Foo',
        lastName: 'Bar'
      },
      computed: {
        fullName: function () {
          return this.firstName + ' ' + this.lastName
        }
      }
    })
  </script>
</body>
  • 计算属性的 setter

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

java 复制代码
<body>
  <div id="app">{{ fullName }}</div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var vm = new Vue({
      el: "#app",
      data: {
        firstName: 'Foo',
        lastName: 'Bar'
      },
      computed: {
        fullName: {
          get: function () {
            console.log("调用get函数")
            return this.firstName + ' ' + this.lastName;
          },
          set: function (newValue) {
            console.log("调用set函数", newValue)
            var names = newValue.split(' ')
            this.firstName = names[0]
            this.lastName = names[names.length - 1]
          }
        }
      }
    })
  </script>
</body>

再运行 vm.fullName = 'zhang san' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。执行结果如图所示:

表单输入绑定

Vue.js 中,表单输入绑定是实现双向数据绑定的重要功能,你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定(自动同步到 Vue 实例的数据中)。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 会忽略所有表单元素的 valuecheckedselected 属性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

  • 文本
html 复制代码
<body>
  <div id="app">
    <input type="text" v-model="message" value="default value">
    <span>输入内容:{{message}}</span>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        "message":null
      },
      methods: {
      }
    })
  </script>
</body>

执行结果如图:


v-model 实际上是一个语法糖,展开后示例代码如下:

html 复制代码
  <div id="app">
    <input type="text" v-model="message" value="default value">
    <span>输入内容:{{message}}</span>

    <!-- 展开后的原生写法 -->
    <input v-bind:value="username" @input="username = $event.target.value">
    <span>输入内容:{{username}}</span>
  </div>

v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件(再事件绑定指令-表单事件详细介绍):

  • texttextarea 元素使用 value 属性和 input 事件;
  • checkboxradio 使用 checked 属性和 change 事件;
  • select 字段将 value 作为属性并将 change 作为事件。

对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件。

  • 多行文本
html 复制代码
<body>
  <div id="app">
    <textarea v-model="message" placeholder="add multiple lines"></textarea>
    <span>输入内容:{{message}}</span>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        "message": null
      },
      methods: {}
    })
  </script>
</body>

在文本区域插值 (<textarea>{``{text}}</textarea>) 并不会生效,应用 v-model 来代替。

  • 复选框

多个复选框,绑定到同一个数组:

html 复制代码
<body>
  <div id="app">
    <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
    <label for="jack">Jack</label>
    <input type="checkbox" id="john" value="John" v-model="checkedNames">
    <label for="john">John</label>
    <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
    <label for="mike">Mike</label>
    <span>输入内容:{{checkedNames}}</span>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        checkedNames: []
      },
      methods: {}
    })
  </script>
</body>

还有另外一种写法,示例代码如下:

html 复制代码
<body>
  <div id="app">
    <!-- 当选中时vm.toggle === 'yes',当没有选中时vm.toggle === 'no' -->
    <input type="checkbox" v-model="toggle" true-value="1" false-value="2">
    <p>当前选中:{{ toggle }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        toggle: ''
      },
      methods: {}
    })
  </script>
</body>

这里的 true-valuefalse-value 属性并不会影响输入控件的 value 属性,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即"yes"或"no"),请换用单选按钮。

  • 单选按钮
html 复制代码
<body>
  <div id="app">
    <input type="radio" id="male" value="male" v-model="gender">
    <label for="male">男</label>
    <input type="radio" id="female" value="female" v-model="gender">
    <label for="female">女</label>
    <p>选择的性别:{{ gender }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        gender: 'male'
      },
      methods: {}
    })
  </script>
</body>
  • 下拉选择框
html 复制代码
<body>
  <div id="app">
    <select v-model="selectedFruit">
      <option value="apple">苹果</option>
      <option value="banana">香蕉</option>
      <option value="orange">橙子</option>
    </select>
    <p>选择的水果:{{ selectedFruit }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        selectedFruit: 'apple'
      },
      methods: {}
    })
  </script>
</body>

如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为"未选中"状态。更推荐像下面这样提供一个值为空的禁用选项。

html 复制代码
<body>
  <div id="app">
    <select v-model="selectedFruit">
      <option value="" disabled>请选择</option>
      <option value="apple">苹果</option>
      <option value="banana">香蕉</option>
      <option value="orange">橙子</option>
    </select>
    <p>选择的水果:{{ selectedFruit }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        selectedFruit: ''
      },
      methods: {}
    })
  </script>
</body>

v-for 渲染的动态选项:

html 复制代码
<body>
  <div id="app">
    <select v-model="selectedFruit">
      <option value="" disabled>请选择</option>
      <option v-for="option in options" v-bind:value="option.value">{{option.name}}</option>
    </select>
    <p>选择的水果:{{ selectedFruit }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        selectedFruit: 'apple',
        options: [
          {'name':'苹果','value':'apple'},
          {'name':'香蕉','value':'banana'},
          {'name':'橙子','value':'orange'}
        ]
      },
      methods: {}
    })
  </script>
</body>
  • 值绑定

对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值):

html 复制代码
<body>
  <div id="app">
    <!-- 当选中时,`picked` 为字符串 "a" -->
    <input type="radio" v-model="picked" value="a">
    <p>当前选中:{{ picked }}</p>
    <!-- `toggle` 为 true 或 false -->
    <input type="checkbox" v-model="toggle">{{toggle}}
    <p>当前选中:{{ toggle }}</p>
    <!-- 当选中第一个选项时,`selected` 为字符串 "苹果",第二个为设置的value -->
    <select v-model="selectedFruit">
      <option>苹果</option>
      <option value="banana">香蕉</option>
      <option>橙子</option>
    </select>
    <p>选择的水果:{{ selectedFruit }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        picked: '',
        toggle: '',
        selectedFruit: ''
      },
      methods: {}
    })
  </script>
</body>

但是有时我们可能想把值绑定到 Vue 实例的一个动态属性上,这时可以用 v-bind 实现,并且这个属性的值可以不是字符串。

  • 修饰符

v-model 提供了一些修饰符来处理特殊场景:

(1).lazy:默认情况下,v-model 会在每次 input 事件触发后更新数据。使用 .lazy 可以改为在 change 事件(即失去焦点或按下回车键)后更新:

html 复制代码
<body>
  <div id="app">
    <input type="text" v-model.lazy="name" v-on:input="nameHandle">
    <p>当前选中:{{ name }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        name: ''
      },
      methods: {
        nameHandle: function () {
          console.log(this.name)
        }
      }
    })
  </script>
</body>

(2).number:如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符,避免字符串类型的问题:

html 复制代码
<body>
  <div id="app">
    <input type="number" v-model.number="age" v-on:input="ageHandle">
    <p>当前选中:{{ age }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        age: ''
      },
      methods: {
        ageHandle: function () {
          console.log(this.age)
        }
      }
    })
  </script>
</body>

(3).trim:如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

html 复制代码
<body>
  <div id="app">
    <input type="text" v-model.trim="name" v-on:input="nameHandle">
    <p>当前选中:{{ name }}</p>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        name: ''
      },
      methods: {
        nameHandle: function () {
          console.log(this.name)
        }
      }
    })
  </script>
</body>

指令

指令(Directives ) 是带有 v- 前缀的特殊属性,用于在模板中声明式地操作 DOM 。它们将特殊行为绑定到元素上,当表达式的值变化时,会自动更新 DOMVue2 提供了多种内置指令,并支持自定义指令以扩展功能。

属性绑定指令

Vue2 中,v-bind 是用于动态绑定 HTML 属性的核心指令,它允许你将 DOM 元素的属性与 Vue 实例中的数据或表达式关联起来。

html 复制代码
<a v-bind:[属性名]="表达式"> ... </a>

也可以简写为:

html 复制代码
<a :[属性名]="表达式"> ... </a>

如果我们按照前面的思想直接使用,并不会生效,示例代码如下:

javascript 复制代码
<body>
    <div id="app">
        <!-- 错误的语法:<a href="{{url}}">这是一个连接</a> -->
        <a href="url">这是一个连接</a>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                "url": "http://www.baidu.com",
            }
        })
    </script>
</body>

正确的写法,示例代码如下:

javascript 复制代码
<body>
    <div id="app">
        <a v-bind:href="url">这是一个连接</a>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                "url": "http://www.baidu.com",
            }
        })
    </script>
</body>
  • 绑定普通 HTML 属性

可以绑定任何 HTML 属性,如 srchrefdisabledtitle 等:

javascript 复制代码
<body>
    <div id="app">
        <!-- 绑定src、alt -->
        <img v-bind:src="src" v-bind:alt="alt">
        <!-- 绑定href -->
        <a v-bind:href="href">连接</a>
        <!-- disabled -->
        <button v-bind:disabled="disabled">禁用按钮</button>
        <!-- 绑定title -->
        <div v-bind:title="tooltip">悬停查看提示</div>
        <!-- 当isChecked为true时,会被选中 -->
        <input type="checkbox" :checked="isChecked">
        <!-- 当isRadio为true时,会被选中 -->
        <input type="radio" :checked="isRadio">
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                "src": "https://img.duoziwang.com/2018/06/2018010151183746.jpg",
                "alt": "快乐小猫",
                "href": "http://www.baidu.com",
                "disabled": false,
                "tooltip":"这是一个提示",
                "isChecked": true,
                "isRadio": false
            }
        })
    </script>
</body>

编译后代码如图所示:

  • 动态Class选择器与Style绑定

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

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

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style type="text/css">
    .active {
      border: 1px black solid;
      background-color: pink;
      width: 100px;
      height: 100px;
    }
  </style>
</head>

<body>
  <div id="app">
    <div v-bind:class="{ active: isActive }">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        isActive: true
      }
    })
  </script>
</body>

</html>

执行结果如图:

上面的语法表示绑定类选择器active,是否显示取决于isActive 属性,也就是说当isActive:false样式不会生效,如图所示:

你也可以绑定多个class,并且也可以与普通的 class 属性共存,示例代码如下:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style type="text/css">
    .active {
      border: 1px black solid;
      background-color: pink;
      width: 100px;
      height: 100px;
    }
    .fontSize{
      font-size: 30px;
    }
    .fontColor{
      color: red;
    }
  </style>
</head>

<body>
  <div id="app">
    <div class="fontSize" v-bind:class="{ active: isActive, fontColor: isColor }">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        isActive: true,
        isColor: true
      }
    })
  </script>
</body>

</html>

执行结果如图:

isActive 或者 isColor 变化时,class 列表将相应地更新。

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

html 复制代码
<body>
  <div id="app">
    <div class="fontSize" v-bind:class="classObject">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        classObject: {
          active: true,
          fontColor: true
        }
      }
    })
  </script>
</body>

我们也可以在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式:

html 复制代码
<body>
  <div id="app">
    <div class="fontSize" v-bind:class="classObject">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        isActive: true,
        isColor: true
      },
      computed: {
        classObject: function(){
          return {
            "active": this.isActive,
            "fontColor": this.isColor
          }
        }
      }
    })
  </script>
</body>

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

html 复制代码
<body>
  <div id="app">
    <div class="fontSize" v-bind:class="[activeClass,colorClass]">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        activeClass: 'active',
        colorClass: 'fontColor'
      }
    })
  </script>
</body>

还可以用三元表达式:

html 复制代码
<body>
  <div id="app">
    <div class="fontSize" v-bind:class="[isActive ? 'active': '', colorClass]">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        isActive: true,
        colorClass: 'fontColor'
      }
    })
  </script>
</body>
  • 绑定内联样式

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

html 复制代码
<body>
  <div id="app">
    <div v-bind:style="{'font-size' : '30px','color' : 'red'}">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {}
    })
  </script>
</body>

直接绑定到一个样式对象通常更好,这会让模板更清晰:

html 复制代码
<body>
  <div id="app">
    <div v-bind:style="styleObject">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        styleObject: {
          'font-size': '30px', 
          'color': 'red'
        }
      }
    })
  </script>
</body>

一样可以将多个样式对象应用到同一个元素上:

html 复制代码
<body>
  <div id="app">
    <div v-bind:style="[fontSizeObj,fontColorObj]">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        fontSizeObj: {
          'font-size': '30px'
        },
        fontColorObj: {
          'color': 'red'
        }
      }
    })
  </script>
</body>

你可以使用 v-bind 绑定到 HTML 规范中定义的所有属性,以及自定义的任意属性。常见的绑定场景包括样式控制、表单状态管理、资源路径动态化等。

  • 缩写

v- 前缀作为一种视觉提示,用来识别模板中 Vue 特定的属性,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue 管理所有模板的单页面应用程序 (SPA - single page application ) 时,v- 前缀也变得没那么重要了。因此,Vuev-bind提供了特定简写:,示例代码如下:

html 复制代码
<body>
  <div id="app">
    <!--  原写法
    <a v-bind:href="href">跳转连接</a>
    <img v-bind:src="src">
    <div v-bind:style="styleObject">这是一个盒子</div> 
    -->
    <!-- 缩写 -->
    <a :href="href">跳转连接</a>
    <img :src="src">
    <div :style="styleObject">这是一个盒子</div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        href: "http://www.baidu.com",
        src: "https://img2.baidu.com/it/u=1178666868,1178839128&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1502",
        styleObject: {
          'color': 'red',
          'font-size': '30px'
        }
      }
    })
  </script>
</body>

事件绑定指令

v-onVue.js 中用于监听 DOM 事件的指令,并在触发时运行一些 JavaScript 代码。

html 复制代码
<body>
  <div id="app">
    <button v-on:click="count++">按钮</button>
    <span>您的账户余额+{{count}}</span>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {}
    })
  </script>
</body>

上述代码点击按钮时对应count变量数值+1,如图所示:

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

html 复制代码
<body>
  <div id="app">
    <button v-on:click="greet">按钮</button>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        count: 0
      },
      methods: {
        greet: function () {
          alert("hello world");
        }
      }
    })
  </script>
</body>

上述代码点击按钮时调用greet()函数,触发弹框,如图所示:

除了直接绑定到一个方法,也可以在调用方法中传递参数:

html 复制代码
<body>
  <div id="app">
    <button v-on:click="say('hi')">say hi</button>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        say: function (message) {
          alert(message)
        }
      }
    })
  </script>
</body>

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

html 复制代码
<body>
  <div id="app">
    <button v-on:click="say('hi', $event)">say hi</button>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        say: function (message, event) {
          alert(message)
          console.log(event)
        }
      }
    })
  </script>
</body>

点击后会看到打印DOM事件的很多详细信息,如图所示:

  • 表单事件

Vuev-on指令为表单元素提供了丰富的事件监听能力,结合事件修饰符可以更灵活地处理用户输入。常用的表单事件包括inputchangeblurfocussubmit等,通过这些事件可以实现数据双向绑定、表单验证、提交处理等功能。

(1)input 事件:输入框内容发生变化时(实时触发,包括输入法未确认的状态)。

html 复制代码
<body>
  <div id="app">
    <input type="text" v-model="message" v-on:input="handleInput">
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        "message": null
      },
      methods: {
        handleInput(event) {
          console.log('输入内容:', event.target.value);
        }
      }
    })
  </script>
</body>

(2)change 事件 ::输入框失去焦点且内容发生变化,或选择框 / 单选框 / 复选框的选项改变。

html 复制代码
<body>
  <div id="app">
    <!-- 下拉框 -->
    <select v-on:change="onSelect" v-model="selected">
      <option value="option1">选项1</option>
      <option value="option2">选项2</option>
    </select>
    <!-- 复选框 -->
    <input type="checkbox" v-model="isChecked" v-on:change="onCheck">
    <!-- 单选框 -->
    <input type="radio" value="apple" v-model="fruit" v-on:change="onRadio">
    <input type="radio" value="banana" v-model="fruit" v-on:change="onRadio">
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        selected: 'option1',
        fruit: 'apple',
        isChecked: false,
      },
      methods: {
        onSelect() {
          console.log('下拉框状态:', this.selected);
        },
        onCheck() {
          console.log('复选框状态:', this.isChecked);
        },
        onRadio() {
          console.log('单选框状态:', this.fruit);
        }
      }
    })
  </script>
</body>

你会发现changeinput事件都可以用于监听表单元素的变化。对于复选框和单选框,使用change更符合惯例。Vue 的v-model对复选框和单选框默认绑定change事件。

(3)blur 事件 :元素失去焦点时。focus 事件:元素获得焦点时。

html 复制代码
<body>
  <div id="app">
    <input type="text" v-on:blur="onBlur">
    <input type="text" v-on:focus="onFocus">
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        onBlur() {
          console.log('失去焦点');
        },
        onFocus() {
          console.log('获得焦点');
        }
      }
    })
  </script>
</body>

(4)submit 事件 :表单提交时,配合.prevent修饰符阻止默认行为。

html 复制代码
<body>
  <div id="app">
    <form v-on:submit.prevent="handleSubmit">
      <input type="text" v-model="username">
      <input type="checkbox" v-model="isChecked">
      <input type="radio" value="apple" v-model="fruit">
      <input type="radio" value="banana" v-model="fruit">
      <button type="submit">提交</button>
    </form>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        "username": null,
        "fruit": "apple",
        "isChecked": false
      },
      methods: {
        handleSubmit() {
          console.log(this.username);
          console.log(this.fruit);
          console.log(this.isChecked);
        }
      }
    })
  </script>
</body>

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。

event.preventDefault()阻止事件的默认行为,但不影响事件在 DOM 树中的传播。场景有:阻止表单提交(避免页面刷新)禁用链接跳转阻止右键菜单弹出

html 复制代码
<body>
  <div id="app">
    <a href="https://www.baidu.com" v-on:click="handleClick">点击我</a>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods:{
        handleClick: function(event){
          event.preventDefault(); // 阻止跳转到 href
        }
      }
    })
  </script>
</body>

event.stopPropagation()阻止事件在 DOM 树中的冒泡阶段继续传播,但不影响默认行为。场景有:
点击内层元素时,不触发外层元素的事件实现模态框的点击穿透问题

html 复制代码
<body>
  <div id="app">
    <div v-on:click="handleOuter">
      <button v-on:click="handleInner">点击我</button>
    </div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods:{
        handleOuter: function(event){
          console.log("触发外部点击事件");
        },
        handleInner: function(event){
          event.stopPropagation(); // 阻止事件冒泡到外层 div
          console.log("触发内部点击事件");
        }
      }
    })
  </script>
</body>

执行结果如图:

尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.jsv-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

  • 事件修饰符

Vue 提供了 .stop.prevent.capture.self.once.passive 等修饰符,用于简化事件处理逻辑:

(1).stop修饰符 :阻止事件冒泡,示例代码如下:

html 复制代码
<body>
  <div id="app">
    <div v-on:click="handleOuter">
      <button v-on:click.stop="handleInner">点击我</button>
    </div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods:{
        handleOuter: function(){
          console.log("触发外部点击事件");
        },
        handleInner: function(){
          event.stopPropagation(); // 阻止事件冒泡到外层 div
          console.log("触发内部点击事件");
        }
      }
    })
  </script>
</body>

点击后,不会传播到父级元素,执行父级函数。

(2).prevent修饰符:阻止默认行为,示例代码如下:

html 复制代码
<body>
  <div id="app">
    <a href="https://www.baidu.com" v-on:click.prevent="handleClick">点击我</a>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods:{
        handleClick: function(){
          console.info('进来了')
        }
      }
    })
  </script>
</body>

点击后不会跳转页面。

(3).capture修饰符 :事件捕获模式,事件传播分为三个阶段:捕获 → 目标元素 → 冒泡。若未使用 .capture 则只在冒泡阶段触发,示例代码如下:

html 复制代码
<body>
  <div id="app">
    <div v-on:click.capture="handleOuter">
      <div v-on:click="handleInner">点击我</div>
      <!-- <a href="https://www.baidu.com" v-on:click="handleInner">点击我</a> -->
    </div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        handleOuter: function(){
          console.log("触发外部点击事件");
        },
        handleInner: function(){
          console.log("触发内部点击事件");
        }
      }
    })
  </script>
</body>

执行结果如图:

(4).self修饰符:仅自身触发,阻止事件冒泡到父元素时触发。示例代码如下:

html 复制代码
<body>
  <div id="app">
    <div v-on:click.self="handleOuter">
      <button v-on:click="handleInner">点击我</button>
    </div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        handleOuter: function(){
          console.log("触发外部点击事件");
        },
        handleInner: function(){
          console.log("触发内部点击事件");
        }
      }
    })
  </script>
</body>

点击 button:仅触发 handleInner。点击 div 空白区域:触发 handleOuter

(5).once修饰符:只触发一次,事件回调只执行一次,执行后自动解绑。示例代码如下:

html 复制代码
<body>
  <div id="app">
      <button v-on:click.once="handleInner">点击我</button>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        handleInner: function(){
          console.log("触发内部点击事件");
        }
      }
    })
  </script>
</body>

(6).passive修饰符 :提升滚动性能,告诉浏览器事件处理函数不会调用 event.preventDefault(),从而避免阻塞页面滚动。示例代码如下:

html 复制代码
<body>
  <div id="app">
    <div @scroll.passive="handleOuter" style="height: 200px; overflow: auto;">
      <!--超级多内容-->
    </div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        handleOuter: function () {
          console.log("触发滚动事件");
        }
      }
    })
  </script>
</body>

滚动时,触发函数。这个 .passive 修饰符尤其能够提升移动端的性能。

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

修饰符还可以组合使用 ,而且可以只使用修饰符不定义函数,示例代码如下:

html 复制代码
<body>
  <div id="app">
    <div v-on:click="handleOuter">
      <a href="https://www.baidu.com" v-on:click.stop.prevent>点击我</a>
    </div>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        handleOuter: function () {
          console.log("触发外部点击事件");
        }
      }
    })
  </script>
</body>

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

  • 按键修饰符

Vue 提供了.enter.tab.delete.esc.space.up.down.left.right等按键修饰符,用于监听键盘事件时过滤特定按键。

html 复制代码
<body>
  <div id="app">.tab、.delete、.esc、.space、.up、.down、.left、.right
    <!-- 监听 Enter 键按下事件 -->
    <input v-on:keyup.enter="handleEnter">
    <!-- 监听 Tab 键按下事件 -->
    <input v-on:keyup.tab="handleTab">
    <!-- 监听 Delete 键按下事件 -->
    <input v-on:keyup.delete="handleDelete">
    <!-- 监听 Esc 键按下事件 -->
    <input v-on:keyup.esc="handleEsc">
    <!-- 监听 Space空格 键按下事件 -->
    <input v-on:keyup.space="handleSpace">
    <!-- 监听 Up上箭头 键按下事件 -->
    <input v-on:keyup.up="handleUp">
    <!-- 监听 Down下箭头 键按下事件 -->
    <input v-on:keyup.down="handleDown">
    <!-- 监听 Left左箭头 键按下事件 -->
    <input v-on:keyup.left="handleLeft">
    <!-- 监听 Right右箭头 键按下事件 -->
    <input v-on:keyup.right="handleRight">
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        handleEnter: function () {
          console.log("触发enter事件");
        },
        handleTab: function () {
          console.log("触发tab事件");
        },
        handleDelete: function () {
          console.log("触发delete事件");
        },
        handleEsc: function () {
          console.log("触发esc事件");
        },
        handleSpace: function () {
          console.log("触发space事件");
        },
        handleUp: function () {
          console.log("触发up事件");
        },
        handleDown: function () {
          console.log("触发down事件");
        },
        handleLeft: function () {
          console.log("触发left事件");
        },
        handleRight: function () {
          console.log("触发right事件");
        }
      }
    })
  </script>
</body>

keyup按键释放时,keydown 按键按下时(持续触发)。

  • 自定义按键码

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

html 复制代码
<input v-on:keyup.13="submit">

你还可以通过全局 config.keyCodes 对象自定义按键修饰符别名:

html 复制代码
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
  • 系统修饰键

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

html 复制代码
<body>
  <div id="app">
    <!-- Ctrl + Click -->
    <div v-on:click.ctrl="ctrlHandle">Do something</div>
    <!-- Alt + C -->
    <input v-on:keyup.alt.67="altHandle">
    <!-- Shift + 上箭头 -->
    <input v-on:keyup.shift.up="shiftHandle">
    <!-- meta + 下箭头 -->
    <input v-on:keyup.meta.down="metaHandle">
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        ctrlHandle: function () {
          console.log("触发ctrl+click事件");
        },
        altHandle: function () {
          console.log("触发alt+c事件");
        },
        shiftHandle: function () {
          console.log("触发shift+up事件");
        },
        metaHandle: function () {
          console.log("触发meta+down事件");
        }
      }
    })
  </script>
</body>

请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。

.exact 修饰符精确匹配按键组合,确保没有其他按键被按下:

html 复制代码
<body>
  <div id="app">
    <!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
    <button v-on:click.ctrl="onClick">A</button>
    <!-- 有且只有 Ctrl 被按下的时候才触发 -->
    <button v-on:click.ctrl.exact="onCtrlClick">B</button>
    <!-- 没有任何系统修饰符被按下的时候才触发 -->
    <button v-on:click.exact="onClick2">C</button>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        onClick: function () {
          console.log("触发A事件");
        },
        onCtrlClick: function () {
          console.log("触发B事件");
        },
        onClick2: function () {
          console.log("触发C事件");
        }
      }
    })
  </script>
</body>
  • 鼠标按钮修饰符

虽然不是严格意义的按键修饰符.left.right.middle,但可用于限制鼠标按钮触发事件:

html 复制代码
<body>
  <div id="app">
    <!-- 点击鼠标左键触发 -->
    <button v-on:click.left="clickLeft">A</button>
    <!-- 点击鼠标右键触发 -->
    <button v-on:click.right="clickRight">B</button>
    <!-- 点击鼠标中键触发 -->
    <button v-on:click.middle="clickMiddle">C</button>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        clickLeft: function () {
          console.log("触发A事件");
        },
        clickRight: function () {
          console.log("触发B事件");
        },
        clickMiddle: function () {
          console.log("触发C事件");
        }
      }
    })
  </script>
</body>

上面介绍事件再JavaScript中都可以实现,只不过使用的少,影响比较浅。

  • 缩写

v-on指令是用来监听 DOM 事件的,它有一个常用的缩写形式@

html 复制代码
<body>
  <div id="app">
    <!-- 原写法 -->
    <!-- <button v-on:click="doSomething('hello world')">点击我</button> -->
     <!-- 缩写 -->
    <button @click="doSomething('hello world')">点击我</button>
  </div>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {},
      methods: {
        doSomething: function (message) {
          alert(message)
        }
      }
    })
  </script>
</body>

条件渲染指令

Vue2 中,条件渲染指令主要用于根据表达式的值来决定是否渲染或显示某个元素。

html 复制代码
<body>
    <div id="app">
        <div v-if="type === 'A'">type a</div>
        <div v-else-if="type === 'B'">type b</div>
        <div v-else>type other</div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                'type': 'B'
            }
        })
    </script>
</body>

执行结果如图所示:

2.1.0 新增v-else-if必须紧跟在带 v-if 或者 v-else-if 的元素之后。v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面。

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。

html 复制代码
<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 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input> 不会被替换掉,仅仅是替换了它的 placeholder

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

javascript 复制代码
<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>

现在,每次切换时,输入框都将被重新渲染。

另一个用于根据条件展示元素的选项是 v-show 指令。不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性display

html 复制代码
<body>
    <div id="app">
        <div v-show="type === 'A'">type a</div>
        <div v-show="type === 'B'">type b</div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: { 
                'type': 'B'
            }
        })
    </script>
</body>
</html>

执行结果如图:

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

如果一组 v-if + v-else 的元素类型相同,最好使用 key (比如两个 <div> 元素)。

javascript 复制代码
在这里插入代码片

列表渲染指令

Vue2 中,列表渲染指令基于数组或对象循环渲染元素。

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

html 复制代码
<body>
    <ul id="app">
        <li v-for="item in items">{{item}}</li>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                'items': ['one', 'two', 'three']
            }
        })
    </script>
</body>

v-for 还支持一个可选的第二个参数,即当前项的索引index属性。示例代码如下:

html 复制代码
<body>
    <ul id="app">
        <li v-for="(item,index) in items">{{index}}-{{item}}</li>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                'items': ['one', 'two', 'three']
            }
        })
    </script>
</body>

执行结果如图:

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

html 复制代码
<body>
    <ul id="app">
        <li v-for="(item,index) of items">{{index}}-{{item}}</li>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                'items': ['one', 'two', 'three']
            }
        })
    </script>
</body>

数组方法是处理数据的重要工具,以下是它们的用法及其他常见数组方法的介绍:

  • 可变方法(会改变原数组)

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

(1)push():在数组末尾添加一个或多个元素,并返回新的长度。

html 复制代码
<body>
    <ul id="app">
        <li v-for="(item,index) of items">{{index}}-{{item}}</li>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                'items': ['one', 'two', 'three']
            }
        })
        const length = app.items.push("four");
        console.log(length) // output: 4
    </script>
</body>

(2)pop():删除数组的最后一个元素,并返回该元素。

html 复制代码
<body>
    <ul id="app">
        <li v-for="(item,index) of items">{{index}}-{{item}}</li>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                'items': ['one', 'two', 'three']
            }
        })
        const last = app.items.pop();
        console.log(last) // output: three
    </script>
</body>

(3)shift():删除数组的第一个元素,并返回该元素。

html 复制代码
<body>
    <ul id="app">
        <li v-for="(item,index) of items">{{index}}-{{item}}</li>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                'items': ['one', 'two', 'three']
            }
        })
        const first = app.items.shift();
        console.log(first) // output: one
    </script>
</body>

(4)unshift():在数组开头添加一个或多个元素,并返回新的长度。

html 复制代码
<body>
    <ul id="app">
        <li v-for="(item,index) of items">{{index}}-{{item}}</li>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                'items': ['one', 'two', 'three']
            }
        })
        const length = app.items.unshift('first');
        console.log(length) // output: 4
    </script>
</body>

(5)splice(startIndex, deleteCount, ...itemsToAdd):删除、替换或添加元素到数组的任意位置,返回删除的元素。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': ['one', 'two', 'three']
      }
    })
    const arr = app.items.splice(0, 2, "一", "二");// 从索引0开始删除两个元素,添加"一", "二"元素
    console.log(arr) // output: ['one', 'two']
  </script>
</body>

(6)sort():对数组元素进行排序(默认按字符串 Unicode 码点排序)。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': ['b', 'a', 'd'],
        'arr': [3, 2, 5, 7]
      }
    })
    const itemSort = app.items.sort();
    console.log(itemSort) // output: ['a', 'b', 'd']
    const arrAsc = app.arr.sort((a, b) => a - b);
    console.log(arrAsc) // output(升序): [2, 3, 5, 7]
    const arrDesc = app.arr.sort((a, b) => b - a);
    console.log(arrDesc) // output(降序): [7, 5, 3, 2]
  </script>
</body>

(7)reverse():反转数组的元素顺序。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': ['b', 'a', 'd']
      }
    })
    const reverse = app.items.reverse();
    console.log(reverse) // output: ['d', 'a', 'b']
  </script>
</body>
  • 不可变方法(不改变原数组)

它们不会变更原始数组,而总是返回一个新数组。

(1)concat():合并多个数组,返回新数组。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': ['b', 'a', 'd'],
        'items2': ['z', 'x', 'f']
      }
    })
    const item = app.items.concat(app.items2);
    console.log(item) // output: ['b', 'a', 'd', 'z', 'x', 'f']
  </script>
</body>

(2)slice():返回数组的部分元素。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': ['b', 'a', 'd']
      }
    })
    const item = app.items.slice(0, 2);
    console.log(item) // output: ['b', 'a'] 从索引0-索引2的前一个元素
  </script>
</body>

(3)join():将数组元素连接成字符串。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': ['b', 'a', 'd']
      }
    })
    const item = app.items.join("-");
    console.log(item) // output: b-a-d
  </script>
</body>

(4)includes():判断数组是否包含某个值。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': ['b', 'a', 'd']
      }
    })
    const item = app.items.includes("e");
    console.log(item) // output: false
  </script>
</body>

(5)find():返回第一个满足条件的元素。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': [2, 4, 6, 7]
      }
    })
    const item = app.items.find(x => x % 2 === 0);
    console.log(item) // output: 2
  </script>
</body>

(6)filter():过滤数组元素,返回符合条件的元素组成的新数组。

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of items">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': [2, 4, 6, 7]
      }
    })
    const item = app.items.filter(x => x % 2 === 0);
    console.log(item) // output: [2, 4, 6]
  </script>
</body>

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

html 复制代码
<body>
  <ul id="app">
    <li v-for="(item,index) of evenNumbers">{{index}}-{{item}}</li>
  </ul>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        'items': [2, 4, 6, 7]
      },
      computed: {
        evenNumbers: function () {
          return this.items.filter(function (number) {
            return number % 2 === 0
          })
        }
      }
    })
  </script>
</body>

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

html 复制代码
<body>
    <ul id="app">
        <template v-for="set in sets">
            <li v-for="n in even(set)">{{ n }}</li>
        </template>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            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>
  • 在 v-for 里使用范围值

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

html 复制代码
<body>
    <ul id="app">
        <div v-for="item in 10">{{item}}</div>
    </ul>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {}
        })
    </script>
</body>
  • v-for 与 v-if 一同使用

不推荐在同一元素上使用 v-ifv-for。一般我们在两种常见的情况下会倾向于这样做:

(1)为了过滤一个列表中的数据 (比如 v-for="user in users" v-if="user.isActive"):

html 复制代码
<body>
  <ul id="app">
    <li v-for="user in users" v-if="user.isActive" :key="user.id">
      {{ user }}
    </li>
  </ul>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        users: [{ "id": 1, "isActive": false, "name": "a" }, { "id": 2, "isActive": true, "name": "b" }, { "id": 3, "isActive": true, "name": "c" }]
      }
    })
  </script>
</body>

Vue 处理指令时,v-forv-if 具有更高的优先级,所以这个模板:

js 复制代码
this.users.map(function (user) {
  if (user.isActive) {
    return user.name
  }
})

因此哪怕我们只渲染出一小部分用户的元素,也得在每次重渲染的时候遍历整个列表,不论活跃用户是否发生了变化。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。

html 复制代码
<body>
  <ul id="app">
    <li v-for="user in activeUsers" :key="user.id">
      {{ user }}
    </li>
  </ul>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        users: [{ "id": 1, "isActive": false, "name": "a" }, { "id": 2, "isActive": true, "name": "b" }, { "id": 3, "isActive": true, "name": "c" }]
      },
      computed:{
        activeUsers: function () {
          return this.users.filter(function (user) {
            return user.isActive;
          })
        }
      }
    })
  </script>
</body>

过滤后的列表只会在 users 数组发生相关变化时才被重新运算,过滤更高效;渲染的时候只遍历活跃用户,渲染更高效,解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。

(2)为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers"),这种情形下,请将 v-if 移动至容器元素上 (比如 ulol)。

html 复制代码
<body>
  <ul id="app" v-if="shouldShowUsers">
    <li v-for="user in users" :key="user.id">
      {{ user }}
    </li>
  </ul>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    var app = new Vue({
      el: "#app",
      data: {
        users: [{ "id": 1, "isActive": false, "name": "a" }, { "id": 2, "isActive": true, "name": "b" }, { "id": 3, "isActive": true, "name": "c" }],
      },
      computed: {
        shouldShowUsers: function () {
          return this.users && this.users.length > 0;
        }
      }
    })
  </script>
</body>

通过将 v-if 移动到容器元素,只检查它一次。

  • 在 v-for 里使用对象

你也可以用 v-for 来遍历一个对象的属性:

html 复制代码
<body>
    <ul id="app">
        <li v-for="value in student">
            {{ value }}
        </li>
    </ul>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                student: {
                    "name": "zhangsan",
                    "age": 18,
                    "sex": 0
                }
            }
        })
    </script>
</body>

你也可以提供第二个的参数也就是键名:

html 复制代码
<body>
    <ul id="app">
        <li v-for="(value, key) in student">
            {{ key }} - {{value}}
        </li>
    </ul>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                student: {
                    "name": "zhangsan",
                    "age": 18,
                    "sex": 0
                }
            }
        })
    </script>
</body>

还可以用第三个参数作为索引:

html 复制代码
<body>
    <ul id="app">
        <li v-for="(value, key, index) in student">
            {{index}} - {{ key }} - {{value}}
        </li>
    </ul>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                student: {
                    "name": "zhangsan",
                    "age": 18,
                    "sex": 0
                }
            }
        })
    </script>
</body>

执行结果如图:

  • 维护状态

Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用"就地更新"的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染(通过索引位置来判断元素是否需要更新)。

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (举例:如果列表项包含一个输入框,当数据重排序后,输入框的内容可能不会跟随数据项移动,而是停留在原位置(因为 DOM 元素未被移动,只是内容被 "就地更新")) 的列表渲染输出。

v-for 的每项提供唯一 key 属性,相当于给 Vue 一个 "身份标识",以便它能跟踪每个节点的身份,从而重用和重新排序现有元素:

html 复制代码
<body>
    <ul id="app">
        <li v-for="(value, key, index) in list" v-bind:key="value.id">
            {{index}} - {{ key }} - {{value}}
        </li>
    </ul>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                list: [{
                    "id": 1,
                    "name": "zhangsan",
                    "age": 18,
                    "sex": 0
                },{
                    "id": 2,
                    "name": "lisi",
                    "age": 20,
                    "sex": 0
                },{
                    "id": 3,
                    "name": "wangwu",
                    "age": 20,
                    "sex": 1
                }]
            }
        })
    </script>
</body>

建议尽可能在使用 v-for 时提供 key属性 ,不要使用对象或数组之类的非基本类型值作为 v-forkey。请用字符串或数值类型的值。有相同父元素的子元素必须有独特的 key,重复的 key 会造成渲染错误。

其他指令

除了上面介绍的指令,还有一些其它指令,比如:v-prev-cloakv-once等指令,用的并不多,了解即可。

v-pre指令:跳过该元素及其子节点的编译过程,直接渲染原始内容。

html 复制代码
<body>
  <div id="app" v-pre>这是一个示例,{{name}}</div>


  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    new Vue({
      el: "#app",
      data:{
        name:'张三'
      }
    })
  </script>
</body>

使用这个命令,只会展示未渲染之前的样子。

v-cloak命令:防止页面加载时出现未编译的 Mustache 语法闪烁。当 Vue 应用加载时,浏览器会先解析 HTML 结构,此时 Vue 实例可能还未初始化完成。在这个短暂的间隙,页面上的 {``{ message }} 这类模板语法会以原始文本的形式显示出来,给用户带来不好的体验。

html 复制代码
<body>

  <div id="app" v-cloak>这是一个示例,{{name}}</div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    new Vue({
      el: "#app",
      data: {
        name: '张三'
      }
    })
  </script>
  <style>
    [v-cloak] {
      display: none;
    }
  </style>
</body>

配合CSSdisplay: none;属性,页面加载完毕后会自动删除该命令,显示内容。

v-once命令:只渲染元素或组件一次,之后不再重新渲染。

html 复制代码
<body>

  <div id="app" v-cloak>
    <h1 v-once>原始值:{{num}}</h1>
    <h1>修改后值:{{num}}</h1>
    <button @click="num++">修改值</button>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    new Vue({
      el: "#app",
      data: {
        num: 1
      }
    })
  </script>
</body>

点击 "修改" 按钮后,"修改后值" 会实时更新,但 "原始值" 保持不变。

指令缩写 (用 : 表示 v-bind:、用 @ 表示 v-on: 和用 # 表示 v-slot:) 应该要么都用要么都不用。

javascript 复制代码
<input
  :value="newTodoText"
  :placeholder="newTodoInstructions"
>
// 或
<input
  v-bind:value="newTodoText"
  v-bind:placeholder="newTodoInstructions"
>
相关推荐
郝亚军2 小时前
顺序栈C语言版本
c语言·开发语言·算法
yugi9878382 小时前
基于MATLAB实现神经网络电能扰动信号特征识别
开发语言·神经网络·matlab
追光天使2 小时前
元组、列表、字符串、字典定义及切割
开发语言·python
沐森2 小时前
使用rust打开node的libuv实现多线程调用三种模式
javascript·rust
C_心欲无痕2 小时前
vue3 - shallowReadonly浅层只读响应式对象
前端·javascript·vue.js
_Kayo_2 小时前
HTML 拖放API
前端·javascript·html
狗头大军之江苏分军2 小时前
2026年了,前端到底算不算“夕阳行业”?
前端·javascript·后端
guygg882 小时前
一维信号模糊熵(Fuzzy Entropy)计算原理与MATLAB实现
开发语言·matlab
今夕资源网2 小时前
go-tcnat内网端口映射 端口穿透 GO语言 免费开源
开发语言·后端·golang·go语言·端口映射·内网端口映射