0、前后端分离的演变史
参考博客:https://blog.youkuaiyun.com/weixin_46036165/article/details/105457877
0.1、后端为主的 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 就够了。
注: 在这期间(2005 年以前),包括早期的 JSP、PHP 可以称之为 Web 1.0 时代。在这里想说一句, 如果你是一名 Java 初学者,请你不要再把一些陈旧的技术当回事了,比如 JSP,因为时代在变、技术在 变、什么都在变(引用扎克伯格的一句话:唯一不变的是变化本身)。
0.2、基于 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 层的绑定等,都不是容易的事情。
0.3、前端为主的 MV* 时代
此处的 MV* 模式如下:
- MVC(同步通信为主):
Model
、View
、Controller
- MVP(异步通信为主):
Model
、View
、Presenter
- MVVM(异步通信为主):
Model
、View
、ViewModel
为了降低前端开发复杂度,涌现了大量的前端框架,比如: AngularJS 、 React 、Vue.js 、 EmberJS 等,这些框架总的原则是先按类型分层,比如 Templates、Controllers、Models,然后再在层内做切分,如下图:
优点:
- 前后端职责很清晰: 前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测试数据的模拟不难,前端可以本地开发。后端则可以专注于业务逻辑的处理,输出 RESTful等接口。
- 前端开发的复杂度可控: 前端代码很重,但合理的分层,让前端代码能各司其职。这一块蛮有意思的,简单如模板特性的选择,就有很多很多讲究。并非越强大越好,限制什么,留下哪些自由,代码应该如何组织,所有这一切设计,得花一本书的厚度去说明。
- 部署相对独立: 可以快速改进产品体验
缺点:
- 代码不能复用。比如后端依旧需要对数据做各种校验,校验逻辑无法复用浏览器端的代码。如果可 以复用,那么后端的数据校验可以相对简单化。
- 全异步,对 SEO 不利。往往还需要服务端做同步渲染的降级方案。
- 性能并非最佳,特别是移动互联网环境下。
- SPA 不能满足所有需求,依旧存在大量多页面应用。URL Design 需要后端配合,前端无法完全掌控。
0.4、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 等方式通信更高效。一切需要在验证中前行。
- 对部署、运维层面的熟练了解,需要更多知识点和实操经验。
- 大量历史遗留问题如何过渡。这可能是最大最大的阻力。
1、第一个Vue程序
1.1、什么是MVVM
MVVM(Model-View-ViewModel)是一种软件设计模式,由微软WPF(用于替代WinForm,以前就是用这个技术开发桌面应用程序的)和Silverlight(类似于Java Applet,简单点说就是在浏览器上运行WPF)的架构师Ken Cooper和Ted Peters开发,是一种简化用户界面的事件驱动编程方式。由John Gossman(同样也是WPF和Sliverlight的架构师)与2005年在他的博客上发表。
MVVM源自于经典的MVC(Model-View-Controller)模式。MVVM的核心是ViewModel层,负责转换Model中的数据对象来让数据变得更容易管理和使用。其作用如下:
- 该层向上与视图层进行双向数据绑定
- 向下与Model层通过接口请求进行数据交互
MVVM已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流行的MVVM框架有Vue.js
,Anfular JS
1.2、为什么要使用MVVM
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大好处:
- 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
- 可复用:你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
- 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewMode),设计人员可以专注于页面设计。
- 可测试:界面素来是比较难以测试的,而现在测试可以针对ViewModel来写。
1.3、MVVM的组成部分
(1)View
View是视图层, 也就是用户界面。前端主要由HTH L和csS来构建, 为了更方便地展现vi eu to del或者Hodel层的数据, 已经产生了各种各样的前后端模板语言, 比如FreeMarker,Thyme leaf等等, 各大MV VM框架如Vue.js.Angular JS, EJS等也都有自己用来构建用户界面的内置模板语言。
(2)Model
Model是指数据模型, 泛指后端进行的各种业务逻辑处理和数据操控, 主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的接口规则
(3)ViewModel
ViewModel是由前端开发人员组织生成和维护的视图数据层。在这一层, 前端开发者对从后端获取的Model数据进行转换处理, 做二次封装, 以生成符合View层使用预期的视图数据模型。
需要注意的是View Model所封装出来的数据模型包括视图的状态和行为两部分, 而Model层的数据模型是只包含状态的
- 比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示)
- 页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互)
视图状态和行为都封装在了View Model里。这样的封装使得View Model可以完整地去描述View层。由于实现了双向绑定, View Model的内容会实时展现在View层, 这是激动人心的, 因为前端开发者再也不必低效又麻烦地通过操纵DOM去更新视图。
MVVM框架已经把最脏最累的一块做好了, 我们开发者只需要处理和维护View Model, 更新数据视图就会自动得到相应更新,真正实现事件驱动编程
。
View层展现的不是Model
层的数据, 而是ViewModel
的数据, 由ViewModel
负责与Model
层交互, 这就完全解耦了View层和Model层, 这个解耦是至关重要的, 它是前后端分离方案实施的重要一环。
1.4、Vue
Vue(读音/vju/, 类似于view)是一套用于构建用户界面的渐进式框架, 发布于2014年2月。与其它大型框架不同的是, Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层, 不仅易于上手, 还便于与第三方库(如:vue-router,vue-resource,vue x) 或既有项目整合。
(1)MVVM模式的实现者
- Model:模型层, 在这里表示JavaScript对象
- View:视图层, 在这里表示DOM(HTML操作的元素)
- ViewModel:连接视图和数据的中间件, Vue.js就是MVVM中的View Model层的实现者
在MVVM架构中, 是不允许数据和视图直接通信的, 只能通过ViewModel来通信, 而View Model就是定义了一个Observer观察者
- ViewModel能够观察到数据的变化, 并对视图对应的内容进行更新
- ViewModel能够监听到视图的变化, 并能够通知数据发生改变
至此, 我们就明白了, Vue.js就是一个MVVM的实现者, 他的核心就是实现了DOM监听与数据绑定!
(2)为什么要使用Vue.js
- 轻量级, 体积小是一个重要指标。Vue.js压缩后有只有20多kb(Angular压缩后56kb+,React压缩后44kb+)
- 移动优先。更适合移动端, 比如移动端的Touch事件
- 易上手,学习曲线平稳,文档齐全
- 吸取了Angular(模块化) 和React(虚拟DOM) 的长处, 并拥有自己独特的功能,如:计算属性
- 开源,社区活跃度高
1.5、第一个Vue程序
【说明】IDEA可以安装Vue的插件!
注意:Vue不支持IE 8及以下版本, 因为Vue使用了IE 8无法模拟的ECMAScript 5特性。但它支持所有兼容ECMAScript 5的浏览器。
(1)下载地址
- 开发版本
- 包含完整的警告和调试模式:https://yuejs.org/js/vue.js
- 删除了警告, 30.96KB min+gzip:https://vuejs.org/js/vue.min.js
- CDN
<script src=“https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js”></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
(2)代码编写
Vue.js的核心是实现了MVVM模式, 她扮演的角色就是View Model层, 那么第一个应用程序就是展示它的数据绑定功能,操作流程如下:
1、创建一个HTML文件
2、导入Vue.js
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
3、创建一个Vue实例
<script>
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
message: "Hello, Vue!"
}
});
</script>
说明:
el: '#vue'
:绑定元素的IDdata:{message:'Hello Vue!'}
:数据对象中有一个名为message的属性,并设置了初始值 Hello Vue!
4、将数据绑定到页面元素
<!--view层: 模板-->
<div id="app">
{{message}}
</div>
说明:只需要在绑定的元素中使用双花括号将Vue创建的名为message属性包裹起来, 即可实现数据绑定功能, 也就实现了View Model层所需的效果, 是不是和EL表达式非常像?
(3)完整的HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
{{message}}
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
message: "Hello, Vue!"
}
});
</script>
</body>
</html>
(4)测试
为了能够更直观的体验Vue带来的数据绑定功能, 我们需要在浏览器进行测试, 操作流程如下:
-
在浏览器上运行第一个Vue应用程序, 进入开发者工具
-
在控制台输入vm.message=‘HelloWorld’, 然后回车, 你会发现浏览器中显示的内容会直接变成HelloWorld
此时就可以在控制台直接输入vm.message来修改值, 中间是可以省略data的, 在这个操作中, 我并没有主动操作DOM, 就让页面的内容发生了变化, 这就是借助了Vue的数据绑定功能实现的; MV VM模式中要求View Model层就是使用观察者模式来实现数据的监听与绑定, 以做到数据与视图的快速响应。
2、基础语法指令
2.1、v-bind
我们已经成功创建了第一个Vue应用!看起来这跟渲染一个字符串模板非常类似, 但是Vue在背后做了大量工作。现在数据和DOM已经被建立了关联, 所有东西都是响应式的。我们在控制台操作对象属性,界面可以实时更新!
我们还可以使用v-bind
来绑定元素特性!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
message: '页面加载于 ' + new Date().toLocaleString()
}
});
</script>
</body>
</html>
你看到的v-bind
等被称为指令。指令带有前缀v以表示它们是Vue提供的特殊特性。可能你已经猜到了, 它们会在渲染的DOM上应用特殊的响应式行为在这里,该指令的意思是:“将这个元素节点的title特性和Vue实例的message属性保持一致”。
如果你再次打开浏览器的JavaScript控制台, 输入app, message=‘新消息’,就会再一次看到这个绑定了title特性的HTML已经进行了更新。
2.2、v-if, v-else
什么是条件判断语句,就不需要我说明了吧,以下两个属性!
v-if
v-else
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
ok: true
}
});
</script>
</body>
</html>
测试:
-
在浏览器上运行,打开控制台!
-
在控制台输入
vm.ok=false
然后回车,你会发现浏览器中显示的内容会直接变成NO
注:使用v-*
属性绑定数据是不需要双花括号
包裹的
v-else-if
- v-if
- v-else-if
- v-else
注:===
三个等号在JS中表示绝对等于(就是数据与类型都要相等)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<h1 v-if="type==='A'">A</h1>
<h1 v-else-if="type==='B'">B</h1>
<h1 v-else-if="type==='D'">D</h1>
<h1 v-else>C</h1>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
type: 'A'
}
});
</script>
</body>
</html>
2.3、v-for
- v-for
格式说明
<div id="app">
<li v-for="(item,index) in items">
{{item.message}}---{{index}} <!--下标-->
</li>
</div>
注:items
是数组,item
是数组元素迭代的别名。我们之后学习的Thymeleaf模板引擎的语法和这个十分的相似!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<li v-for="item in items">
{{item.message}}
</li>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
items: [
{message: "cwlin学Java"},
{message: "cwlin学Vue"},
{message: "cwlin学后端"},
]
}
});
</script>
</body>
</html>
测试:在控制台输入vm.items.push({message:"cwlin学后端"})
,尝试追加一条数据,你会发现浏览器中显示的内容会增加一条cwlin学后端
。
2.4、v-on
v-on
监听事件
事件有Vue的事件、和前端页面本身的一些事件!我们这里的click
是vue的事件, 可以绑定到Vue中的methods
中的方法事件!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<button v-on:click="sayHi">click me</button>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
message: "cwlin学Java"
},
methods: { //方法必须定义在 Vue 的 Method 对象中
sayHi: function () {
alert(this.message); //'this'在方法里面指向当前Vue实例
}
}
});
</script>
</body>
</html>
3、表单双绑、组件(核心)
3.1、什么是双向数据绑定
Vue.js是一个MV VM框架, 即数据双向绑定, 即当数据发生变化的时候, 视图也就发生变化, 当视图发生变化的时候,数据也会跟着同步变化。这也算是Vue.js的精髓之处了。
值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的非UI控件不会涉及到数据双向绑定。单向数据绑定是使用状态管理工具的前提。如果我们使用vue x那么数据流也是单项的,这时就会和双向数据绑定有冲突。
为什么要实现数据的双向绑定?
在Vue.js
中,如果使用vuex
, 实际上数据还是单向的, 之所以说是数据双向绑定,这是用的UI控件来说, 对于我们处理表单, Vue.js
的双向数据绑定用起来就特别舒服了。即两者并不互斥,在全局性数据流使用单项,方便跟踪;局部性数据流使用双向,简单易操作。
3.2、在表单中使用双向数据绑定
你可以用v-model
指令在表单<input>
、<textarea>
及<select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇, 但v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
注意:v-model
会忽略所有表单元素的value
、checked
、selected
特性的初始值而总是将Vue
实例的数据作为数据来源。你应该通过JavaScript
在组件的data
选项中声明初始值!
3.2.1、单行文本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
输入的文本:<input type="text" v-model="message"> {{message}}
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
message: "123"
}
});
</script>
</body>
</html>
3.2.2、多行文本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
输入的文本:<textarea name="" id="" cols="30" rows="10" v-model="message"></textarea> {{message}}
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
message: "123"
}
});
</script>
</body>
</html>
3.2.3、单选按钮
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
性别:
<input type="radio" name="sex" value="男" v-model="checked">男
<input type="radio" name="two" value="女" v-model="checked">女
<span>选中的值:{{checked}}</span>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
checked: ''
}
});
</script>
</body>
</html>
3.2.4、下拉框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
选项:
<select v-model="selected">
<option value="" disabled>---请选择---</option>
<option>A</option>
<option>B</option>
<option>C</option>
<option>D</option>
</select>
<span>value: {{selected}}</span>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
selected: ''
}
});
</script>
</body>
</html>
3.2.5、单复选框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
单复选框:
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{checked}}</label>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
checked: false
}
});
</script>
</body>
</html>
3.2.6、多复选框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
多复选框:
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="join" value="Join" v-model="checkedNames">
<label for="join">Jack</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<span>选中的值:{{checkedNames}}</span>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
checkedNames:[]
}
});
</script>
</body>
</html>
注意:v-model
表达式的初始值未能匹配任何选项,元系将被渲染为“未选中”状态。 在iOS中, 这会使用户无法选择第一个选项,因为这样的情况下,iOS不会触发change
事件。因此,更推荐像上面这样提供一个值为空的禁用选项。
3.3、什么是组件
组件是可复用的Vue
实例, 说白了就是一组可以重复使用的模板, 跟JSTL
的自定义标签、Thymeleal 的th:fragment
等框架有着异曲同工之妙,通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
3.3.1、第一个Vue组件
注意:在实际开发中,我们并不会用以下方式开发组件,而是采用vue-cli
创建,vue
模板文件的方式开发,以下方法只是为了让大家理解什么是组件。
使用Vue.component()
方法注册组件,格式如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<cwlin></cwlin>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
// 定义一个Vue组件component
Vue.component("cwlin", {
template: '<li>Hello</li>'
});
var vm = new Vue({
el: "#app",
//model层: 数据
data: {}
});
</script>
</body>
</html>
说明:
Vue.component()
:注册组件cwlin
:自定义组件的名字template
:组件的模板
3.3.2、使用props
属性传递参数
像上面那样用组件没有任何意义,所以我们是需要传递参数到组件的,此时就需要使用props
属性了!
注意:默认规则下props属性里的值不能为大写;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<cwlin v-for="item in items" v-bind:value="item"></cwlin>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
// 定义一个Vue组件component
Vue.component("cwlin", {
props: ['value'],
template: '<li>{{value}}</li>'
});
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
items: ["Java", "Vue", "后端"]
}
});
</script>
</body>
</html>
说明:
v-for="item in items"
:遍历Vue
实例中定义的名为items
的数组,并创建同等数量的组件v-bind:value="item"
:将遍历的item
项绑定到组件中props
定义名为item
属性上;= 号左边的value
为props
定义的属性名,右边的为item in items
中遍历的item项的值
4、Axios异步通信
4.1、什么是Axios
Axios是一个开源的可以用在浏览器端和Node JS
的异步通信框架, 其主要作用就是实现AJAX异步通信,其功能特点如下:
- 从浏览器中创建
XMLHttpRequests
- 从node.js创建http请求
- 支持Promise API[JS中链式编程]
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防御XSRF(跨站请求伪造)
GitHub:https://github.com/axios/axios
4.2、为什么要使用Axios
由于Vue.js
是一个视图层框架并且作者(尤雨溪) 严格准守SoC(关注度分离原则)所以Vue.js
并不包含AJAX的通信功能, 为了解决通信问题, 作者单独开发了一个名为vue-resource
的插件, 不过在进入2.0版本以后停止了对该插件的维护并推荐了Axios
框架。少用jQuery, 因为它操作Dom太频繁!
4.3、第一个Axios应用程序
咱们开发的接口大部分都是采用JSON格式, 可以先在项目里模拟一段JSON数据, 数据内容如下:创建一个名为data.json的文件并填入上面的内容, 放在项目的根目录下
{
"name": "cwlin",
"url": "https://blog.youkuaiyun.com/coder_lcw",
"page": 1,
"isNonProfit": true,
"address": {
"street": "xx#xxx",
"city": "Fujian Fuzhou",
"country": "China"
},
"links": [
{
"name": "cwlin",
"url": "https://blog.youkuaiyun.com/coder_lcw"
},
{
"name": "bilibili",
"url": "https://www.bilibili.com/"
},
{
"name": "Baidu",
"url": "https://www.baidu.com/"
}
]
}
测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
[v-cloak]{
/*v-cloak 解决闪烁问题*/
display: none;
}
</style>
</head>
<body>
<!--view层: 模板-->
<div id="app" v-cloak>
<div>name: {{info.name}}</div>
<div>address: {{info.address.street}}, {{info.address.city}}, {{info.address.country}}</div>
<div>url: <a v-bind:href="info.url" target="_blank" rel="noopener noreferrer">{{info.url}}</a> </div>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data(){ //data方法,区别于data属性
return{
//请求的返回参数格式,必须和json字符串一样
info: {
name: null,
url: null,
address: {
street: null,
city: null,
country: null
}
}
}
},
mounted(){ //钩子函数,链式编程,必须支持ES6新特性
axios.get('../data.json').then(response => (this.info = response.data));
}
});
</script>
</body>
</html>
说明:
- 在这里使用了v-bind将a:href的属性值与Vue实例中的数据进行绑定;
- 使用axios框架的get方法请求AJAX并自动将数据封装进了Vue实例的数据对象中;
- 我们在data中的数据结构必须和
Ajax
响应回来的数据格式匹配!
4.4、Vue的生命周期
官方文档:https://cn.vuejs.org/v2/guide/instance.html#生命周期图示
Vue实例有一个完整的生命周期,也就是从开始创建初女台化数据、编译模板、挂载DOM、渲染一更新一渲染、卸载等一系列过程,我们称这是Vue的生命周期。通俗说就是Vue实例从创建到销毁的过程,就是生命周期。
在Vue的整个生命周期中,它提供了一系列的事件,可以让我们在事件触发时注册JS方法,可以让我们用自己注册的JS方法控制整个大局,在这些事件响应方法中的this直接指向的是Vue的实例。
5、计算属性、内容分发、自定义事件
5.1、计算属性
计算属性的重点突出在属性两个字上(属性是名词),首先它是个属性
其次这个属性有计算
的能力(计算是动词),这里的计算
就是个函数:简单点说,它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性),仅此而已;可以想象为缓存!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<p>{{message}}</p>
<p>Time1: {{currentTime1()}}</p>
<p>Time2: {{currentTime2}}</p>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
message: "Hello, Vue!"
},
methods: {
currentTime1: function () {
return Date.now(); //返回一个时间戳
}
},
computed: { //计算属性
currentTime2: function(){ //methods,computed方法名不能重名,重名之后,只会调用methods的方法
alert(this.message); //弹出一个窗口
return Date.now(); //返回一个时间戳
}
}
});
</script>
</body>
</html>
注意:methods和computed里的东西不能重名!
说明:
- methods:定义方法, 调用方法使用currentTime1(), 需要带括号
- computed:定义计算属性, 调用属性使用currentTime2, 不需要带括号
- this.message:是为了能够让currentTime2观察到数据变化而变化
- 如果在方法中的值发生了变化,则缓存就会刷新!可以在控制台使用
vm.message="hello"
,改变数据的值,再次测试观察效果!
结论:
- 调用方法时,每次都需要讲行计算,既然有计算过程则必定产生系统开销,那如果这个结果是不经常变化的呢?此时就可以考虑将这个结果缓存起来,采用计算属性可以很方便的做到这点。
- 计算属性的主要特性就是为了将不经常变化的计算结果进行缓存,以节约我们的系统开销。
5.2、内容分发
在Vue.js
中我们使用<slot>
元素作为承载分发内容的出口,作者称其为插槽,可以应用在组合组件的场景中;
测试
比如准备制作一个学习清单组件(ToDo) , 该组件由标题(todo-title) 和内容(todo-items)组成,但这三个组件又是相互独立的,该如何操作呢?
第一步:定义一个待办事项的组件
<!--view层: 模板-->
<div id="app">
<ToDo></ToDo>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
// 定义一个Vue组件component
Vue.component("ToDo", {
template:
`<div>
<div>学习清单</div>
<ul>
<li>Java</li>
</ul>
</div>`
});
</script>
第二步:我们需要让代办事项的标题和值实现动态绑定,通过插槽加以实现!
- 将上面的代码留出一个插槽,即slot。注意:自定义事件名字不能大写!!!
//slot: 插槽, 自定义事件名字不能大写!!!
Vue.component("ToDo", {
template:
`<div>
<slot name="todo-title"></slot>
<ul>
<slot name="todo-items"></slot>
</ul>
</div>`
});
- 定义 待办标题组件todo-title 和 待办内容组件todo-items
Vue.component("todo-title", {
props: ['title'],
template:'<div>{{title}}</div>'
});
Vue.component("todo-items", {
props: ['items', 'index'],
template:'<li>{{index+1}}: {{items}}</li>'
});
- 实例化Vue,并初始化数据
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
title: "学习清单",
todoItems: ["Java", "Vue", "后端"]
}
});
- 将这些值,通过插槽插入组件
<div id="app">
<ToDo>
<todo-title slot="todo-title" :title="title"></todo-title>
<todo-items slot="todo-items" v-for="(item, index) in todoItems" :items="item" :index="index"></todo-items>
</ToDo>
</div>
说明:我们的todo-title和todo-items组件分别被分发到了ToDo组件的todo-title和todo-items插槽中。
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<!--<p>列表书籍</p>
<ul>
<li>Java</li>
<li>Spring</li>
<li>Vue</li>
</ul>-->
<ToDo>
<todo-title slot="todo-title" :title="title"></todo-title>
<todo-items slot="todo-items" v-for="(item, index) in todoItems" :items="item" :index="index"></todo-items>
</ToDo>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
// 定义一个Vue组件component
//slot: 插槽, 自定义事件名字不能大写!!!
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:'<div>{{title}}</div>'
});
Vue.component("todo-items", {
props: ['items', 'index'],
template:'<li>{{index+1}}: {{items}}</li>'
});
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
title: "学习清单",
todoItems: ["Java", "Vue", "后端"]
}
});
</script>
</body>
</html>
5.3、自定义事件
通过以上代码不难发现,数据项在Vue的实例中, 但删除操作要在组件中完成, 那么组件如何才能删除Vue实例中的数据呢?
此时就涉及到参数传递与事件分发了, Vue为我们提供了自定义事件的功能很好的帮助我们解决了这个问题;
使用 this.$emit(“自定义事件名”, 参数) , 操作过程如下:
- 在vue的实例中增加了methods对象并定义一个名为removeItems的方法
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
title: "学习清单",
todoItems: ["Java", "Vue", "后端"]
},
methods: {
removeItems: function (index) {
alert("删除了 "+this.todoItems[index]+", Success!");
//splice()方法向/从数组中添加/删除项目,然后返回被删除的项目,其中,index是开始元素,1是删除个数
this.todoItems.splice(index, 1); //一次删除一个元素
}
}
});
- 修改todo-items待办内容组件的代码,增加一个删除按钮,并且绑定事件!
Vue.component("todo-items", {
props: ['items', 'index'],
template:'<li>{{index+1}}: {{items}} <button @click="remove">Delete</button></li>',
methods:{
remove: function (index) {
//只能绑定当前组件的方法
//这里的remove是自定义事件名称,需要在HTML中使用v-on:remove的方式
//this.$emit 自定义事件分发
this.$emit('remove',index);
}
}
});
- 修改todo-items待办内容组件的HTML代码,增加一个自定义事件,比如叫remove,可以和组件的方法绑定,然后绑定到vue的方法!
<!--增加了v-on:remove="removeItems(index)"自定义事件,该组件会调用Vue实例中定义的removeItems-->
<todo-items slot="todo-items"
v-for="(item, index) in todoItems" :items="item" :index="index"
v-on:remove="removeItems(index)">
</todo-items>
对上一个代码进行修改,实现删除功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层: 模板-->
<div id="app">
<!--<p>列表书籍</p>
<ul>
<li>Java</li>
<li>Spring</li>
<li>Vue</li>
</ul>-->
<ToDo>
<todo-title slot="todo-title" :title="title"></todo-title>
<!--增加了v-on:remove="removeItems(index)"自定义事件,该组件会调用Vue实例中定义的removeItems-->
<todo-items slot="todo-items"
v-for="(item, index) in todoItems" :items="item" :index="index"
v-on:remove="removeItems(index)">
</todo-items>
</ToDo>
</div>
<!--1.导入vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
// 定义一个Vue组件component
//slot: 插槽, 自定义事件名字不能大写!!!
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:'<div>{{title}}</div>'
});
Vue.component("todo-items", {
props: ['items', 'index'],
template:'<li>{{index+1}}: {{items}} <button @click="remove">Delete</button></li>',
methods:{
remove: function (index) {
//只能绑定当前组件的方法
//这里的remove是自定义事件名称,需要在HTML中使用v-on:remove的方式
//this.$emit 自定义事件分发
this.$emit('remove',index);
}
}
});
var vm = new Vue({
el: "#app",
//model层: 数据
data: {
title: "学习清单",
todoItems: ["Java", "Vue", "后端"]
},
methods: {
removeItems: function (index) {
alert("Delete "+this.todoItems[index]+", Success!");
//splice()方法向/从数组中添加/删除项目,然后返回被删除的项目,其中,index是开始元素,1是删除个数
this.todoItems.splice(index, 1); //一次删除一个元素
}
}
});
</script>
</body>
</html>
逻辑理解
5.4、Vue入门小结
核心:数据驱动,组件化
优点:借鉴了AngularJS的模块化开发和React的虚拟Dom,虚拟Dom就是把Demo操作放到内存中执行;
常用的属性:
- v-if
- v-else-if
- v-else
- v-for
- v-on绑定事件,简写@
- v-model数据双向绑定
- v-bind给组件绑定参数,简写:
组件化:
- 组合组件slot插槽
- 组件内部绑定事件需要使用到
this.$emit("事件名",参数);
- 计算属性的特色,缓存计算数据
遵循SoC关注度分离原则,Vue是纯粹的视图框架,并不包含,比如Ajax之类的通信功能,为了解决通信问题,我们需要使用Axios框架做异步通信;
说明:
- Vue的开发都是要基于NodeJS,实际开发采用Vue-cli脚手架开发,vue-router路由,vuex做状态管理;Vue UI,界面我们一般使用ElementUI(饿了么出品),或者ICE(阿里巴巴出品)来快速搭建前端项目~~
- ElementUI:https://element.eleme.cn/#/zh-CN
- ICE:https://ice.work/
6、第一个vue-cli项目
6.1、什么是vue-cli
vue-cli官方提供的一个脚手架,用于快速生成一个vue的项目模板;
预先定义好的目录结构及基础代码,就好比咱们在创建Maven项目时可以选择创建一个骨架项目,这个估计项目就是脚手架,我们的开发更加的快速;
项目的功能
- 统一的目录结构
- 本地调试
- 热部署
- 单元测试
- 集成打包上线
6.2、需要的环境
下载地址
-
Node.js:http://nodejs.cn/download/
安装就是无脑的下一步就好,安装在自己的环境目录下
确认nodejs安装成功:
-
cmd下输入
node -v
,查看是否能够正确打印出版本号即可! -
cmd下输入
npm -v
,查看是否能够正确打印出版本号即可!这个npm,就是一个软件包管理工具,就和linux下的apt软件安装差不多!
安装Node.js淘宝镜像加速器(cnpm)
# -g 就是全局安装
npm install cnpm -g
# 或使用如下语句解决npm速度慢的问题,优先用这个!
npm install --registry=https://registry.npm.taobao.org
虽然安装了cnpm,但是尽量少用!安装的位置:C:\Users\administrator\AppData\Roaming\npm
安装vue-cli
cnpm install -g vue-cli
#测试是否安装成功
#查看可以基于哪些模板创建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.
6.3、第一个vue-cli应用程序
- 创建一个Vue项目,我们随便建立一个空的文件夹在电脑上,我这里在D盘下新建一个目录
E:\...\Vue-cwlin
- 创建一个基于webpack模板的vue应用程序
#1、首先需要进入到对应的目录
cd /d E:\...\Vue-cwlin
#2、这里的vue-02是顶日名称,可以根据自己的需求起名
vue init webpack vue-02
- 使用默认项目名称vue-02,作者名可以修改,然后一路都选择no即可(还是选yes吧,不然会疯掉的!!!)
- 说明:
- Project name:项目名称,默认回车即可
- Project description:项目描述,默认回车即可
- Author:项目作者,默认回车即可
- Install vue-router:是否安装vue-router,选择n不安装(后期需要再手动添加)
- Use ESLint to lint your code:是否使用ESLint做代码检查,选择n不安装(后期需要再手动添加)
- Set up unit tests:单元测试相关,选择n不安装(后期需要再手动添加)
- Setupe2etests with Nightwatch:单元测试相关,选择n不安装(后期需要再手动添加)
- Should we run npm install for you after the,project has been created:创建完成后直接初始化,选择n,我们手动执行;运行结果!
-
初始化并运行
cd vue-02 npm install npm run dev
-
执行完成后,目录多了很多依赖!当出现问题时,可以查看提示进行处理如下:
7、webpack使用
7.1、什么是Webpack
- 本质上, webpack是一个现代JavaScript应用程序的静态模块打包器(module bundler) 。当webpack处理应用程序时, 它会递归地构建一个依赖关系图(dependency graph) , 其中包含应用程序需要的每个模块, 然后将所有这些模块打包成一个或多个bundle。
- Webpack是当下最热门的前端资源模块化管理和打包工具, 它可以将许多松散耦合的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分离,等到实际需要时再异步加载。通过loader转换, 任何形式的资源都可以当做模块, 比如Commons JS、AMD、ES 6、CSS、JSON、Coffee Script、LESS等;
- 伴随着移动互联网的大潮, 当今越来越多的网站已经从网页模式进化到了WebApp模式。它们运行在现代浏览器里, 使用HTML 5、CSS 3、ES 6等新的技术来开发丰富的功能, 网页已经不仅仅是完成浏览器的基本需求; WebApp通常是一个SPA(单页面应用) , 每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加载越来越多的JS代码,这给前端的开发流程和资源组织带来了巨大挑战。
- 前端开发和其他开发工作的主要区别,首先是前端基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器的,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。
7.2、模块化的演进
7.2.1、Script标签
<script src = "module1.js"></script>
<script src = "module2.js"></script>
<script src = "module3.js"></script>
-
这是最原始的JavaScript文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在window对象中,不同模块的调用都是一个作用域。
-
这种原始的加载方式暴露了一些显而易见的弊端:
- 全局作用域下容易造成变量冲突
- 文件只能按照
<script>
的书写顺序进行加载 - 开发人员必须主观解决模块和代码库的依赖关系
- 在大型项目中各种资源难以管理,长期积累的问题导致代码库混乱不堪
7.2.2、CommonsJS
服务器端的NodeJS遵循CommonsJS规范,该规范核心思想是允许模块通过require方法来同步加载所需依赖的其它模块,然后通过exports或module.exports来导出需要暴露的接口。
require("module");
require("../module.js");
export.doStuff = function(){};
module.exports = someValue;
优点
- 服务器端模块便于重用
- NPM中已经有超过45万个可以使用的模块包
- 简单易用
缺点
- 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的
- 不能非阻塞的并行加载多个模块
实现
- 服务端的NodeJS
- Browserify,浏览器端的CommonsJS实现,可以使用NPM的模块,但是编译打包后的文件体积较大
- modules-webmake,类似Browserify,但不如Browserify灵活
- wreq,Browserify的前身
7.2.3、AMD
Asynchronous Module Definition规范其实主要一个主要接口define(id?,dependencies?,factory);它要在声明模块的时候指定所有的依赖dependencies,并且还要当做形参传到factory中,对于依赖的模块提前执行。
define("module",["dep1","dep2"],functian(d1,d2){
return someExportedValue;
});
require(["module","../file.js"],function(module,file){});
优点
- 适合在浏览器环境中异步加载模块
- 可以并行加载多个模块
缺点
- 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不畅
- 不符合通用的模块化思维方式,是一种妥协的实现
实现
- RequireJS
- curl
7.2.4、CMD
Commons Module Definition规范和AMD很相似,尽保持简单,并与CommonsJS和NodeJS的Modules规范保持了很大的兼容性。
define(function(require,exports,module){
var $=require("jquery");
var Spinning = require("./spinning");
exports.doSomething = ...;
module.exports=...;
});
优点:
- 依赖就近,延迟执行
- 可以很容易在NodeJS中运行缺点
- 依赖SPM打包,模块的加载逻辑偏重
实现
- Sea.js
- coolie
7.2.5、ES6模块
EcmaScript 6标准增加了JavaScript语言层面的模块体系定义。ES 6模块的设计思想,是尽量静态化,使编译时就能确定模块的依赖关系,以及输入和输出的变量。Commons JS和AMD模块,都只能在运行时确定这些东西。
import "jquery"
export function doStuff(){}
module "localModule"{}
优点
- 容易进行静态分析
- 面向未来的Ecma Script标准
缺点
- 原生浏览器端还没有实现该标准
- 全新的命令,新版的Node JS才支持
实现
- Babel
大家期望的模块
- 系统可以兼容多种模块风格,尽量可以利用已有的代码,不仅仅只是JavaScript模块化,还有CSS、图片、字体等资源也需要模块化。
7.3、安装Webpack
WebPack是一款模块加载器兼打包工具, 它能把各种资源, 如JS、JSX、ES 6、SASS、LESS、图片等都作为模块来处理和使用。
#安装
npm install webpack -g
npm install webpack-cli -g
#测试安装成功
webpack -v
webpack-cli -v
7.4、配置Webpack
创建 webpack.config.js
配置文件
- entry:入口文件, 指定Web Pack用哪个文件作为项目的入口
- output:输出, 指定WebPack把处理完成的文件放置到指定路径
- module:模块, 用于处理各种类型的文件
- plugins:插件, 如:热更新、代码重用等
- resolve:设置路径指向
- watch:监听, 用于设置文件改动后直接打包
module.exports = {
entry:"",
output:{
path:"",
filename:""
},
module:{
loaders:[
{test:/\.js$/,;\loade:""}
]
},
plugins:{},
resolve:{},
watch:true
}
直接运行webpack
命令打包
7.5、使用webpack
- 创建项目 vue-03
- 创建一个名为modules的目录,用于放置JS模块等资源文件
- 在modules下创建模块文件,如hello.js,用于编写JS模块相关代码
//exports 暴露一个方法
exports.sayHi = function () {
document.write("<h1>cwlin学Java</h1>")
}
- 在modules下创建一个名为main.js的入口文件,用于打包时设置entry属性
//require 导入一个模块,就可以调用这个模块中的方法了var hello = require("./hello");hello.sayHi();
- 在项目目录下创建webpack.config.js配置文件
module.exports = { entry: "./modules/main.js", output: { filename:"./js/bundle.js" }}
- 使用
webpack
命令打包:在IDEA控制台中直接执行webpack;如果失败的话,使用管理员权限运行即可! - 在项目目录下创建HTML页面,如index.html,导入webpack打包后的JS文件
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script src="dist/js/bundle.js"></script> </body></html>
- 运行HTML看效果
- 监听webpack的变化,实现热部署
#参数 --watch 用于监听变化webpack --watch
8、vue-router路由
8.1、说明
- 学习的时候,尽量的打开官方的文档
- Vue Router是Vue.js官方的路由管理器。它和Vue.js的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于Vue js过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的CSS class的链接
- HTML5 历史模式或hash模式, 在IE 9中自动降级
- 自定义的滚动行为
8.2、安装
-
基于第一个vue-cli应用程序进行测试学习; 先查看node modules中是否存在vue-router
-
vue-router是一个插件包, 所以我们还是需要用npm / cnpm来进行安装的。打开命令行工具,进入你的项目目录,输入下面命令。
npm install vue-router --save-dev或cnpm install vue-router --save-dev
- 如果在一个模块化工程中使用它,必须要通过Vue.use()明确地安装路由功能:
import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter)
8.3、测试
-
先删除没有用的东西
-
components
目录下存放我们自己编写的组件 -
定义一个
Content.vue
的组件
<template> <h1>内容</h1></template><script>export default { name: 'Content'}</script><style scoped></style>
- 定义一个
Main.vue
组件
<template> <h1>首页</h1></template><script>export default { name: 'Main'}</script><style scoped></style>
- 定义一个
Cwlin.vue
组件
<template> <h1>cwlin</h1></template><script>export default { name: 'Cwlin'}</script><style scoped></style>
- 安装路由,在src目录下,新建一个文件夹:
router
,专门存放路由,配置路由index.js
import Vue from 'vue'// 导入路由插件import VueRouter from 'vue-router'// 导入上面定义的组件import Content from '../components/Content'import Main from '../components/Main'// 安装路由Vue.use(VueRouter)// 配置路由export default new VueRouter({ routes: [ { // 路由路径 path: '/Content', // 路由名称 name: 'Content', // 跳转到组件 component: Content }, { // 路由路径 path: '/Main', // 路由名称 name: 'Main', // 跳转到组件 component: Main } ]})
- 在
main.js
中配置路由
/* eslint-disable */ // ignore all warnings in a fileimport Vue from 'vue'import App from './App'//导入上面创建的路由配置目录import router from './router' // 自动扫描里面的路由配置//关闭生产模式下给出的提示Vue.config.productionTip = false/* eslint-disable no-new */new Vue({ el: '#app', // 配置路由 router, components: { App }, template: '<App/>'})
- 在
App.vue
中使用路由
<template> <div id="app"> <h1>Vue-Router</h1> <router-link to="/main">首页</router-link> <router-link to="/content">内容页</router-link> <router-link to="/cwlin">cwlin</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>
8.3、Tips
- 修改
./config/index.js
中的useEslint: false
,以实现禁用eslint检测! - use /* eslint-disable */ ignore all warnings in a file
- /* eslint-disable no-new */:eslint-disable 的意思是完全禁用ESLint进行检测,这里就是禁用no-new的效验。
- Strings must use singlequote:在
vue
的项目开发中,如果我们在通过vue-cli
脚手架构建项目的时候使用了Eslint
严格模式,那么对于字符串类型的数据String
必须要使用单引号,不能使用双引号,否则会报异常。 - Expected space or tab after ‘//’ in comment:注释的//后面要有一个空格。
- Extra semicolon:使用 vue-cli 构建的项目,模版是 webpack ,默认的 eslint 配置是以 standard 为基础的,要求是不使用分号。
9、实战快速上手
我们采用实战教学模式并结合ElementUI组件库,将所需知识点应用到实际中,以最快速度带领大家掌握Vue的使用;
9.1、创建工程
注意:命令行都要使用管理员模式运行
- 创建一个名为
vue-04
的工程
vue init webpack vue-04
- 安装依赖, 我们需要安装vue-router、element-ui、sass-loader和node-sass四个插件
#进入工程目录
cd vue-04
#安装vue-routern
npm install vue-router --save-dev
#安装element-ui
npm i element-ui -S
#安装依赖
npm install
# 安装SASS加载器
cnpm install sass-loader node-sass --save-dev
#启功测试
npm run dev
- 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为该命令的缩写
9.2、创建登录页面
-
删除多余的初始化内容,在源码目录中创建以下结构:
- assets:用于存放资源文件
- components:用于存放Vue功能组件
- views:用于存放Vue视图组件
- router:用于存放vue-router配置
-
创建首页视图,在views目录下创建一个名为
Main.vue
的视图组件;
<template>
<h1>首页</h1>
</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="dialogVisiable" 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>
- 创建路由,在router目录下创建一个名为
index.js
的vue-router路由配置文件
// 导入vue
import Vue from "vue";
import Router from "vue-router";
// 导入组件
import Main from "../views/Main";
import Login from "../views/Login";
//使用
Vue.use(Router);
//导出
export default new Router({
routes: [
{ // 首页
path: '/main',
component: Main
},
{ // 登录页
path: '/login',
component: Login
},
]
})
- App.vue
<template>
<div id="app">
<router-link to="/main">main</router-link> <!--提供一个入口-->
<router-link to="/login">login</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>
- main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
new Vue({
el: '#app',
router,
render: h => h(App) // ElementUI
})
- 测试:在浏览器打开 http://localhost:8080/#/login
- 将Login.vue中的
<style lang="scss" scoped>
改为<style scoped>
;(亲测有效) - 如果出现错误,可能是因为sass-loader的版本过高导致的编译错误,当前最高版本是8.0.2,需要退回到7.3.1,将package.json文件里的 "sass-loader"的版本更换成7.3.1,然后重新npm install就可以了。
- 将Login.vue中的
9.3、路由嵌套
嵌套路由又称子路由,在实际应用中,通常由多层嵌套的组件组合而成。
- 创建用户信息组件,在 views/user 目录下创建一个名为
Profile.vue
的视图组件;
<template>
<h1>个人信息</h1>
</template>
<script>
export default {
name: "UserProfile"
}
</script>
<style scoped>
</style>
- 在用户列表组件在 views/user 目录下创建一个名为
List.vue
的视图组件;
<template>
<h1>用户列表</h1>
</template>
<script>
export default {
name: "UserList"
}
</script>
<style scoped>
</style>
- 修改首页视图,我们修改
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-submenu index="3">
<template slot="title"><i class="el-icon-caret-right"></i>系统管理</template>
<el-menu-item-group>
<el-menu-item index="3-1">用户设置</el-menu-item>
<el-menu-item index="3-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>
</el-header>
<el-main>
<!--在这里展示视图-->
<router-view />
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
export default {
name: "Main"
}
</script>
<style scoped>
.el-header {
background-color: #97c5fa;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
a, .router-link-visited {
color: #333;
text-decoration: none;
}
.router-link-active {
color: #0099ff;
}
</style>
- 配置嵌套路由修改 router 目录下的
index.js
路由配置文件,使用children放入main中写入子模块,代码如下:
// 导入vue
import Vue from "vue";
import Router from "vue-router";
// 导入组件
import Main from "../views/Main";
import Login from "../views/Login";
//导入子模块
import UserProfile from "../views/user/Profile";
import UserList from "../views/user/List";
//使用
Vue.use(Router);
//导出
export default new Router({
routes: [
{ // 首页
path: '/main',
component: Main, // 嵌套路由
// 写入子模块
children: [
{
path: '/user/profile',
component: UserProfile
},
{
path: '/user/list',
component: UserList
}
]
},
{ // 登录页
path: '/login',
component: Login
},
]
})
- 测试路由嵌套效果!
9.4、参数传递
9.4.1、第一种取值方式:使用占位符
- 修改路由配置,在router下的
index.js
中的 path 属性中增加 :id 这样的占位符。此时,路由名称 name 不能省略!
{
path: '/user/profile/:id',
name: 'UserProfile',
component: UserProfile
}
- 传递参数
- 此时我们在
Main.vue
中的 route-link 位置处 to 改为了 :to,是为了将这一属性当成对象使用,注意 router-link 中的 name 属性名称 一定要和路由中的 name 属性名称匹配,因为这样 Vue 才能找到对应的路由路径;
<!--name 传递组件名称,params 传递参数,如果要传参数的话就需要用v:bind:来绑定-->
<router-link :to="{name:'UserProfile', params:{id:1}}">个人信息</router-link>
- 在组件
Profile.vue
中,使用 {{$route.params.id}}来接收参数
<template>
<!--所有的元素必须在根节点下-->
<div>
<h1>个人信息</h1>
{{$route.params.id}}
</div>
</template>
<script>
export default {
name: "UserProfile"
}
</script>
<style scoped>
</style>
9.4.2、第二种取值方式:使用 props 减少耦合
- 修改路由配置 , 在router下的
index.js
中的路由属性中增加 props: true 属性
{
path: '/user/list',
name:'UserList',
component: UserList,
props: true
}
- 传递参数和之前一样,在
Main.vue
中修改 route-link
<!--name 传递组件名称,params 传递参数,如果要传参数的话就需要用v:bind:来绑定-->
<router-link :to="{name:'UserList', params:{id:1}}">用户列表</router-link>
- 在
List.vue
中接收参数,为目标组件增加 props 属性,并使用参数
<template>
<!--所有的元素必须在根节点下-->
<div>
<h1>用户列表</h1>
{{id}}
</div>
</template>
<script>
export default {
name: "UserList",
props: ["id"]
}
</script>
<style scoped>
</style>
9.5、组件重定向
-
Vue 中的重定向:作用在路径不同但组件相同的情况下,比如:
-
修改在 router 下面的
index.js
的配置
{
path: '/main',
component: Main
},
{ // 回到首页
path: '/goHome',
redirect: '/main' // 组件重定向
}
- 说明:这里定义了两个路径,一个是 /main ,一个是 /goHome,其中 /goHome 重定向到了 /main 路径,由此可以看出重定向不需要定义组件!
- 在
Main.vue
设置对应路径即可
<el-menu-item index="1-3">
<router-link to="/goHome">回到首页</router-link>
</el-menu-item>
9.6、应用:用户名显示
- 删除
App.vue
中的 router-link 标签
<router-link to="/main">main</router-link> <!--提供一个入口-->
<router-link to="/login">login</router-link>
- 修改
Login.vue
中的 onSubmit 方法,添加this.form.username
methods:{
onSubmit(formName){
//为表单绑定验证功能
this.$refs[formName].validate((valid)=>{
if(valid){
//使用vue-router路由到指定界面,该方式称为编程式导航
this.$router.push('/main/' + this.form.username);
}else{
this.dialogVisible=true;
return false;
}
});
}
}
- 修改路由配置 , 在router下的
index.js
的 path 属性中增加 :name 占位符,并增加 props: true 属性(参数传递)
export default new Router({
mode: 'history',
routes: [
{ // 首页
path: '/main/:name',
component: Main, // 嵌套路由
props: true,
// 写入子模块
children: [
{
path: '/main/:name/user/profile/:id',
name: 'UserProfile',
component: UserProfile
},
{
path: '/main/:name/user/list',
name:'UserList',
component: UserList,
props: true
}
]
},
{ // 登录页
path: '/login',
component: Login
},
{ // 回到首页
path: '/goHome',
name: 'GoHome',
redirect: '/main/:name', // 组件重定向
props: true //在重定向组件中,props属性可有可无
}
]
})
- 在
Main.vue
中接收参数,为目标组件增加 props 属性,并将参数 name 传递到重定向组件 GoHome 中(组件重定向)
<span>{{name}}</span>
<script>
export default {
name: "Main",
props: ["name"]
}
</script>
<el-menu-item index="1-3">
<router-link :to="{name:'GoHome', params:{name:name}}">回到首页</router-link>
</el-menu-item>
- 在
Main.vue
中,在组件头部显示用户名 name
<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>{{name}}</span>
</el-header>
9.7、路由模式与 404
9.7.1、路由模式
-
路由模式有两种:
- hash:路径带 # 符号,如 http://localhost/#/login
- history:路径不带 # 符号,如 http://localhost/login
-
修改路由配置,代码如下:
export default new Router({ mode: 'history', routes: [ // 这里省略路由的定义 ] });
9.7.2、404 Demo
- 创建一个
NotFound.vue
视图组件
<template>
<div>
<h1>404,你的页面走丢了</h1>
</div>
</template>
<script>
export default {
name: "NotFound"
}
</script>
<style scoped>
</style>
- 修改路由配置
index.js
// 导入组件
import NotFound from "../views/NotFound";
// 添加路由
{
path: '*',
component: NotFound
}
- 测试效果:http://localhost:8080/main
- 注意:直接访问 main 页面,无法进入主页!因此,页面跳转到404!
9.8、路由钩子与异步请求
9.8.1、路由钩子
- 钩子类型:
- beforeRouteEnter:在进入路由前执行
- beforeRouteLeave:在离开路由前执行
- 举个栗子:修改
Profile.vue
中组件的属性,进行测试
<script>
export default {
name: "UserProfile",
// 钩子函数:过滤器
beforeRouteEnter: (to, from, next) => {
console.log("准备进入个人信息页"); // 控制台显示
alert("准备进入个人信息页"); // 弹窗显示
next();
},
beforeRouteLeave: (to, from, next) => {
console.log("准备离开个人信息页"); // 控制台显示
alert("准备离开个人信息页"); // 弹窗显示
next();
}
}
</script>
- 参数说明:
- to:路由将要跳转的路径信息
- from:路径跳转前的路径信息
- next:路由的控制参数
- next() 跳入下一个页面
- next(’/path’) 改变路由的跳转方向,使其跳到另一个路由
- next(false) 返回原来的页面
- next(vm => {}) 仅在 beforeRouteEnter 中可用,vm 是组件实例
9.8.2、在钩子函数中使用异步请求
- 安装 Axios(axios 和 vue-axios)
cnpm install --save axios
cnpm install --save vue-axios
- 在
main.js
中导入 Axios
import axios from 'axios';
import VueAxios from 'vue-axios';
Vue.use(VueAxios, axios);
-
准备数据:只有 static 目录下的文件是可以被访问到的,因此把静态数据文件存放在该目录下,即
static/mock/data.json
注意:这里使用的数据和之前用的json数据相同,详细内容见 4.3 节。
{
"name": "cwlin",
"url": "https://blog.youkuaiyun.com/coder_lcw",
"page": 1,
"isNonProfit": true,
"address": {
"street": "xx#xxx",
"city": "Fujian Fuzhou",
"country": "China"
},
"links": [
{
"name": "cwlin",
"url": "https://blog.youkuaiyun.com/coder_lcw"
},
{
"name": "bilibili",
"url": "https://www.bilibili.com/"
},
{
"name": "Baidu",
"url": "https://www.baidu.com/"
}
]
}
- 在
Profile.vue
中使用 beforeRouteEnter 进行异步请求,并在前端展示!
<template>
<!--所有的元素必须在根节点下-->
<div>
<h1>个人信息</h1>
<!--{{$route.params.id}}-->
<div id="userInfo" v-cloak>
<div>Name: {{info.name}}</div>
<div>Address: {{info.address.street}}, {{info.address.city}}, {{info.address.country}}</div>
<div>URL: <a v-bind:href="info.url" target="_blank" rel="noopener noreferrer">{{info.url}}</a> </div>
</div>
</div>
</template>
<script>
export default {
name: "UserProfile",
data(){ //data方法,区别于data属性
return{
//请求的返回参数格式,必须和json字符串一样
info: {
name: null,
url: null,
address: {
street: null,
city: null,
country: null
}
}
}
},
// 钩子函数:过滤器
beforeRouteEnter: (to, from, next) => {
console.log("准备进入个人信息页"); // 控制台显示
// alert("准备进入个人信息页"); // 弹窗显示
next(vm => {
vm.info = vm.getData(); // 进入路由之前,执行getData方法来加载数据
});
},
beforeRouteLeave: (to, from, next) => {
console.log("准备离开个人信息页"); // 控制台显示
// alert("准备离开个人信息页"); // 弹窗显示
next();
},
// axios:异步请求
methods: {
getData: function () {
var _this = this;
this.axios({
method: 'get',
url: 'http://localhost:8080/static/mock/data.json'
}).then(function (response) {
console.log(response); // 控制台显示
_this.info = response.data;
})
return _this.info;
}
}
}
</script>
<style scoped>
#userInfo, a:link, a:visited{ /* 未访问和已访问的链接 */
color: #333;
font-size: 16px;
}
a:hover{
color: blue;
}
a:active{
color: red;
}
/*不存在闪烁问题,以下代码可以省略*/
[v-cloak]{
/*v-cloak 解决闪烁问题*/
display: none;
}
</style>
- 测试结果,好好体会一下 getData 方法中异步请求的思路!