文章目录
感谢李老师: www.funtl.com
前端三要素
- HTML(结构):超文本标记语言(Hyper Text Markup Language),决定网页的结构和内容
- CSS(表现):层叠样式表(Cascading Style Sheets),设定网页的表现样式
- JavaScript(行为):是一种弱类型脚本语言,其源代码不需经过编译,而是由浏览器解释运行,用于控制网页的行为
结构层(HTML)
略
表现层(CSS)
CSS 层叠样式表是一门标记语言,并不是编程语言,因此不可以自定义变量,不可以引用等,换句话说就是不具备任何语法支持,它主要缺陷如下:
- 语法不够强大,比如无法嵌套书写,导致模块化开发中需要书写很多重复的选择器;
- 没有变量和合理的样式复用机制,使得逻辑上相关的属性值必须以字面量的形式重复输出,导致难以维护;
这就导致了我们在工作中无端增加了许多工作量。为了解决这个问题,前端开发人员会使用一种称之为
【CSS 预处理器】
的工具,提供 CSS 缺失的样式层复用机制、减少冗余代码,提高样式代码的可维护性。大大提高了前端在样式上的开发效率。
什么是 CSS 预处理器
CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加了一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行 CSS 的编码工作。转化成通俗易懂的话来说就是“
用一种专门的编程语言,进行 Web 页面样式设计,再通过编译器转化为正常的 CSS 文件,以供项目使用
”。
常用的 CSS 预处理器有哪些
- SASS:基于 Ruby,通过服务端处理,功能强大。解析效率高。需要学习 Ruby 语言,上手难度高于 LESS。
- LESS:基于 NodeJS,通过客户端处理,使用简单。功能比 SASS 简单,解析效率也低于 SASS,但在实际开发中足够了,所以我们后台人员如果需要的话,建议使用 LESS。
行为层(JavaScript)
JavaScript 一门弱类型脚本语言,其源代码在发往客户端运行之前不需经过编译,而是将文本格式的字符代码发送给浏览器由浏览器解释运行。
Native 原生 JS 开发
原生 JS 开发,也就是让我们按照
【ECMAScript】
标准的开发方式,简称是 ES,特点是所有浏览器都支持。截止到当前博客发布时间(2018 年 12 月 04 日),ES 标准已发布如下版本:
- ES3
- ES4(内部,未正式发布)
- ES5(全浏览器支持)
- ES6(常用,当前主流版本)
- ES7
- ES8
- ES9(草案阶段)
区别就是逐步增加新特性。
TypeScript 微软的标准
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。由安德斯·海尔斯伯格(C#、Delphi、TypeScript 之父;.NET 创立者)主导。
该语言的特点就是除了具备 ES 的特性之外还纳入了许多不在标准范围内的新特性,所以会导致很多浏览器不能直接支持 TypeScript 语法,需要编译后(编译成 JS)才能被浏览器正确执行。
JavaScript 框架
- jQuery:大家熟知的 JavaScript 框架,优点是简化了 DOM 操作,缺点是 DOM 操作太频繁,影响前端性能;在前端眼里使用它仅仅是为了兼容 IE6、7、8;
- Angular:Google 收购的前端框架,由一群 Java 程序员开发,其特点是将后台的 MVC 模式搬到了前端并增加了模块化开发的理念,与微软合作,采用 TypeScript 语法开发;对后台程序员友好,对前端程序员不太友好;最大的缺点是版本迭代不合理(如:1代 -> 2代,除了名字,基本就是两个东西;截止发表博客时已推出了 Angular6)
-
React:Facebook 出品,一款高性能的 JS 前端框架;特点是提出了新概念
【虚拟 DOM】
用于减少真实 DOM 操作,在内存中模拟 DOM 操作,有效的提升了前端渲染效率;缺点是使用复杂,因为需要额外学习一门
【JSX】
语言; -
Vue
:一款渐进式 JavaScript 框架,所谓渐进式就是逐步实现新特性的意思,如实现模块化开发、路由、状态管理等新特性。其特点是
综合了 Angular(模块化) 和 React(虚拟 DOM) 的优点;
-
Axios
:前端通信框架;因为
Vue
的边界很明确,就是为了处理 DOM,所以并不具备通信能力,此时就需要额外使用一个通信框架与服务器交互;当然也可以直接选择使用 jQuery 提供的 AJAX 通信功能;
UI 框架
- Ant-Design:阿里巴巴出品,基于 React 的 UI 框架
-
ElementUI:饿了么出品,基于 Vue 的 UI 框架
- Bootstrap:Twitter 推出的一个用于前端开发的开源工具包
- AmazeUI:又叫“妹子 UI”,一款 HTML5 跨屏前端框架
JavaScript 构建工具
- Babel:JS 编译工具,主要用于浏览器不支持的 ES 新特性,比如用于编译 TypeScript
- WebPack:模块打包器,主要作用是打包、压缩、合并及按序加载
注:以上知识点已将 WebApp 开发所需技能全部梳理完毕
三端统一
混合开发(Hybrid App)
主要目的是实现一套代码三端统一(PC、Android、iOS)并能够调用到设备底层硬件(如:传感器、GPS、摄像头等),打包方式主要有以下两种:
-
云打包:HBuild -> HBuildX,DCloud 出品;API Cloud
-
本地打包: Cordova(前身是 PhoneGap)
打包的时候 嵌入框架去【AOP 面向切面编程】。
微信小程序
详见微信官网.
这里就是介绍一个方便微信小程序 UI 开发的框架:
WeUI
后端技术
前端人员为了方便开发也需要掌握一定的后端技术,但我们 Java 后台人员知道后台知识体系极其庞大复杂,所以为了方便前端人员开发后台应用,就出现了 NodeJS 这样的技术。
NodeJS 的作者已经声称放弃 NodeJS(说是架构做的不好再加上笨重的 node_modules,可能让作者不爽了吧),开始开发全新架构的 Deno
既然是后台技术,那肯定也需要框架和项目管理工具,NodeJS 框架及项目管理工具如下:
- Express:NodeJS 框架
- Koa:Express 简化版
- NPM:项目综合管理工具,类似于 Maven
- YARN:NPM 的替代方案,类似于 Maven 和 Gradle 的关系
附:扩展阅读
当前主流前端框架
Vue.js
主要用于手机端
iView
iview 是一个强大的基于 Vue 的 UI 库,有很多实用的基础组件比 elementui 的组件更丰富,主要服务于 PC 界面的中后台产品。使用单文件的 Vue 组件化开发模式 基于 npm + webpack + babel 开发,支持 ES2015 高质量、功能丰富 友好的 API ,自由灵活地使用空间。
备注:属于前端主流框架,选型时可考虑使用,主要特点是移动端支持较多
ElementUI
主要用于PC端
Element 是饿了么前端开源维护的 Vue UI 组件库,组件齐全,基本涵盖后台所需的所有组件,文档讲解详细,例子也很丰富。主要用于开发 PC 端的页面,是一个质量比较高的 Vue UI 组件库。
备注:属于前端主流框架,选型时可考虑使用,主要特点是桌面端支持较多
ICE
持续关注
ICE
框架
飞冰是阿里巴巴团队基于 React/Angular/Vue 的中后台应用解决方案,在阿里巴巴内部,已经有 270 多个来自几乎所有 BU 的项目在使用。飞冰包含了一条从设计端到开发端的完整链路,帮助用户快速搭建属于自己的中后台应用。
备注:主要组件还是以 React 为主,截止 2019 年 02 月 17 日更新博客前对 Vue 的支持还不太完善,目前尚处于观望阶段
VantUI
Vant UI 是有赞前端团队基于有赞统一的规范实现的 Vue 组件库,提供了一整套 UI 基础组件和业务组件。通过 Vant,可以快速搭建出风格统一的页面,提升开发效率。
AtUI
at-ui 是一款基于 Vue 2.x 的前端 UI 组件库,主要用于快速开发 PC 网站产品。 它提供了一套 npm + webpack + babel 前端开发工作流程,CSS 样式独立,即使采用不同的框架实现都能保持统一的 UI 风格。
CubeUI
cube-ui 是滴滴团队开发的基于 Vue.js 实现的精致移动端组件库。支持按需引入和后编译,轻量灵活;扩展性强,可以方便地基于现有组件实现二次开发。
混合开发
Flutter
全平台开发,很重要!!!
Flutter 是谷歌的移动端 UI 框架,可在极短的时间内构建 Android 和 iOS 上高质量的原生级应用。Flutter 可与现有代码一起工作, 它被世界各地的开发者和组织使用, 并且 Flutter 是免费和开源的。
备注:Google 出品,主要特点是快速构建原生 APP 应用程序,如做混合应用该框架为必选框架
Ionic
Ionic 既是一个 CSS 框架也是一个 Javascript UI 库,Ionic 是目前最有潜力的一款 HTML5 手机应用开发框架。通过 SASS 构建应用程序,它提供了很多 UI 组件来帮助开发者开发强大的应用。它使用 JavaScript MVVM 框架和 AngularJS/Vue 来增强应用。提供数据的双向绑定,使用它成为 Web 和移动开发者的共同选择。
微信小程序
mpvue
mpvue 是美团开发的一个使用
Vue.js
开发小程序的前端框架,目前支持
微信小程序
、
百度智能小程序
,
头条小程序
和
支付宝小程序
。 框架基于
Vue.js
,修改了的运行时框架
runtime
和代码编译器
compiler
实现,使其可运行在小程序环境中,从而为小程序开发引入了
Vue.js
开发体验。
备注:完备的 Vue 开发体验,并且支持多平台的小程序开发,推荐使用
WeUI
WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。包含 button、cell、dialog、toast、article、icon 等各式元素。
前端重要的框架Vue.js、ElementUI
持续关注:ICE
全平台开发适合的平台:Fultte
小程序开发语言:mpvue
是什么?
为什么?
怎么用?
了解前后分离的演变历史
为什么需要前后分离
后端为主的 MVC 时代
为了降低开发的复杂度,以后端为出发点,比如:Struts、SpringMVC 等框架的使用,就是后端的 MVC 时代
以
SpringMVC
流程为例:
-
发起请求到前端控制器(
DispatcherServlet
) -
前端控制器请求
HandlerMapping
查找
Handler
,可以根据
xml
配置、注解进行查找 -
处理器映射器
HandlerMapping
向前端控制器返回
Handler
-
前端控制器调用处理器适配器去执行
Handler
-
处理器适配器去执行
Handler
-
Handler
执行完成给适配器返回
ModelAndView
-
处理器适配器向前端控制器返回
ModelAndView
,
ModelAndView
是
SpringMVC
框架的一个底层对象,包括
Model
和
View
-
前端控制器请求视图解析器去进行视图解析,根据逻辑视图名解析成真正的视图(
JSP
) -
视图解析器向前端控制器返回
View
-
前端控制器进行视图渲染,视图渲染将模型数据(在
ModelAndView
对象中)填充到
request
域 - 前端控制器向用户响应结果
优点
MVC 是一个非常好的协作模式,能够有效降低代码的耦合度,从架构上能够让开发者明白代码应该写在哪里。为了让 View 更纯粹,还可以使用
Thymeleaf
、Freemarker 等模板引擎,使模板里无法写入 Java 代码,让前后端分工更加清晰。
缺点
-
前端开发重度依赖开发环境,开发效率低,这种架构下,前后端协作有两种模式:
- 第一种是前端写 DEMO,写好后,让后端去套模板。好处是 DEMO 可以本地开发,很高效。不足是还需要后端套模板,有可能套错,套完后还需要前端确定,来回沟通调整的成本比较大;
- 另一种协作模式是前端负责浏览器端的所有开发和服务器端的 View 层模板开发。好处是 UI 相关的代码都是前端去写就好,后端不用太关注,不足就是前端开发重度绑定后端环境,环境成为影响前端开发效率的重要因素。
-
前后端职责纠缠不清:模板引擎功能强大,依旧可以通过拿到的上下文变量来实现各种业务逻辑。这样,只要前端弱势一点,往往就会被后端要求在模板层写出不少业务代码。还有一个很大的灰色地带是
Controller
,页面路由等功能本应该是前端最关注的,但却是由后端来实现。
Controller
本身与
Model
往往也会纠缠不清,看了让人咬牙的业务代码经常会出现在
Controller
层。这些问题不能全归结于程序员的素养,否则 JSP 就够了。 -
对前端发挥的局限性:性能优化如果只在前端做空间非常有限,于是我们经常需要后端合作,但由于后端框架限制,我们很难使用
【Comet】
、
【BigPipe】
等技术方案来优化性能。
注:在这期间(2005 年以前),包括早期的 JSP、PHP 可以称之为 Web 1.0 时代。在这里想说一句,如果你是一名 Java 初学者,请你不要再把一些陈旧的技术当回事了,比如 JSP,因为时代在变、技术在变、什么都在变(引用扎克伯格的一句话:唯一不变的是变化本身);当我们 千锋教育 机构去给大学做实训时,有些同学会认为我们没有讲什么 干货 ,其实不然,只能说是你认知里的干货对于市场来说早就过时了而已。
什么是前后分离
基于 AJAX 带来的 SPA 时代
时间回到 2005 年
AJAX
(Asynchronous JavaScript And XML,异步 JavaScript 和 XML,老技术新用法) 被正式提出并开始使用
CDN
作为静态资源存储,于是出现了 JavaScript 王者归来(在这之前 JS 都是用来在网页上贴狗皮膏药广告的)的 **SPA(Single Page Application)**单页面应用时代。
轻应用、无状态应用、十二要素应用
优点
这种模式下,
前后端的分工非常清晰,前后端的关键协作点是 AJAX 接口
。看起来是如此美妙,但回过头来看看的话,这与 JSP 时代区别不大。复杂度从服务端的 JSP 里移到了浏览器的 JavaScript,浏览器端变得很复杂。类似 Spring MVC,
这个时代开始出现浏览器端的分层架构
:
缺点
-
前后端接口的约定:
如果后端的接口一塌糊涂,如果后端的业务模型不够稳定,那么前端开发会很痛苦;不少团队也有类似尝试,通过接口规则、接口平台等方式来做。
有了和后端一起沉淀的 接口规则 ,还可以用来模拟数据,使得前后端可以在约定接口后实现高效并行开发
。 -
前端开发的复杂度控制:
SPA 应用大多以功能交互型为主,JavaScript 代码过十万行很正常。大量 JS 代码的组织,与 View 层的绑定等,都不是容易的事情。
前端为主的 MV* 时代
此处的 MV* 模式如下:
- MVC(同步通信为主):Model、View、Controller
- MVP(异步通信为主):Model、View、Presenter
-
MVVM(异步非阻塞通信为主
):Model、View、ViewModel
为了降低前端开发复杂度,涌现了大量的前端框架,比如:
AngularJS
、
React
、
Vue.js
、
EmberJS
等,这些框架总的原则是
先按类型分层
,比如
Templates、Controllers、Models,然后再在层内做切分
,如下图:
Templates 模板 ,也叫视图
Controller控制器,负责页面和页面跳转,前端页面的路由
model 模型
优点
-
前后端职责很清晰:
前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测试数据的模拟不难,前端可以本地开发。后端则可以专注于业务逻辑的处理,输出 RESTful 等接口。 -
前端开发的复杂度可控:
前端代码很重,但合理的分层,让前端代码能各司其职。这一块蛮有意思的,简单如模板特性的选择,就有很多很多讲究。并非越强大越好,限制什么,留下哪些自由,代码应该如何组织,所有这一切设计,得花一本书的厚度去说明。 -
部署相对独立:
可以快速改进产品体验
缺点
- 代码不能复用。比如后端依旧需要对数据做各种校验,校验逻辑无法复用浏览器端的代码。如果可以复用,那么后端的数据校验可以相对简单化。
- 全异步,对 SEO 不利。往往还需要服务端做同步渲染的降级方案。(服务降级,BASE理论,保证基本可用,保证主要业务可用性)
- 性能并非最佳,特别是移动互联网环境下。
- SPA 不能满足所有需求,依旧存在大量多页面应用。URL Design 需要后端配合,前端无法完全掌控。
NodeJS 带来的全栈时代
前端为主的 MV* 模式解决了很多很多问题,但如上所述,依旧存在不少不足之处。随着 NodeJS 的兴起,JavaScript 开始有能力运行在服务端。这意味着可以有一种新的研发模式:
在这种研发模式下,前后端的职责很清晰。对前端来说,两个 UI 层各司其职:
- Front-end UI layer 处理浏览器层的展现逻辑。通过 CSS 渲染样式,通过 JavaScript 添加交互功能,HTML 的生成也可以放在这层,具体看应用场景。
- Back-end UI layer 处理路由、模板、数据获取、Cookie 等。通过路由,前端终于可以自主把控 URL Design,这样无论是单页面应用还是多页面应用,前端都可以自由调控。后端也终于可以摆脱对展现的强关注,转而可以专心于业务逻辑层的开发。
通过 Node,Web Server 层也是 JavaScript 代码,这意味着部分代码可前后复用,需要 SEO 的场景可以在服务端同步渲染,由于异步请求太多导致的性能问题也可以通过服务端来缓解。前一种模式的不足,通过这种模式几乎都能完美解决掉。
与 JSP 模式相比,全栈模式看起来是一种回归,也的确是一种向原始开发模式的回归,不过是一种螺旋上升式的回归。
基于 NodeJS 的全栈模式,依旧面临很多挑战:
- 需要前端对服务端编程有更进一步的认识。比如 TCP/IP 等网络知识的掌握。
- NodeJS 层与 Java 层的高效通信。NodeJS 模式下,都在服务器端,RESTful HTTP 通信未必高效,通过 SOAP 等方式通信更高效。一切需要在验证中前行。
- 对部署、运维层面的熟练了解,需要更多知识点和实操经验。
- 大量历史遗留问题如何过渡。这可能是最大最大的阻力。
注意:
看到这里,相信很多同学就可以理解,为什么我总在课堂上说:“前端想学后台很难,而我们后端程序员学任何东西都很简单”;就是因为我们后端程序员具备相对完善的知识体系。**
总结
综上所述,模式也好,技术也罢,没有好坏优劣之分,只有适合不适合;前后分离的开发思想主要是基于
SoC
(关注度分离原则),上面种种模式,都是让前后端的职责更清晰,分工更合理高效。
什么是 MVVM
MVVM(Model-View-ViewModel)是一种软件架构设计模式,由微软 WPF(用于替代 WinForm,以前就是用这个技术开发桌面应用程序的)和 Silverlight(类似于 Java Applet,简单点说就是在浏览器上运行的 WPF) 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的
事件驱动编程方式
。由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于 2005 年在他的博客上发表。
架构是为了解耦。高内聚,低耦合
MVVM 源自于经典的 MVC(Model-View-Controller)模式(期间还演化出了 MVP(Model-View-Presenter) 模式)。
MVVM 的核心是 ViewModel 层
,负责转换 Model 中的数据对象来让数据变得更容易管理和使用,其作用如下:
- 该层向上与视图层进行双向数据绑定
- 向下与 Model 层通过接口请求进行数据交互
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mvmr6Afd-1575078558846)(https://note.youdao.com/yws/public/resource/ddff7b3e04d31b3b182725e7beb9067e/xmlnote/415D3C8A5F7F47FDBA59A15A6B4A20D2/11439)]
MVVM 已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流行的 MVVM 框架有
Vue.js
,
AngularJS
等。
模型分为两种;
数据承载模型
数据处理模型
数据双向绑定。 view <——-> Model
为什么要使用 MVVM
MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model),有几大好处
-
低耦合:
视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。 -
可复用:
你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。 -
独立开发:
开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。 -
可测试:
界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。
MVVM 的组成部分
thymeleaf 模板引擎
<th:if/>
View
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建,为了更方便地展现 ViewModel 或者 Model 层的数据,已经产生了各种各样的前后端模板语言,比如
FreeMarker
、
Thymeleaf
等等,各大 MVVM 框架如
Vue.js
,
AngularJS
,
EJS
等也都有自己用来构建用户界面的内置模板语言。
Model
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的
接口规则
ViewModel
ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的
- 比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示)
- 页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互)
视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层
由于实现了双向绑定,ViewModel 的内容会实时展现在 View 层,这是激动人心的,因为前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图
MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新,真正实现
事件驱动编程
。
View 层展现的不是
Model
层的数据,而是
ViewModel
的数据,由
ViewModel
负责与
Model
层交互,
这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
Vue入门知识
Vue js框架,是一个对MVVM思想的实现这,
MVVM分层达到数据双向绑定(viewmodel),
viewmodel实现了数据和页面的解耦,
使用了观察者模式来达到解耦。
为什么使用vue?
-
轻量级,体积小是一个重要指标。Vue.js 压缩后有只有
20多kb
(Angular 压缩后
56kb+
,React 压缩后
44kb+
) -
移动优先。更适合移动端,比如移动端的 Touch 事件
- 易上手,学习曲线平稳,文档齐全
-
吸取了 Angular(
模块化
)和 React(
虚拟 DOM
)的长处,并拥有自己独特的功能,如:
计算属性
- 开源,社区活跃度高
数据驱动模型
虚拟DOM中进行渲染处理的。
下载地址
- 开发版本
-
包含完整的警告和调试模式:
https://vuejs.org/js/vue.js
-
删除了警告,30.96KB min + gzip:
https://vuejs.org/js/vue.min.js
-
删除了警告,30.96KB min + gzip:
-
CDN
-
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
第一个vue应用程序
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>第一个 Vue 应用程序</title> </head> <body> <div id="vue"> {{message}} </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script type="text/javascript"> var vm = new Vue({ el: '#vue', data: { message: 'Hello Vue!' } }); </script> </body> </html>
-
说明
-
el:'#vue'
:绑定元素的 ID -
data:{message:'Hello Vue!'}
:数据对象中有一个名为
message
的属性,并设置了初始值
Hello Vue!
<div id="vue">{{message}}</div>
说明:只需要在绑定的元素中使用
双花括号
将 Vue 创建的名为
message
属性包裹起来,即可实现数据绑定功能,也就实现了 ViewModel 层所需的效果,是不是和
EL
表达式非常像?
为了能够更直观的体验 Vue 带来的数据绑定功能,我们需要在浏览器测试一番,操作流程如下:
-
在
Chrome
浏览器上运行第一个 Vue 应用程序,并按
F12
进入
开发者工具
说明
在之前的代码中,我们创建了一个名为
vm
的 Vue 实例
<script>
var vm = new Vue({
el: '#vue',
data: {
message: 'Hello Vue!'
}});
</script>
此时就可以在控制台直接输入
vm.message
来修改值,中间是可以省略
data
的,在这个操作中,我并没有主动操作 DOM,就让页面的内容发生了变化,这就是借助了 Vue 的
数据绑定
功能实现的;MVVM 模式中要求 ViewModel 层就是使用
观察者模式
来实现数据的监听与绑定,以做到数据与视图的快速响应。
Vue生命周期
- 开始创建
- 初始化数据
- 编译模板
- 挂载DOM
- 渲染—>更新—->渲染
- 卸载
Vue 实例从创建到销毁的过程,就是生命周期。
—————–AOP面向切面思想;
Vue 的整个生命周期中,
它提供了一系列的事件,
可以让我们在事件触发时注册 JS 方法,
可以让我们用自己注册的 JS 方法控制整个大局,
在这些事件响应方法中的
this
直接指向的是 Vue 的实例。
hook 钩子函数—相当于回调函数;webhook 相当于发送http请求外部URL
钩子函数的触发时机
beforeCreate
在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
created
实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
beforeMount
在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
beforeUpdate
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
回调函数!!!
Vue基础语法
-
条件判断
- v-if
- v-else-if
- v-else
<div id="vue">
<h1 v-if="type === 'A'">
A
</h1>
<h1 v-else-if="type === 'B'">
B
</h1>
<h1 v-else>
C
</h1>
</div>
<script>
var vue = new Vue({
el:"#vue",
data: {
type:'B'
}
});
</script>
在浏览器的控制台输入:
vue.type='A'、'B'、'C'
查看到页面数据的变化
- 循环 v-for
<ul id="vue">
<li v-for="item in items">{{item.message}}</li>
</ul>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el:"#vue",
data:{
items:[
//message也可以不用双引号的
{"message":"JAVA"},
{"message":"PHP"},
{"message":"C#"}
]
}
});
</script>
在浏览器的控制台输入
vm.items.push({message:"PHP"})
尝试追加一条数据,你会发现浏览器中显示的内容会增加一条
PHP
Vue 绑定事件
<div id="vue">
<button v-on:click="sayHai()">
提交
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el:"#vue",
data:{
message:"hello Vue"
}
#使用methods表示对象总定义方法,相当于编写click事件的函数
methods:{
sayHai:function(){
alert(this.message);//this在方法中指向当前vue实例。
}
}
})
</script>
在vue中写方法
methods
Vue通信
关注度分离原则
为什么使用axios
由于 Vue.js 是一个
视图层框架
并且作者(尤雨溪)严格准守
SoC
(
关注度分离原则
),所以 Vue.js 并不包含 AJAX 的通信功能,为了解决通信问题,作者单独开发了一个名为
vue-resource
的插件,不过在进入 2.0 版本以后停止了对该插件的维护并推荐了 Axios 框架
Axios异步通信框架
-
从浏览器中创建
XMLHttpRequests
-
从
node.js
创建
http
请求 -
支持
Promise API
类似于链式编程,比如String.valueOf(“2”)返回类型是String。 - 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
-
自动转换
JSON
数据 -
客户端支持防御
XSRF
(跨站请求伪造)
GitHub:
https://github.com/axios/axios
引入JS文件:
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
初始化在mounted函数中编写对象,函数对象
axios进行通信
data.json
{
"name": "广州千锋",
"url": "http://www.funtl.com",
"page": 88,
"isNonProfit": true,
"address": {
"street": "元岗路.",
"city": "广东广州",
"country": "中国"
},
"links": [
{
"name": "Google",
"url": "http://www.google.com"
},
{
"name": "Baidu",
"url": "http://www.baidu.com"
},
{
"name": "SoSo",
"url": "http://www.SoSo.com"
}
]
}
vue-axios.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="vue">
<div>{{info.name}}</div>
<div>
<a v-bind:href="info.url">{{info.url}}</a>
</div>
<div>{{info.address.street}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="js/axios.min.js"></script>
<script>
var vm = new Vue({
el:"#vue",
//数据承载模型,data 就是来存放数据的
// ()表示函数 {}表示对象 (){}表示函数对象
// data(){} 函数对象,要有返回值,并且返回值为一个对象
// return {}
data(){
return {
//info 是随意命令下面Mounted会用到
info:{
name:"",
url:"",
address:{
street:"",
city:"",
country:""
},
//思考:
//links返回的是一个数组对象,数据承载如何承载,
//遍历到时候可以使用v-for标签进行遍历
}
}
},
mounted() {
axios.get("data.json").then(response => (this.info = response.data))
}
});
</script>
</body>
</html>
总结vue使用的标签:
v-if
v-else
v-else-if
v-on:click
v-for
v-bind:href 绑定href属性
JS函数对象写法
Utils.js
// 原生js面向对象的写法
var Utils = function(){
//函数对象
//私有属性
var username;
//私有方法
var setUsername = function(username){
this.username = username;
}
var getUsername = function(){
return this.username;
}
return {
//公共方法
setUsername : function(username){
setUsername(username);
},
getUsername:function(){
return getUsername();
}
}
}();
testFunObject.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>函数对象</title>
</head>
<body>
<button onclick="show()">点击我</button>
<script src="js/Utils.js"></script>
<script>
var show = function(){
Utils.setUsername("zhongxu");
var username = Utils.getUsername()
alert(username)
}
</script>
</body>
</html>
SpringBoot方式 axios异步请求,
public class User{
private String name;
public setName(String name){
this.name = name;
}
public void getName(){
return this.name;
}
}
@RestController
public class UserController{
@RequestMapping("/user")
public User showUser(){
User user = new User();
user.setName("zhongxu");
return user;
}
}
http://localhost:8080/user 返回的是user的json对象
vue-axios.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="vue">
{{user.name}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el:"#vue",
data(){
user:{
name:""
}
},
mounted() {
axios.get("http://localhost:8080/user").then(response =>user.name = response.data);
}
});
</script>
</body>
</html>
会报一个跨站请求的错误,这个时候需要在UserController上加上一个
@CrossOrigin(“*”) 允许所有请求访问
Vue表单输入
双向数据绑定
数据双向绑定一定是针对UI控件(下拉框、单双按钮这种叫UI控件),
类似于ajax 形式提交表单形式。
在vue.js中使用
v-model
指令在表单
<input>
、
<textarea>
及
<select>
元素上创建双向数据绑定,会根据控件类型自动选取正确的方法来跟新元素。viemodel的观察者模式来进行处理.
异步式编程/响应式编程方式
注意:
v-model
会忽略所有表单元素的
value
、
checked
、
selected
特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的
data
选项中声明初始值。
-
单行文本
<div id="vue">
<input type="text" v-model="message"/> 文本内容:{{message}}
</div>
<script>
var vm = new Vue({
el:"#vue",
data:{
message:"初始值为helloword"
}
})
</script>
-
多行文本
<div id="vue">
多行文本:<textarea v-model="message"></textarea> 多行文本是:{{message}}
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#vue',
data: {
message: "Hello Textarea"
}
});
</script>
-
单复选框
<div id="vue">
单复选框:<input type="checkbox" id="checkbox" v-model="checked"> <label for="checkbox">{{ checked }}</label>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#vue',
data: {
checked: false
}
});
</script>
-
多复选框
<div id="vue">
多复选框:
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<span>选中的值: {{ checkedNames }}</span>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#vue',
data: {
checkedNames: []
}
});
</script>
-
单选按钮
<div id="vue">
单选按钮:
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<span>选中的值: {{ picked }}</span>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#vue',
data: {
picked: ''
}
});
</script>
-
下拉框
<div id="vue">
下拉框:
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>选中的值: {{ selected }}</span>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#vue',
data: {
selected: ''
}
});
</script>
注意:
如果
v-model
表达式的初始值未能匹配任何选项,
<select>
元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。
Vue组件基础
一组可以重复使用的模板,跟
JSTL
的自定义标签、
Thymeleaf
的
th:fragment
以及
Sitemesh3
框架有着异曲同工之妙。通常一个应用会以一棵嵌套的组件树的形式来组织
页面是组件的容器.
学习前端我们关注:
布局
控件/组件
网络
事件
第一个vue组件
**注意:**在实际开发中,我们并不会用以下方式开发组件,
而是采用
vue-cli
创建
.vue
模板文件的方式开发,
以下方法只是为了让大家理解什么是组件
component组件
template 模板
<div id="vue">
<!--
v-bind:name中的name指的是props:['name']中的name
双引号中的name,是v-for="name in names"中的name
-->
<my-li v-for="name in names" v-bind:name="name"></my-li>
</div>
<script>
//注入组件
Vue.component("my-li",{
props:['name'],
template:"<li style='color:red'>{{name}}</li>"
});
//再实例化vue
var vm = new Vue({
el:"#vue",
data:{
name:["java","C#","Vue","Python"]
}
});
</script>
Vue计算属性(Vue的特色)
计算属性的重点突出在
属性
两个字上(
属性是名词
),首先它是个
属性
其次这个属性有
计算
的能力(
计算是动词
),
这里的
计算
就是个函数;
简单点说,
它就是一个能够将计算结果缓存起来的属性(
将行为转化成了静态的属性
),仅此而已;
//调用函数的时候,CPU在进行计算;String.valueOf()
//String.length 调用属性的时候,是从内存中拿数据。
一个计算得出的结果,该结果不怎么变化,那么应该把它变成属性,放到内存中,下次取的时候,从内存中取,从而提高性能
computed 计算
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>计算属性</title>
</head>
<body>
<div id="vue">
<div>获取当前时间:{{getCurrentData()}}</div>
<div>获取当前时间2:{{getCurrentData2}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el:"#vue",
methods:{
getCurrentData:function(){
return new Date().getTime();
}
},
computed:{
//这里的方法名不能和methods中定义的方法名重名
//属性,属性,在使用的时候没有括号;
//第一次计算得到值缓存在内存中,为下次调用的时候提高性能
getCurrentData2:function(){
return new Date().getTime();
}
}
});
</script>
</body>
</html>
Vue内容分发和自定义事件
slot 插槽 有点像占位符的意思。
页面是组件(component)的容器
组件里面可以放内容
插槽slot ,有点像占位符的意思;有可能整个组件,不是完全自定义里面的。有可能跨越一些东西。
div
image span
span
页面元素,纯粹弄一个组件
有可能在span里面放东西,但是我不知道放什么
div
image <span>
ul
img
ol
div
</span>
在这个span里面可以放任何想放的东西,如果是组件,我需要写死在里面.但是不可能写死,
组件里面的东西可能需要变化.
替换里面的内容
=====>插槽 内容分发.放一个占位符,想传什么就传什么,我自己说了算
定义了一个组件,组件里面的内容不确定,使用插槽的方式,想传什么就传什么。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="vue">
<todo>
<todo-title slot="todo-title"></todo-title>
<todo-items slot="todo-items"></todo-items>
</todo>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
Vue.component("todo",{
template:"<div><slot name='todo-title'></slot><ul><slot name='todo-items'></slot></ul></div>"
});
Vue.component("todo-title",{
template:"<h1>标题</h1>"
});
Vue.component("todo-items",{
template:"<li>java</li>"
})
var vm = new Vue({
el:"#vue",
data:{
}
});
</script>
</body>
</html>
记得template:” ” 后面没有分号!!!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="vue">
<todo>
<todo-title slot="todo-title" v-bind:title="title"></todo-title>
<todo-items slot="todo-items" v-for="item in items" v-bind:item="item"></todo-items>
</todo>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
Vue.component("todo",{
template:"<div><slot name='todo-title'></slot><ul><slot name='todo-items'></slot></ul></div>"
});
Vue.component("todo-title",{
props:['title'],
template:"<h1>{{title}}</h1>"
});
Vue.component("todo-items",{
props:["item"],
template:"<li>{{item}}</li>"
})
var vm = new Vue({
el:"#vue",
//数据承载模型
data:{
title:"应用标题",
items:["hello","vue","zhongxu","dd"]
}
});
</script>
</body>
</html>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uA0E0Ih4-1575078558850)(https://note.youdao.com/yws/public/resource/ddff7b3e04d31b3b182725e7beb9067e/xmlnote/07850AC7442543F88770AC39AD02C626/11497)]
组件绑定删除
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="vue">
<todo>
<todo-title slot="todo-title" v-bind:title="title"></todo-title>
<todo-items slot="todo-items" v-for="(item,index) in items" v-bind:item="item" v-bind:index="index" v-on:myremove="removeItems(index)"></todo-items>
</todo>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
Vue.component("todo",{
template:"<div><slot name='todo-title'></slot><ul><slot name='todo-items'></slot></ul></div>"
});
Vue.component("todo-title",{
props:['title'],
template:"<h1>{{title}}</h1>"
});
Vue.component("todo-items",{
props:["item","index"],
template:"<li>{{index}}----{{item}} <button @click='remove(index)'>删除</button></li>",
methods:{
remove:function(index){
this.$emit("myremove",index)
}
}
})
var vm = new Vue({
el:"#vue",
data:{
title:"应用标题",
items:["hello","vue","zhongxu","dd"]
},
methods:{
removeItems:function(index){
//splice 删除,第一个参数从什么索引位置开始删除,"1" 表示从index位置开始删除1个元素,如果是"2"表示从index开始删除两个元素
this.items.splice(index,1)
}
}
});
</script>
</body>
</html>
Vue-Cli(开发方式)
安装环境准备
1.安装Node.js
2.安装vue-cli
npm install vue-cli -g --registry=https://registry.npm.taobao.org
# 输出如下
npm WARN deprecated coffee-script@1.12.7: CoffeeScript on NPM has moved to "coffeescript" (no hyphen)
C:\Users\Administrator\AppData\Roaming\npm\vue -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\vue-cli\bin\vue
C:\Users\Administrator\AppData\Roaming\npm\vue-init -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\vue-cli\bin\vue-init
C:\Users\Administrator\AppData\Roaming\npm\vue-list -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\vue-cli\bin\vue-list
+ vue-cli@2.9.6
added 241 packages from 206 contributors in 24.481s
npm 类似于maven ,类似于apt;npm 包管理器
测试是否安装成功
# 查看可以基于哪些模板创建 vue 应用程序,通常我们选择 webpack
vue list
# 输出如下
Available official templates:
★ browserify - A full-featured Browserify + vueify setup with hot-reload, linting & unit testing.
★ browserify-simple - A simple Browserify + vueify setup for quick prototyping.
★ pwa - PWA template for vue-cli based on the webpack template
★ simple - The simplest possible Vue setup in a single HTML file
★ webpack - A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.
★ webpack-simple - A simple Webpack + vue-loader setup for quick prototyping.
第一个vue-cli应用程序
创建一个基于 webpack 模板的 vue 应用程序
# 这里的 hello-vue-cli 是项目名称,可以根据自己的需求起名
vue init webpack hello-vue-cli
# 输出如下
? Project name hello-vue-cli
? Project description A Vue.js project
? Author Lusifer <topsale@vip.qq.com>
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) no
vue-cli · Generated "hello-vue-cli".
# Project initialization finished!
# ========================
To get started:
cd hello-vue-cli
npm install (or if using yarn: yarn)
npm run dev
Documentation can be found at https://vuejs-templates.github.io/webpack
webpack 打包编译,把ES6的语法编译成ES5
说明
-
Project name
:项目名称,默认
回车
即可 -
Project description
:项目描述,默认
回车
即可 -
Author
:项目作者,默认
回车
即可 -
Install vue-router
:是否安装
vue-router
,选择
n
不安装(后期需要再手动添加) -
Use ESLint to lint your code
:是否使用
ESLint
做代码检查,选择
n
不安装(后期需要再手动添加) -
Set up unit tests
:单元测试相关,选择
n
不安装(后期需要再手动添加) -
Setup e2e tests with Nightwatch
:单元测试相关,选择
n
不安装(后期需要再手动添加) -
Should we run npm install for you after the project has been created
:创建完成后直接初始化,选择
n
,我们手动执行
初始化并运行
把工程导入IDEA中或者使用DOS命令来运行前端工程项目。
# 初始化
cd hello-vue-cli
npm install --registry=https://registry.npm.taobao.org
# 输出如下
npm WARN deprecated browserslist@2.11.3: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.
npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features!
npm WARN deprecated browserslist@1.7.7: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.
> core-js@2.6.9 postinstall D:\Workspace\Study\other\hello-vue-cli\node_modules\core-js
> node scripts/postinstall || echo "ignore"
Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!
The project needs your help! Please consider supporting of core-js on Open Collective or Patreon:
> https://opencollective.com/core-js
> https://www.patreon.com/zloirock
Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -)
> uglifyjs-webpack-plugin@0.4.6 postinstall D:\Workspace\Study\other\hello-vue-cli\node_modules\webpack\node_modules\uglifyjs-webpack-plugin
> node lib/post_install.js
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN ajv-keywords@3.4.1 requires a peer of ajv@^6.9.1 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
added 1207 packages from 667 contributors and audited 11765 packages in 81.571s
found 10 vulnerabilities (6 moderate, 4 high)
run `npm audit fix` to fix them, or `npm audit` for details
# 运行 dev 开发环境
npm run dev
# 输出如下
DONE Compiled successfully in 3226ms
I Your application is running here: http://localhost:8080
访问链接:http://localhost:8080
目录结构介绍
-
build
和
config
:WebPack 配置文件 -
node_modules
:用于存放
npm install
安装的依赖文件 -
src
:项目源码目录 -
static
:静态资源文件 -
.babelrc
:Babel 配置文件,主要作用是将 ES6 转换为 ES5 -
.editorconfig
:编辑器配置 -
eslintignore
:需要忽略的语法检查配置文件 -
.gitignore
:git 忽略的配置文件 -
.postcssrc.js
:css 相关配置文件,其中内部的
module.exports
是 NodeJS 模块化语法 -
index.html
:首页,仅作为模板页,实际开发时不使用 -
package.json:项目的配置文件
-
name
:项目名称 -
version
:项目版本 -
description
:项目描述 -
author
:项目作者 -
scripts
:封装常用命令 -
dependencies
:生产环境依赖 -
devDependencies
:开发环境依赖
-
源码目录src
│ App.vue
│ main.js
│
├─assets
│ logo.png
│
└─components
HelloWorld.vue
‘@’: resolve(‘src’), 在webpack.base.conf.js中配置的。@表示src目录
main.js
项目的入口文件,我们知道所有的程序都会有一个入口
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
-
import Vue from 'vue'
:ES6 写法,会被转换成
require("vue");
(require 是 NodeJS 提供的模块加载器) -
import App from './App'
:意思同上,但是指定了查找路径,
./
为当前目录 -
Vue.config.productionTip = false
:关闭浏览器控制台关于环境的相关提示 -
new Vue({…}):实例化 Vue
-
el: '#app'
:查找 index.html 中 id 为 app 的元素 -
template: ''
:模板,会将 index.html 中
替换为
-
components: { App }
:引入组件,使用的是
import App from './App'
定义的 App 组件
-
App.vue
组件模板
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
<!-- 字体 -->
font-family: 'Avenir', Helvetica, Arial, sans-serif;
<!-- 文字平滑效果 -->
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
-
template
:HTML 代码模板,会替换 “ 中的内容 -
import HelloWorld from './components/HelloWorld'
:引入 HelloWorld 组件,用于替换
template
中的 “ -
export default{…}:导出 NodeJS 对象,作用是可以通过
import
关键字导入-
name: 'App'
:定义组件的名称 -
components: { HelloWorld }
:定义子组件
-
HelloWorld.vue
基本同上,不解释…
关于
style scope
的说明:CSS 样式仅在当前组件有效,声明了样式的作用域
VueRouter第一个路由
所谓的路由,相当于页面的跳转。
而vue的路由它指的是组件之间的跳转。vue 是一个mvvm模式。关注度分离原则,页面的跳转、交互等和vue没有关系,所以没有放到vue框架里面,而是以插件形式实现组件与组件跳转.
vue router 插件.
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于 Vue.js 过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的 CSS class 的链接
- HTML5 历史模式或 hash 模式,在 IE9 中自动降级
- 自定义的滚动条行为
相当于在单页面中一个组件跳转到另一个组件去,类似于后台中的重定向转发
关注点分离原则
页面是组件的容器。
安装
vue-router 是一个插件包,所以我们还是需要用 npm/cnpm 来进行安装的。打开命令行工具,进入你的项目目录,输入下面命令。
npm install vue-router --save-dev --registry=https://registry.npm.taobao.org
# 输出如下
npm WARN ajv-keywords@3.4.1 requires a peer of ajv@^6.9.1 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
+ vue-router@3.0.7
added 1 package from 1 contributor and audited 11766 packages in 11.33s
found 10 vulnerabilities (6 moderate, 4 high)
run `npm audit fix` to fix them, or `npm audit` for details
如果在一个模块化工程中使用它,必须要通过
Vue.use()
明确地安装路由功能:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
使用
案例使用的是hello-vue-cli项目中使用vue-router
创建组件页面
名为
src/components
的目录专门放置我们开发的 Vue 组件,在
src/components
目录下创建一个名为
Content.vue
的组件
<template>
<div>
我是内容页
</div>
</template>
<script>
export default {
name: "Content"
}
</script>
<style >
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
template
下只能有一个根元素。
安装路由
在src目录下创建一个router的目录,该目录专门放置路由配置;
在
src/router
目录下创建一个名为
index.js
路由配置文件
import Vue from 'vue'
//导入路由插件 Router 可以随意取, vue-router实实在在存在的
import Router from 'vue-router'
//导入上面的组件 ; @ 表示src目录.webpack中有说明
import Content from '@/components/Content'
//安装路由 显示调用
Vue.use(Router);
//配置路由
export default new Router({
routers:[
{
//路由路径
path:'/content',
//路由名称
name:'Content',
//跳转到组件
component:Content
}
]
});
配置路由
修改
main.js
入口文件,增加配置路由的相关代码
import Vue from 'vue'
import App from './App'
// 导入上面创建的路由配置目录
import router from './router'
Vue.config.productionTip = false;
new Vue({
el:"#app",
//配置路由
router,
components:{ App },
template:'<App/>'
});
使用路由
修改
App.vue
组件,代码如下:
<template>
<div id="app">
<router-link to="/">首页</router-link>
<router-link to="/content">内容</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
说明:
-
router-link:
默认会被渲染成一个
<a>
标签,
to
属性为指定链接 -
router-view:
用于渲染路由匹配到的组件
MVVM
关注度分离原则
VueRouter第一个工程
JS —————>JS ES
jQuery———->Vue.js
Bootstarap —>ElementUI
AdminEL ——->Vue ElementUI Admin
1.创建hello-vue-element项目
-
创建一个名为
hello-vue-element
的工程
# 使用 webpack 打包工具初始化一个名为 hello-vue-element 的工程
vue init webpack hello-vue-element
- 安装依赖
需要安装
vue-router
、
element-ui
、
sass-loader
和
node-sass
四个插件
# 进入工程目录
cd hello-vue-element
# 安装 vue-router
npm install vue-router --save-dev --registry=https://registry.npm.taobao.org
# 安装 element-ui
npm i element-ui -S --registry=https://registry.npm.taobao.org
# 安装 SASS 加载器
npm install sass-loader node-sass --save-dev --registry=https://registry.npm.taobao.org
# 安装依赖
npm install --registry=https://registry.npm.taobao.org
- 启动项目并访问
npm run dev
在浏览器中访问 http://localhost:8080
2.第一个ElementUI页面
- 插件首页视图
在
hello-vue-element
项目下创建
views
目录,在
views
目录下创建一个名为
Main.vue
的视图组件;
该组件在当前章节无任何作用,主要用于登录后展示登录成功的跳转效果;
<template>
<div>
首页
</div>
</template>
<script>
export default {
name: "Main"
}
</script>
<style scoped>
</style>
- 创建登录页视图
在
views
目录下创建一个名为
Login.vue
的视图组件,其中
el-*
的元素为 ElementUI 组件;
<template>
<div>
<el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box">
<h3 class="login-title">欢迎登录</h3>
<el-form-item label="账号" prop="username">
<el-input type="text" placeholder="请输入账号" v-model="form.username"/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" placeholder="请输入密码" v-model="form.password"/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-on:click="onSubmit('loginForm')">登录</el-button>
</el-form-item>
</el-form>
<el-dialog
title="温馨提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<span>请输入账号和密码</span>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
form: {
username: '',
password: ''
},
// 表单验证,需要在 el-form-item 元素中增加 prop 属性
rules: {
username: [
{required: true, message: '账号不可为空', trigger: 'blur'}
],
password: [
{required: true, message: '密码不可为空', trigger: 'blur'}
]
},
// 对话框显示和隐藏
dialogVisible: false
}
},
methods: {
onSubmit(formName) {
// 为表单绑定验证功能
this.$refs[formName].validate((valid) => {
if (valid) {
// 使用 vue-router 路由到指定页面,该方式称之为编程式导航
this.$router.push("/main");
} else {
this.dialogVisible = true;
return false;
}
});
}
}
}
</script>
<style lang="scss" scoped>
.login-box {
border: 1px solid #DCDFE6;
width: 350px;
margin: 180px auto;
padding: 35px 35px 15px 35px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
box-shadow: 0 0 25px #909399;
}
.login-title {
text-align: center;
margin: 0 auto 40px auto;
color: #303133;
}
</style>
- 创建路由
在
hello-vue-element
项目下创建
router
目录,在
router
目录下创建一个名为
index.js
的
vue-router
路由配置文件
import Vue from 'vue'
import Router from 'vue-router'
import Login from "../views/Login"
import Main from '../views/Main'
Vue.use(Router);
export default new Router({
routes: [
{
// 登录页
path: '/login',
name: 'Login',
component: Login
},
{
// 首页
path: '/main',
name: 'Main',
component: Main
}
]
});
-
配置路由
-
修改
main.js
入口代码import Vue from 'vue' import VueRouter from 'vue-router' import router from './router' // 导入 ElementUI import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import App from './App' // 安装路由 Vue.use(VueRouter); // 安装 ElementUI Vue.use(ElementUI); new Vue({ el: '#app', // 启用路由 router, // 启用 ElementUI render: h => h(App) });
-
修改
App.vue
组件代码<template> <div id="app"> <router-view/> </div> </template> <script> export default { name: 'App', } </script>
-
扩展阅读
NPM 相关命令说明
-
npm install moduleName
:安装模块到项目目录下 -
npm install -g moduleName
:
-g
的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置 -
npm install -save moduleName
:
--save
的意思是将模块安装到项目目录下,并在 package 文件的 dependencies 节点写入依赖,
-S
为该命令的缩写 -
npm install -save-dev moduleName
:
--save-dev
的意思是将模块安装到项目目录下,并在 package 文件的 devDependencies 节点写入依赖,
-D
为该命令的缩写
嵌套路由
嵌套路由又称子路由,在实际应用中,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件
/user/foo/profile /user/foo/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
创建嵌套视图组件
用户信息组件
在
views/user
目录下创建一个名为
Profile.vue
的视图组件;该组件在当前章节无任何作用,主要用于展示嵌套效果;
<template>
<div>
个人信息
</div>
</template>
<script>
export default {
name: "UserProfile"
}
</script>
<style scoped>
</style>
用户列表组件
在
views/user
目录下创建一个名为
List.vue
的视图组件;该组件在当前章节无任何作用,主要用于展示嵌套效果;
<template>
<div>
用户列表
</div>
</template>
<script>
export default {
name: "UserList"
}
</script>
<style scoped>
</style>
配置嵌套路由
修改
router
目录下的
index.js
路由配置文件,代码如下:
import Vue from 'vue'
import Router from 'vue-router'
import Login from "../views/Login"
import Main from '../views/Main'
// 用于嵌套的路由组件
import UserProfile from '../views/user/Profile'
import UserList from '../views/user/List'
Vue.use(Router);
export default new Router({
routes: [
{
// 登录页
path: '/login',
name: 'Login',
component: Login
},
{
// 首页
path: '/main',
name: 'Main',
component: Main,
// 配置嵌套路由
children: [
{path: '/user/profile', component: UserProfile},
{path: '/user/list', component: UserList},
]
}
]
});
说明:主要在路由配置中增加了
children
数组配置,用于在该组件下设置嵌套路由
修改首页视图
接着上一节的代码,我们修改
Main.vue
视图组件,此处使用了
ElementUI 布局容器组件
,代码如下:
<template>
<div>
<el-container>
<el-aside width="200px">
<el-menu :default-openeds="['1']">
<el-submenu index="1">
<template slot="title"><i class="el-icon-caret-right"></i>用户管理</template>
<el-menu-item-group>
<el-menu-item index="1-1">
<router-link to="/user/profile">个人信息</router-link>
</el-menu-item>
<el-menu-item index="1-2">
<router-link to="/user/list">用户列表</router-link>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-caret-right"></i>内容管理</template>
<el-menu-item-group>
<el-menu-item index="2-1">分类管理</el-menu-item>
<el-menu-item index="2-2">内容列表</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item>退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>Lusifer</span>
</el-header>
<el-main>
<router-view />
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
export default {
name: "Main"
}
</script>
<style scoped lang="scss">
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
说明:
-
在
<el-main>
元素中配置了
<router-view />
用于展示嵌套路由 -
主要使用
<router-link to="/user/profile">个人信息</router-link>
展示嵌套路由内容
VueRouter 参数传递与重定向
经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。此时我们就需要传递参数了;
参数传递
使用路径匹配的方式
-
修改路由配置,主要是在
path
属性中增加了
:id
这样的占位符
{path: '/user/profile/:id', name:'UserProfile', component: UserProfile}
-
router-link
方式传递
<router-link :to="{name: 'UserProfile', params: {id: 1}}">个人信息</router-link>
注意:
此时我们将
to
改为了
:to
,是为了将这一属性当成对象(Model)使用,注意
router-link 中的 name 属性名称
一定要和
路由中的 name 属性名称
匹配,因为这样 Vue 才能找到对应的路由路径;
- 代码方式传递
this.$router.push({ name: 'UserProfile', params: {id: 1}});
- 在目标组件中使用以下方式接收参数
{{ $route.params.id }}
使用 props 的方式
-
修改路由配置,主要增加了
props: true
属性
{path: '/user/profile/:id', name:'UserProfile', component: UserProfile, props: true}
-
router-link
方式传递
<router-link :to="{name: 'UserProfile', params: {id: 1}}">个人信息</router-link>
- 代码方式传递
this.$router.push({ name: 'UserProfile', params: {id: 1}});
-
接收参数,为目标组件增加
props
属性
export default {
props: ['id'],
name: "UserProfile"
}
- 模板中使用接收参数
{{id}}
重定向
Vue 中的重定向是作用在路径不同但组件相同的情况下
修改路由配置
{
path: '/main',
name: 'Main',
component: Main
},
{
path: '/goHome',
redirect: '/main'
}
说明:这里定义了两个路径,一个是
/main
,一个是
/goHome
,其中
/goHome
重定向到了
/main
路径,由此可以看出重定向不需要定义组件;
重定向到组件
设置对应路径即可
<router-link to="/goHome">回到首页</router-link>
带参数的重定向
- 修改路由配置
{
// 首页
path: '/main/:username',
name: 'Main',
component: Main
},
{
path: '/goHome/:username',
redirect: '/main/:username'
}
- 重定向到组件
<router-link to="/goHome/Lusifer">回到首页</router-link>
VueRouter 路由模式与 404
路由模式
路由模式有两种
-
hash:路径带
#
符号,如
http://localhost/#/login
-
history:路径不带
#
符号,如
http://localhost/login
修改路由配置,代码如下:
export default new Router({
mode: 'history',
routes: [
]
});
处理 404
创建一个名为
NotFound.vue
的视图组件,代码如下:
<template>
<div>
页面不存在,请重试!
</div>
</template>
<script>
export default {
name: "NotFount"
}
</script>
<style scoped>
</style>
修改路由配置,代码如下:
{
path: '*',
component: NotFound
}
VueRouter 路由钩子与异步请求
路由中的钩子函数
-
beforeRouteEnter
:在进入路由前执行 -
beforeRouteLeave
:在离开路由前执行
案例代码如下:
export default {
props: ['id'],
name: "UserProfile",
beforeRouteEnter: (to, from, next) => {
console.log("准备进入个人信息页");
next();
},
beforeRouteLeave: (to, from, next) => {
console.log("准备离开个人信息页");
next();
}
}
参数说明:
-
to
:路由将要跳转的路径信息 -
from
:路径跳转前的路径信息 -
next:路由的控制参数
-
next()
跳入下一个页面 -
next('/path')
改变路由的跳转方向,使其跳到另一个路由 -
next(false)
返回原来的页面 -
next((vm)=>{})
仅在 beforeRouteEnter 中可用,vm 是组件实例
-
在钩子函数中使用异步请求
安装 Axios
npm install axios -s --registry=https://registry.npm.taobao.org
引用 Axios
import axios from 'axios'
Vue.prototype.axios = axios;
在
beforeRouteEnter
中进行异步请求,案例代码如下:
export default {
props: ['id'],
name: "UserProfile",
beforeRouteEnter: (to, from, next) => {
console.log("准备进入个人信息页");
// 注意,一定要在 next 中请求,因为该方法调用时 Vue 实例还没有创建,此时无法获取到 this 对象,在这里使用官方提供的回调函数拿到当前实例
next(vm => {
vm.getData();
});
},
beforeRouteLeave: (to, from, next) => {
console.log("准备离开个人信息页");
next();
},
methods: {
getData: function () {
this.axios({
method: 'get',
url: 'http://localhost:8080/static/data.json'
}).then(function (repos) {
console.log(repos);
}).catch(function (error) {
console.log(error);
});
}
}
}
Vuex 状态管理
Vuex 是一个专为 Vue.js 应用程序开发的
状态管理模式
。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
安装
- 在项目根目录执行如下命令来安装 Vuex
npm install vuex --save --registry=https://registry.npm.taobao.org
-
修改
main.js
文件,导入 Vuex,关键代码如下:
import Vuex from 'vuex'
Vue.use(Vuex);
状态管理
我们利用路由钩子
beforeEach
来判断用户是否登录,期间会用到
sessionStorage
存储功能
修改 Login.vue
在表单验证成功方法内增加如下代码:
// 设置用户登录成功
sessionStorage.setItem('isLogin', 'true');
修改 main.js
利用路由钩子
beforeEach
方法判断用户是否成功登录,关键代码如下:
// 在跳转前执行
router.beforeEach((to, form, next) => {
// 获取用户登录状态
let isLogin = sessionStorage.getItem('isLogin');
// 注销
if (to.path == '/logout') {
// 清空
sessionStorage.clear();
// 跳转到登录
next({path: '/login'});
}
// 如果请求的是登录页
else if (to.path == '/login') {
if (isLogin != null) {
// 跳转到首页
next({path: '/main'});
}
}
// 如果为非登录状态
else if (isLogin == null) {
// 跳转到登录页
next({path: '/login'});
}
// 下一个路由
next();
});
配置 Vuex
- 创建 Vuex 配置文件
在
src
目录下创建一个名为
store
的目录并新建一个名为
index.js
文件用来配置 Vuex,代码如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
// 全局 state 对象,用于保存所有组件的公共数据
const state = {
// 定义一个 user 对象
// 在组件中是通过 this.$store.state.user 来获取
user: {
username: ''
}
};
// 实时监听 state 值的最新状态,注意这里的 getters 可以理解为计算属性
const getters = {
// 在组件中是通过 this.$store.getters.getUser 来获取
getUser(state) {
return state.user;
}
};
// 定义改变 state 初始值的方法,这里是唯一可以改变 state 的地方,缺点是只能同步执行
const mutations = {
// 在组件中是通过 this.$store.commit('updateUser', user); 方法来调用 mutations
updateUser(state, user) {
state.user = user;
}
};
// 定义触发 mutations 里函数的方法,可以异步执行 mutations 里的函数
const actions = {
// 在组件中是通过 this.$store.dispatch('asyncUpdateUser', user); 来调用 actions
asyncUpdateUser(context, user) {
context.commit('updateUser', user);
}
};
export default new Vuex.Store({
state,
getters,
mutations,
actions
});
-
修改
main.js
增加刚才配置的
store/index.js
,关键代码如下:
import Vue from 'vue'
import Vuex from 'vuex'
import store from './store'
Vue.use(Vuex);
new Vue({
el: '#app',
store
});
浏览器刷新 Vuex 数据消失
问题描述
Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。但是有一个问题就是:vuex 的存储的数据只是在页面的中,相当于我们定义的全局变量,刷新之后,里边的数据就会恢复到初始化状态。但是这个情况有时候并不是我们所希望的。
解决方案
监听页面是否刷新,如果页面刷新了,将 state 对象存入到 sessionStorage 中。页面打开之后,判断 sessionStorage 中是否存在 state 对象,如果存在,则说明页面是被刷新过的,将 sessionStorage 中存的数据取出来给 vuex 中的 state 赋值。如果不存在,说明是第一次打开,则取 vuex 中定义的 state 初始值。
修改代码
在
App.vue
中增加监听刷新事件
export default {
name: 'App',
mounted() {
window.addEventListener('unload', this.saveState);
},
methods: {
saveState() {
sessionStorage.setItem('state', JSON.stringify(this.$store.state));
}
}
}
修改
store/index.js
中的 state
const state = sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')) : {
user: {
username: ''
}
};
模块化
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
创建 user 模块
在
store
目录下创建一个名为
modules
的目录并创建一个名为
user.js
的文件,代码如下:
const user = {
// 因为模块化了,所以解决刷新问题的代码需要改造一下
state: sessionStorage.getItem('userState') ? JSON.parse(sessionStorage.getItem('userState')) : {
user: {
username: ''
}
},
getters: {
getUser(state) {
return state.user;
}
},
mutations: {
updateUser(state, user) {
state.user = user;
}
},
actions: {
asyncUpdateUser(context, user) {
context.commit('updateUser', user);
}
}
};
export default user;
修改 store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
// this.$store.state.user
user
}
});
备注:
由于组件中使用的是
getters
和
actions
处理,所以调用代码不变
修改 App.vue
export default {
name: 'App',
mounted() {
window.addEventListener('unload', this.saveState);
},
methods: {
saveState() {
// 模块化后,调用 state 的代码修改为 this.$store.state.user
sessionStorage.setItem('userState', JSON.stringify(this.$store.state.user));
}
}
}