01_Vue 基本语法

[tocc]

Vue.js 基本语法

一、Vue基础

Vue官网

Vue概述

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

Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,简而言之:动态构建用户界面,把后台的数据动态显示在前端页面中

Vue.js是以数据驱动和组件化的思想构建的

何谓渐进式框架

对于“渐进式”的解释,我在知乎上看到了一个不错的回答,这个答案也被Vue的设计者点了赞。这个回答 的角度很好,主要从与React、Angular的比较来阐述的。

在我看来,渐进式代表的含义是:主张最少。

每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有 强有弱,它的强势程度会影响在业务开发中的使用方式。

比如说,Angular,它两个版本都是强主张的,如果你用它,必须接受以下东西: 必须使用它的模块机制, 必须使用它的依赖注入,必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免)。所以Angular是带有比较强的排它性的,如果你的应用不是从头开始,而是要不断考虑是否跟其 他东西集成,这些主张会带来一些困扰。

比如React,它也有一定程度的主张,它的主张主要是函数式编程的理念,比如说,你需要知道什么是副 作用,什么是纯函数,如何隔离副作用。它的侵入性看似没有Angular那么强,主要因为它是软性侵入。

Vue可能有些方面是不如React,不如Angular,但它是渐进的,没有强主张,你可以在原有大系统的上 面,把一两个组件改用它实现,当jQuery用;也可以整个用它全家桶开发,当Angular用;还可以用它 的视图,搭配你自己设计的整个下层用。你可以在底层数据逻辑的地方用OO和设计模式的那套理念,也 可以函数式,都可以,它只是个轻量视图而已,只做了自己该做的事,没有做不该做的事,仅此而已。

渐进式的含义,我的理解是:没有多做职责之外的事。

另外一种理解是:Vue的库由核心库和一系列相关的插件组成;核心库比较轻量,可以实现一些基础的 功能;我们可以只使用Vue的核心功能,如果需要一些其他的功能,我们可以在核心库的基础上添加进 来其他的插件。

我个人理解渐进式就是阶梯式向前。渐进式就是指我们可以由浅入深、由简单到复杂的方式去使用Vue.js。 “Progressive(渐进式的)”——这个词在英文中定义是渐进,一步一步,不是说你必须一竿子把所有的东西都用上。就像是搭积木,我们可以根据需求,利用社区良好的生态,借助已有的工具和库 搭建我们的项目,用最小、最快的成本一步步搭建

数据驱动

Vue是一种MVVM框架。而DOM是数据的一个种自然映射。传统的模式是通过Ajax请求从model请求数 据,然后手动的触发DOM传入数据修改页面。Vue中,Directives对view进行了封装,当model里的数 据发生变化时,Vue就会通过Directives指令去修改DOM。同时也通过DOM Listener实现对视图view的监听,当DOM改变时,就会被监听到,实现model的改变,实现数据的双向绑定。

image-20230817145228763

组件化

组件化实现了扩展HTML元素,封装可用的代码。页面上每个独立的可视/可交互区域视为一个组件;每 个组件对应一个工程目录,组件所需要的各种资源在这个目录下就近维护;页面不过是组件的容器,组 件可以嵌套自由组合形成完整的页面。

image-20230817145252673

(vue) => {渐进式}:https://www.jianshu.com/p/31d2e7cd1f17

为什么要学习流行框架

企业为了提高开发效率,在企业中,时间就是效率,效率就是金钱,花最少的钱,让员工更多的工作, 所以要引用框架。

开发历程:

  • 原生js:但要处理大量的兼容性;

  • jQuery:解决兼容性,DOM操作相对容易,但依然要拼接大量字符串;

  • 前端模板引擎;

模版引擎在渲染页面时方便,但在一些情况下效率很低,比如有下面一个数据:

[
	{id:1, name:'zs1'},
	{id:3, name:'zs3'},
	{id:2, name:'zs2'},
	{id:4, name:'zs4'},
]

当需要进行点击表格的id头,可以对所有数据进行按照id从小到大排序时,模板引擎会从第1个到第4个 都做一次重绘,而第2个和第3个本来不需要的。如果有1000条数据,假如只需要对第2个和第3个排序, 却对所有的数据排序,就浪费浏览器进行重绘,严重影响了效率,这时候就要使用vue.js 和 angular.js 了。

angular.js/vue.js能够帮我们减少不必要的DOM操作,提高渲染效率。让用户不再去操作DOM元素,解放了双手,让程序可以更多的时间去关注业务逻辑。

也就是说,在前端岗位上,程序员只需要关心数据的业务逻辑,依赖vue.jsangular.js 的双向数据绑定特性,不再关心DOM如何渲染。

框架和类库的区别

框架,是一套完整的解决方案,对项目侵入性大,项目如果需要更换框架,则需要重新架构整个项目。

库,提供某些小的功能,对项目侵入性小,如果某个库无法满足需求,可以很容易切换到其他库实现需 求。比如,从jQuery切换到 Zepto ,或者从EJS切换到art-template。

MVC和MVVM

MVC、MVVM 是两种常见的前端架构模式,这种架构模式是抽象分离出来的为了解决某一类问题的方法。一种架构模式也可以派生出很多的设计模式,从而来解决不同的问题。

MVC和MVVM 中的特点:

  • 相同点:其中M 指的是MODEL , V 指的是VIEW

  • 不同点:这两种架构中,不同的只是MV 之间的纽带部分。

MVC

MVC 架构指的是MODEL-VIEW-CONTROLLER ,是模型(model)-视图(view)-控制器(controller)的缩写, MVC 允许在不改变视图的情况下,改变用户对视图输入的响应方式,用户的VIEW 操作交给了CONTROLLER 处理,在CONTOROLLER 中响应VIEW 的事件调用MODEL 的接口对数据进行操作,只要MODEL 发生变化,就响应给相关的视图进行更新操作。

如果用原生的HTML+CSS+JS 来比喻形容的话,可以比作用户通过HTML ( VIEW )层操作, JS( CONTROLLER )通过事件监听,监听到VIEW 的变动,然后通过AJAX ( MODEL )进行数据的交互(向服务器端的接收和发送)。随即更新数据。

image-20230817145724025

  • M 是数据的model层,主要处理数据的curd,创建(Create)、更新(Update)、读取(Retrieve)和删除(Delete)。

  • V view 层,当做前端页面

  • C control 层 业务逻辑层,包括路由、登录、注销

MVC一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一 个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发 展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

MVVM

这里的VM 指的是: VIEWMODEL

MVVM 和MVC 模型的最大区别是:实现了VIEW 和MODEL 的自动更新,也就是说,当MODEL 的属性发生改变是,我们不再自己手动的操作DOM ,而是改变之后的属性对应的VIEW 层会自动的发生改变。因为在MVVM中,View不知道Model的存在,Model和 ViewModel 也观察不到View,这种低耦合模式提高代码的可重用性。典型的对应的前端的框架: VUE 。

image-20230817145940173

MVVM 是前端概念,它来源于MVC,只不过将C更换成VM了,核心思想是把每个页面,分成了M,V和VM,其中VM是MVVM思想的核心;因为VM是M和V之间的调度者。前端页面中使用了MVVM的思想, 主要是为了让我们开发更加方便,因为MVVM提供了数据的双向绑定,注意:数据的双向绑定是由VM提 供的。

image-20230817150024645

核心思路:

  • DOM Listeners和Data Bindings两个工具,它们是实现双向绑定的关键。

  • 从View侧看,ViewModel中的DOM Listeners工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据;

  • 从Model侧看,当我们更新Model中的数据时,Data Bindings工具会帮我们更新页面中的DOM元素。

在MVVM中,我们仅需关注是数据的处理和业务逻辑

安装

直接用 <script> 引入

直接下载并用 标签引入, Vue 会被注册为一个全局变量(类似jQuery和layer)。

在开发环境下不要使用压缩版本,不然你就失去了所有常见错误相关的警告! 开发版本:包含完整的警告和调试模式。

生产版本:删除了警告,33.30KB min+gzip。

可以直接在官方网站上复制代码后引入 index.html:

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 也可以下载到本地引入 -->
<script src="./js/vue.js"></script>

NPM

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

# 最新稳定版
$ npm install vue

初体验

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

编码

通过 <script>标签引入vue.js, Vue 会被注册为一个全局变量。

这样就相当于在浏览器的内存中,多一个一个Vue的构造函数,之后就可以再网页中创建Vue的实例了,每个 Vue 应用都是通过用vue函数创建一个新的 Vue 实例开始的:

// Vue全局变量是用来创建vue实例对象; vue实例对象就是我们MVVM中的VM
// Vue构造函数需要传递一个配置对象
// VM是干什么的? 监听View数据的改变,然后修改Model的数据; Model数据发生改变,反馈到View上
var vm = new Vue({
// 选项
})

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

当创建一个 Vue 实例时,你可以传入一个选项对象

el意思让vue这个实例的接管(控制)某个html元素,怎么接管呢?为div添加id属性即可。

<body>
	<div id="root">Hello world !</div>
	<script src="js/vue.js"></script>
	<!-- 当我们引入vue.js之后,会把Vue注册成为一个全局变量; -->
	<script>
		new Vue({
		// el->element,让vue实例对象来管理和控制某个html元素,在el里面定义元素的id选择器即可
		el:"#root"
		})
	</script>
</body>

还可以把root这个div,当做new出来的Vue对象实例的一个挂载点。

data是数据的意思,作用是为挂载点绑定多个数据,只要在挂载点也就是root内使用双花括号将数据的key绑定即可。当一个 Vue 实例被创建时,它将 对象中的所有的属性加入到 Vue 的响应式系统中。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

{{}}的用法叫做插值表达式

<body>
	<div id="root">{{msg}}</div>
	<script>
		new Vue({
			el:"#root",
			data:{
				msg:"Hello world !"
			}
		})
	</script>
</body>

我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台 (就在这个页面打开),并修改新。的值,你将看到上例相应地更新。

注意我们不再和 HTML 直接交互了。一个 Vue 应用会将其挂载到一个 DOM 元素上 (对于这个例子是#app ) 然后对其进行完全控制。那个HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。

如果操作DOM,那么代码应当是这样的:

<body>
	<div id="root">{{msg}}</div>
	<script>
		var dom = document.getELementById("root");
        dom.innerHTML = "Hello world!";
	</script>
</body>

通过Vue提供的指令,很方便的就能把数据渲染到页面上,程序员不再手动操作DOM元素了。其实 Vue这类的框架,不提倡我们去手动操作DOM元素。

【练习】:

有如下商品信息:

var shopData = {
	goodsName:"荣耀9",
	price:"1888",
	soldCount:1500
}

试着在网页中将数据使用Vue加载出来

【小结】

  • root即我们的v

  • new出来的Vue这个对象,即是MVVM中的调度者VM

  • v 和vm 都有了,那么model呢

  • 就是data属性的数据,专门用来保存页面的数据

在创建 Vue 实例时,会将所有在 data 对象中找到的属性,都添加到 Vue 的响应式系统中。每当这些属性的值发生变化时,视图都会“及时响应”,并更新相应的新值。

即:当有数据时,我们可以直接赋予Vue实例里data属性

Vue 开发者工具调试

在使用 Vue 时,我们推荐在你的浏览器上安装 Vue Devtools。它允许你在一个更友好的界面中审查和调试 Vue 应用。

1、把Chorme插件拖拽到Chorme的扩展程序里面;

2、在扩展程序中,把Vue插件给固定显示出来;

3、在开发者工具中会显示Vue菜单项;

image-20230817152725881

4、可以在工具中,看到内存中的数据;

二、绑定数据的指令

绑定数据常用的有4种方式: {{}}、v-text、v-html、template。

插值表达式{

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

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

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

但是这样写有个缺点。当网络较慢,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码。我们可以使用 v-cloak 指令来解决这一问题。

<body>
	<!--view-->
	<div id="root">
		<!--
			使用插值表达式有个缺点,当网速慢时,差插表达式在渲染时, 由于vue.js加载缓慢,导致双花括号会在页面里闪烁,
			解决方法是就是 为div元素添加 v-cloak属性
		-->
		<h1 v-cloak>{{msg}}</h1>
	</div>
	<script type="text/javascript"> 
        var vm=new Vue({
			el:"#root", 
        	data:{
				msg:"hello world!"
			}
		})
	</script>
</body>

在简单项目中,使用 v-cloak 指令是解决屏幕闪动的好方法。但在大型、工程化的项目中(webpack、vue-router)只有一个空的 div 元素,元素中的内容是通过路由挂载来实现的,这时我们就不需要用 v- cloak 指令。

v-text

更新元素的 textContent 。如果要更新部分的 textContent ,需要{{Mustache}}使用插值。

具体用法是,为挂载点内的某个元素添加 v-text属性,属性值为实例里data的某个属性;

<div id="root">
    <!-- 等价于{{}} -->
    <p v-text="msg"></p>
	<p v-text="htmlMsg"></p>
</div>
<script type="text/javascript">
    var vm = new Vue({
		el: "#root",
		data: {
			msg: "Hello Vue",
			htmlMsg: "<span>Hello Vue</span>"
		}
	})
</script>

v-html

插值表达式v-text会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v- html。

<div id="root">
    <p>使用插值表达式: {{ rawHtml }}</p>
    <p>使用v-html指令: <span v-html="rawHtml"></span></p>
</div>
<script type="text/javascript">
    var vm = new Vue({
        el: "#root",
        data: {
        	rawHtml: "<span style='color:red'>Hello Vue</span>"
        }
    })
</script>

template

root内部HTML代码,就是模板的全部内容。可以用另一种绑定数据的方法(我们把它叫做方法2)来理 解模板的含义,如果为Vue实例添加templete属性,则挂载点内部就不需有内容了(其实从最终观察来 看,是用来替代挂载点):

小结

1.{{}} 插值表达式

缺点:当网速过慢的情况下,页面还在加载vue.js,导致vue没有来得及渲染{{ }},可能显示源码

解决方法:为{{ }} 所在的元素添加v-cloak来解决这个问题

2.v-text指令

指令必须在开始标签上,作为标签的属性存在

<开始标签 v-text="data中的变量名"></结束标签>

v-text等价于{{ }},但是解决了{{ }}的缺点

3.v-html指令

和v-text用法一致

唯一的区别:通过v-html引入的变量,会被当成html文本被浏览器解析,

4.template配置项:实例化vue的配置项

如果我们定义了template配置项,那么配置项的内容会直接替换挂载点

目前我们看似非常鸡肋,但是在做单页面应用的时候会用到

template配置项的写法:

  • 可以直接写html文本
  • 可以通过id选择器引入一个模板

三、设定属性或事件的指令

属性绑定

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

语法

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

编码

给超链接或图片动态绑定url;

<div id="root">
    <!-- 以下两种语法都是错误的,Vue解析失败 -->
    <!-- <a href="{{linkUrl}}">百度一下</a>
    <img src="{{imgUrl}}" alt=""> -->
    
    <a v-bind:href="linkUrl">百度一下</a>
    <img v-bind:src="imgUrl" alt="">
</div>
<script type="text/javascript">
    var vm = new Vue({
        el: "#root",
        data: {
            linkUrl: "https://www.baidu.com",
            imgUrl: "https://cn.vuejs.org/images/logo.png"
   		}
   	})
</script>

对于布尔 attribute (它们只要存在就意味着值为 true ), v-bind 工作起来略有不同,在这个例子中:

<div id="root">
	<button v-bind:disabled="isButtonDisabled">Button</button>
</div>
<script type="text/javascript">
    var vm = new Vue({
        el: "#root",
        data: {
            // isButtonDisabled: true
            isButtonDisabled: false
        }
    })
</script>

如果isButtonDisabled 的值是 null 、 undefined 或 false ,则disabled attribute 甚至不会被包含在渲染出来的button元素中。

使用 JavaScript 表达式

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

编码

<div id="root">
    {{ number + 1 }}
    {{ ok ? 'YES' : 'NO' }}
    {{ message.split('').reverse().join('') }}
    {{like.toUpperCase()}}
    <div v-bind:id="'list-' + id"></div>
</div>
<script type="text/javascript">
    var vm = new Vue({
        el: "#root",
        data: {
            number: 100,
            ok: true,
            message: "我爱你",
            like: "i like Web",
            id: "demo"
        }
    })
</script>

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

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

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

事件绑定

为元素添加属性命令: v-on:事件名=“方法名” ,而方法则需要在Vue配置里的methods属性里定义

编码

<div id="root">
    <h1 v-on:click="clickHandle1" v-cloak>{{msg1}}</h1>
    <h1 v-on:click="clickHandle2" v-cloak>{{msg2}}</h1>
</div>
<script type="text/javascript">
// 需求:点击h1,将里面字面都转变成大写
    var vm = new Vue({
    el: "#root",
    data: {
        msg1: "hello world!",
        msg2: "javascript"
    },
    methods: {
        clickHandle1: function () {
        	this.msg1 = this.msg1.toUpperCase();
    	},
        clickHandle2: function () {
            this.msg2 = this.msg2.toUpperCase();
        }
   	 }
    })
</script>        

在原来的知识里,改变网页内容时就去操作DOM,但是在vue里不用去操作dom,当数据发生变化时,vue的框架的vm模块会自动处理后显示到view层。

缩写

v- 前缀作为一种视觉提示,用来识别模板中 Vue 特定的 attribute。当你在使用 Vue.js 为现有标签添加动态行为 (dynamic behavior) 时, v- 前缀很有帮助,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue 管理所有模板的单页面应用程序 (SPA - single page application) 时,v- 前缀也变得没那么重要了。因此,Vue 为 v-bindv-on 这两个最常用的指令,提供了特定简写:

v-bind 缩写

<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

v-on 缩写

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

它们看起来可能与普通的 HTML 略有不同,但 : 与 @ 对于 attribute 名来说都是合法字符,在所有支持Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的,但随着你更深入地了解它们的作用,你会庆幸拥有它们。

总结

在一个vue实例里:

  • 由el属性来绑定挂载点;

  • 由data来提供数据模型;

  • 由methods属性来提供方法集合;

  • 在vue环境下,不是面向DOM对象编程,是面向数据编程;

事件处理

监听事件

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

<div id="app">
    <button v-on:click="counter += 1">Add 1</button>
    <p>这个按钮被点击了 {{ counter }} 次.</p>
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
        	counter: 0
        }
    })
</script>

事件处理方法

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

<div id="app">
    <!-- `greet` 是在下面定义的方法名 -->
    <button v-on:click="greet">Greet</button>
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
        	name: 'Vue.js'
        },
    	// 在 `methods` 对象中定义方法
    	methods: {
    		greet() {
    			// `this` 在方法里指向当前 Vue 实例
   				alert('Hello ' + this.name + '!')
    		}
    	}
    })
</script>

内联处理器中的方法

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

<div id="app">
    <!-- 调用方法的时候,传递指定的参数 -->
    <button v-on:click="say('hi')">Say hi</button>
    <button v-on:click="say('what')">Say what</button>
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
        },
        methods: {
        	say: function (message) {
       		 alert(message)
       		}
        }
    })
</script>

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

<div id="app">
    <!-- 点击按钮获取标签内容并弹出 -->
    <button @click="test1">test1</button>
    <button @click="test2('abc')">test2</button>
    <button @click="test3('abcd', $event)">test3</button>
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
        },
        methods: {
            // 绑定事件的时候,没有传递任何参数,默认有一个Event代表事件对象;即使不显式声明也可以使用
            test1(event) {
                alert(event.target.innerHTML)
            },
            // 绑定事件的时候,传递了参数,那么肯定代表的是传递进来的值
            test2(event) {
                alert(event)
            },
            // 如果需要传递参数,并使用事件对象,那么必须使用$event显式传入进来
            test3(msg, event) {
                alert(msg + '---' + event.target.textContent)
            }
        }
    })
</script>

事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理DOM 事件细节。

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

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

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

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

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

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

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

案例

<div id="app">
    <a href="http://www.baidu.com" @click.prevent="test4">百度一下</a>
    <div style="width: 200px;height: 200px;background: red" @click="test5">
        <div style="width: 100px;height: 100px;background: blue" @click.stop="test6">
        </div>
    </div>
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
        },
        methods: {
            test4() {
                alert('点击了链接,但是超链接的默认行为被阻止了!')
            },
            test5() {
                alert('外层Div')
            },
            test6() {
                alert('内层Div')
            },
        }
    })
</script>

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

新增

<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

不像其它只能对原生的 DOM 事件起作用的修饰符, .once 修饰符还能被用到自定义的组件事件上。如果你还没有阅读关于组件的文档,现在大可不必担心。

按键修饰符

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

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

你可以直接将 KeyboardEvent.key (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)暴露的任意有效按键名转换为 kebab-case 来作为修饰符。

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

在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。

按键码

使用 keyCode attribute 也是允许的:

<input v-on:keyup.13="submit">

为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

案例

<div id="app">
    <!-- 当按enter键的时候,弹出输入的内容 -->
    <input type="text" @keyup.13="test7">
    <input type="text" @keyup.enter="test7">
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
        },
        methods: {
            test7(event) {
                console.log(event.keyCode)
                alert(event.target.value)
            }
        }
    })
</script>

四、表单输入绑定(双向数据绑定)

基础用法

当用户修改model数据时,页面dom发生变化;

当用户修改页面dom时,model数据发生变化;

v-model是v-on和v-bind两个指令的语法糖(集合指令)

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

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

v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;

  • checkbox 和 radio 使用 checked 属性和 change 事件;

  • select 字段将 value 作为 prop 并将 change 作为事件。

单行文本

<!--
v-text
v-html
    是单向数据绑定,data里面的数据改变了,页面的数据随着改变。
v-model
    双向数据绑定
        data里面的数据改变了,页面的数据随着改变。
        页面数据改变了,data也跟着改变
-->
<!-- 现在有个需求,当用户在input内输入一些值时,在下面的div能将输入的值实时的显示出来 -->
<div id="root">
    <input type="text" v-model="inputMsg" placeholder="请输入内容" />
    <input type="text" v-model="inputMsg" />
    <div id="showDiv">
        {{inputMsg}}
    </div>
</div>
<script type="text/javascript">
    var vm = new Vue({
        //挂载点
        el: "#root", //element,表示页面中的哪个元素需要通过vue进行管理(el一般称之为挂载点)
        //模型层,挂载点可以使用的数据
        data: { //由vue进行管理的数据
            inputMsg: ""
        }
    })
</script>

多行文本

<div id="root">
    <span>多行文本框的内容是:</span>
    <p style="white-space: pre-line;">{{ multilineMsg }}</p>
    <br>
    <textarea v-model="multilineMsg" placeholder="请输入多行文本框内容"
        style="width: 300px;height: 120px;resize: none;"></textarea>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            multilineMsg: ""
        }
    })
</script>

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

复选框

单个复选框,绑定到布尔值:

<div id="root">
    <input type="checkbox" id="checkbox" v-model="checked">
    <label for="checkbox">{{ checked }}</label>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            checked: true
        }
    })
</script>

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

<div id="root">
    <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>
    <br>
    <span>Checked names: {{ checkedNames }}</span>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            checkedNames: []
        }
    })
</script>

单选按钮

<div id="root">
    <input type="radio" id="one" value="One" v-model="picked">
    <label for="one">One</label>
    <br>
    <input type="radio" id="two" value="Two" v-model="picked">
    <label for="two">Two</label>
    <br>
    <span>Picked: {{ picked }}</span>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            picked: ''
        }
    })
</script>

选择框

单选时:

<div id="root">
    <select v-model="selected">
        <option disabled value="">请选择</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
    </select>
    <span>Selected: {{ selected }}</span>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            selected: ''
        }
    })
</script>

如果 v-model 表达式的初始值未能匹配任何选项, <select> 元素将被渲染为“未选中”状态。在 iOS中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

多选时 (绑定到一个数组):

<div id="root">
    <select v-model="selected" multiple style="width: 50px;">
        <option>A</option>
        <option>B</option>
        <option>C</option>
    </select>
    <br>
    <span>Selected: {{ selected }}</span>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            selected: []
        }
    })
</script>

v-for 渲染的动态选项:

<div id="root">
    <select v-model="selected">
        <option v-for="option in options" v-bind:value="option.value">
            {{ option.text }}
        </option>
    </select>
    <span>Selected: {{ selected }}</span>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            selected: 'A',
            options: [{
                text: 'One',
                value: 'A'
            },
            {
                text: 'Two',
                value: 'B'
            },
            {
                text: 'Three',
                value: 'C'
            }
            ]
        }
    })
</script>

修饰符

.lazy

在默认情况下, v-model 在每次 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合

文字时)。你可以添加

修饰符,从而转为在

事件之后进行同步:

.number

如果想自动将用户的输入值转为数值类型,可以给

添加 修饰符:

这通常很有用,因为即使在 时,HTML 输入元素的值也总会返回字符串。如果这个值

无法被

.trim

解析,则会返回原始的值。

如果要自动过滤用户输入的首尾空白字符,可以给 添加 修饰符:

走马灯的效果

点击移动按钮,文字内容向左移动,并将第一个文字放置到文字末端,每0.5秒执行再一次这样的效果;

点击停止按钮,文字停止移动。

提示内容如下:

<div id="root">
    <!-- 给按钮添加事件 -->
    <button type="button" @click="start">开始走马灯</button>
    <button type="button" @click="stop">停止走马灯</button>
    <h3>{{msg}}</h3>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            msg: "人无我有人有我优",
            interId: ""
        }
    })
</script>

分析实现:

1、给开始按钮绑定点击事件

2、在按钮的时间处理程序中,写业务逻辑代码:拿到msg的字符串,然后截取第一个字符,再放到字符串最后面;

3、使用setInterval()存放第二部的业务代码,反复执行

代码实现:

<div id="root">
    <!-- 给按钮添加事件 -->
    <button type="button" @click="start">开始走马灯</button>
    <button type="button" @click="stop">停止走马灯</button>
    <h3>{{msg}}</h3>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            msg: "人无我有人有我优",
            interId: ""
        },
        methods: {
            start() {
                this.interId = setInterval(() => {
                    var firstStr = this.msg.substr(0, 1);
                    var lastStr = this.msg.substr(1);
                    this.msg = lastStr + firstStr;
                }, 500)
            },
            stop() {
                clearInterval(this.interId)
            }
        }
    })
</script>

五、Class 与 Style 绑定

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

绑定 HTML Class

对象语法

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

<div id="root">
    <!--
    样式类名为对象名称,值为boolean类型。true表示样式类生效,false表示样式类无效
    样式类名中有特殊符号需要使用引号包裹
    -->
    <div v-bind:class="{ active: isActive }"></div>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            isActive: true
        }
    })
</script>

上面的语法表示 active 这个 class 存在与否将取决于数据属性 isActive 的 真假。

你可以在对象中传入更多字段来动态切换多个 class。此外, v-bind:class 指令也可以与普通的 class attribute 共存。当有如下模板:

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

和如下 data:

data: {
    isActive: true,
    hasError: false
}

结果渲染为:

<div class="static active"></div>

isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError 的值为true ,class 列表将变为 "static active text-danger"

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

<div v-bind:class="classObject"></div>
data: {
    classObject: {
        active: true,
        'text-danger': false
    }
}

渲染的结果和上面一样。我们也可以在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式:

<div id="root">
    <div v-bind:class="classObject"></div>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            isActive: true,
            error: null
        },
        computed: {
            classObject: function () {
                return {
                    active: this.isActive && !this.error,
                    'text-danger': this.error && this.error.type === 'fatal'
                }
            }
        }
    })
</script>

数组语法

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

<div id="root">
    <!--
    样式类名作为数组中的元素。
    将需要添加在该元素的样式类名以字符串的方式放置在数组中即可。
    -->
    <div v-bind:class="[activeClass, errorClass]"></div>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            activeClass: 'active',
            errorClass: 'text-danger'
        }
    })
</script>

渲染为:

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

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

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

这样写将始终添加 errorClass ,但是只有在 isActive 是 true时才添加 activeClass 。

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

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

用在组件上

这个章节需要你已经对 Vue 组件有一定的了解。当然你也可以先跳过这里,稍后再回过头来看。

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

例如,如果你声明了这个组件:

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

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

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

HTML 将被渲染为:

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

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

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

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

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

使用内联样式

对象语法

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

<div id="root">
    <!--
    对象属性名称为样式属性名,值为样式值,可以为字符串类型、数值等类型
    样式属性名中带有特殊符号,那么需要使用引号包裹或者使用驼峰写法
    -->
    <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">
        Hello EveryOne
    </div>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            activeColor: 'red',
            fontSize: 30
        }
    })
</script>

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

<div v-bind:style="styleObject"></div>
data: {
    styleObject: {
        color: 'red',
        fontSize: '13px'
    }
}

同样的,对象语法常常结合返回对象的计算属性使用。

数组语法

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

<div id="root">
    <div v-bind:style="[baseStyles, overridingStyles]">Hello</div>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            baseStyles: {
                color: 'red',
                fontSize: '30px'
            },
            overridingStyles: {
                fontSize: '14px',
                background: "yellow"
            }
        }
    })
</script>

自动添加前缀

v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform ,Vue.js 会自动侦测并添加相应的前缀。

六、计算和监听属性

计算属性

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

<div id="root">
    {{ message.split('').reverse().join('') }}
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            message: "Hello VUE"
        }
    })
</script>

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

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

基础例子

个人理解:计算得出来一个属性,这个属性会成为data的属性。

<div id="root">
    <p>原始的message: "{{ message }}"</p>
    <p>计算属性得到的反转字符串: "{{ reversedMessage }}"</p>
</div>
<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            message: "Hello"
        },
        computed: {
            // 计算属性的 getter
            reversedMessage() {
                //`this` 指向 vm 实例
                return this.message.split('').reverse().join('')
            }
        }
    })
</script>

这里我们声明了一个计算属性 reversedMessage 。我们提供的函数将用作属性 vm.reversedMessage的 getter 函数:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你可以打开浏览器的控制台,自行修改例子中的 vm。 vm.reversedMessage 的值始终取决于vm.message 的值。

你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于vm.message ,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。

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

<div id="root">
    <p>完整姓名: "{{ fullName }}"</p>
    <p>姓名: "{{ firstName }}" , 名字: "{{lastName}}"</p>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            firstName: '张',
            lastName: '三丰'
        },
        computed: {
            fullName: function () {
                return this.firstName + this.lastName
            }
        }
    })
</script>

计算属性缓存 vs 方法

你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:

<div id="root">
    <p>反转字符串: "{{ reversedMessage() }}"</p>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            message: "Hello"
        },
        // 在组件中
        methods: {
            reversedMessage() {
                //`this` 指向 vm 实例
                return this.message.split('').reverse().join('')
            }
        }
    })
</script>

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

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
    now: function () {
    	return Date.now()
    }
}

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

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

计算属性的 setter

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

<div id="root">
    <p>完整姓名: "{{ fullName }}"</p>
    <p>姓名: "{{ firstName }}" , 名字: "{{lastName}}"</p>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            firstName: '张',
            lastName: '三丰'
        },
        computed: {
            fullName: {
                // getter
                get: function () {
                    return this.firstName + this.lastName
                },
                // setter
                set: function (newValue) {
                    this.firstName = newValue.substr(0, 1);
                    this.lastName = newValue.substr(1);
                }
            }
        }
    })
</script>

现在再控制台运行: vm.fullName = "张晓明",setter 会被调用, vm.firstName 和 vm.lastName 也会相应地被更新。

注:计算属性使用起来不难,但是难点在于你不知道什么时候要使用计算属性;当我们需要的一个值是经过data里的数据计算得到的时候(依赖于data里面的数据),就要使用计算属性;好处是:计算属性关联的数据发生改变了,计算属性也会跟着改变;计算属性有缓存,提高效率;

侦听器

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

当挂载点内部的绑定数据发生变化,如果我们向需要执行一些处理程序才处理业务逻辑,就可以使用vue的侦听器 watch属性。

编码

比如:我们想要在名字和姓氏,加上侦听功能,即当input内容发生变化时,在网页里显示变化的次数。

<div id="root">
    姓氏: <input type="text" v-model="firstName" /> <br>
    名字: <input type="text" v-model="lastName" />
    <p>完整姓名: "{{ fullName }}"</p>
    <p>姓名: "{{ firstName }}" , 名字: "{{lastName}}"</p>
    <p>发生了 {{num}} 次变化</p>
</div>
<script type="text/javascript">
    let vm = new Vue({
        el: "#root",
        data: {
            firstName: '张',
            lastName: '三丰',
            num: 0
        },
        computed: {
            fullName: {
                // getter
                get: function () {
                    return this.firstName + this.lastName
                },
                // setter
                set: function (newValue) {
                    this.firstName = newValue.substr(0, 1);
                    this.lastName = newValue.substr(1);
                }
            }
        },
        //侦听器:侦听数据的变化
        //如果数据发生改变,那么侦听器的侦听函数就自动执行
        //侦听函数:名称与数据保持一致,形参1:data数据变化之后的值,形参2:data数据变化之前的值
        watch: {
            //当name发生改变的时候,执行某种操作
            firstName: function () {
                return this.num++;
            },
            lastName: function () {
                return this.num++;
            }
        }
    })
</script>

七、条件渲染

v-if

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

我们可以给v-if赋予布尔值,来实现html元素的显示和隐藏

比如,如果想给挂载点内的一个元素设置隐藏:

<div id="root">
    <div v-if=false>
        显示或隐藏
    </div>
</div>

其实,直接赋值的作用与下面的结构作用相同:

<div id="root">
    <div v-if="showHide">
        显示或隐藏
    </div>
</div>
<script type="text/javascript">
    let vm = new Vue({
        el: "#root",
        data: {
            showHide:false
        }
    })
</script>

现在我们来实现一个需求:

点击一个按钮,让挂载点内一个元素显示和隐藏:

<div id="root">
    <button type="button" @click="showHideMethod">点击显示/隐藏</button>
    <div v-if="showHide" style="width: 220px;height: 120px;background: red;">
        显示或隐藏
    </div>
</div>
<script type="text/javascript">
    let vm = new Vue({
        el: "#root",
        data: {
            showHide:false
        },
        methods:{
            showHideMethod(){
                this.showHide = !this.showHide
            }
        }
    })
</script>

<template> 元素上使用 v-if 条件渲染分组

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

<template v-if=true>
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
</template>

v-else

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

<div id="root">
    <div v-if="Math.random() > 0.5">
        随机值大于0.5
    </div>
    <div v-else>
        随机值小于等于0.5
    </div>
    
    <p v-if="sex == '男'">
        <input type="radio" name="sex" checked>男
        <input type="radio" name="sex">女
    </p>
    <p v-else>
        <input type="radio" name="sex">男
        <input type="radio" name="sex" checked>女
    </p>
</div>

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

v-else-if

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

<div id="root">
    <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>
    <h1>你的成绩评估结果是:</h1>
    <p v-if="score >=90 && score <= 100"> 优秀 </p>
    <p v-else-if="score >= 75 && score < 90"> 良好 </p>
    <p v-else-if="score >= 60 && score < 75"> 及格 </p>
    <p v-else-if="score < 60 && score > 0"> 不及格 </p>
    <p v-else>成绩无效</p>
</div>
<script type="text/javascript">
    let vm = new Vue({
        el: "#root",
        data: {
            type:'D',
            score: 118
        }
    })
</script>

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

v-show

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

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

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

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

v-if vs v-show

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

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

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

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

v-if 与 v-for 一起使用

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

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

八、列表渲染

当model里有多个数据需要在view里显示时,我可以使用v-for指令来进行操作

用 v-for 把一个数组对应为一组元素

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

<div id="root">
    <!--方法1:常规遍历-->
    <ul>
        <li v-for="item in goodsList">{{item}}</li>
    </ul>
    <!--方法2:使用:key命令,来提升循环效率-->
    <ul>
        <li v-for="item in goodsList" :key="item">{{item}}</li>
    </ul>

    <!--方法3:更为标准的用法,迭代器的体现-->
    <ul>
        <li v-for="(item,index) in goodsList" :key="index">{{item}}</li>
    </ul>
</div>
<script type="text/javascript">
    var vm=new Vue({
        el:"#root",
        data:{
            goodsList:["小米","OPPO","华为","vivo","锤子","金立","魅族","360"]
        }
    })
</script>

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

<div id="root">
    <ul>
            <li v-for="(item,index) of goodsList" :key="item">{{praentMsg}} 第 {{index+1}} 名: {{ item}}</li>
    </ul>
</div>
<script type="text/javascript">
    var vm=new Vue({
        el:"#root",
        data:{
            parentMessage:"热门手机",
            goodsList:["小米","OPPO","华为","vivo","锤子","金立","魅族","360"]
        }
    })
</script>

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

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

在 v-for 里使用对象

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

<div id="root">
    <h3>班级信息:</h3>
    <ul>
        <li v-for="value in classes">
            {{ value }}
        </li>
    </ul>
</div>
<script type="text/javascript">
    var vm=new Vue({
        el:"#root",
        data:{
            classes: {
                title: '前端41班',
                teacher: 'Jimbo',
                beginTime: '2019-10-21'
            }
        }
    })
</script>

你也可以提供第二个的参数为 property 名称 (也就是键名):

<div id="root">
    <h3>班级信息:</h3>
    <ul>
        <li v-for="(value,name) in classes">
            {{name}} - {{ value }}
        </li>
    </ul>
</div>

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

<div id="root">
	<h3>班级信息:</h3>
	<ul>
		<li v-for="(value,name,index) in classes">
		    {{index}} : {{name}} - {{ value }}
		</li>
	</ul>
</div>

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

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

维护状态

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

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

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

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

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

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

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

更多 key attribute 的细节用法请移步至 key 的 API 文档

数组更新检测

由于 JavaScript 的限制,Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

<div id="root">
    <ul>
        <li v-for="(value,index) in items">
            {{index}} - {{ value }}
        </li>
    </ul>
</div>
<script type="text/javascript">
    var vm=new Vue({
        el:"#root",
        data:{
            items: ['a', 'b', 'c']
        }
    })
</script>

通过控制台测试:

vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

变更方法

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

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

你可以打开控制台,然后对前面例子的 items 数组尝试调用变更方法。比如 vm.items.push('D')

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将在响应式系统内触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

案例CRUD

​ 结合插值表达式、事件绑定、循环变量实现简单的CRUD;

<!-- CRUD -->
<div id="app">
    <ul>
        <!-- 查询:把数据遍历到View中 -->
        <li v-for="(person,index) in persons">
            {{ person.name }} - {{ person.age }} -
            <button @click="deletePerson(index)">删除</button>
            <!-- 点击修改按钮的时候,不仅要显示修改界面,还要显示要修改的数据 -->
            <button @click="showAndQuery(index)">修改</button>
        </li>
    </ul>
    <input type="text" v-model="newPerson.name">
    <input type="text" v-model="newPerson.age">
    <button @click="addPerson">添加</button>

    <div v-if="updateStatus">
        <input type="text" v-model="updatePerson.name">
        <input type="text" v-model="updatePerson.age">
        <button @click="updatePersonMethod">提交</button>
    </div>
</div>

<script src="../js/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {
            updateStatus: false,
            updateIndex: '',
            newPerson: {
                name: '',
                age: ''
            },
            updatePerson: {
                name: '',
                age: ''
            },
            persons: [{
                name: 'Tom',
                age: 18
            },
                      {
                          name: 'Jack',
                          age: 17
                      },
                      {
                          name: 'Bob',
                          age: 19
                      },
                      {
                          name: 'Mary',
                          age: 16
                      }
                     ]
        },
        methods: {
            addPerson() {
                this.persons.push({
                    name: this.newPerson.name,
                    age: this.newPerson.age
                })
            },
            deletePerson(delIndex) { // delIndex 形参
                this.persons.splice(delIndex, 1)
            },
            updatePersonMethod() {
                this.persons.splice(this.updateIndex, 1, this.updatePerson);
                this.updatePerson = {
                    name: '',
                    age: ''
                }
                this.updateStatus = false;
            },
            showAndQuery(queryIndex) {
                this.updateIndex = queryIndex;
                this.updatePerson.name = this.persons[queryIndex].name;
                this.updatePerson.age = this.persons[queryIndex].age;
                this.updateStatus = true;
            }
        },
    })
</script>

替换数组

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

vm.items = vm.items.filter(function (item) {
    return item > 18;
})

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

对象更新检测

还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` 现在是响应式的

vm.b = 2
// `vm.b` 不是响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。例如,对于:

var vm = new Vue({
    data: {
        userProfile: {
            name: 'Anika'
        }
    }
})

你可以添加一个新的 age 属性到嵌套的 userProfile 对象:

Vue.set(vm.userProfile, 'age', 27)

显示过滤/排序后的结果

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

例如:

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers: function () {
    return this.numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

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

<ul v-for="set in sets">
    <li v-for="n in even(set)">{{ n }}</li>
</ul>
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
    })
  }
}

使用计算属性简单排序

<div id="root">
    <h1>原始数组</h1>
    <ul>
        <li v-for="n in numbers">{{n}}</li>
    </ul>
    <h1>原数组中偶数的元素(过滤)</h1>
    <ul>
        <li v-for="n in evenNumbers">{{n}}</li>
    </ul>
    <h1>对原数组排序后的结果</h1>
    <ul>
        <li v-for="n in sortNumbers">{{n}}</li>
    </ul>
</div>

<script src="../js/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: "#root",
        data: {
            numbers: [12, 2, 36, 4, 51]
        },
        computed: {
            evenNumbers() {
                // 第一个return是赛选后的数组
                return this.numbers.filter(item => {
                    // return 符合结果的元素
                    return item % 2 === 0;
                })
            },
            sortNumbers() {
                const arr = [...this.numbers];
                return arr.sort(function (a, b) {
                    // 后面的参数减去前面的参数就是降序
                    return b - a;
                })
            }
        },
    })
</script>

综合案例

​ 点击按钮,对数据进行升序或降序排序;根据输入内容检索用户姓名数据;

<div id="app">
    <input type="text" v-model="searchName">
    <ul>
        <li v-for="(p, index) in filterPersons" :key="index">
            {{index}}--{{p.name}}--{{p.age}}
        </li>
    </ul>
    <div>
        <button @click="setOrderType(2)">年龄升序</button>
        <button @click="setOrderType(1)">年龄降序</button>
        <button @click="setOrderType(0)">原本顺序</button>
    </div>
</div>

<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
    new Vue({
        el: '#app',
        data: {
            searchName: '',
            orderType: 0, // 0代表不排序, 1代表降序, 2代表升序
            persons: [{
                name: 'Tom',
                age: 18
            },
                      {
                          name: 'Jack',
                          age: 17
                      },
                      {
                          name: 'Bob',
                          age: 19
                      },
                      {
                          name: 'Mary',
                          age: 16
                      }
                     ]
        },

        computed: {
            filterPersons() {
                // 取出相关数据
                const {
                    searchName,
                    persons,
                    orderType
                } = this
                let arr = [...persons]
                // 过滤数组
                if (searchName.trim()) {
                    arr = persons.filter(p => p.name.indexOf(searchName) !== -1)
                }
                // 排序
                if (orderType) {
                    arr.sort(function (p1, p2) {
                        if (orderType === 1) { // 降序
                            return p2.age - p1.age
                        } else { // 升序
                            return p1.age - p2.age
                        }

                    })
                }
                return arr
            }
        },

        methods: {
            setOrderType(orderType) {
                this.orderType = orderType
            }
        }
    })
</script>

在 v-for 里使用值范围

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

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

<template> 上使用 v-for

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

<ul>
    <template v-for="item in items">
        <li>{{ item.msg }}</li>
        <li class="divider" role="presentation"></li>
    </template>
</ul>

v-for 与 v-if 一同使用

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

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

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

上面的代码将只渲染未完成的 todo。

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

<ul v-if="todos.length">
    <li v-for="todo in todos">
        {{ todo }}
    </li>
</ul>
<p v-else>No todos left!</p>

九、过滤器

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。

​ 功能: 对要显示的数据进行特定格式化后再显示

​ 注意: 并没有改变原本的数据, 可是产生新的对应的数据

基本语法

定义过滤器

你可以在一个组件的选项中定义本地的过滤器(局部):

filters: {
    // 传递过来的value就是管道之前的数据
    filterName: function (value[,arg1,arg2,...]) {
        // 进行一定的数据处理
        return newValue
    }
}
// 比如
filters: {
    capitalize: function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
    }
}

或者在创建 Vue 实例之前全局定义过滤器:

Vue.filter(filterName, function(value[,arg1,arg2,...]){ 
    // 进行一定的数据处理 
    return newValue 
})

// 比如
Vue.filter('capitalize', function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
    // ...
})

当全局过滤器和局部过滤器重名时,会采用局部过滤器。

使用过滤器

过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:

<!-- 在双花括号中 -->
{{ message | capitalize }}

{{ message | capitalize(arg) }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

基本示例

示例一

下面这个例子用到了 capitalize 过滤器:把数据的首字母改为大写;

<div id="app">
    <input type="text" v-model="name"> <br>
    {{name | capitalize}}
</div>

<script type="text/javascript">
    Vue.filter('capitalize', function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
    })
    new Vue({
        el: '#app',
        data: {
            name: ''
        }
    })
</script>

示例二

使用javascript替换字符串中的java,不区分大小写;

<div id="root">
    <h2>1.基本格式</h2>
    <p>{{msg | msgFormat1}}</p>
</div>
<script type="text/javascript">
    //匿名函数的参数:
    //第一个参数是传入的要过滤数据,即原数据。
    //第二个参数开始就是html调用过滤器的时候传入的参数。
    Vue.filter('msgFormat1', function (msg) {
        // return msg.replace('java', 'javascript');  //替换第一个java
        // return msg.replace(/java/g, 'javascript'); //替换全部的java,但是大写开头的Java没有被替换
        return msg.replace(/java/ig, 'javascript'); //不区分大小写全部替换
    });
    var vue = new Vue({
        el: '#root',
        data: {
            msg: '我爱java,java是最热门的语言,Java爱我。'
        }
    });
</script>

传入参数,传入多个参数

单个参数:

<div id="root">
	<h2>2.带参数格式</h2>
	<p>{{msg | msgFormat2('vue')}}</p>
</div>
<script type="text/javascript">
	//动态传递一个匹配字符串
	Vue.filter('msgFormat2',function(msg, pattern){
		return msg.replace(/java/ig, pattern);
	});
	var vue = new Vue({
		el: '#root',
		data: {
			msg: '我爱java,java是最热门的语言,Java爱我。'
		}
	});
</script>

多个参数

<div id="root">
	<h2>3.带多个参数格式</h2>
	<p>{{msg | msgFormat3('python','bigdata')}}</p>
</div>
<script type="text/javascript">
	//其实本质和上面的区别不大
	Vue.filter('msgFormat3',function(msg, pattern1, pattern2){
		return msg.replace(/java/ig, pattern1+'与'+pattern2);
	});
	var vue = new Vue({
		el: '#root',
		data: {
			msg: '我爱java,java是最热门的语言,Java爱我。'
		}
	});
</script>

设置多个过滤器

<div id="root">
    <h2>4.多个匹配器</h2>
    <p>{{msg | msgFormat3('python','bigdata') | msgFormat4}}</p>
</div>
<script type="text/javascript">
    //其实本质和上面的区别不大
    Vue.filter('msgFormat3', function (msg, pattern1, pattern2) {
        return msg.replace(/java/ig, pattern1 + '与' + pattern2);
    });
    Vue.filter('msgFormat4', function (msg) {
        return msg + '技多不压身 - 全局';
    });

    var vue = new Vue({
        el: '#root',
        data: {
            msg: '我爱java,java是最热门的语言,Java爱我。'
        },
        filters: {
            msgFormat4(msg) {
                return msg + '技多不压身 - 局部';
            }
        }
    });
</script>

应用实例

对时间进行格式化显示

<div id="test">
    <h2>显示格式化的日期时间</h2>
    <p>当前时间为: {{time}}</p>
    <p>最完整的时间: {{time | dateString}}</p>
    <p>年月日: {{time | dateString('YYYY-MM-DD')}}</p>
    <p>时分秒: {{time | dateString('HH-mm-ss')}}</p>
</div>

<script type="text/javascript" src="https://cdn.bootcss.com/moment.js/2.22.1/moment.js"></script>
<script>
    // 定义过滤器
    Vue.filter('dateString', function (value, format) {
        return moment(value).format(format || 'YYYY-MM-DD HH:mm:ss');
    })


    new Vue({
        el: '#test',
        data: {
            time: new Date()
        },
        mounted() {
            setInterval(() => {
                this.time = new Date()
            }, 1000)
        }
    })
</script>

十、内置指令与自定义指令

常用内置指令

  • v:text : 更新元素的 textContent
  • v-html : 更新元素的 innerHTML
  • v-if : 如果为 true, 当前标签才会输出到页面
  • v-else-if: 如果为 true, 当前标签才会输出到页面
  • v-else: 如果为 false, 当前标签才会输出到页面
  • v-show : 通过控制 display 样式来控制显示/隐藏
  • v-for : 遍历数组/对象
  • v-on : 绑定事件监听, 一般简写为@
  • v-bind : 强制绑定解析表达式, 可以省略 v-bind
  • v-model : 双向数据绑定
  • v-cloak : 防止闪现, 与 css 配合: [v-cloak]
  • ref : 指定唯一标识, vue 对象通过$refs 属性访问这个元素对象
<div id="example">
    <!-- v-text -->
    <p v-text="content"></p>
    <!-- v-html -->
    <p v-html="content"></p>
    <!-- v-if/v-else -->
    <p v-if="show">show为true显示</p>
    <p v-else>show为false显示</p>
    <!-- v-show -->
    <p v-show="show">show为true显示</p>
    <!-- v-for -->
    <p>
        <span v-for="v in stus">{{v}}</span>
    </p>
    <!-- v-on -->
    <button @click="clickFun">点击事件</button> <br>
    <!-- v-bind -->
    <img :src="imgUrl" alt="" style="width: 100px;"> <br>
    <!-- v-model -->
    <input type="text" v-model="userName"> <br>
    <!-- v-cloak -->
    <p v-cloak>{{content}}</p>
    <!-- ref -->
    <p ref="msg">abcd</p>
    <button @click="hint">提示</button>
</div>

<script type="text/javascript">
    new Vue({
        el: '#example',
        data: {
            content: '<a href="http://www.baidu.com">百度一下</a>',
            show: true,
            stus: ["张三", "李四", "王武"],
            imgUrl: "https://cn.vuejs.org/images/logo.png",
            userName: ""
        },
        methods: {
            clickFun() {
                alert("点击了");
            },
            hint() {
                alert(this.$refs.msg.innerHTML)
            }
        }
    })
</script>

自定义指令

除了核心功能默认内置的指令 (v-modelv-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

基本案例

当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

// 如果想注册局部指令,组件中也接受一个 directives 的选项:
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

// 比如v-text="msg"指令,就是把msg对应的内容显示到页面中;

然后你可以在模板中任何元素上使用新的 v-focus property,如下:

<input v-focus>

钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind:只调用一次,指令与元素解绑时调用。

接下来我们来看一下钩子函数的参数 (即 elbindingvnodeoldVnode)。

img

三者的区别,主要执行时机不同。

img

钩子函数参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • binding:一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

这是一个使用了这些 property 的自定义钩子样例:

<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      'vnode keys: ' + Object.keys(vnode).join(', ')
})

new Vue({
  el: '#hook-arguments-example',
  data: {
    message: 'hello!'
  }
})

结果如下:

name: "demo"
value: "hello!"
expression: "message"
argument: "foo"
modifiers: {"a":true,"b":true}
vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder

案例理解钩子函数

比如自定义一个设置字体颜色的指令,就需要给bind函数里设置。

和js有关的操作,最好放在inserted里,和样式有关的设置,最好放在bind里

<div id="root">
	<input type="text" v-model="msg" v-focus />
	<h2>{{msg}}</h2>
</div>

<script type="text/javascript">
	Vue.directive('focus', {
		// 定义一个指令对象可以提供如下几个钩子函数
		// bind:只调用一次,指令第一次绑定到元素时调用。
		// 给元素设置样式的代码,通常写在bind里面。
		bind: function(el){
			el.style.width = '240px';
			el.style.height = '50px';
			el.style.fontSize = '40px';
		},
		inserted: function(el){
			// inserted:当被绑定的元素插入到 DOM 中时调用。
			// 给元素设置js效果时,通常写在inserted里。
			el.focus();
		},
		update: function(el){
			// update:当绑定的DOM结构刷新时,会执行多次
			console.log(el.value);
		}

	})

	var vue = new Vue({
		el: '#root',
		data:{
			msg: 'hello vuejs'
		}
	});
</script>

应用案例

文本转换

<!--
需求: 自定义2个指令
1. 功能类型于v-text, 但转换为全大写
2. 功能类型于v-text, 但转换为全小写
-->

<div id="test">
    <p v-upper-text="msg"></p>
    <p v-lower-text="msg"></p>
</div>

<div id="test2">
    <p v-upper-text="msg"></p>
    <p v-lower-text="msg"></p>
</div>

<script type="text/javascript">
    // 注册一个全局指令
    // el: 指令所在的标签对象
    // binding: 包含指令相关数据的容器对象
    Vue.directive('upper-text', function (el, binding) {
        console.log(el, binding)
        el.textContent = binding.value.toUpperCase()
    })
    new Vue({
        el: '#test',
        data: {
            msg: "I Like You"
        },
        // 注册局部指令
        directives: {
            'lower-text'(el, binding) {
                console.log(el, binding)
                el.textContent = binding.value.toLowerCase()
            }
        }

    })

    new Vue({
        el: '#test2',
        data: {
            msg: "I Like You Too"
        }
    })
</script>

改变背景

<div id="root">
	<div v-bgcolor="'red'">
		{{msg}}
	</div>
</div>
<script type="text/javascript">
	Vue.directive('bgcolor', {
		bind: function(el, binding){
			el.style.background = binding.value;
		}
	});
	var vue = new Vue({
		el: '#root',
		data: {
			msg: 'hello vuejs'
		}
	});
</script>

//有时候我们不需要其他钩子函数,我们可以简写函数,如下格式:
<div id="root">
	<div v-bgcolor="'red'">
		{{msg}}
	</div>
</div>
<script type="text/javascript">
	Vue.directive('bgcolor', function(el, binding){
		el.style.background = binding.value;
	});
	var vue = new Vue({
		el: '#root',
		data: {
			msg: 'hello vuejs'
		}
	});
</script>

指令函数可接受所有合法的 JavaScript 表达式,以下实例传入了 JavaScript 对象:

<div id="root">
	<div v-bgcolor="{color:'red',background:'yellow',text:'Hello Vue'}">
	
	</div>
</div>
<script type="text/javascript">
	Vue.directive('bgcolor', function(el, binding){
		el.style.color = binding.value.color;
		el.style.background = binding.value.background;
		el.innerHTML = binding.value.text;
	});
	var vue = new Vue({
		el: '#root',
		data: {
			msg: 'hello vuejs'
		}
	});
</script>

定义私有指令

如果需要定义私有指令,则在Vue({})里directive里定义即可。

<div id="root">
	<div v-bgcolor="{color:'red',background:'yellow',text:'Hello Vue'}">
	
	</div>
</div>
<script type="text/javascript">
	var vue = new Vue({
		el: '#root',
		//私有指令
		directives:{
			"bgcolor":{
				bind:function (el,binding){
					el.style.color = binding.value.color;
					el.style.background = binding.value.background;
					el.innerHTML = binding.value.text;
				}
			}
		}
	});
</script>

十一、Vue实例的生命周期

生命周期概述

​ 什么是生命周期,从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件。而这些事件,被称为生命周期

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

比如 created钩子可以用来在一个实例被创建之后执行代码:

new Vue({
  data: {
    a: 1
  },
  created: function () {
    // `this` 指向 vm 实例
    console.log('a is: ' + this.a)
  }
})
// => "a is: 1"

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mountedupdateddestroyed。生命周期钩子的 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 之类的错误。

生命周期图示

下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。

img

生命周期钩子:即生命周期函数,也即是生命周期事件的别名而已。

主要的生命周期函数分类:

​ 创建/初始化阶段的生命周期函数(4个函数):

​ 运行/更新阶段的生命周期函数(2个函数):

​ 销毁阶段的生命周期函数(2个函数):

beforeCreate()

在实例初始化之后,数据观测(data)和事件配置(methods)之前被调用,此时组件的选项对象还未创建,el 和 data 并未初始化,因此无法访问methods, data, computed等上的方法和数据。

结论:beforeCreated生命周期函数:无法访问data,methods,dom中的元素

<div id="root">
    {{msg}}
</div>
<script type="text/javascript">
    new Vue({
        el:"#root",
        data: {
            msg:"vuejs"
        },
        methods:{
            act(){
                console.log(this.msg)
            }
        },
        //beforeCreate()生命周期函数执行的时候,data和methods里面的数据还没有被初始化
        beforeCreate(){
            console.log(this.msg);//undefined
            this.act(); // 报错:this.act is not a function  
        }
    })
</script>

created()

实例已经创建完成之后被调用,在这一步,实例已完成以下配置:数据观测、属性和方法的运算,watch/event事件回调,完成了data 数据的初始化,el没有。 然而,挂载阶段还没有开始, $el属性目前不可见,这是一个常用的生命周期,因为你可以调用methods中的方法,改变data中的数据,并且修改可以通过vue的响应式绑定体现在页面上,获取computed中的计算属性等等,也有一些人喜欢在这里发ajax请求,值得注意的是,这个周期中是没有什么方法来对实例化过程进行拦截的,因此假如有某些数据必须获取才允许进入页面的话,并不适合在这个方法发请求,建议在组件路由钩子beforeRouteEnter中完成。

<div id="root">
	{{msg}}
</div>
<script type="text/javascript">
	new Vue({
		el:"#root",
		data: {
			msg:"vuejs"
		},
		methods:{
			act(){
				console.log(this.msg)
			}
		},
		//created()生命周期函数中,data和methods里面的数据都已经初始化好了
		//如果调用methods里面的方法或操作data的数据,最早也得从created开始.
		created(){
			console.log(this.msg); //vuejs
			this.act(); //vuejs
		}
	})
</script>

beforeMount()

挂载开始之前被调用,相关的render函数首次被调用(虚拟DOM),实例已完成以下的配置: 编译模板,把data里面的数据和模板生成html,完成了el和data 初始化,注意此时还没有挂在html到页面上。

<div id="root">
	{{msg}}
</div>
<script type="text/javascript">
	new Vue({
		el:"#root",
		data: {
			msg:"vuejs"
		},
		methods:{
			act(){
				console.log(this.msg)
			}
		},
		//beforeMount()生命周期函数,表示挂载之前运行,那么挂载点肯定没有应用到数据
		//这个阶段表示模版已经在内存中编辑完成,但是尚未把模版渲染到页面中,DOM中的内容还没被替换,只是写了一些模版字符串
		//可以访问data和methods,无法访问到确切的dom值
		beforeMount(){
			var txt = document.getElementById("root").innerHTML;
			console.log(txt) //{{msg}}
		}
	})
</script>

mounted()

挂载完成,也就是模板中的HTML渲染到HTML页面中,此时一般可以做一些ajax操作,mounted只会执行一次。

只要执行完了mounted,叫表示整个Vue实例已经初始化完毕了,此时组件已经脱离了创建阶段,进入了运行阶段

<div id="root">
	{{msg}}
</div>
<script type="text/javascript">
	new Vue({
		el:"#root",
		data: {
			msg:"vuejs"
		},
		methods:{
			act(){
				console.log(this.msg)
			}
		},
		//表示内存中的编辑好的模版,已经挂载到页面中,用户可以看到渲染到页面的数据啦.
		//mounted()是实例创建周期的最后一个声明周期函数,当执行完mounted表示实例已经创建完毕啦.
		//可以访问data数据和methods里面的方法,也可以获取dom真实数据
		mounted(){
			var txt = document.getElementById("root").innerHTML;
			console.log(txt) //vuejs
		}
	})
</script>

beforeUpdate()

当组件或实例的数据更改之后,会立即执行beforeUpdate,然后vue的虚拟dom机制会重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染,一般不做什么事儿。

组件运行期间,只有两个生命周期函数beforeUpdate和updated。

在beforeUpdate()里的执行时机,界面还没有被更新,数据被更新了吗??当然被更新了,否则也不会激活beforeUpdate();

【案例】修改数据,检测beforeUpdate()执行的时机

<div id="root">
	<p>{{msg}}</p>
	<button type="button" @click="act">点击修改msg的数据</button>
</div>
<script type="text/javascript">
	new Vue({
		el:"#root",
		data: {
			msg:"vuejs"
		},
		methods:{
			act(){
				console.log(this.msg+="!!!!!!")
			}
		},
		//结论: 当执行beforeUpdate()的时候,页面中显示的数据还是旧的,但data的数据是新的,只是还没有渲染到页面中
		beforeUpdate(){
			console.log("data里面的数据:"+this.msg); //data里面的数据:vuejs!!!!!!
			
			var txt = document.getElementById("root").querySelector('p').innerHTML;
			console.log("页面中的数据:"+txt) //页面中的数据:vuejs
		}
	})
</script>

beforeUpdate生命周期函数:

​ 页面刷新,数据没有发生改变,该函数不会执行,只有当data发生改变后,才会进入该函数;

​ 表示data中的数据已经被更新,但是仅仅更新了内存中的vm,还没有更新到dom树;

​ 可以访问data,method,可以访问到最新的值

​ dom中的元素的值,也可以被访问到,但是获取的是改变之前的旧值;

​ vue实例处于运行阶段

updated()

当更新完成后,执行updated,数据已经更改完成,dom也重新render完成。

<div id="root">
	<p>{{msg}}</p>
	<button type="button" @click="act">点击修改msg的数据</button>
</div>
<script type="text/javascript">
	new Vue({
		el:"#root",
		data: {
			msg:"vuejs"
		},
		methods:{
			act(){
				console.log(this.msg+="!!!!!!")
			}
		},
		//结论:当执行updated的时候,data和页面中的数据都是最新的.
		updated(){
			console.log("data里面的数据:"+this.msg); //data里面的数据:vuejs!!!!!!
			var txt = document.getElementById("root").querySelector('p').innerHTML;
			console.log("页面中的数据:"+txt) //页面中的数据:vuejs!!!!!!
		}
	})
</script>

updated生命周期函数:

​ 表示data和dom树的数据都是最新的

​ 可以访问data,methods,dom元素的值可以访问到最新值

​ vue实例处于运行阶段

beforeDestory()和destoryed()

​ 当经过某种途径调用$destroy方法后,立即执行beforeDestroy,一般在这里做一些善后工作,例如清除计时器、清除非指令绑定的事件等等。

​ 当执行beforeDestory()方法时,Vue实例就已经从运行阶段,进入到了销毁阶段;

​ 组件的数据绑定、监听...去掉后只剩下dom空壳,这个时候,执行destroyed,在这里做善后工作也可以

​ 当执行beforeDestory()的 时候,实例身上所有的data和methods以及过滤器、指令等都处于可用状态,此时,还没有真正执行销毁过程。

​ 当执行到destroyed()方法时,Vue组件里的滤器、指令、方法都不可用了。

常用的生命周期方法:

​ created()/mounted(): 发送ajax请求, 启动定时器等异步任务

​ beforeDestory(): 做收尾工作, 如: 清除定时器

生命周期演示案例

​ 演示:页面打开的时候,每隔1s显示或隐藏p元素;

​ 演示:生命周期函数的执行流程

<div id="test">
    <button @click="destroyVue">destory vue</button>
    <p v-if="isShow">新开普教育</p>
</div>

<script type="text/javascript">
    new Vue({
        el: '#test',
        data: {
            isShow: true
        },

        beforeCreate() {
            console.log('beforeCreate()')
        },

        created() {
            console.log('created()')
        },

        beforeMount() {
            console.log('beforeMount()')
        },

        mounted() {
            console.log('mounted()')
            // 执行异步任务
            this.intervalId = setInterval(() => {
                console.log('-----')
                this.isShow = !this.isShow
            }, 1000)
        },


        beforeUpdate() {
            console.log('beforeUpdate()')
        },
        updated() {
            console.log('updated()')
        },


        beforeDestroy() {
            console.log('beforeDestroy()')
            // 执行收尾的工作
            clearInterval(this.intervalId)
        },

        destroyed() {
            console.log('destroyed()')
        },

        methods: {
            destroyVue() {
                this.$destroy()
            }
        }
    })
</script>

十二、用户管理系统

创建模板页面

页面效果图

静态页面实现

<!doctype html>
<html>

	<head>
		<title>页面标题</title>
		<meta charset="utf-8">
		<!-- 为了适配移动端,如果想要使用media query兼容移动端,必须添加这句话 -->
		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
		<!-- 为了IE9以下的浏览器兼容HTML5和CSS3 -->
		<!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
    <![endif]-->
		<!-- 让IE浏览器用最新的文档模式来渲染页面 -->
		<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
		<!-- 引入bootstrap提供的样式文件,字体图标文件不需要引入,因为字体图标文件是用来被CSS使用的 -->
		<link rel="stylesheet" href="css/bootstrap.min.css">

		<!-- 引入jQuery文件,而且jQuery文件必须在1.9版本以上,因为Bootstrap提供的动画效果是基于jQuery来写的 -->
		<script src="js/jquery.min.js"></script>

		<!-- 引入bootstrap.min.js -->
		<script src="js/bootstrap.min.js"></script>

		<!-- 引入vue.js -->
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	</head>

	<body>
		<!-- 定义Bootstrap容器 -->
		<div id="root" class="container">
			<!-- 定义系统标题 -->
			<h1 v-cloak>用户管理系统</h1>

			<!-- 定义添加员工 -->
			<div class="panel panel-primary">
				<div class="panel-heading">
					<h3 class="panel-title">添加用户</h3>
				</div>
				<div class="panel-body form-inline">
					<div class="col-md-7">
						<div class="form-group">
							<label for="userId">用户编号:</label>
							<input type="text" class="form-control" id="userId" placeholder="请输入用户编号">
						</div>
						<div class="form-group">
							<label for="userName">用户姓名:</label>
							<input type="text" class="form-control" id="userName" placeholder="请输入用户姓名">
						</div>
						<button type="submit" class="btn btn-info">添加</button>
					</div>
					<div class="col-md-4 col-md-offset-1">
						<div class="form-group">
							<label for="search" class="sr-only">搜索:</label>
							<input type="search" class="form-control" id="search" placeholder="搜索">
						</div>
						<button type="submit" class="btn btn-primary">搜索</button>
					</div>
				</div>
			</div>

			<!-- 定义表格 -->
			<table class="table table-bordered table-striped table-hover">
				<thead>
					<tr class="bg-primary">
						<th>用户编号</th>
						<th>用户姓名</th>
						<th>创建时间</th>
						<th>操作</th>
					</tr>
				</thead>
				<tbody>
					<tr>
						<td>1001</td>
						<td>张三</td>
						<td>2019-12-22 09:44:02</td>
						<td>
							<button type="button" class="btn btn-warning btn-xs">修改</button>
							<button type="button" class="btn btn-danger btn-xs">删除</button>
						</td>
					</tr>
					<tr>
						<td>1002</td>
						<td>李四</td>
						<td>2018-05-22 22:12:52</td>
						<td>
							<button type="button" class="btn btn-warning btn-xs">修改</button>
							<button type="button" class="btn btn-danger btn-xs">删除</button>
						</td>
					</tr>
					<tr>
						<td>1003</td>
						<td>王武</td>
						<td>2017-08-22 09:44:02</td>
						<td>
							<button type="button" class="btn btn-warning btn-xs">修改</button>
							<button type="button" class="btn btn-danger btn-xs">删除</button>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
	</body>
</html>

创建一个html+vue的模板页面

<!doctype html>
<html>

	<head>
		<title>页面标题</title>
		<meta charset="utf-8">
		<!-- 为了适配移动端,如果想要使用media query兼容移动端,必须添加这句话 -->
		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
		<!-- 为了IE9以下的浏览器兼容HTML5和CSS3 -->
		<!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
    <![endif]-->
		<!-- 让IE浏览器用最新的文档模式来渲染页面 -->
		<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
		<!-- 引入bootstrap提供的样式文件,字体图标文件不需要引入,因为字体图标文件是用来被CSS使用的 -->
		<link rel="stylesheet" href="css/bootstrap.min.css">

		<!-- 引入jQuery文件,而且jQuery文件必须在1.9版本以上,因为Bootstrap提供的动画效果是基于jQuery来写的 -->
		<script src="js/jquery.min.js"></script>

		<!-- 引入bootstrap.min.js -->
		<script src="js/bootstrap.min.js"></script>

		<!-- 引入vue.js -->
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	</head>

	<body>
		<!-- 定义Bootstrap容器 -->
		<div id="root" class="container">
			<!-- 定义系统标题 -->
			<h1 v-cloak>{{system_title}}</h1>

			<!-- 定义添加员工 -->
			<div class="panel panel-primary">
				<div class="panel-heading">
					<h3 class="panel-title">添加用户</h3>
				</div>
				<div class="panel-body form-inline">
					<div class="col-md-7">
						<div class="form-group">
							<label for="userId">用户编号:</label>
							<!-- 把用户编号输入框和data中的userId进行双向绑定 -->
							<input type="text" class="form-control" v-model="userId" id="userId" placeholder="请输入用户编号">
						</div>
						<div class="form-group">
							<label for="userName">用户姓名:</label>
							<!-- 把用户姓名输入框和data中的userName进行双向绑定 -->
							<input type="text" class="form-control" v-model="userName" id="userName" placeholder="请输入用户姓名">
						</div>
						<!-- 当我们点击按钮的时候,执行addUser方法,添加员工无非就是把data里面的userId和userName获取到,加上获取当前系统时间 -->
						<button type="submit" class="btn btn-info" @click="addUser">添加</button>
					</div>
					<div class="col-md-4 col-md-offset-1">
						<div class="form-group">
							<label for="search" class="sr-only">搜索:</label>
							<!-- 把搜索框和data里面的searchValue进行双向绑定 -->
							<input type="search" class="form-control" v-model="searchValue" id="search" placeholder="搜索">
						</div>
						<!-- 当我们点击搜索按钮的时候,根据输入的值进行查询 -->
						<button type="submit" class="btn btn-primary" @click="searchUser(searchValue)">搜索</button>
					</div>
				</div>
			</div>

			<!-- 定义表格 -->
			<table class="table table-bordered table-striped table-hover">
				<thead>
					<tr class="bg-primary">
						<th>用户编号</th>
						<th>用户姓名</th>
						<th>创建时间</th>
						<th>操作</th>
					</tr>
				</thead>
				<tbody>
					<!-- 使用循环渲染得到data中userList的数据 -->
					<tr v-for="(item,index) of userList" :key="item.userId">
						<!-- 获取userList每个对象的具体数据 -->
						<td>{{item.userId}}</td>
						<td>{{item.userName}}</td>
						<td>{{item.createTime}}</td>
						<td>
							<!-- 点击修改按钮的时候,把当前对象数据绑定作为参数对象传递给函数 -->
							<button type="button" class="btn btn-warning btn-xs" data-target="#updateModal" data-toggle="modal" @click="updateUser(item)">修改</button>
							<!-- 当我们点击删除按钮的时候,根据传入的id删除对应数据 -->
							<button type="button" class="btn btn-danger btn-xs" @click="deleteUser(item.userId)">删除</button>
						</td>
					</tr>
				</tbody>
			</table>
			
			
			<!-- 修改学生的模态框 -->
			<div class="modal fade" id="updateModal" >
				<div class="modal-dialog" role="document">
					<div class="modal-content">
						<div class="modal-header">
							<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
							<h4 class="modal-title" id="myModalLabel">修改学生</h4>
						</div>
						<div class="modal-body form-horizontal">
							<div class="form-group">
							    <label for="updateId" class="col-sm-2 control-label">用户编号:</label>
							    <div class="col-sm-10">
									<p class="form-control-static">{{updateItem.userId}}</p>
							    </div>
							  </div>
							  <div class="form-group">
							    <label for="updateName" class="col-sm-2 control-label">用户姓名:</label>
							    <div class="col-sm-10">
							      <input type="text" class="form-control" v-model="updateItem.userName" id="updateName" placeholder="Password">
							    </div>
							  </div>
						</div>
						<div class="modal-footer">
							<button type="button" class="btn btn-primary" data-dismiss="modal">确认修改</button>
						</div>
					</div>
				</div>
			</div>
		</div>

		

		<script type="text/javascript">
			var vm = new Vue({
				el: "#root",
				data: {
					system_title: "用户管理系统",
					userId: "",
					userName: "",
					searchValue: "",
					userList: [{
							userId: 1001,
							userName: "张三",
							createTime: new Date()
						},
						{
							userId: 1002,
							userName: "李四",
							createTime: new Date()
						},
						{
							userId: 1003,
							userName: "王武",
							createTime: new Date()
						}
					],
                    updateItem: {
                        userId:"",
						userName:""
                    }
				},
				methods: {
					addUser() {
						console.log("添加用户!")
					},
					deleteUser(id) {
						console.log("删除用户:" + id)
					},
					updateUser(id) {
						console.log("修改用户:" + id)
					},
					searchUser(str) {
						console.log("查询用户:" + str)
					}
				}
			})
		</script>
	</body>
</html>

添加用户的功能

1、 为添加按钮绑定事件处理程序
2、 创建新用户对象
3、 将新用户对象添加到用户列表内
4、 同时清空两个输入表单的数据

addUser() {
	// console.log("添加用户!")
	let newUser = {
		userId: this.userId,
		userName: this.userName,
		createTime: new Date()
	}
	this.userList.push(newUser); 
	this.userId = this.userName = "";
}

使用过滤器格式化日期:

<!-- 使用循环渲染得到data中userList的数据 -->
<tr v-for="(item,index) of userList" :key="item.userId">
	<!-- 获取userList每个对象的具体数据 -->
	<td>{{item.userId}}</td>
	<td>{{item.userName}}</td>
	<td>{{item.createTime|dateFormate}}</td>
	<td>
		<!-- 点击修改按钮的时候,把当前对象数据绑定给data里面的updateItem上,然后弹出模态框,在模态框中得到updateItem的数据 -->
		<button type="button" class="btn btn-warning btn-xs" data-target="#updateModal" data-toggle="modal" @click="updateItem=item">修改</button>
		<!-- 当我们点击删除按钮的时候,根据传入的id删除对应数据 -->
		<button type="button" class="btn btn-danger btn-xs" @click="deleteUser(item.userId)">删除</button>
	</td>
</tr>
Vue.filter("dateFormate",function(dateStr){
	var dt = new Date(dateStr);
	var y = dt.getFullYear();
	var m = dt.getMonth()+1;
	var d = dt.getDate();
	m<10?m+="0":m;
	d<10?d+="0":d;
	return `${y}-${m}-${d}`
})

删除用户的功能

1、 为删除按钮绑定事件处理程序

2、 遍历用户列表,根据传入处理程序的参数id找到要删除的元素进行删除

deleteUser(id) {
	console.log("删除用户:" + id)
	var userList=this.userList;
	for(let i=0;i<userList.length;i++){
		if(userList[i].userId==id){
			var tf = confirm("是否删除?");
			if(tf){
				userList.splice(i,1);
			}
		}
	}
}

修改用户功能

思路:

点击修改按钮,将选中的行的数据,存到临时数据updateItem里,如果点击确认,updateItem的替换原来list里的数据,并清空updateItem,否则只请求空updateItem。

同时要将修改数据里的表单和updateItem双向绑定。

模态框添加两个点击事件,就要有两个方法。

模态框效果

<!-- 修改学生的模态框 -->
<div class="modal fade" id="updateModal" >
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close" @click="updateCancel"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title" id="myModalLabel">修改学生</h4>
            </div>
            <div class="modal-body form-horizontal">
                <div class="form-group">
                    <label for="updateId" class="col-sm-2 control-label">用户编号:</label>
                    <div class="col-sm-10">
                        <p class="form-control-static">{{updateItem.userId}}</p>
                    </div>
                </div>
                <div class="form-group">
                    <label for="updateName" class="col-sm-2 control-label">用户姓名:</label>
                    <div class="col-sm-10">
                        <input type="text" class="form-control" v-model="updateItem.userName" id="updateName" placeholder="Password">
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-primary" data-dismiss="modal" @click="updateSure">确认修改</button>
            </div>
        </div>
    </div>
</div>

实现逻辑

updateUser(item) {
	this.updateItem.userId = item.userId;
	this.updateItem.userName = item.userName;

},
updateCancel(){
	console.log("取消修改");
	this.updateItem.userId = "";
	this.updateItem.userName = "";
},
updateSure(){
	console.log("确认修改",this.updateItem.userId,this.updateItem.userName)
	var updateId = this.updateItem.userId;
	var userList=this.userList;
	for(let i=0;i<userList.length;i++){
		if(userList[i].userId==updateId){
			userList[i].userId = this.updateItem.userId;
			userList[i].userName = this.updateItem.userName;
		}
	}
	this.updateItem.userId = "";
	this.updateItem.userName = "";
},

查询用户功能

绑定搜索功能:

<div class="col-md-4 col-md-offset-1">
    <div class="form-group">
        <label for="search" class="sr-only">搜索:</label>
        <!-- 把搜索框和data里面的searchValue进行双向绑定 -->
        <input type="search" class="form-control" v-model="searchValue" id="search" placeholder="搜索">
    </div>
    <!-- 当我们点击搜索按钮的时候,根据输入的值进行查询 -->
    <button type="submit" class="btn btn-primary" @click="searchUser(searchValue)">搜索</button>
</div>


<!-- 使用循环渲染得到data中searchUser(searchValue)得到的数据 -->
<tr v-for="(item,index) of searchUser(searchValue)" :key="item.userId">
    <!-- 获取userList每个对象的具体数据 -->
    <td>{{item.userId}}</td>
    <td>{{item.userName}}</td>
    <td>{{item.createTime|dateFormate}}</td>
    <td>
        <!-- 点击修改按钮的时候,把当前对象数据绑定给data里面的updateItem上,然后弹出模态框,在模态框中得到updateItem的数据 -->
        <button type="button" class="btn btn-warning btn-xs" data-target="#updateModal" data-toggle="modal" @click="updateUser(item)">修改</button>
        <!-- 当我们点击删除按钮的时候,根据传入的id删除对应数据 -->
        <button type="button" class="btn btn-danger btn-xs" @click="deleteUser(item.userId)">删除</button>
    </td>
</tr>

实现js功能:

searchUser(str) {
    console.log("查询用户:" + str)
    let userList = this.userList;
    //查询到的结果
    let searchList = [];
    for(let i=0;i<userList.length;i++){
        if(userList[i].userName.indexOf(str) != -1){
            searchList.push(userList[i]);
        }
    }
    console.log(searchList)
    return searchList;
}

绑定按键功能

【在项目里为添加数据的按钮新增功能】

img

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>用户管理</title>
        <link href="bootstrap-3.3.7-dist/css/bootstrap.css" rel="stylesheet">
        <!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
    </head>
    <body>
        <div id="app">
            <h1 v-text="tit"></h1>
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">添加用户</h3>
                </div>
                <div class="panel-body form-inline input-group-sm">
                    <label for="id">
                        用户ID:<input type="text" id="id" class="form-control" v-model="id" />
                    </label>
                    <label for="name">
                        用户姓名:<input type="text" id="name" class="form-control" v-model="name" @keyup.enter="addUser"/>
                    </label>
                    <input type="button" class="btn btn-info"  id="" value="添加" @click="addUser" >
                    <div class="input-group">
                        <input type="text" class="form-control" v-model="sv" placeholder="搜索" v-focus>
                        <span class="input-group-btn">
                            <button class="btn btn-primary" type="button" @click="search(sv)" >搜索</button>
                        </span>
                    </div><!-- /input-group -->
                </div>
            </div>
            <table class="table table-bordered table-striped table-hover">
                <thead>
                    <tr class="bg-primary">
                        <th>ID</th><th>姓名</th><th>创建时间</th><th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(items,index) of search(sv)" :key="items.id">
                        <td v-text="items.id"></td>
                        <td v-text="items.name"></td>
                        <td v-cloak>
                            <!--{{items.ctime}}-->
                            {{ items.ctime | dateFormat('YY-mm-dd') }}
                        </td>
                        <td>
                            <button class="btn btn-warning btn-xs" data-target="#update" data-toggle="modal"
                                    @click="editItem=items"
                                    >修改</button>
                            <!--<button class="btn btn-danger btn-xs" @click="delUser(items.id-1)">删除</button>-->
                            <!--data-target="#deldata" 点击按钮指向id符合条件的元素--> 
                            <!--data-toggle="model" 设置显示效果为modal类型-->
                            <button class="btn btn-danger btn-xs" data-target="#deldata" data-toggle="modal"
                                    @click="nowId=items.id">删除</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
        <script src="js/vue.js"></script>
        <script src="js/jquery-1.11.1.js"></script>
        <script src="bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
        <script type="text/javascript">

            var vm=new Vue({
                el:"#app",
                data:{
                    tit:"用户管理系统",
                    id:"",
                    name:"",
                    sv:"",
                    list:[
                        {id:1,name:"孙龙",ctime:new Date()},
                        {id:2,name:"赵磊",ctime:new Date()},
                        {id:3,name:"赵峰",ctime:new Date()}
                    ],
                    nowId:"",
                    editItem:{}
                },
                methods:{
                    addUser(){
                    },
                    delUser(id){
                    },
                    updateUser(id){
                    },

                    search(str){								
                    }
                }
            })
        </script>
    </body>
</html>
posted @ 2023-11-04 17:29  城市炊烟  阅读(88)  评论(0编辑  收藏  举报