Vue.js前端开发基础与项目实战
人民邮电出版社
北京
图书在版编目(CIP)数据
Vue.js前端开发基础与项目实战/郑韩京编著.--北京:人民邮电出版社,2020.4
ISBN 978-7-115-53210-7
Ⅰ.①V… Ⅱ.①郑… Ⅲ.①网页制作工具—程序设计 Ⅳ.①TP392.092.2
中国版本图书馆CIP数据核字(2019)第291754号
◆编著 郑韩京
责任编辑 张天怡
责任印制 王郁 马振武
◆人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
三河市君旺印务有限公司印刷
◆开本:787×1092 1/16
印张:19.25
字数:475千字 2020年4月第1版
印数:1-2500册 2020年4月河北第1次印刷
定价:69.00元
读者服务热线:(010)81055410 印装质量热线:(010)81055316
反盗版热线:(010)81055315
广告经营许可证:京东工商广登字20170147号
本书以项目实战的方式引导读者渐进式学习 Vue.js。本书从 Vue.js 的基础语法讲起,然后介绍ES6的语法规范,最后通过项目构建、项目部署介绍 Vue.js 项目开发的全套流程。本书内容侧重于Vue.js项目实战开发中的组件复用、代码解耦等操作,读者不但可以系统地学习Vue.js的相关知识,而且能对Vue.js的开发应用有更为深入的理解。
本书分为基础准备篇和项目实战篇。基础准备篇主要介绍 Vue.js 的核心功能,包括但不限于Vue.js的语法与组件、ES6的语法规范、前后端项目框架的构建、数据库及其相关操作。项目实战篇主要以网页版知乎为例讲解实战开发流程与方法,所涉及的项目分析、开发流程、项目部署等内容可帮助读者融会贯通地应用所学知识。阅读本书,读者能够掌握Vue.js框架主要 API 的使用方法、组件开发、前后端项目联调等内容。
本书示例丰富、侧重实战,适用于刚接触或即将接触Vue.js的开发者,也适用于对Vue.js有过开发经验,但需要进一步提升的开发者。
Foreword
刚开始接触Vue.js框架时,我就被其轻量、组件化和友好的应用程序接口(Application Programming Interface,API)所吸引。之后经过深入研究与实际开发,更加意识到Vue.js的奇妙。再后来研究了其他MVVM框架,几经对比后,发现Vue.js依然是最适合初学者学习的MVVM框架之一,其学习成本低、效果好,是前端开发的不二之选。
我将多年的知识积累与实际开发经验浓缩成这本书,从简到难,并且通过实际开发的案例来分析,深入浅出、图文并茂,力求将枯燥的知识用诙谐幽默、浅显直白的方式叙述出来。本书抛开了冗余难懂的理论化内容,除实战需要用到的必备知识外,没有其他多余的内容,绝不贪多求全,尤其强调实际操作、快速上手,并且侧重点绝不是展示示例(Demo),而是更注重实战开发——从如何分析涉及项目、如何构建项目框架到项目实际开发、数据库配置、后端接口的配置,读者学到的都是项目开发中所需要的知识,项目构建的整体过程。
本书主要分为基础准备篇与项目实战篇,其中基础准备篇涵盖了项目开发需要的各种内容与工具,并做了详细的讲解。
1.前端历史的介绍
“以史为镜,可以知兴替”。从前端最开始的故事讲起,介绍这20多年来前端的发展。讲解前端如何从静态网页到如今的单页面应用,其间不断的革新是由无数人一步一个脚印走出来的,发展的艰辛可想而知。了解前端的历史的目的,主要就是明确前端的发展方向,以便有的放矢地学习。
2. Vue.js基础知识的介绍
这部分主要介绍了后期实战所需用到的Vue.js相关知识,从两个简单的Demo先了解Vue.js的特性,之后根据这些特性来逐步分析学习Vue.js的知识。此外,还单独列出一章,讲解Vue.js组件的相关知识,不仅仅是因为组件知识比较重要,还因为组件知识的内容也比较复杂,尤其是组件之间信息的传递,相对来说比较复杂。
3. ES6语法介绍
后期实战代码中较多使用了ES6语法,而ES6与前代ES5语法区别较大,若不加以讲解,可能有些读者无法理解,有可能严重影响开发进度。书中详细讲解ES6的重点特性,并辅以例子方便读者理解。
4.前后端项目框架构建
为项目实战做好基础工作,在脚手架工具的基础上二次开发,得到了具备一定程度上自动化解析代码的方法,为项目的开发提供了很好的底层内容。
第2篇为项目实战篇,以网页版知乎为例,介绍如何实现主要的增、删、改、查功能,帮助读者了解项目实际开发中需要经历的流程,并且熟练使用Vue.js来构建项目。
1.基础部分的开发
本部分介绍页面整体框架与用户登录、登出功能的实现方法,如何使用Cookies存储用户登录信息以及页面相应的展示状态,并对项目的逻辑层也进行简单介绍。
2.文章问题回答等一系列内容的增、删、改、查
实战项目的主要内容,从增、删、改、查的角度为读者介绍前后端的操作逻辑,让读者对项目的整体流程有一个更立体的认识,能熟练使用Vue.js对数据进行各种形式的展示及灵活调用组件。本部分对代码的解耦和复用性也做了一定程度上的介绍,帮助读者养成更好的开发习惯,促进职业道路的发展。
3.个人信息的展示与修改
对整个项目的展示与修改,通过规范化配置与组件的灵活调用,力求用最少的代码实现此功能,同时对整个项目的内容进行梳理,对每种类型的内容的存在与意义有了更深刻的认识。
4.前后端项目的部署
从购买服务器、配置服务器到项目的实际部署,一步步教会读者独立部署项目,并且介绍相关工具的使用,扩展读者的知识面,让读者对开发有更长远的认识。
本书读者对象
本书包括基础内容和实战项目,适用于刚接触Vue.js的前端或后端开发者。当然,有一定Vue.js开发经验的读者也能从中收获不少实战经验。
在开始讲解技术之前,首先需要了解一下关于前端开发的一些事——前端开发的历史、前端的发展过程、前端的现状等。这些知识并非毫无用处,因为只有对前端有了更好的理解,我们对整个开发过程的认识才能更加深刻,对前端的功能改善与整体项目的布局才会有更好的想法。
本章主要涉及的知识点如下。
前端开发的历史
前端的三大框架
前端开发工具的日常使用
我们知道,历史有其独特性,是无法抹去的。只有牢记历史,才能更好地面对未来。前端出现的时间不长,迄今为止只有20多年。但这20多年间,前端的变化可以说是翻天覆地的。
1994年秋天,网景推出了第一版Navigator,也是这个秋天,万维网(World Wide Web, W3C)在麻省理工学院计算机科学实验室成立。同年,CSS的概念被提了出来(之前只有RRP)。几个月后,一个加拿大人为了追踪访问他个人主页的用户的数据,开发出PHP的前身。以上种种事件的发生,宣告着前端正式出现在人们的视野中。
万维网创建的初衷是为了方便欧洲核子研究组织的科学家们查看文档、上传论文。这也就解释了为什么Web网页都是基于Document的。Document就是用“标记语言+超链接”写成的由文字和图片构成的HTML页面,这样的功能已经完全可以满足学术交流的需要,所以网页的早期形态和Document一样,完全基于HTML页面,并且所有内容都是静态的。这也解释了为什么原生JavaScript(简称“JS”)用document.getElementBy获取页面元素。
最开始的网页在很多方面都受到限制,当时JS还没有出现,没有任何手段可以对页面进行修改,就连最简单的显示或隐藏都做不到,所以,不管网页之间的变化有多小,只要有变化,就要重新加载一个页面。同时本地无法对数据进行任何操作,所有计算都是在服务端完成的。虽说这些在现在看来,可能只是些很好解决的小问题,但是当时的网络运行不流畅,网速与现在相比完全没有可比性。所以,在当时用户提交一个表单后,屏幕首先会出现一片雪白,经过漫长的等待,可能返回一个一模一样的页面,只是在输入框下面出现了一行红字——用户名或密码输入错误!
除此之外,纯静态页面还带来了另外一个问题:例如一个电商平台有1000种商品,就算布局一模一样,但因为商品不同,还是要写1000个页面,即使是修改其中某个商品,困难都不敢想象。
退一步说,就算网速提高了,但是服务器也受不了这种任何数据计算都要请求的情况,不仅是数据的存储,其数据的处理和返回也对服务器有着极大的要求。所以,前端的数据处理和修改文档对象模型(Document Object Model,DOM)元素的能力真的很重要。
因此,JS于1995年应运而生,它不仅实现了客户端的计算任务,而且减轻服务器压力的用时,降低了网速慢带来的限制。1996年,微软又推出了iframe标签,实现了局部的异步加载。1999年,XMLHttpRequest技术出现,谷歌使用其开发了Gmail和谷歌地图之后, XMLHttpRequest获得了巨大的关注。2006年,XMLHttpRequest被W3C正式纳入标准,同时有了新的名字——Ajax。
Ajax的出现不仅解决了早期前端的众多问题,同时将我们从Web网页时代带到了Web应用时代,也就是常说的Web 2.0时代,同时提出了前后端分离的概念。
Web网页时代与Web应用时代的区别是十分巨大的:在Web网页时代,网页都是服务端渲染的,服务器先渲染出HTML页面,之后糅合JS和CSS文件,再发送给浏览器,浏览器解析这个类似文档的内容,展示给用户。如此便把所有压力都放在服务器,客户端只负责解析服务器返回的文档。但是在Web应用时代,客户端可以自己对数据进行处理,并且做出相应的渲染。不仅分摊了服务器的压力,同时由于数据量的减少,页面的反馈速度也提升了,缺点就是对机能提出了一定的要求,但随着计算机性能的不断提高,这点要求变得微不足道。
在Web 2.0时代,网页在某种程度上被当作一个App,浏览器就是运行这个App的容器,在这个App里,前端会对数据进行很多操作,只要不对数据进行永久化修改,就无须请求服务,这使网页独立成为一个整体。
jQuery的出现促进了Web 2.0时代的进步,其优雅的语法、符合直觉的事件驱动型的编程思维使其极易上手,因此很快风靡全球,大量基于jQuery的插件构成了一个庞大的生态系统,更加稳固了jQuery作为JS“库一哥”的地位。
同样,谷歌的V8引擎也搭了把手,因为即使有了jQuery,若是浏览器的解析不到位,依然会制约JS的使用。但V8的出现彻底解决了这个问题,也终止了当时微端对浏览器的垄断。有了V8引擎,浏览器可以更好地对JS进行解析,前端机能一下变得过剩。同时ES5也发布出来,前端整体的发展环境得到了很大的提高,迈入了一个崭新的时代。
在所有准备工作都做好的时候,各大框架出现了,从2009年的AngularJS,到2010年的backbone.js,再到2014年的React、Ember、Vue.js等,这些框架增进了前后端分离的进程。
前后端分离使得前端工程师可以更加专注地开发前端功能,同时避免了前后端共同开发的一些分歧。在前后端分离的架构中,后端只负责按照约定的数据格式为前端提供可调用的API服务即可。前后端之间通过HTTP请求进行交互,前端获取数据后进行组装和渲染,最终展示在浏览器上。
前后端分离的代码库也进行了一定的操作,代码组织方式如图1.1所示。
在前后端没有分离的时代,前端工程师进行开发的时候,必须把整个项目都导入开发工具中,页面中可能夹杂了些许后端代码,使项目的修改变得十分复杂,一不小心就可能造成不可预料的后果。
有些前端工程师不满足于仅仅涉猎前端,但后端语言学习的成本又很高,那怎么办呢?Node.js是一个不错的选择。2009年,Node.js出现了,它是一个基于V8引擎的服务端JS运行环境,类似于一个虚拟机,也就是说,JS在服务端语言中占据了一席之地。至此,仅凭JS一门语言就可以开发整个系统了。
其实Web 2.0的眼光更加长远,其更多的是放在替代传统软件上。Web应用相比传统的应用有着太多的好处——无须针对系统来开发不用的版本,无须安装,无须审核,无须升级等。其中最大的好处就是降低了软件的开发成本。虽然理想很好,但是当前制约Web应用的因素也有很多。例如浏览器的处理速度跟不上,对系统功能的调用不完善等。但是目前这些弊端正在渐渐消失,系统权限正在逐步开发出来,Luminosity API、Orientation API、Camera API等日渐完善。谷歌为了促进Web应用的发展,推出了Chromebook,在此款笔记本中,谷歌浏览器(Chrome)被整合到系统中,并且系统中只有谷歌浏览器这一个应用,用户的所有操作都是在谷歌浏览器中完成的,这足以证明Web应用是完全可行的。
除了Chromebook,还有很多程序可以证明Web应用正在逐渐替代传统的应用,最有名的应当是微信小程序,开发者只需要在自己的App中嵌入Weex的SDK,就可以通过撰写HTML/CSS/JS来开发Native级别的Weex界面。Weex界面的生成码就是一段很小的JS,可以像发布网页一样轻松地部署在服务端,然后在App中请求执行。目前,Web应用时代正在如火如荼的发展,相信不久的将来,真的会如阮一峰所说:“未来只有两种工程师——端工程师(PC端、手机端、TV端、VR端……)和云工程师”。
在开始讲解本节内容之前,先举一个例子,如图1.2所示。
这是一个很简单的计数器,单击“减”按钮,数字就会减1;单击“加”按钮,数字就会加1。
接下来需要知道的是,在MV系列框架中,M和V指Model层和View层,但是其功能会因为框架的不同而变化。Model层很好理解,就是存储数据;View层则是展示数据,读者能看见这个例子,完全就是因为存在View层。虽然在不同的框架中, View层和Model层的内容可能会有所差别,但是其基础功能不变,变的只是数据的传输方式。
下面就从这个例子开始了解MV系列框架的概念。
MVC框架是MVC、MVP、MVVM这3个框架中历史最悠久的。20世纪70年代,施乐公司发明了Smalltalk语言,用来编写图形界面的应用程序,脱离了DOS系统,让系统可视化,不用一直看着黑白的界面。
在Smalltalk发展到80版本的时候,MVC框架被一位工程师提出来,MVC框架的出现在很大程度上降低了应用程序的管理难度,之后被广泛应用于构架桌面和服务器应用程序。MVC框架如图1.3所示(实线表示调用,虚线表示通知)。
Controller是MVC中的C,指控制层,在Controller层会接收用户所有的操作,并根据写好的代码进行相应的操作——触发Model层,或者触发View层,抑或是两者都触发。需要注意:Controller层触发View层时,并不会更新View层中的数据,View层中的数据是通过监听Model层数据变化而自动更新的,与Controller层无关。MVC框架流程如图1.4所示。
从图1.4中可以看出,MVC框架的大部分逻辑都集中在Controller层,代码量也都集中在Controller层,这带给Controller层很大的压力,而已经有独立处理事件能力的View层却没有用到。还有一个问题,就是Controller层和View层之间是一一对应的,断绝了View层复用的可能,因而产生了很多冗余代码。为了解决这个问题,MVP框架被提出来。
首先需要知道,MVP不是指Most Valuable Player,而是指Model-View-Presenter。
MVP框架比MVC框架大概晚出现20年,1990年,MVP由IBM的子公司Taligent公司提出,它最开始好像是一个用于C++ CommonPoint的框架,这种说法正确与否这里不做考证,先来看一下MVP框架图(图1.5)。
在MVC框架中,View层可以通过访问Model层来更新,但在MVP框架中,View层不能再直接访问Model层,必须通过Presenter层提供的接口,然后Presenter层再去访问Model层。这看起来有点多此一举,但用处着实不小。首先是因为Model层和View层都必须通过Presenter层来传递信息,所以完全分离了View层和Model层,也就是说,View层与Model层一点关系也没有,双方是不知道彼此存在的,在它们眼里,只有Presenter层。其次,因为View层与Model层没有关系,所以View层可以抽离出来做成组件,在复用性上比MVC模型好很多。MVP框架流程如图1.6所示。
从图1.6中可以看出,View层与Model层确实互不干涉,View层也自由了很多。但还是有问题,因为View层和Model层都需经过Presenter层,致使Presenter层比较复杂,维护起来会有一定的问题。而且因为没有绑定数据,所有数据都需要Presenter层进行“手动同步”,代码量比较大,虽然比MVC模型好很多,但也是有比较多的冗余部分。为了让View层和Model的数据始终保持一致,避免同步,MVVM框架出现了。
MVVM最早是由微软在使用Windows Presentation Foundation和SilverLight时定义的,2005年微软正式宣布MVVM的存在。VM是ViewModel层,ViewModel层 把Model层 和View层的数据同步自动化了,解决了MVP框架中数据同步比较麻烦的问题,不仅减轻了ViewModel层的压力,同时使得数据处理更加方便——只需告诉View层展示的数据是Model层中的哪一部分即可,MVVM框架如图1.7所示。
读者可能感觉MVVM的框架图与MVP的框架图相似,确实如此,两者都是从View层开始触发用户的操作,之后经过第三层,最后到达Model层。但是关键问题是这第三层的内容, ViewModel层双向绑定了View层和Model层,因此,随着View层的数据变化,系统会自动修改Model层的数据,反之同理。而Presenter层是采用手动写方法来调用或者修改View层和Model层,两者孰优孰劣不言而喻。MVVM框架流程图如图1.8所示。
从图1.9可以看出,View层和Model层之间数据的传递也经过了ViewModel层, ViewModel层并没有对其进行“手动绑定”,不仅使速度有了一定的提高,代码量也减少很多,相比于MVC和MVP,MVVM有了长足的进步。
至于双向数据绑定,可以这样理解:双向数据绑定是一个模板引擎,它会根据数据的变化实时渲染。这种说法可能不是很恰当,但是很好理解,如图1.9所示。
如图1.9所示,View层和Model层之间的修改都会同步到对方。MVVM模型中数据绑定方法一般有以下3种。
数据劫持
发布-订阅模式
脏值检查
Vue..js使用的是数据劫持和发布-订阅模式两种方法。首先来了解3个概念。
Observer:数据监听器
Compiler:指定解析器
Watcher:订阅者
Observer用于监听数据变化,如果数据发生改变,不论是在View层还是Model层, Oberver都会知道,然后告诉Watcher。Compiler的作用是对数据进行解析,之后绑定指定的事件,在这里主要用于更新视图。
Vue.js数据绑定的流程:首先将需要绑定的数据用数据劫持方法找出来,之后用Observer监听这堆数据,如果数据发生变化,Observer就会告诉Watcher,然后Watcher会决定让哪个Compiler去做出相应的操作,这样就完成了数据的双向绑定。
详细了解MV系列框架之后,相信读者已经了解MVC、MVP、MVVM这三者的优劣了。其实从MVC到MVP再到MVVM,是一个不断进步的过程,后两者都是在MVC的基础上做的变化,使MVC更进一步,使用起来也更加方便。MVC、MVP、MVVM三者的主要区别就在于除View层和Model层之外的第三层,这一层的不同使得MV系列框架区分开来。
其实很难说出MVC、MVP、MVVM哪一个更好,从表面上看,显然是MVVM最好,使用起来更方便,代码相对也较少。但问题是MVVM的框架体积较大,相比于MVC的不用框架、MVP的4KB框架,MVVM遥遥领先。虽然MVVM框架可以单独引用,但现在更多使用前端脚手架工具进行开发,并且使用打包工具,这样一来,它跟MVC相比,体积是天差地别。虽然机能过剩更令人放心,但是轻巧一些的框架会令项目锦上添花。所以要根据实际项目的需求来选择MVC、MVP、MVVM,只有最适合的模式才是最好的框架。
每项新技术都要经历一个从一开始不被大众认可到后来人尽皆知的过程,其实就是一个改变的过程。只要这个框架能跟上时代的潮流,满足人们开发的需求,这就是一个合适的框架。
因此,如果你想真正从事开发这一行业(尤其是前端),需要拥有一颗不惧变化的心。
“工欲善其事,必先利其器”,虽然这句话是2000多年前孔子说的,但放到今天依然十分受用。不仅是前端开发,做任何事情有了合适的工具,便可事半功倍,令人“有着丝滑般的享受”。
这里给大家介绍一些常用的开发工具,这些开发工具并不是下载安装后就非常好用,重点在于它们的可扩展性,扩展让这些工具有着更多的可能。
先从最基础的浏览器开始介绍,基本上每个前端开发人员都会使用Chrome。但是有多少开发人员能用好Chrome呢?下面先介绍一些常用的插件。
1. Vue.js devtools
开发Vue.js时,这个插件是必不可少的,可以方便地用它查看当前路由和组件内容,及时地反馈变量内容的变化。
2. AdBlock
AdBlock是用来屏蔽广告的,功能十分强大,一般广告都会被屏蔽掉,给用户一个“干净”的网页。但是要注意,有些网站可能不支持AdBlock,打开AdBlock之后,网页的样式和架构可能会乱掉,这时可以选择把这个网页网址加入AdBlock的白名单里,这样AdBlock就不会对此网站进行过滤,简单方便。
3. JSONView
Chrome上其实有很多查看JSON数据的插件,但是经过测试,此款插件的展示效果非常好,除了放大、缩小、折叠之外,没有其他多余功能,简单易用。
4. Momentum
这个插件与开发没有多大关系,它用来替换初始页的背景图片,并且提供了一个TodoList,每次新开一个页面的时候,都会随机出现一张令人赏心悦目的风景图,同时TodoList还会提醒你接下来要做什么,在开发之余,还能放松心情。
5. minerBlock
此插件的作用是防止电脑被人恶意当作矿机使用。现在开源的东西比较多,可能就会有人利用这一点,曾经有人开源了一款JS插件,在插件中藏有恶意挖矿的代码,若是使用了这样的插件,每次运行的时候,都是在帮制作者挖矿。所以找到了minerBlock,用来防止类似事件的发生,有需要的读者可以安装一下。
介绍完插件,下面来了解一些常用的断点功能。断点功能可以说是代码调试中最常用的功能,有经验的读者可以直接跳过这部分内容。
断点调试:断点可以让程序运行到某一行的时候,将程序的整个运行状态进行冻结。你可以清晰地看到这一行所有的作用域变量、函数参数、函数调用堆栈。总而言之,就是比console.log强了不止一个档次,运行速度也快很多。一般情况下,调试网页的时候,都会打开Chrome控制台,在控制台界面有Source栏,如图1.10所示。
在开发环境下可以看到当前项目的所有文件,之后找到需要的文件,在需要暂停的行上单击行号,页面如图1.11所示。
页面一片灰色,同时出现两个按钮:第一个按钮用于逐过程执行,可以理解为直接跳到下一断点处,如果没有断点,会直接执行完。第二个按钮用于逐语句执行,单击它之后,程序会到下一条语句处暂停,不管这条语句有没有断点。在控制台中可以输出当前状态下所有变量,如函数参数等关键信息,这样一来,整个项目的运行过程更加清晰明了,寻找漏洞(Bug)也就更方便了。Chrome的断点功能还有更高级的用法,例如条件断点,在此不再赘述。
VS Code是一款编辑器,全称Visual Sutdio Code,它是由微软开发的,与Visual Sutdio无关。编辑器一直是程序员们争论不休的焦点,我使用过很多编辑器,觉得VS Code最顺手。
VS Code一直在走一条比较中庸的路线,从大小上来说,它没有Sublime那么小,但相比Atom和Webstorm来说却小了很多,Windows系统安装包大概40MB,完全可以接受。而且它运行流畅,在复杂项目上,打开时间比Sublime快很多。
虽然2015年才发布VS Code,但是其插件种类十分丰富,目前已经有8000多个,完全可以满日常使用需求。下面介绍一些日常使用的插件。
1. Chinese (Simplified) Language Pack for Visual Studio Code
这是VS Code的汉化包,之前的VS Code可以在设置中修改语言类型,在某一版本后取消了多语言的支持,改为使用插件来汉化编辑器。VS Code汉化包就是官方开发的中文语言包。
2. vscode-icons
几乎每个用户都会装一个这种图表式的插件,这是为了更方便地看见文件的类型,同时对文件进行区分。这款插件是VS Code官方推荐的,图标种类无比丰富,几乎涵盖了所有类型的文件。
3. ESLint
一般前端都会使用ESLint来对代码格式进行规范,这可以解决很多不必要的Bug,在项目中也可以使用,但是只有在项目运行时的Terminal才能看见,这样相对比较麻烦,不利于修改。在VS Code中可以直接安装ESLint插件,在编写代码的时候,就可以看见自己的语法有哪些错误,利于修改。
4. Vetur
Vetur是针对Vue.js的语法高亮、提示和补全的插件,同时还可以在VS Code内部进行Debugg,或者进行相应的代码格式化,是Vue.js开发者必备插件之一。
5. Beautify
Beautify属于代码格式规范插件,可以针对HTML、CSS、Sass、JS进行代码格式整理,虽然Vetur也有代码规范功能,但仅限于Vue.js文件,对于一般的文件,Beautify使用起来更加方便。
6. Markdown Preview Enhanced
它可以增强对Markdown格式文件的预览,比原生的预览好看了很多。
7. One Dark Pro
这是一个暗黑系的Atom主题,虽然不是五花八门的黑,但也不是简简单单的纯黑,看上去比较舒服。
8. Solarized-light Theme
暖黄色的主题,对护眼比较看重的读者可以看看。
需要注意:在这里没有安装任何语法提示的插件,因为VS Code原生已经支持了HTML、CSS和JS的语法提示,十分全面。
Terminal其实就是命令行工具,用来启动和查看系统的运行状态。macOS系统使用起来比较方便,Windows系统就有些问题,自带的CMD不是很好用,PowerShell也是勉勉强强,下面介绍根据不同的系统使用的不同Terminal软件。
1. macOS
虽然自带的已经可以满足部分需求,但是人总是得朝着更高的目标追求。iTerm可是说是macOS上最好用的Terminal软件之一,目前版本是2,也就是iTerm2。下载也很方便,登录官网,单击下载即可。
下载之后进行安装即可使用,完全可以替代原生的Terminal,而且在分屏分页展示上更加方便。这里再推荐两个命令行工具的插件,可以让命令行工具用起来更方便。
Oh My Zs界面很好看,共有142个皮肤可以选择。而且提供了超强的补全功能,使用Tab键可以展示出所有的可选项,同时可以用方向键切换,按Enter键进入,方便快捷。不仅可以补全目录,还可以补全git分支以及之前输入的命令,基本上可以补全任何东西。还有一个非常好的功能就是系统记录所有的操作日志,并存放在zsh_history文件夹下,每次记录还会同步记录时间,这点配合补全操作记录,效果简直无懈可击。只要你想,就可以一直用上方向键找到你以前输入的命令。
Tmux用于分屏,虽然iTerm2有分屏功能,但是软件的分屏起始位置总是在当前用户的文件夹。如果用Tmux,分屏之后依然会保存分屏之前的状态。这个状态不仅指位置,而且指当前窗口的软件状态。例如当前窗口的Node.js版本是4.5,而系统默认的是8.9。如果使用iTerm2打开新窗口,Node.js版本会是8.9。如果使用Tmux分屏,则会保留Node.js的状态,依然是4.5,这一特性在很多情况下都是很有用的。还有一些常用的快捷键,可以让用户的使用更加方便,例如当前窗口太小了,但是内容很多,想要全屏展示,若是用iTerm2,就要单独把这个窗口挪出去,才能实现全屏显示效果,但是Tmux中的快捷键可以直接将当前窗口全屏,并且不影响其他窗口。全屏查看完之后,可以使用快捷键将当前窗口变回原来的大小,所有窗口均不受影响。
如果你的电脑是macOS系统,那么上述工具和插件可以让你的Terminal实现质的飞越,美观性和实用性都会得到很大提高,带来“飞一般”的感觉。
2. Windows
如果说macOS系统里原生的Terminal勉强可以使用,那么Windows就变得无从入手了,尤其是CMD。所幸cmder让我脱离了苦海。
虽然这款软件的名字含有“cmd”,但是其内容和使用方式已经无限接近Linux环境了,很多Windows系统不支持的命令在这里面都是支持的,对文件的操作更加得心应手,还可以执行分屏等操作。因为系统本身原因,Windows系统不支持命令行工具插件,所以无法使用oh My Zsh和Tmux,而其没有替代品,除非安装一个虚拟机,例如Docker之类的,之后把代码放到Docker里执行,但这样的操作比较复杂。VS Code上还有SFTP工具可以同步代码到Docker里,配合起来还是挺好的。
关于Terminal的内容就介绍到这里,这些工具已经能够满足大部分前端开发人员的使用需要,开始使用时,因为比较陌生,可能记不住快捷键,但是熟练掌握之后,可以在很大程度上提升工作效率。
在本章里,回顾了前端的发展历史、三大主流框架和一些日常使用的工具。基本上囊括了一个前端需要了解的基础知识,当然仅仅了解这些知识,在技术上可能不会有多大的进步,但在判断一个项目应该怎样开发、使用何种技术进行开发时,可以通过这些知识进行快速判断,给出合理的解决方案。
从MVC、MVP、MVVM这3个框架的发展历史可以看出,它们一开始并不是为前端开发准备的,前端使用这些框架都是基于其先进的思想从别的语言借鉴来的。在MVC框架出现的时候,前端还没有开始发展,MVP框架出现的时候,前端也只能算是刚刚开始发展,就是现在最火的MVVM框架,也是在Ajax刚推出时才出现,那时,前端根本无法使用MVVM框架进行开发。
由此可知前端虽然出现较晚,而且基本都是从别的语言借鉴来的,但它的发展势头迅猛。现在前端能够独挡一面,可以独立开发出一整套服务了,即从前端到后端都使用JS进行开发,所以前端的潜力还是很大的。随着移动业务的开展,手机端也变得重要起来,除小程序外,手机上的很多宣传页都是采用网页的方式。方便快捷,无须客户端,展示效果好,这些都是前端的优势。虽然现在前端竞争压力很大,但只要真正掌握核心技术,市场需求这么大,还愁找不到工作吗?
前端工具的发展与前端的发展相辅相成,JS的每次进步都会带动浏览器厂商和相关开发工具的进步,这一点毋庸置疑,但需要注意的是,这种进步所需的时间很难估计,例如ES6出现两年多了,浏览器还没有做到完全适配,所以在尝试新技术的时候要小心,尤其是在公司开发项目的时候,不成熟的技术最好不用,但若是自己开发博客之类的,则不用担心,遇到问题时,可以去社区寻找答案,或提问都可以,维护项目的人员基本上都会给你一个完美的解释。
还有一点需要注意,就是浏览器的兼容问题,这个问题目前没有一个合理的办法去解决,主要是看用户群体。如果项目的主要用户群体对电脑不是十分了解,那么就需要考虑兼容性的问题,不要想当然认为现在没人会用IE8之类的浏览器,要知道IE8在浏览器市场上所占无几的份额可能代表着庞大的用户数量,难保你的用户就不是其中之一。所以这时候就要考虑框架的兼容性了,本书所讲的Vue.js就不兼容IE8及以下的浏览器,所以只能考虑换框架,或者不使用框架,在插件的选择上也是同理。
至此,关于前端开发,你需要知道的知识已经介绍完了,在下面的章节将会介绍Vue.js的一些基础概念以及用法,这部分的内容是后续实战开发的基础。
第1章介绍了前端开发的历史,有了这些铺垫,会对Vue.js的内容理解更深刻。本章主要介绍Vue.js项目的构建,然后通过Vue.js的两个Demo了解Vue.js的特性,最后将逐一讲解Vue.js的语法,让读者对Vue.js有一个清晰的认识。
本章主要涉及的知识点如下。
Vue.js的两种引用方法
Vue.js简单Demo的介绍
Vue.js特性的讲解
Vue.js很适合搭建类似于网页版知乎这种表单项繁多,且内容需要根据用户的操作进行修改的网页版应用。学会使用它,可以更快、更简单、更高效地进行项目开发。
要想学好Vue.js,首先要了解Vue.js的开发环境搭建和Vue.js项目的构建,下面将介绍Vue.js的两种构建方法,由于第一种方法使用范围较小,局限性较大,所以着重介绍第二种方法。
第一种方法就是直接在HTML文件中引入,此种方法相对比较简单,直接使用<script>标签引入内容,此时的Vue.js会被注册为一个全局变量,直接使用即可。
首先了解一下<script>标签的defer和async属性,这两个属性会让<script> 标签引入的文件异步加载,也就是与DOM元素同时加载,这样即使把<script>标签放在head中,也不会影响页面的加载速度。但是在async属性下,<script>标签引入的文件没有顺序,谁加载得快就先用谁。在<script>标签文件相互依赖的情况下,这种加载方式会直接导致报错。若使用defer属性,文件就会按照顺序依次加载,保证了文件的先后顺序,就不会出现上面的问题,所以这里推荐在<script>标签上添加defer属性,之后将<script>标签放在head中,与DOM元素同时加载,新建HTML文件,代码如下。
01 <!DOCTYPE html>
02 <html>
03 <head>
04 <title>Script标签引入Demo</title>
05 <script defer src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
06 </head>
07 <body>
08 <h1>Script标签引入Demo</h1>
09 <script type="text/javascript">
10 // 需要执行的JS语句
11 </script>
12 </body>
13 </html>
<script>标签中的内容可以是本地文件,也可以是一个内容分发网格(Content Delivery Network,CDN)的地址。如果在本地开发,将Vue.js文件放到本地会快些,本地文件可以从Vue.js官网获取。如果在线上开发,当服务器不够快,可以使用CDN链接提速,这里为了方便,直接使用CDN链接。(关于CDN链接,读者可自行查阅相关资料,供应商较多,可自行选择。)
用户可以直接在<script>标签中编写Vue.js代码,十分方便,但是局限性较大。首先,这种方法只适用于小型项目的开发,若项目比较大,则会浪费很多资源,造成代码的冗余。其次就是插件较多时不利于插件的引入与使用,而且看起来不够直观。所以一般只用于做个Demo来演示简单的内容。大部分情况下,都会使用下面脚手架配置方法构建Vue.js项目。
这里的脚手架,其实与盖房子时用的脚手架类似,使用脚手架工具可以很方便地构建出项目的基本模型,并且有很多插件可以丰富我们的项目,省得去一个个安装。例如项目里一般都会引入Eslint规范代码格式,引入Babel进行ES6语法转译,还会使用一个打包工具对项目进行打包,减小线上的大小。若是手动安装,则需要花费较多时间;使用脚手架,只需要简单的几步操作即可,省时省力。
Vue-CLI是Vue.js官方推荐的脚手架工具,现在已经到3.0版本。至于3.0以前的版本,它的功能与其他脚手架工具差不多,但是到3.0版本,出现图形用户界面(Graphical User Interface,GUI),对于新手来说,不再友好,下面就来了解Vue-CLI3.0的简单使用方法。
首先配置本地环境。Node.js必不可少,这是一个基于谷歌V8 引擎的 JS运行环境。简单来说,就是先要在这里执行项目代码,否则不能启动项目。Node不管macOS系统还是Windows系统,依照官网的指示进行下载与安装即可。
Node.js安装包自带npm,无须独立安装。npm是一个包含众多JS包的管理工具,通过npm命令可以安装很多JS插件,不管是安装在本地还是项目中,都是可以的。下面使用npm安装Vue-CLI脚手架,打开命令行工具,输入以下命令。
npm install -g @vue/cli
其中-g表示全局安装,指在本地任何地方打开命令行工具都可以调用这个安装包;@表示最新的版本。
npm还有很多类似的命令,例如-S是安装到生成环境的依赖,-D是安装到开发环境的依赖,其余命令可以查阅相关资料。Vue-CLI 3.0有一个要求是,Node.js版本最低是8.9,这里安装最新的稳定版即可。
直接使用npm时,有些读者可能会感觉很慢,不用担心,可以使用cnpm来进行代替。cnpm是淘宝的一个npm镜像,每10分钟和npm同步一次,不用担心插件的版本问题。在命令行工具中执行如下命令。
npm install -g cnpm --registry=https://registry.npm.taobao.org全局安装cnpm即可,这样以后遇到npm命令时,可以直接使用cnpm替换。安装完成后,可以查看当前开发环境软件的各种版本,如图2.1所示。
至此,开发环境的安装已经完成,接下来运行Vue-CLI的图形化界面,执行以下命令。
vue ui
命令行工具如图2.2所示,此时就可以通过浏览器访问http://localhost:8000打开VueCLI3.0运行界面,如图2.3所示。
单击“创建”按钮会出现图2.4所示的界面。
选择一个合适的文件夹,如在桌面新建一个VueDemo文件夹。单击“在此创建新项目”按钮,运行Vue-CLI配置,如图2.5所示。
首先配置“详情”界面,给项目起一个名字,如HelloWorld;然后在“包管理器”中选择npm,有经验的用户也可以选择yarn;最后,选择“初始化git仓库(建议)”。如果代码只用在本地使用,就不用选择这一项。
接下来配置预设“界面”,因为默认配置无法进行自定义修改,我们这里不使用默认配置。然后到“功能”界面选择需要的包,例如选择2个默认的包——Babel和Linter/Formatter, Babel用来转译ES6语法,Linter/Formatter用来规范代码格式。乍一看这种选择默认包的操作跟默认配置一样,其实并不是,默认配置不可以使用配置项来修改ESlint的规则。使用手动预设可以在配置里选择ESlint中的Airbnb规则,这是ESlint中最严格的规则,可以最大化地规范代码格式。
有些读者可能不习惯用ESlint,因为写起来比较麻烦,经常一个缩进、一个标点有问题就会报错。可这却是规范自身代码的必经之路,试想你看别人代码的时候,是有规范的代码看起来舒服,还是毫无章法可言的代码舒服呢?为了不给别人带来困扰,也为了提高编程水平,使用ESlint还是很有必要的。尤其对一些新手来说,ESlint会强制性使用ES6代码规范,帮助熟悉ES6。
单击“创建”按钮的时候,系统会提示是否保存预设,可以保存,也可以不保存,之后就是等待项目的创建与依赖的安装,安装完成后,项目结构如图2.6所示。
此时在命令行工具中进入项目文件夹,启动项目。
npm run serve
调用Vue-CLI 3.0的服务来启动服务,成功后,项目运行命令行工具如图2.7所示。
此处给了两个网址:第一个Local是内网访问的地址,本机只要在浏览器上访问这个网址即可看到项目;第二个是外网网址,别的用户可以通过访问这个网址来访问本地地址,对于多设备测试来说很方便。此时访问http://localhost:8080/就可以看到刚刚新建的项目,项目运行界面如图2.8所示。
项目刚刚生成时,只有Vue-CLI默认的一些样式,由此可见新建项目比较简单,使用GUI之后,界面会更加清晰,现在看一下Vue-CLI平台,如图2.9所示。
Vue-CLI带来了当前项目的分析功能,首先就是安装包的状态,之后还有项目依赖和项目配置等功能,不仅看上去十分炫酷,而且用起来也非常方便。Vue-CLI还可以用来查看项目状态,对包进行一些管理操作。同时整个项目是热加载的,修改项目内容后,无须重启,网页会自动刷新。
至此完成了使用官方自带脚手架工具——Vue-CLI创建新项目的整个过程,Vue-CLI还有很多功能没有介绍,读者可以自行深入体验,绝对会给你带来不一样的感受。
2.1节完成了项目的构建,下面将讲解脚手架生成文件的内容,之后会编写两个Demo来介绍一下Vue.js的基础特性,让读者更进一步理解Vue.js。
一下子生成了很多脚手架文件,大家可能会觉得一头雾水,其实相比前几代脚手架,VueCLI 3.0已经简化了生成文件的内容,基本上都是必不可少的组件,下面来为大家一一讲解。
生成的文件主要有三大部分:public文件夹、src文件夹和其余文件(node_modules文件夹是插件安装包的内容,可以忽略)。public文件夹提供了项目基础的HTML文件,可以不管。src文件夹用来存放项目的主要代码,其余文件都是一些项目的配置文件。.gitignore文件是git的配置文件,里面可以填写一些文件的名称或者目录,这样在git上传的时候,就会自动忽略掉这些文件。babel.config.js是babel的配置文件。package.json文件是npm的一些信息,存放着一些启动脚本的指令和依赖的内容。README.md是一个Markdown格式的文件,里面可以放一些项目简介之类的内容,让别人了解到这个项目是做什么的以及使用方法。
src文件夹内包含assets、components和views3这3个子文件夹。此外,还有App.vue、main.js和router.js这3个文件。先从后面提到的3个文件说起,App.vue是整个项目的主体框架,这个文件上的内容会存在于整个项目的每个页面,上面有一些基础的样式,可以把这些都删掉,删掉之后的App.vue如下。
01 <template>
02 <div id="app">
03 <router-view/> // 路由展示
04 </div>
05 </template>
</template>标签用来包裹HTML结构,而且在</template>标签内部只能存在一个外标签,上面的代码在根节点也只有一个<div>标签,若是有两个则会报错。<router-view/>用来展示路由页面的信息,可能有读者不太理解路由信息从何而来,这就涉及router.js文件。在这个文件内部规范了不同路由所代表的不同页面,代码如下。
01 import Vue from 'vue'; // 引入Vue
02 import Router from 'vue-router'; // 引入vue-router
03 import Home from './views/Home.vue'; // 引入Home组件
04
05 Vue.use(Router);
06
07 export default new Router({
08 routes: [
09 {
10 path: '/', // 路由的路径
11 name: 'home', // 路由的名称
12 component: Home, // 路由的组件
13 },
14 {
15 path: '/about', // 路由的路径
16 name: 'about', // 路由的名称
17 component: () => import('./views/About.vue'),// 路由的组件
18 },
19 ],
20 });
上述代码在文件的开头引入了Vue-router官方路由组件,之后引入Home路由的组件。最后用Vue.use()命令在项目中使用Vue-router。上述代码第07行~第20行就是Vue-router配置文件。
可以看出这里使用ES6的export方法暴露出路由的配置。配置的方法很简单,先规定path、name和component属性。path就是展示在地址栏上的内容;name属性是对当前路由的命名,在跳转路由时,可以使用这个属性来跳转到指定的路由,在实战部分会详细讲解;component就是当前路由展示的组件,组件有两种引入方式:第一种就是像Home组件一样,直接在文件的开头引入;第二种就是像about路由一样,在componets属性中引入,这里的import也是ES6引入的语法,如此便完成Vue-router的简单使用。
接下来就是main.js文件,代码如下。
01 import Vue from 'vue'; // 引入vue
02 import App from './App.vue'; // 引入App.vue文件
03 import router from './router'; // 引入Vue-router配置文件
04
05 Vue.config.productionTip = false; // 关掉生产环境的提示
06
07 new Vue({ // new一个Vue实例
08 router,
09 render: h => h(App), // 渲染App.vue文件
10 }).$mount('#app'); // 将内容挂载到id为app的标签下
这个文件是此项目的入口文件,什么是入口文件呢?就是在运行项目的时候,先从这个文件开始执行。首先引入vue、App.vue文件和Vue-router配置文件。然后修改vue的默认配置,如果不修改这个配置,每次启动项目的时候,就会出现当前是生产环境的提示。然后new(新建)一个Vue实例,这个实例这样理解:有一个函数,调用了Vue这个函数,之后的一切操作都是在这个函数中执行的,每个Vue.js应用都是通过用 Vue 函数创建一个新的Vue 实例开始的,无一例外。
在这个Vue实例中,还增加了一些配置,包括Vue-router和初始文件模板文件。render用于渲染App.vue文件,先将这个文件中的HTML元素放到当前页面上,之后再将项目的所有内容挂载到id为App的标签下,项目就会自动进行路由的判断和路由组件的渲染。至此,这个项目就可以开始运行了。
读者可能不知道在哪里调用main.js这个文件,原本是在package.json文件中调用的,但是在Vue-CLI 3.0版本中,提供了一种新的启动项目的方式,也就是之前执行的以下命令。
npm run serve
其中serve指Vue-CLI的serve,它可以直接找到main.js文件,然后运行这个文件,无须手动调用。
剩下的3个文件夹内容都是围绕着上面的主题,assets文件夹一般用来存放一些静态文件,如图片、字体,也可以用来存放样式表文件,样式表也可以放在views文件夹内。views文件夹一般用来存路由配置的主页面,这些主页面也可以包含一些组件,组件就是放在components文件夹中的内容。如果项目比较大,可以新建二级文件夹,用来给组件或者页面进行分类,使结构更加清晰,对开发和维护都是大有裨益的。
新建项目默认的路由文件配置了home和about两个路由,这两个路由代表的页面被放在views文件夹里面,分别是Home.vue和About.vue。在Home.vue中还引用了HelloWorld组件, HelloWorld组件被放在components文件夹下。
基础的文件内容与架构就介绍到这里,其实文件的架构不是固定的,可以根据项目具体情况和个人开发习惯进行修改。
对于任何语言的学习来说,Hello World都是必不可少的,本书也不例外,下面介绍一个Hello World的Demo。
先来清空默认文件的内容,并且增加新的内容,修改Home.vue文件。
01 <template>
02 <div class="home">
03 <HelloWorld /> // 调用组件
04 </div>
05 </template>
06
07 <script>
08 import HelloWorld from '@/components/HelloWorld.vue'; // 引入组件
09
10 export default {
11 name: 'home', // 文件命名
12 components: { // 注册组件
13 HelloWorld,
14 },
15 };
16 </script>
这里只留下HelloWorld组件。首先介绍组件的使用,在<script>标签里首先引入HelloWorld.vue文件,并且把它称为HelloWorld。之后输出一些默认的配置和函数等相关信息。在export中先将当前文件命名为home,也就是主页。之后在components属性中注册组件,格式是key: value。因为key和value的名字一样,所以使用ES6的对象扩展方法进行了简写。在注册之后,即可在上面的<template>标签中使用,标签末尾要加斜线(/),这与所有自闭合标签一样。
如此即可在Home.vue文件调用HelloWorld这个组件,比较简单,组件之间的通信在下一小节会用到。
简化并修改HelloWorld.vue文件,代码如下。
01 <template>
02 <div class="hello">
03 <input v-model="words" type="text">// 在<input>标签中双向绑定words变量
04 <h3>{{words}}</h3> // 展示words变量的内容
05 </div>
06 </template>
07
08 <script>
09 export default {
10 name: 'HelloWorld', // 文件命名
11 data() { // 定义当前文件中需要使用的数据
12 return {
13 words: 'Hello World', // 给words变量赋值为Hello World
14 };
15 },
16 };
17 </script>
上述代码说明了Vue.js双向绑定的特性。与Home.vue文件一样,需要在<script>标签中默认暴露出一个对象以供使用,先命名为HelloWorld,之后在data属性中定义当前文件中需要使用的变量,直接return即可。此处定义了words变量,并给其默认赋值“HelloWorld”。这样便可直接在<template>标签中使用这个变量。
在<template>标签中,先新建一个<input>标签,之后使用v-model属性绑定words变量,最后在<h3>标签中展示这个变量,使用双大括号的Mustache语法。现在看不懂没关系,这个Demo是用来体验Vue.js双向绑定的特性,具体内容会在后面的章节中介绍。
完成HelloWorld测试项目主体内容的编写后,读者还可以给class为home的div加个样式,如下所示,放在文件最后即可。
01 <style>
02 .home {
03 margin: 0 auto;
04 text-align: center;
05 font-size: 20px;
06 }
07 </style>
此时可以运行项目,看一看效果,在命令行工具中输入以下命令。
npm run serve
在浏览器中访问http://localhost:8000/,可以看到图2.10所示的内容。
表面上看可能很普通,可当试着修改input输入框中的内容时就会发现,<h3>标签中的内容也随之变化!这就是数据绑定,在1.2.3小节中也曾讲到,当时只是理论知识,可能不太好理解,现在这个例子就很形象地解释了数据绑定。其实很简单,仅仅是使用了v-model这个方法,就将变量与DOM元素的值绑定起来,两者便会相互影响,这对于数据的展示来说,真的很方便,不用监听数据的变化,可以省略很大一部分代码,同时数据的展示因为有了Mustache语法的关系,也方便了不少。
这个HelloWorld的Demo介绍了插件的简单引用和Vue.js数据双向绑定的简单实用,语法很简单,关系也不复杂,读者可以好好理解一下这个Demo的构建,最好实际操作一次,让自己的记忆更加深刻,下面开始做一个比较复杂但也很经典的TodoList。
TodoList是一个备忘录,主要功能是记录用户输入的信息,将其展示出来,并且可以进行删除和修改,TodoList展示图如图2.11所示。
样式上没有很复杂,甚至看上去有些简陋,有兴趣的读者可以自行美化。
接下来分析Demo的功能:最上方有一个input输入框和一个插入按钮,在input输入框内输入一些信息后,单击“插入”按钮,下面的列表末端就会插入一条数据。列表上每条数据都有一个“×”标识,单击它可删除当前标签。若是单击标签,当前标签则会变成一个input输入框,输入框中的内容就是当前标签的内容,修改完成之后,单击任意空白处,数据会自动保存。
这个功能虽然简单,但是包含了经典的增、删、改、查四大模块,用来练习再合适不过。了解功能后,即可开始项目设计,首先简化开头,先把数据的修改和删除去掉,只保留展示数据功能,那么就需要一个input和插入按钮,还有一个列表来展示用户存储的信息。
首先使用Vue-CLI新建一个项目,命名为TodoList,之后删掉无用的默认内容。因为此次Demo的重点不在样式上,这里仅展示样式表的内容,在/src/assets文件夹下新建style.css文件。
01 .home ul{
02 margin: 0;
03 padding: 0;
04 list-style-type: none;
05 }
06 .home ul li .item p span {
07 padding:0 20px;
08 }
09 .home ul li .item p span:nth-child(1) {
10 color:blue;
11 }
12 .home ul li .item p span:nth-child(2) {
13 color: palevioletred;
14 }
接下来在components文件夹下新建TodoList.vue文件,用来存储Demo的主体部分,根据上文的架构,编写HTML部分的内容,修改TodoList.vue文件。
01 <template>
02 <div>
03 <div class='input-part'>
04 <input type="text"> // 输入框
05 <button>插入</button> // 插入按钮
06 </div>
07 <ul>
08 <li>
09 <p>内容</p> // 展示内容
10 </li>
11 </ul>
12 </div>
13 </template>
接下来进行数据定义和插入函数的开发。
01 <script>
02 export default {
03 name: 'TodoList',
04 data() {
05 return {
06 words: '', // 输入框中的内容
07 list: [], // 列表内容
08 };
09 },
10 methods: { // methods属性用来存储当前文件中所有的方法
11 insertItem() { // 新建插入函数
12 this.list.push(this.words); // 用数组自带的push方法添加新内容
13 },
14 },
15 };
16 </script>
这里新建了两个变量:一个是words,代表着当前输入框中的内容,使用v-model指令将其与<input>输入框绑定在一起;另一个是list,它是一个数组,用来存储所有的数据。之后在methods属性中添加一个插入函数,methods是Vue.js中用来存储方法的属性,所有的方法都需要在这里进行定义,之后才可以在别的地方调用。在此定义了insertItem函数,就是插入一个元素,此函数使用JS数组自带的push方法将当前输入框中的数据直接插入list数组中的最后一位。在方法和变量新建好之后就需要将数据与当前页面结合起来,结合后HTML模板如下。
01 <template>
02 <div>
03 <div class='input-part'>
04 <input v-model="words" type="text">// 数据的双向绑定
05 <button v-on:click="insertItem">插入</button>// 单击按钮触发insertItem方法
06 </div>
07 <ul>
08 <li v-for="(item, index) in list" v-bind:key="index">// 循环列表数据
09 <p>{{item}}</p> // 展示数据
10 </li>
11 </ul>
12 </div>
13 </template>
首先对input输入框进行数据的双向绑定,因此words变量的值会与输入框中的数据同步。接下来在插入按钮上绑定了单击事件,单击触发insertItem方法,保存当前输入框中的数据。其中v-on是Vue.js中绑定事件的指令,可以绑定单击事件,或者绑定提交时间和按键事件,读者可以试着给按钮绑定回车事件,这样按下回车键也可以插入新数据。
然后使用v-for指令来根据数组的选项列表进行渲染,v-for指令需要使用item in items形式的特殊语法,items是源数据数组,并且item是数组元素迭代的别名。上面的代码中,item代表每一条数据,list代表所有数据。
之后使用v-bind指令给li元素绑定key属性。v-bind指令可以动态地绑定一个或多个属性,也就是说class或者style之类的都可以绑定,并且可以根据条件做出不同的变化。当然,这里只是简单地绑定了key属性。key属性在Vue.js 2.2.0之前不是必需的,但是在2.2.0后变成了强制要求的属性。使用v-for指令后,为了保持Dom元素的唯一性,Vue.js使用key属性来区分列表中的DOM元素,对于我们来说用处可能不大。在给<li>标签循环之后,即可在标签内获取到item,使用<p>标签展示出来。这里的item就是数组中的一个元素,还可以是一个字符串、对象或者数组。
最后需要调用一下这个组件,才能在页面上看到效果,修改Home.vue文件。
01 <template>
02 <div class="home">
03 <TodoList /> // 调用TodoList组件
04 </div>
05 </template>
06
07 <script>
08 import TodoList from '@/components/TodoList.vue';// 引入TodoList组件
09 import '@/assets/style.css'; // 引入样式表
10
11 export default {
12 name: 'home',
13 components: {
14 TodoList, // 注册TodoList组件
15 },
16 };
17 </script>
这里删掉自带的HelloWorld组件,使用刚刚开发的TodoList组件,调用组件的方法跟上文说的一样,即先引入,再注册,最后调用。
如果不出意外,项目现在应该如图2.12所示。
现在可以在input输入框中输入数据,单击“插入”按钮,在下方的列表中会出现新的数据。到这一步,实现数据的增加与查询功能了,整体效果还是不错的,下面开发删除和修改功能。
首先要明白删除和修改的本质是对数组中元素进行操作,可以使用JS原生的splice方法。其次,既然每条数据都可以进行删除和修改操作,那么可以把单条数据提出来做成一个组件,这样操作起来更加方便。先在/componets文件夹下新建TodoListItem.vue文件,文件的HTML模板如下。
01 <template>
02 <div class="item">
03 <p>
04 <span>{{item}}</span> // 展示数据
05 <span>
06 <input type="text"> // 修改数据的输入框
07 </span>
08 <span>X</span> // 删除按钮
09 </p>
10 </div>
11 </template>
结构比较简单,为了方便,直接使用大写的X来代替删除按钮,将所有的元素都放在<p>标签中,之后用<span>标签包裹,元素就会整体地排列成一行。接下来想一下删除和修改函数应该放在哪?放在TodoList.vue文件中还是TodoListItem.vue文件中?这是一个问题。
若将函数放在TodoList.vue文件中,就需要在TodoListItem.vue文件中绑定TodoList. vue文件中传过来的函数;若将函数放在TodoListItem.vue文件中,需要将其数据的变化传给TodoList.vue文件。既然都要反馈给TodoList.vue文件,那么把函数放在TodoList.vue文件中显然是更好的选择,还能省略一些代码。下面是在TodoList.vue文件中添加删除和修改函数的代码。
01 methods: {
02 insertItem() { // 之前的插入元素函数
03 this.list.push(this.words);
04 },
05 deleteItem(index) { // 删除元素函数
06 this.list.splice(index, 1);// 使用splice方法删除从index位置开始的一个元素
07 },
08 modifyItem(newContent, index) { // 修改元素函数
09 // 使用splice方法替换从index位置开始的一个元素
10 this.list.splice(index, 1, newContent);
11 },
12 },
上述代码在methods属性中添加了deleteItem和modifyItem函数,使用splice方法来对list数组进行操作,比较简单。之后需要引入TodolistItem组件,将元素信息和删除修改函数传递过去,修改TodoList.vue文件。
01 <li v-for="(item, index) in list" v-bind:key="index">
02 <TodoListItem
03 v-bind:item="item" // 给TodoListItem传入item变量
04 v-bind:index="index" // 给TodoListItem传入index变量
05 v-on:deleteItem="deleteItem"// 给TodoListItem传入deletItem函数
06 v-on:modifyItem="modifyItem"// 给TodoListItem传入modifyItem函数
07 />
08 </li>
调用TodoListItem组件时,直接把变量和函数传递过去,因此在TodoListItem组件中即可直接调用这些函数或者变量。
01 <template>
02 <div class="item">
03 <p>
04 // 通过isActive变量判断是否隐藏数据内容,绑定单击事件,展示item变量
05 <span v-show="!isActive" v-on:click="activeItem">{{item}}</span>
06 <span v-show="isActive"> // 通过isActive变量判断是否展示input输入框
07 // 绑定content变量的值,绑定失去焦点事件
08 <input v-model="content" v-on:blur="inactiveItem" type="text">
09 </span>
10 <span v-on:click="$emit('deleteItem', index)">X</span>// 绑定删除事件
11 </p>
12 </div>
13 </template>
14 <script>
15 export default {
16 name: 'TodoListItem',
17 props: ['item', 'index'], // 接收父组件传入的item和index变量
18 data() {
19 return {
20 content: '', // 定义input输入框内容变量
21 isActive: false, // 定义是否展示input输入框变量
22 };
23 },
24 methods: {
25 activeItem() { // 激活修改状态的函数
26 this.isActive = true; // 当前修改状态为是
27 this.content = this.item; // 给input输入框赋值
28 },
29 inactiveItem() { // 关闭修改状态的函数
30 this.$emit('modifyItem', this.content, this.index);// 保存当前input输入框中的数据
31 this.isActive = false; // 关闭激活状态
32 },
33 },
34 };
35 </script>
回想一下要实现的效果:单击“×”按钮可删除当前数据,单击数据内容展示input输入框,单击任意空白处可隐藏输入框,同时保存数据。这样总共需要3个方法:第一个方法就是单击“×”按钮删除当前数据,由于删除方法已经从TodoList组件传过来了,所以可以直接用v-on指令绑定删除事件,使用$emit方法直接调用TodoList组件传过来的方法,第一个变量是函数名,第二个到最后一个是传给函数的参数。在调用deleteItem函数的时候将当前数据的Index传了过去,deleteItem方法会根据Index的值来删除指定数据。
第二个方法就是单击当前数据展示input输入框的函数,名为activeItem,激活当前元素。在此方法中,先是将isActive变量改为true,之后再将item的值赋给content变量。如此DOM元素就可以经由v-show指令来判断isActive变量的值是隐藏还是展示。给input输入框和展示数据的<span>标签使用v-show指令,判断isActive变量的状态。若isActive变量是false,则展示数据的<span>标签会展示,而input输入框则会隐藏;若isActive变量是true,情况则会相反。因为在新建isActive变量的时候,默认值是false,所以一开始并不会将input输入框展示出来,只有在单击<span>标签时,才会展示input输入框。
第三个方法就是在input输入框为焦点时,单击页面的其他地方会隐藏输入框并且保存数据。Vue.js中有默认的blur事件来判断当前元素是否失去焦点。若失去焦点,则会触发函数。使用v-on指令绑定blur事件,若失去焦点,会触发inactiveItem方法,不激活当前元素。在inactiveItem方法中,先使用$emit调用TodoList组件传过来的modifyItem方法,并且将当前input输入框中的内容和当前元素的Index作为变量传过去,这样会直接修改原数组。之后再修改isActive变量,将input输入框隐藏起来,如此便回到展示内容的状态。
至此就完成了TodoList中的Demo的创建,在Demo中可以新建、修改和删除指定的数据,实现基本功能。由于没有使用本地存储的方法来存储数据,所以,当页面刷新的时候,数据会全部消失。读者可能会有疑问,在修改或者删除数据的时候没有修改Dom元素的方法,为什么页面展示的效果会根据数据的变化而变化呢?这就是Vue.js自带的数据监听方法,数据若是有变动,Dom元素会自动重新渲染,无须手动操作。
我们通过两个Demo基本了解了Vue.js的主要特性,下面针对这些特性来具体认识Vue.js。
本节会详细介绍Vue.js的一些特性及指令,从新建实例到模板语法,从条件渲染到事件处理。可以说,在认真阅读这一小节之后,大家可以使用Vue.js开发一些简单的项目。
在开始时,需要了解一下实例,从根本上来说,实例类似于一个对象,里面包含组件需要使用的一些数据,如data、methods、components等。每个Vue.js项目都是通过Vue函数创建一个新的Vue实例开始的,代码如下。
01 const vm = new Vue{(
02 // 配置
03 )}
为什么用vm来作为实例的名称?这就要用到之前介绍过的Vue.js框架了,虽然不全都是MVVM框架,但是Vue.js也受到了很大的影响,所以这里简写了ViewModel层,用vm来作为实例名称。
配置主要有两部分:第一部分是Vue.js实例挂载的位置,这个位置可以用JS选择的一个DOM元素,也可以是一个CSS选择器,代码如下。
01 <div class="app"></div>
02 const vm = new Vue{(
03 el: document.getElementById("app") // JS选择DoM元素
04 el: "#app" // CSS选择器
05 )}
上文代码中,el属性指明了Vue.js实例的挂载位置,可以在本地新建HTML文件,之后引入Vue.js的CDN链接进行操作。上文是为了给大家展示el可以选择内容,实际开发中只能有一个el配置。在el挂载成功之后,就可以通过Vue.js自带的一些方法访问具体的某个元素,在后续的内容中会讲到。
第二部分的配置内容就是data。
01 const vm = new Vue{(
02 el: "#app",
03 data: { // 定义data配置
04 words: "Hello World" // 定义words属性的,值为"Hello World"
05 }
06 )}
07 console.log(vm.words); // 输出: Hello World
这里可以直接输出vm实例中的words属性,但是要想输出el或者data就得加上$前缀,例如vm.$data。$的作用是区分用户定义的属性与原生暴露的属性。
或者直接将data属性指向一个变量。
01 const myData = { // 自定义myData变量
02 words: "Hello World"
03 };
04 const vm = new Vue{( // 新建实例
05 el: "#app",
06 data: myData // 绑定myData变量
07 )}
08 console.log(vm.words); // 输出: Hello World
09 myData.words = "Hello Vue" // 修改myData对象
10 console.log(vm.words); // 输出: Hello Vue
11 vm.words = "Hello Vue World" // 修改实例中的对象
12 console.log(myData.words) // 输出: Hello Vue World
从上面代码中可以看出,若是data属性指向一个已有的变量,那么data属性会和变量双向绑定。修改变量值,data属性会随之改变;修改data属性,变量值也会改变。
从字面上来看,生命周期很好理解,放在一个人身上,就是由生到死的过程,包括从幼年期,到成长期,再到成人期、老年期等,是逐步变化的。放在Vue.js中,就是Vue.js实例初始化的过程。这个过程根据功能的不同,分为很多周期,可以使用对应的生命周期钩子在合适的生命周期上执行代码。
对于生命周期来说,不用现在就开始详细地了解,随着不断的学习和使用,对生命周期的理解会越来越深,同时生命周期的作用也越来越大。下面介绍一些常用的生命周期钩子函数。
1. craeted
在实例创建之后立即被调用。在这个周期内,实例已经完成了数据观测,属性和方法的计算。但是尚未开始挂载,也就是说,$el目前无法使用,简单来说,就是已经做好了前期的准备工作,但因为还没挂载,所以处于不可见状态。
2. mounted
这一步已经挂载好了,$el也可以使用,可以开始第一个业务逻辑了。但有一点需要注意,这里并没有挂载所有的子组件,如果需要整个视图都渲染完毕,可以使用vm.$nextTick。代码如下。
01 mounted: () => { // mounted钩子
02 this.$nextTick(() => { // 使用nextTick来等到所有的视图都渲染完毕
03 // 执行代码
04 })
05 }
3. updated
数据变动导致重新渲染的时候会调用这个钩子,此时DOM元素已经被更新了,可以进行相关的操作。需要注意的是,如果是服务端渲染,此钩子不会被调用,关于服务端渲染的问题在后面的章节会了解到。
4. beforeDestroy
这个钩子会在实例被销毁之前调用,之前若是绑定了某些监听事件,可在这里进行解绑操作。
那么,如何调用钩子呢?举个例子(下面的代码可以使用2.1.1中的方法进行本地调试)。
01 const vm = new Vue{( // 新建实例
02 el: "#app", // 指定实例挂载位置
03 data: { // 定义data属性
04 words: "Hello World"
05 },
06 created: () => { // 实例挂载前的钩子
07 console.log(this.words); // 输出 "Hello World"
08 },
09 mounted: () => { // 实例挂载后的钩子
10 console.log(this.#el); // 输出 <div id="app"></div>
11 }
12 )}
只需在钩子函数下面执行相应的代码或函数即可,十分简单,重点在于钩子函数的理解和选择。不用担心,随着学习的深入,对钩子函数的理解会更加深刻,使用起来也会更加得心应手。
说到模板语法,首先要明白的就是Vue.js使用的是基于HTML的模板语法。在Vue.js底层的实现上,Vue.js把模板编译成虚拟的DOM元素,之后再结合响应系统,判断出最少渲染的组件数量,最大限度地减少对DOM元素的操作。
在模板语法中最常见的就是“Mustache”语法(双大括号),此语法可以用来进行文本插值,代码如下。
<span>Words: {{ info }}</span>
在渲染之后,<Mustache>标签中的info变量会被替换成其所代表的值,而且无论何时,只要info代表的值发生改变,<Mustache>标签中的内容也会随之改变。
若是想插入HTML片段,则不能使用Mustache语法,这是为了防止注入攻击,<Mustache>标签中的内容会被解析成文本,而不是HTML代码,所以,在输入HTML片段的时候,需要使用v-html指令,代码如下。
01 // rawHtml代指HTML代码
02 <p>使用Mustaches: {{ rawHtml }}</p> // 使用<Mustaches>标签无法解析
03 // 使用v-html指令即可正确解析
04 <p>使用v-html指令: <span v-html="rawHtml"></span></p>
同时<Mustaches>标签页不能用在HTML的相关属性上,例如将一个变量作为一个HTML标签的class,使用Mustaches是不可取的,Vue.js有相应的指令来解决此类问题。
上面阐述了Mustaches语法的限制,那么Mustaches语法有哪些优点呢?在<Mustaches>标签中可以随意使用JS表达式,代码如下。
01 {{ number + 1 }} // JS简单计算
02 {{ ok ? 'YES' : 'NO' }} // JS三元表达式
03 {{ arr.split('').reverse().join('') }} // 对数组的链式操作
加减乘除、三元表达式、链式操作等操作完全没有问题,也可以调用某些函数,前提是函数有返回值。需要注意的是,虽然可以使用JS表达式,但仅限于单个表达式,这是什么意思呢?单个表达式不是指一行表达式,而是指不能进行连续的表达式操作。例如:
01 {{ var a = 1 }} // 这是语句,不是表达式
02 {{ if (ok) { return message } }} // 流控制也不会生效,可以使用三元表达式
这段代码比较简单,能加深对表达式的理解。
计算属性和过滤器都是用来对数据进行相应的修改的方法,尽管两者的目的是相同的,但是在使用方式和逻辑上却有较大的差异。先说计算属性,回想一下2.3.3节模板语法中的一个例子:
{{ arr.split('').reverse().join('') }} // 在<Mustaches>标签中对数据进行操作
在这个例子中,对arr进行了比较复杂的处理,很久之后再看这段代码,可能需思考一会儿才能得出结论,这说明对于代码的理解不是很友好。而且经常这么操作会增加模板的复杂程度,这与模板的设计初衷背道而驰,因为模板语法是用来进行简单的运算的。所以在需要处理复杂逻辑的时候,可以使用计算属性来简化操作。
01 // HTML
02 <div id="example"> // 实例挂载的DOM元素
03 <p>原始字符串: "{{ info }}"</p> // 展示原始字符串
04 <p>修改后字符串: "{{ reversedInfo }}"</p> // 展示修改后字符串
05 </div>
06 // JS
07 const vm = new Vue({ // 新建实例
08 el: '#example', // 挂载的DOM元素
09 data: {
10 info: "Hello World" // 新建数据
11 },
12 computed: { // 计算属性模块
13 reversedInfo: () => { // 计算属性的名字
14 return this.info.split('').reverse().join(''); // 返回处理后的结果
15 }
16 }
17 })
在上述代码中,对info变量值进行了处理,使之由“Hello World”变成“dlroW olleH”,字母的顺序颠倒了。显示部分就不详细讲解了,简单地展示变量而已,重点是computed属性,也就是计算属性。先声明一个reversedInfo函数,函数返回(return)处理之后的数据,调用这个函数,即可获取处理之后的结果。
读者可能会感到疑惑,这与函数有什么区别?从功能上来说是没有区别的,不同的是,计算属性是根据其中依赖的数据缓存的。也就是说,如果info的值不变,reversedInfo是不会再次计算的。如果是函数,每调用一次,则会重新执行一次。所以,使用计算属性可以在很大限度上减少资源的浪费。
下面了解一下过滤器。严格意义上讲,过滤器是计算属性的简化模式,运算比较简单的适合使用过滤器,比较复杂的则应使用计算属性,例如:
01 // HTML
02 <div id="example"> // 实例挂载的DOM元素
03 <p>原始数字: "{{ number }}"</p> // 展示原始数字
04 <p>修改后数字: "{{ number | currencyFilter }}"</p>// 展示修改后数字
05 </div>
06 // JS
07 const vm = new Vue({ // 新建实例
08 el: '#example', // 挂载的DOM元素
09 data: {
10 number: 666 // 新建数据
11 },
12 filter: { // 过滤器模块
13 currencyFilter: (num) => { // 过滤器的名字
14 return num.toString() + "$" // 返回处理后的结果
15 }
16 }
17 })
在上面代码中,数字原本是666,经过滤器处理后就变成“666$”,摇身一变成了“货币格式”,类似的功能还有格式化数字日期等。过滤器的调用方法是在变量后加上中隔线(|),在中隔线之后加上过滤器的名字。这样过滤器会自动将前面的数据作为变量添加进去, currencyFilter自动接收number作为参数,再返回处理后的结果。新建方法和计算属性没有多大的差别,重点在于调用。与计算属性相比,过滤器可以在多个地方使用,不像计算属性那样只能处理同样的数据;与函数相比,过滤器的调用更加简洁。例如,当需要多个过滤器处理数据时,代码如下。
01 {{ message | filterA | filterB }} // 使用过滤器处理数据
02 {{ filterB(filterB(message)) }} // 使用函数处理数据
从上述代码可以看出,若是调用较多,过滤器看起来依然清晰明朗,但是函数则有些混乱。若有更多调用,简直无法直视。比计算属性适用性强,比函数使用更方便,这就是过滤器存在的意义。
计算属性和过滤器在使用上没有绝对性,针对不同的数据处理,可以使用不同的方法,减少资源浪费,提高代码的复用性,这是开发项目中很重要的两件事,也是程序员成长过程中必须要学会的。
样式是前端页面展示中很重要的一个组成部分,动态绑定样式是一个常见需求。在没有使用框架时,比较方便的解决办法可能就是jQuery,但是在Vue.js中,可以使用v-bind指令中的class和style来最大限度简化此类代码。
最常见的用法就是根据某个变量的值来判断是否添加一个class。
01 // HTML
02 <div id="example"> // 实例挂载的DOM元素
03 // 根据isActive的值来判断是否添加active样式
04 <div v-bind:class="{ active: isActive }"></div>
05 </div>
06 // JS
07 const vm = new Vue({ // 新建实例
08 el: '#example', // 挂载的DOM元素
09 data: {
10 isActive: false, // 新建数据
11 }
12 })
若此处的isActive为true,则绑定的div会被添加上active这个class;若为false,则不添加。v-bind:class后面接的是一个对象,对象的key和value是对应的class和变量。当然,也可以传入更多的属性来进行多个class的切换,并且v-bind:class可以和原生class并存,互不干涉。
01 // HTML
02 <div
03 class="static" // 普通class
04 v-bind:class="{ active: isActive, error: hasError }"// v-bind绑定的class
05 >
06 </div>
07 // JS
08 const vm = new Vue({ // 新建实例
09 el: '#example', // 挂载的DOM元素
10 data: { // 新建数据
11 isActive: true,
12 hasError: false
13 }
14 })
上述代码中,class会随着isActive和hasError这两个变量变化,如果isActive是true, hasError是false,那么class则会有static、active这两个。当然,不必把所有的绑定都放在HTML模板中。
01 // HTML
02 <div
03 class="static" // 普通class
04 v-bind:class="classObject" // v-bind绑定的class对象
05 >
06 </div>
07 // JS
08 const vm = new Vue({ // 新建实例
09 el: '#example', // 挂载的DOM元素
10 data: { // 新建数据
11 classObject: { // 定义class对象
12 active: true,
13 error: false
14 }
15 }
16 })
上述代码把多个class绑定对象糅合在一起放在data中,形成一个更大的变量,v-bind:class会对这样的对象自动进行解析,如此HTML模板看上去就不那么复杂。如果样式的判定更加复杂,可以使用2.3.4节讲到的计算属性来简化HTML模板。
01 // HTML
02 <div
03 class="static" // 普通class
04 v-bind:class="classObject" // v-bind绑定的class对象
05 >
06 </div>
07 // JS
08 const vm = new Vue({ // 新建实例
09 el: '#example', // 挂载的DOM元素
10 data: { // 新建数据
11 isActive: true,
12 hasError: false
13 },
14 computed: {
15 classObject: () => { // 新建名为classObject的计算属性
16 return {
17 // 判定active是否为true
18 active: this.isActive && !this.hasError,
19 // 判定error是否为true
20 error: this.hasError && this.hasError.type === 'fatal'
21 }
22 }
23 }
24 })
利用计算属性强大的功能,可以在保证HTML模板简洁性的同时完成复杂的class运算。若在class还是比较多的情况下,在v-bind:class上使用数组也是一个不错的选择。
01 // HTML
02 <div v-bind:class="[activeClass, errorClass]"></div>
03 // JS
04 data: {
05 activeClass: 'active',
06 errorClass: 'error'
07 }
上述代码中有activeClass和errorClass两个变量,渲染之后的代码如下。
<div class="active error"></div>
在数组中也可以使用三元表达式来切换class。
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
但当class的条件比较多或者有多个条件class时,这样写就比较复杂了,可以在数组中使用对象语法来进行相应的简化。
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
下面这部分内容目前来看可能有些难度,因为还没有讲Vue.js组件,看不懂的读者可以先跳过,等学完组件内容再回头学习。
除了可以在DOM元素上使用v-bind:class动态更改样式,在组件上依然可以进行相同的操作,并且组件本身自带的样式也不会被覆盖。
01 // 新建组件
02 Vue.component('test-component', {
03 template: '<p class="sentence">Hello World</p>'
04 })
05 // 调用组件
06 <test-component class="active"></test-component>
07 // 渲染结果
08 <p class="sentence active">Hello World</p>
09 // 数据绑定class
10 <test-component v-bind:class="{ active: isActive }"></test-component>
11 // 渲染结果(isActive为true)
12 <p class="sentence active">Hello World</p>
从上面代码可以看出,组件的动态样式并不复杂,与DOM元素的操作没有差别,渲染之后也不会对原生的class有影响。
下面介绍内联样式的修改,它的根本逻辑和class差不多,只是语法上有一定区别。
01 // HTML
02 // 使用v-bind:style绑定样式
03 <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
04 // JS
05 data: { // 自定义样式
06 activeColor: 'red',
07 fontSize: 30
08 }
也可以直接绑定一个对象,这样HTML模板看起来会更清晰。
01 // HTML
02 // 使用v-bind:style绑定对象
03 <div v-bind:style="styleObject"></div>
04 // JS
05 data: {
06 styleObject: { // 自定义样式对象
07 color: 'red',
08 fontSize: '13px'
09 }
10 }
最后,Vue.js还为样式的兼容性提供了一些小小的帮助。众所周知,有些样式在不同的浏览器中有不同的前缀,例如-webkit,-ms等,在Vue.js中可以这样简写。
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
虽然这里增加前缀,但实际渲染的时候,会根据浏览器的不同来渲染不同的样式,不会出现样式重复的情况。
上述关于样式表的修改内容,对于日常使用已经足够。由于项目中很少使用内联样式表,读者可以把重点放在class的动态绑定上。
说到条件,相信很多人的第一反应就是if和else。其实条件渲染也是由if和else构成的。
首先来了解一下条件渲染的3个指令:v-if、v-else和v-else-if。这和JS中条件的逻辑差不多,不同的是在JS中,符合条件会执行相应的代码块,在Vue.js中,符合条件会渲染相应的HTML模块。
01 <div v-if="type === 'A'"> // type是A展示此div
02 A
03 </div>
04 <div v-else-if="type === 'B'"> // type是B展示此div
05 B
06 </div>
07 <div v-else-if="type === 'C'"> // type是C展示此div
08 C
09 </div>
10 <div v-else> // type不是A、B、C展示此div
11 Not A/B/C
12 </div>
上面这些代码很好地解释了v-if系列指令的作用,如果变量type的值是“A”,则会渲染第一个div;如果type的值是“B”,则会渲染第二个div;以此类推。需要注意的是,v-if可以单独使用,不用搭配v-else或者v-else-if。但是v-else和v-else-if是依赖于v-if的,这和JS中的条件判断是相似的。若在JS中单独使用else或者else-if,JS会报错,在Vue.js中也会有相应的提示。
比较类似的指令是v-show,v-show没有像v-if一样的v-if-else指令,只能单独使用,用法与v-if相同,两者的区别在于效果。v-if在渲染的时候会判断条件是否满足,不满足则不会渲染DOM元素。而v-show不管满足不满足条件都会渲染DOM元素,只是在条件不满足的时候,会给DOM元素加上“display:none”的属性,让DOM元素不可见。
所以根据两者的特性可以得出这样的一个结论:如果DOM元素的状态需要频繁切换,更适合使用v-show;若DOM元素的条件很少改变,v-if会是更好的选择。因为v-if有着更高的切换开销,也就是说,v-if在切换状态的时候,会消耗更多的资源,而v-show会在初始渲染的时候,消耗更多的资源。
列表渲染也很好理解,就是将一个数组或者集合循环展示出来。
01 // HTML
02 <ul id="example"> // 实例挂载的DOM元素
03 <li v-for="item in items"> // 循环li元素
04 {{ item.message }} // 展示循环中元素的内容
05 </li>
06 </ul>
07 // JS
08 const example = new Vue({ // 新建实例
09 el: '#example', // 挂载的DOM元素
10 data: {
11 items: [ // 创建模拟集合
12 { message: 'Hello' },
13 { message: 'World' }
14 ]
15 }
16 })
此处给ul这个无序列表下的li加上v-for指令,v-if后面跟的是“item in items”的特殊语法,item是数组或者集合的子元素,items是数组。之后将item元素中的message属性展示出来。这里的items被定义为一个集合,里面是一个个的对象,每个对象都有一个massage属性。这里用“item in items”语法将集合中的元素循环出来,意味着item代表集中的对象。当然,也可以用其他名字,如“object in items”或“balaba in items”。只是如果名字太另类,Vue.js会给一个警告(warning),告诉你名字差得太远。在获取item之后,就可以对item做任何操作了,例如根据item的值展示不同的样式。
这就是v-for对集合的基本用法,比较简单。如果不满足于仅仅获取item值,还想获取key和index,则代码如下。
01 <ul id="example">
02 <li v-for="(item, key, index) in items">// 获取items的值和序号
03 {{ index }}.{{ key }}: {{ item.message }}
04 </li>
05 </ul>
需要注意,如果items仅仅是个数组,则没有key,只有item和index。另外,使用v-for必须绑定key属性,这里的key不是刚才循环出来的元素的key,而是Vue.js为了区分每个DOM元素用的,要保证key的唯一性。在Vue.js 2.2.0之前key是可选项,但是在Vue.js 2.2.0之后key成了必选项,如果不写,Vue.js会报错。
一般利用v-bind来绑定key,可以使用v-bind的简写模式“:”,2.3.5节讲到的v-bind:class也可以简写为: class。
01 <ul id="example">
02 <li v-for="(item, key, index) in items" :key="index">// 获取items的值和序号
03 {{ index }}. {{ key }}: {{ item.message }}
04 </li>
05 </ul>
为了避免key的重复,可以使用当前元素的index,若itmes中有其他唯一的属性,也可以使用,如id。
下面介绍如何触发v-for的修改。Vue.js是有数据的双向绑定的,在修改v-for绑定的数据之后,v-for会重新渲染,将改变展示在页面上。但是对于数组的某些变化,Vue.js是无法感知的,例如:
01 // JS
02 const vm = new Vue({ // 新建实例
03 data: {
04 items: ["a", "b", "c"] // 创建模拟数据
05 }
06 })
07 vm.items[1] = "x" // Vue.js无法检测的改变
08 vm.items.length = 2 // Vue.js无法检测的改变
09 vm.words = "Hello World" // Vue.js无法检测的改变
从上述代码中可以看到,当利用索引值修改一个元素的内容或者直接修改数组长度,无法检测到Vue.js。为了解决这种问题,可以使用Vue.js自带的set方法或者JS中的splice方法来修改数组,代码如下。
01 // 解决通过索引值修改数组中元素内容问题
02 Vue.set(vm.items, indexOfItem, newValue); // 使用Vue.js自带的set方法来修改元素内容
03 vm.items.splice(indexOfItem, 1, newValue);// 使用splice方法来修改元素内容
04 // 解决直接修改数组长度问题
05 vm.items.splice(newLength); // 使用splice来修改数组长度
如果使用lodash类的库对数组进行操作,Vue.js也是可以检测到的,因为其从结果上来说,都是返回一个新数组。
Vue.js另外一种无法检测到的改变是对象数据的添加和删除。例如,vm.words=“Hello Word”,这行代码给vm新增了一个数据——words。因为已经新建过vm这个实例,所以再手动添加的话,Vue.js就无法识别,想要让Vue.js知道实例的修改,可以使用Vue.js的set方法。
01 const vm = new Vue({ // 新建实例
02 data: {
03 userInfo: { // 创建模拟数据
04 name: 'Rex'
05 }
06 }
07 })
08 Vue.set(vm.userInfo, 'age', 18); // 使用set方法修改数据
格式是Vue.set(object, key, value),object是修改对象的名字,key是新增属性的名称, value是新属性的内容。
和2.3.5节相同,v-for也可以用在组件上,让组件循环展示,可以使用props来将循环出来的元素传递给组件。
01 <test-component // 测试组件
02 v-for="(item, index) in items" // 循环items
03 v-bind:item="item" // 将元素作为item传递给组件
04 v-bind:index="index" // 将元素序号作为index传递给组件
05 v-bind:key="item.id" // 给每个组件绑定不同的key
06 ></test-component>
上述代码中,循环了test-componet这个组件,同时将items中元素的内容和index传递给组件,使得组件内部可以直接调用元素内容。
永远不要把v-if和v-for放在同一个DOM元素上,例如:
01 // 错误示例
02 <li v-for="item in items" v-if="item.id === 3"> // 同时在<li>标签上使用v-for和v-if指令
03 {{ item.id }}
04 </li>
在这种情况下,会大量浪费系统资源,严重时可造成系统运行不畅,甚至死机。因为在Vue.js中,v-for的优先级比v-if更高,这就造成了两者若是同时使用,v-for会首先循环,忽略掉v-if的条件。若v-for的元素很多,即使只想展示一小部分数据,系统还是会把所有的数据都渲染出来,之后再通过v-if的条件来决定是否要去掉这些DOM元素,十分消耗系统资源,所以正确的做法如下。
01 // 正确示例
02 <ul v-if="items.length"> // 在<ul>标签上使用v-if指令
03 <li v-for="item in items" :key="item.id">// 在<li>标签上使用v-for指令
04 {{ item.id }}
05 </li>
06 </ul>
上述代码中,先在<ul>标签上判断items的长度。若是items的长度不大于1,则不会渲染里面的内容;若是items的长度大于或等于1,才会渲染里面的<li>标签。渲染的时候会根据v-for指令来循环,这也是符合Vue.js语法风格的一种写法。
在JS中,单击和按键的事件是比较常用的,也是比较重要的,因为前端交互有很大一部分都是这两种事件。同样,在Vue.js中,对这两种事件也有着全面的处理,也就是v-on指令。
v-on指令和v-bind一样,后面接不同的内容就有不同的意义,例如v-bind:class和v-bind:key。同样,v-on也可以接不同的内容,单击事件就是通过v-on后面接click来绑定的。
01 // HTML
02 <div id="example"> // 实例挂载的DOM元素
03 <button v-on:click="counter += 1">单击一次</button> // 单击一次counter加1
04 <p>你已单击了{{ counter }}次</p> // 展示单击次数
05 </div>
06 // JS
07 const vm= new Vue({ // 新建实例
08 el: '#example',
09 data: {
10 counter: 0, // 创建模拟数据
11 }
12 })
上述代码在<button>标签上使用v-on:click绑定了单击事件,单击事件的内容就是让counter这个变量自增1。counter是在新建实例的时候创建的,值为0。之后每单击一次这个按钮,都会触发"counter += 1"这句代码,之后counter会被展示在页面中,用户可以看到counter的变化。
当然,不仅可以在v-on:click上绑定程序语句,函数也可以。
01 // HTML
02 <div id="example"> // 实例挂载的DOM元素
03 <button v-on:click="add(1)">加3</button> // 单击一次counter加3
04 <p>结果:{{ counter }}</p> // 展示counter内容
05 </div>
06 // JS
07 const vm= new Vue({ // 新建实例
08 el: '#example',
09 data: {
10 counter: 0, // 创建模拟数据
11 },
12 methods: {
13 add (num) => { // 创建添加方法
14 this.counter += num; // 增加counter变量
15 },
16 },
17 })
在上述代码中,<button>上绑定了add函数,同时传一个num参数,参数的值为3。这样每单击一次这个按钮,都会触发add函数,然后给counter加3,counter的变化也会展示在页面上。
与单击事件类似的是冒泡事件,Vue.js中把这些相关事件做成事件修饰符,传递给v-on:click。
01 // 单击事件只触发一次
02 <a v-on:click.once="doThis"></a>
03
04 // 阻止单击事件继续传播
05 <a v-on:click.stop="doThis"></a>
06
07 // 提交事件不再重载页面
08 <form v-on:submit.prevent="onSubmit"></form>
09
10 // 修饰符可以串联
11 <a v-on:click.stop.prevent="doThat"></a>
12
13 // 只有修饰符
14 <form v-on:submit.prevent></form>
15
16 // 添加事件监听器时使用事件捕获模式
17 // 即元素自身触发的事件先在此处理,然后才交由内部元素进行处理
18 <div v-on:click.capture="doThis">...</div>
19
20 // 只当在 event.target 是当前元素自身时触发处理函数
21 // 即事件不是从内部元素触发的
22 <div v-on:click.self="doThat">...</div>
23
24 // 滚动事件的默认行为 (即滚动行为) 将会立即触发
25 // 而不会等待 'onScroll' 完成
26 // 这其中包含 'event.preventDefault()' 的情况
27 <div v-on:scroll.passive="onScroll">...</div>
在使用这些修饰符的时候,要注意它们的顺序,因为相对的代码会根据修饰符的顺序产生。如v-on:click.prevent.self会阻止所有的单击,而v-on:click.self.prevent只会阻止对元素自身的单击。上面这些修饰符较多,记住常用的即可。重点需要记住的是使用方法,而不是繁杂的API。
在Vue.js中,可以使用v-on: keyup.键值来绑定按键事件。例如:
01 // 在keyCode是13时调用submint函数
02 <input v-on:keyup.13="submit">
13是键值,对应Enter键。想要记住所有的键值是不可能的,Vue.js贴心地给常用的按键起了别名。
01 // Enter键
02 <input v-on:keyup.enter="doThis">
03 // Tab键
04 <input v-on:keyup.tab="doThis">
05 // 向前删除和向后删除键
06 <input v-on:keyup.delete="doThis">
07 // Esc键
08 <input v-on:keyup.esc="doThis">
09 // 上方向键
10 <input v-on:keyup.up="doThis">
11 // 下方向键
12 <input v-on:keyup.down="doThis">
13 // 左方向键
14 <input v-on:keyup.left="doThis">
15 // 右方向键
16 <input v-on:keyup.right="doThis">
17 // Ctrl键
18 <input v-on:keyup.ctrl="doThis">
19 // Alt键
20 <input v-on:keyup.alt="doThis">
21 // Shift键
22 <input v-on:keyup.shift="doThis">
23 // macOS系统上是Command键,Windows上是Windows键
24 <input v-on:keyup.meta="doThis">
这样就方便了,但是Vue.js还支持按键事件的key。这是什么呢?如按一下PageUp键,会触发一个keyboardEvent事件,这是JS的原生事件。此事件中有一个属性,名为key。PageUp键的key就是page-up,进而可以直接使用page-up来绑定按钮。
01 // 向上翻页键
02 <input v-on:keyup.page-up="doThis">
因为事件绑定使用频率很高,每次都这样写,容易使人厌烦,因此,Vue.js提供了缩写——“@”。
01 <button @click="doThis" /> // 单击事件的缩写
02 <input @keyup.page-up="doThis"> // 按键事件的缩写
单击事件和按键事件的其他内容实用性不强,这里不再赘述。
作为Vue.js的主要特点之一,双向绑定是一个语法糖(它是结合使用多种语法,并不是一种新的语法)。它的本质是通过监听用户的输入或者数据的更新,之后触发DOM元素的变化。原理简单,但是实际应用上,会根据不同的组件,有不同的变化,这些变化就是本节将要介绍的重点。
从最简单的文本开始。
01 // HTML
02 // 使用v-model绑定inputInfo变量值给input
03 <input v-model="inputInfo">
04 // 展示inputInfo的内容
05 <p>input输入框的内容: {{ inputInfo}}</p>
06 // 使用v-model绑定textareaInfo变量值给textarea
07 <textarea v-model="textareaInfo"></textarea>
08 // 展示textareaInfo的内容
09 <p>textarea输入框的内容: {{ textareaInfo}}</p>
数据绑定,首先要有数据,这里的inputInfo和textareaInfo是预先定义好的变量,之后使用Vue.js的v-model指令来将这两个变量分别绑定到这两个输入框中。这样不管是修改输入框中的内容还是直接修改变量,两边的数据都是同步的。
接下来看看多选框,可以选择多个选项。
01 // HTML
02 <div id='example'> // 实例挂载的DOM元素
03 // 使用v-model指令给checkbox绑定checked变量
04 <input type="checkbox" id="apple" value="apple" v-model="checked">
05 <label for="jack">apple</label>
06 <input type="checkbox" id="banana" value="banana" v-model="checked">
07 <label for="john">banana</label>
08 <input type="checkbox" id="orange" value="orange" v-model="checked">
09 <label for="mike">orange</label>
10 <br>
11 <span>当前选中项:{{ checked }}</span> // 展示checked变量的值
12 </div>
13 // JS
14 const vm= new Vue({ // 新建实例
15 el: '#example',
16 data: {
17 checked: [], // 创建选中变量,类型为数组
18 }
19 })
上述代码中,checked变量被初始化为一个空数组,每个input都是一个checkbox。使用v-model指令给它们绑定了checked变量。如果单击input,那么在checked中就会出现input的value属性的值。如果选中名为apple的input,那么checked数组变为“['apple']”,如果再选中名为orange的input,那么checked数组变为“["apple","orange"]”。如果取消选中,那么checked数组中对应的值就会消失。
单选按钮对于input的数据绑定来说是一样的,只是变量不再是一个数组,而是一个字符串,字符串的内容就是当前选中的input的值,代码如下。
01 // HTML
02 <div id='example'> // 实例挂载的DOM元素
03 // 使用v-model指令给radio绑定picked变量
04 <input type="radio" id="one" value="One" v-model="picked">
05 <label for="one">One</label>
06 <br>
07 <input type="radio" id="two" value="Two" v-model="picked">
08 <label for="two">Two</label>
09 <br>
10 <span>当前选中项:{{ picked }}</span> // 展示picked变量的值
11 </div>
12 // JS
13 const vm= new Vue({ // 新建实例
14 el: '#example',
15 data: {
16 picked: '' // 创建选中变量,类型为字符串
17 }
18 })
选择框中的单选和多选操作与input相似,区别在于绑定的值是一个字符串还是一个数组,下面以一个单选框为例。
01 // HTML
02 <div id='example'> // 实例挂载的DOM元素
03 // 使用v-model指令给select绑定selected变量
04 <select v-model="selected">
05 <option disabled value="">请选择</option>
06 <option value="apple">apple</option>
07 <option value="banana">banana</option>
08 <option value="orange">orange</option>
09 </select>
10 <span>当前选中项:{{ selected }}</span> // 展示selected 变量的值
11 </div>
12 // JS
13 const vm= new Vue({ // 新建实例
14 el: '#example',
15 data: {
16 selected : '', // 创建选中变量,类型为字符串
17 }
18 })
因为select和input不同,没有多余的元素,所以只需要使用v-model把selected变量绑定在select上即可,之后selected变量就会自动随着选中option的变化而变化。
细心的读者可能会发现,这里有一个多余的option选项,这其实是为iOS系统做的一个兼容。因为当v-model绑定变量的初始值不匹配任何选项时,select会被渲染成“未选中”的状态。在iOS系统中,用户会因此无法选中第一个选项。因为在这种情况下,iOS系统不会触发change事件。所以,提供一个空选项有时是一个更好的选择。
对于多选系列来说,绑定的变量只能是一个数组;而对于单选系列来说,绑定的值不仅是一个字符串,也可以是一个布尔值。
<input type="checkbox" v-model="toggle"> // toggle 为 true 或 false
这里的toggle变量就是一个布尔值,checkbox选中时,toggle为true;未选中时,toggle是false。
对于多选系列来说,为了省时省力,可以使用v-for来循环渲染:
01 // HTML
02 <div id='example'> // 实例挂载的DOM元素
03 // 使用v-model指令给select绑定selected变量
04 <select v-model="selected">
05 <option disabled value="">请选择</option>
06 // 使用v-for指令循环options的值,使用v-bind指令的简写模式给option绑定value属性
07 <option v-for="option in options" :value="option">
08 {{ option }}
09 </option>
10 </select>
11 <span>当前选中项:{{ selected }}</span> // 展示selected 变量的值
12 </div>
13 // JS
14 const vm= new Vue({ // 新建实例
15 el: '#example',
16 data: {
17 options: [ "apple", "banana", "orange"], // 创建选项数据
18 selected : '', // 创建选中变量,类型为字符串
19 }
20 })
上述代码中,先给select绑定selected作为选中变量,之后在<option>标签上使用v-for指令循环options变量来渲染,同时使用v-bind指令给<option>标签绑定了value属性。因为options数组中的元素是字符串而不是对象,所以直接使用即可。
最后,Vue.js还给事件绑定提供了一些修饰符,以方便在某些情况下处理数据。
01 // .lazy 修改默认触发事件为change
02 <input v-model.lazy="msg" >
03 // .number 将输入的值转化为数字类型
04 <input v-model.number="age" type="number">
05 // .trim 去掉用户输入的首尾空格
06 <input v-model.trim="msg">
lazy可能不太好理解,其本质是使用v-model指令,在每次 input事件触发后,将输入框的值与数据同步转变为使用 change事件进行同步。简单来说,就是减少数据同步的频率,例如默认1秒同步一次,使用lazy后,可能就是10秒同步一次了。number是为了方便解析,因为即使给input的type属性赋值为number,input返回的数据也还会是字符串类型,加上number之后,就会强行转化成数字类型,不用手动将变量转化成数字类型。trim就很简单——去掉用户输入的首尾空格,省去手动处理的步骤。
双向绑定的部分语法不难,重点在于针对不同的场景使用不同的方式,多加掌握对后续的开发会有很大的帮助。
在本章中,介绍了Vue.js的大部分特性,同时对其运行原理也有了一定的认识。
对于挂载实例和生命周期的介绍,让我们认识到Vue.js项目运行的整个过程。对于Vue.js项目,首先需要新建一个Vue.js实例,在实例中可以进行一些初始化配置,之后运行项目时, Vue.js会自动解析这些配置,例如把Vue.js实例挂载到指定的地方,新建准备好的变量,渲染模板等操作。而这些操作都是有一定过程的,Vue.js把这些过程根据其功能的不同分成周期,可以使用生命周期钩子函数进入这些周期,执行相应代码。
使用Vue.js提供的模板语法进行数据展示,不用在JS中修改DOM元素的value,直接放在HTML模板中,清晰明了。还可以使用计算属性或者过滤器处理数据,如果处理过程的适用性不强,计算属性会是一个好的选择。如果有很多地方都会用到类似的计算,更加倾向于使用过滤器。
直接在HTML模板上动态绑定样式真的是一件很愉快的事,利用一些变量的值来判断当前是否需要添加一个class是十分方便的。条件渲染可以直接去掉不适用于当前场景的HTML元素,比起display:none更是干脆彻底。列表渲染可以减少大量的重复代码,一个个去手写相同的DOM元素真是一个费力不讨好的工作。
对于事件的处理,不用在JS代码中绑定事件,而是直接使用v-on指令绑定在HTML模板中。数据双向绑定也是,直接在DOM元素中绑定变量的值,如此双向的修改都会触发另一方的变化。
由于Vue.js在DOM元素上使用很多指令,使得HTML模板看起来更加复杂DOM元素的构成也变得更加复杂,如果放在一行,可能会出现超长DOM元素的情况,当DOM元素的属性超过3个时,可以换行处理。
01 <div id='example'>
02 <li
03 v-for="(item, index) in items" // 循环items
04 :class="{ active: item.id === 0 }" // 第一个item添加名为active的class
05 :key="item.id" // 绑定key属性为当前item的id属性
06 @click="show(item)" // 绑定单击事件,单击触发show方法,传递item作为参数
07 >
08 {{ item.content }} // 展示item的内容
09 </li>
10 </div>
换行后,每个指令清晰明了,这就是Vue.js的目标——简单看一眼HTML模板,就能清楚定位JS中相应的代码,同时了解当前DOM元素的状态。这样做还有一个优点,就是方便测试,因为事件都绑定在DOM元素上了,如果DOM元素发生变化,也不用管,只要不影响绑定事件的DOM即可。最后就是事件的清除,因为所有的事件都被绑定在DOM元素上,也就是MVVM框架中的ViewModel层,当这个ViewModel层被销毁,所有事件处理器都会自动删除,无须手动清理。
总的来说,使用Vue.js模板需要花时间去习惯它的逻辑和语法风格,熟悉之后才会发现Vue.js神奇的“魔法”。