Vue基础
官网:https://v3.cn.vuejs.org/
视频:BV18E411a7mC
文章目录
1、一些概念
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框架:Vue.js
,Anfular JS
1.2 为什么要用MVVM
MVVM的目的:分离视图(View)和模型(Model)
这样做的好处:
- 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
- 可复用:可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
- 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewMode),设计人员可以专注于页面设计。
- 可测试:界面素来是比较难以测试的,而现在测试可以针对ViewModel来写。
MVVM组成部分
View
视图层,即用户界面。前端主要由HTH L和csS来构建。为了简化开发,工程师又开发出了一些快速构建视图层的框架,如vue.js、Angular.js等,它们有自己用来构建用户界面的内置模板语言
Model
数据模型,泛指后端进行的各种业务逻辑处理和数据处理, 主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的接口规则
ViewModel
由前端开发人员组织生成和维护的视图数据层。
在这一层, 前端开发者对从后端获取的Model数据进行转换处理, 做二次封装, 以生成符合View层使用预期的视图数据模型。
ViewModel所封装出来的数据模型包括视图的状态和行为两部分,由于实现了双向绑定, View Model的内容会实时展现在View层。至此,前端开发者再也不必低效又麻烦地通过操纵DOM去更新视图。开发者只需要处理和维护ViewModel, 更新数据视图就会自动得到相应更新,真正实现事件驱动编程。
View层展现的不是Model层的数据, 而是ViewModel的数据, 由ViewModel负责与Model层交互。这就完全解耦了View层和Model层, 这个解耦是前后端分离方案实施的重要一环
1.3 Vue
Vue(读音/vju/, 类似于view) 是一套用于构建用户界面的渐进式框架, 发布于2014年2月。与其它大型框架不同的是, Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层, 不仅易于上手, 还便于与第三方库(如:vue-router,vue-resource,vue x) 或既有项目整合
MVVM模式的实现者
- Model:模型层, 在这里表示JavaScript对象
- View:视图层, 在这里表示DOM(HTML操作的元素)
- ViewModel:连接视图和数据的中间件, Vue.js就是MVVM中的View Model层的实现者
在MVVM架构中, 不允许数据和视图直接通信, 只能通过ViewModel来通信, 而View Model就是定义了一个Observer观察者。
- ViewModel能够观察到数据的变化, 并对视图对应的内容进行更新
- ViewModel能够监听到视图的变化, 并能够通知数据发生改变
Vue.js就是一个MVVM的实现者, 它的核心就是实现了DOM监听与数据绑定
为什么使用Vue.js
- 轻量级, 体积小是一个重要指标。Vue.js压缩后有只有20多kb(Angular压缩后56kb+,React压缩后44kb+)
- 移动优先。更适合移动端, 比如移动端的Touch事件
- 易上手,学习曲线平稳,文档齐全
- 吸取了Angular(模块化) 和React(虚拟DOM) 的长处, 并拥有自己独特的功能,如:计算属性
- 开源,社区活跃度高
1.4 Hello vue
通过标签导入vue.js
<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>
创建一个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/vue/2.1.3/vue.js"></script>
<script type="text/javascript">
//创建一个vue实例
var vm=new Vue({
el:"#app",
// 存储数据
data:{
message:"hello vue"
}
});
</script>
</body>
</html>
el:#app
用于绑定元素的ID
data:{message:"hello vue"}
代表数据对象中有一个名为message的属性,并设置了初始值
{{message}}
实现数据绑定功能,绑定的ID写在当前的div容器中
理解View Model:它可以类比成一个观察者,监测到了数据的变化,就立马更新页面与之绑定的值,无需更新页面,也无需操作DOM对象,相当于一个虚拟DOM对象。
2、Vue基础语法
2.1 v-bind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息
</span>
</div>
<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",
data:{
message:"hello vue"
}
});
</script>
</body>
</html>
标签中的v-bind
属性 被称为指令。指令带有前缀 v-
,以表示它们是 Vue 提供的特殊属性。它们会在渲染的 DOM 上应用特殊的响应式行为。
通过控制台修改vm的message
属性,显示效果也会实时更新
2.2 v-if、v-else
使用例1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h1 v-if="type">Yes</h1>
<h1 v-else>No</h1>
</div>
<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",
data:{
type:true
}
});
</script>
</body>
</html>
使用例2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h1 v-if="type==='A'">A</h1>
<h1 v-else-if="type==='B'">B</h1>
<h1 v-else-if="type==='C'">C</h1>
<h1 v-else>D</h1>
</div>
<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",
data:{
type:'A'
}
});
</script>
</body>
</html>
2.3 v-for
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<li v-for="(item,index) in items">
{{item.message}}----{{index}}
</li>
</div>
<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",
data:{
items:[
{message:'vue.js'},
{message: 'node.js'},
{message: 'javascript'}
]
}
});
</script>
</body>
</html>
items
是数组,item
是数组元素迭代的别名,index是迭代的序号
2.4 v-on
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码
使用例1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<button v-on:click="counter+=1">+1</button>
<p>按钮已经按下 {{counter}} 次</p>
</div>
<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",
data:{
counter: 0
}
});
</script>
</body>
</html>
使用例2
许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<button v-on:click="greet">Greet</button>
</div>
<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",
data:{
name:'Vue.js'
},
// 在methods对象中定义方法
methods:{
greet: function(event){
// this 方法里指向当前vue实例
alert('Hello'+this.name)
// event 是原生的DOM事件
if(event){
alert(event.target.tagName)
}
}
}
});
</script>
</body>
</html>
3、Vue表单双向绑定和组件
3.1 表单数据双向绑定
数据双向绑定:当数据发生变化的时候, 视图也就发生变化, 当视图发生变化的时候,数据也会跟着同步变化
注:数据双向绑定,一定是基于UI控件的
可以用v-model
指令在表单、及元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model
负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的value、checked、selected
特性的初始值而总是将Vue实例的数据作为数据来源。你应该通过JavaScript在组件的data选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
单行文本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input v-model="message" placeholder="edit me">
<p>current message is: {{message}}</p>
</div>
<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",
data:{
message:''
}
});
</script>
</body>
</html>
多行文本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{message}}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
</div>
<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",
data:{
message:''
}
});
</script>
</body>
</html>
复选框
单个复选框绑定到布尔值
<div id="app">
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{checked}}</label>
</div>
<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",
data:{
checked:true
}
});
</script>
多个复选框,绑定到同一个数组
<div id="app">
<input type="checkbox" id="alice" value="Alice" v-model="checkedNames">
<label for="alice">Alice</label>
<input type="checkbox" id="emma" value="Emma" v-model="checkedNames">
<label for="emma">Emma</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names:{{checkedNames}}</span>
</div>
<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",
data:{
checkedNames:[]
}
});
</script>
单选按钮
<div id="app">
<input type="radio" id="cheese" value="🧀" v-model="picked">
<label for="cheese">🧀</label>
<input type="radio" id="burger" value="🍔" v-model="picked">
<label for="cheese">🍔</label>
<input type="radio" id="pizza" value="🍕" v-model="picked">
<label for="cheese">🍕</label>
<br>
<span>Picked food:{{picked}}</span>
</div>
<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",
data:{
picked:''
}
});
</script>
选择框
<div id="app">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected:{{selected}}</span>
</div>
<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",
data:{
selected:''
}
});
</script>
如果 v-model
表达式的初始值未能匹配任何选项,`` 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。
3.2 组件
组件是可复用的Vue
实例, 就是一组可以重复使用的模板, 跟JSTL
的自定义标签、Thymeleal
的th:fragment
等框架有着异曲同工之妙,通常一个应用会以一棵嵌套的组件树的形式来组织
比如一个博客网站,可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件
自定义组件
<div id="app">
<Infinite></Infinite>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
Vue.component("infinite",{
template:'<li>diy template</li>'
});
var vm=new Vue({
el:"#app",
data:{
}
});
</script>
Vue.component()
:注册组件infinite
:自定义组件的名字template
:组件的模板
使用
props
属性传递参数
<div id="app">
<Infinite v-for="item in items" v-bind:course="item"></Infinite>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
Vue.component("infinite",{
props:['course'],
template:'<li>{{course}}</li>'
});
var vm=new Vue({
el:"#app",
data:{
items:["java","vue","spring"]
}
});
</script>
v-for=“item in items”
:遍历Vue实例中定义的名为items
的数组,并创建同等数量的组件v-bind:course=“item”
:将遍历的item
项绑定到组件中props定义名为course
属性上;= 号左边的course
为props
定义的属性名,右边的为item in items
中遍历的item项的值
4、Axios异步通信
4.1 Axios简介
Axios是一个开源的可以用在浏览器端和Node JS的异步通信框架。
它的主要作用就是实现AJAX异步通信,其功能特点如下:
- 从浏览器中创建XMLHttpRequests
- 从node.js创建http请求
- 支持Promise API[JS中链式编程]
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防御XSRF(跨站请求伪造)
由于Vue.js是一个视图层框架并且作者(尤雨溪) 严格准守SoC(关注度分离原则)所以Vue.js并不包含AJAX的通信功能, 为了解决通信问题, 作者单独开发了一个名为vue-resource的插件, 不过在进入2.0版本以后停止了对该插件的维护并推荐了Axios框架。由于jQuery操作Dom太频繁,所以建议少用。
4.2 使用Axios
同目录下写两个文件(目录可以不同,不过需要修改json的路径)
data.json
{
"name": "infinite",
"url": "https://blog.csdn.net/INFINITE_WAR?type=blog",
"page": 1,
"isNonProfit": true,
"address": {
"street": "A街",
"city": "B市",
"country": "CN"
},
"links": [
{
"name": "bilibili",
"url": "https://www.bilibili.com/"
},
{
"name": "百度",
"url": "https://www.baidu.com/"
},
{
"name": "wolframalpha",
"url": "https://www.wolframalpha.com/"
}
]
}
axios_test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
<!--v-cloak 解决闪烁问题-->
[v-cloak]{
display:none;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<div>地名: {{info.name}}</div>
<div>地址: {{info.address.country}}---{{info.address.city}}----{{info.address.street}}</div>
<div>链接: <a v-bind:href="info.url">{{info.url}}</a> </div>
</div>
<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",
data(){
return{
info:{
name:null,
address:{
country:null,
city:null,
street:null
},
url:null
}
}
},
mounted(){ //钩子函数
axios.get("data.json")
.then(response=>(this.info=response.data));
}
});
</script>
</body>
</html>
- 在这里使用了
v-bind
将a:href
的属性值与Vue实例中的数据进行绑定 - 使用axios框架的get方法请求AJAX并自动将数据封装进了Vue实例的数据对象中
- 我们在data中的数据结构必须和
Ajax
响应回来的数据格式匹配
Vue生命周期图
5、Vue计算属性、内容分发、自定义事件
5.1 计算属性
计算属性的重点突出在属性
两个字上(属性是名词),首先它是个属性
其次这个属性有计算
的能力(计算是动词),这里的计算
就是个函数:简单点说,它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性),仅此而已;可以想象为缓存
使用例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--View层:模板-->
<div id="app" v-cloak>
<!--注意method和computed的使用区别-->
<p>currentTime1:{{currentTime1()}}</p>
<p>currentTime2:{{currentTime2}}</p>
</div>
<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",
data:{
message:"infinite"
},
methods:{
//返回一个时间戳
currentTime1:function(){
return Date.now();
}
},
computed:{
currentTime2:function(){
this.message;
return Date.now();//返回一个时间戳
}
}
})
</script>
</body>
</html>
methods
和computed
里的东西不能重名, 重名之后,只会调用methods
的方法。
methods
:定义方法, 调用方法使用currentTime1()
, 需要带括号computed
:定义计算属性, 调用属性使用currentTime2
, 不需要带括号:this.message
是为了能够让currentTime2
观察到数据变化而变化- 如果在方法中的值发生了变化,则缓存就会刷新。可以在控制台使用vm.message=”xxxxx", 改变数据的值,再次测试观察效果。
调用方法时,每次都需要讲行计算,既然有计算过程则必定产生系统开销。
那如果这个结果是不经常变化的呢?此时就可以考虑将这个结果缓存起来,采用计算属性可以很方便的做到这点,计算属性的主要特性就是为了将不经常变化的计算结果进行缓存,以节约我们的系统开销
5.2 内容分发
在Vue.js
中我们使用``元素作为承载分发内容的出口,可以称其为插槽,可以应用在组合组件的场景中
现要将下面的内容,让标题和内容通过插槽写入
<p>标题</p>
<ul>
<li>abcd</li>
<li>abcd</li>
<li>abcd</li>
</ul>
-
定义一个代办事情的组件
Vue.component('todo',{ template:'<div>\ <div>代办事项</div>\ <ul>\ <li>=========head==========</li>\ </ul>\ </div>' });
-
将上面的代码预留出一个插槽(
slot
)Vue.component('todo',{ template:'<div>\ <slot></slot>\ <ul>\ <slot></slot>\ </ul>\ </div>' });
-
定义一个名为
todo-title
的代办标题组件和todo-items的代办内容组件Vue.component('todo-title',{ props:['title'], template:'<div>{{title}}</div>' }); //这里的index,就是数组的下标,使用for循环遍历的时候,可以循环取出 Vue.component("todo-items",{ props:["item","index"], template:"<li>{{index+1}},{{item}}</li>" });
-
slot
通过name
和组件绑定Vue.component('todo',{ template:'<div>\ <slot name="todo-title"></slot>\ <ul>\ <slot name="todo-items"></slot>\ </ul>\ </div>' });
-
实例化Vue并初始化数据
Vue.component('todo',{ template:'<div>\ <slot name="todo-title"></slot>\ <ul>\ <slot name="todo-items"></slot>\ </ul>\ </div>' });
-
将数据通过插槽插入预留出来的位置
<todo> <todo-title slot="todo-title" v-bind:title="title"></todo-title> <todo-items slot="todo-items" v-for="item in todoItems" :item="item" ></todo-items> </todo>
slot
:绑定组件:title
: 是v-bind:title
的缩写 。:item
同理
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<todo>
<todo-title slot="todo-title" v-bind:title="title"></todo-title>
<!--<todo-items slot="todo-items" v-for="{item,index} in todoItems" v-bind:item="item"></todo-items>-->
<!--以下为简写-->
<todo-items slot="todo-items" v-for="item in todoItems" :item="item" ></todo-items>
</todo>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
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:['item'],
template:'<li>{{item}}</li>'
});
var vm=new Vue({
el:"#app",
data:{
title:"============head============",
todoItems:['test1','test2','test3']
}
});
</script>
</body>
</html>
5.3 自定义事件
对于上述代码,如果删除操作要在组件中完成,那么组件如何删除Vue实例中的数据?比如上述上面结果中的"test2"。
Vue为我们提供了自定义事件的功能,很地d解决了这个问题; 组件中使用this.$emit(‘自定义事件名’, 参数)
,在视图层通过自定义事件绑定Vue中的删除操作的方法。
-
在Vue实例中定义一个删除操作地方法
removeItems()
methods:{ removeItems: function(index){ this.todoItems.splice(index,1); } }
splice(index,n)
方法是操作index下标开始的n个元素 -
在视图层中自定义事件并绑定到Vue实例中的方法
<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 todoItems" :item="item" :index="index" v-on:remove="removeItems(index)"></todo-items> </todo> </div>
自定义事件为
remove
通过v-on
绑定removeItems
方法 -
在响应的组件中绑定自定义事件
Vue.component("todo-items",{ props:["item","index"], template:"<li>{{item}}---{{index}}<button @click='remove'>删除</button></li>", methods: { remove: function (index) { this.$emit('remove',index); } } });
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<todo>
<todo-title slot="todo-title" v-bind:title="title"></todo-title>
<todo-items slot="todo-items" v-for="(item,index) in todoItems" :item="item" :index="index"
v-on:remove="removeItems(index)"></todo-items>
</todo>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
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:['item','index'],
template:'<li>{{item}}---{{index}}<button @click="remove">删除</button></li>',
methods:{
remove:function(index){
this.$emit('remove',index);
}
}
});
var vm=new Vue({
el:"#app",
data:{
title:"============head============",
todoItems:['test1','test2','test3']
},
methods:{
removeItems:function(index){
this.todoItems.splice(index,1);
}
}
});
</script>
</body>
</html>
运行过程图示:
小结
核心:数据驱动,组件化
优点:借鉴了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框架做异步通信
6、第一个vue-cli项目
6.1 vue-cli简介
vue-cli官方提供的一个脚手架,用于快速生成一个vue的项目模板
预先定义好的目录结构及基础代码,就好比在创建Maven项目时可以选择创建一个骨架项目,这个骨架项目就是脚手架,可以提高开发效率
项目的功能
- 统一的目录结构
- 本地调试
- 热部署
- 单元测试
- 集成打包上线
6.2 环境配置
Node.js:http://nodejs.cn/ cmd中输入node -v
来检测是否安装成功
Git:http://git-scm.com/download/
安装node.js淘宝镜像加速器(cnpm)
https://registry.npmmirror.com/binary.html?path=git-for-windows/
# -g 就是全局安装
npm install cnpm -g
# 或使用如下语句解决npm速度慢的问题,但是每次install都需要(妈发)
npm install --registry=https://registry.npm.taobao.org
安装vue-cli
cnpm instal1 vue-cli-g
#测试是否安装成功#查看可以基于哪些模板创建vue应用程序,通常我们选择webpack
vue list
6.3 第一个vue-cli程序
-
创建一个空目录,获取路径
-
cmd进入该路径,创建一个基于webpack模板的vue应用程序
#1、首先需要进入到对应的目录 cd E:\study\Java\workspace\workspace_vue #2、这里的myvue是顶日名称,可以根据自己的需求起名 vue init webpack myvue
-
创建过程中的选项暂时都选no
按照提示进行初始化
cd myvue
npm install #安装基本依赖(内容比较大)
npm run dev #在本地指定的端口(默认为8080)运行项目
7、webpack使用
WebPack是一款模块加载器兼打包工具, 它能把各种资源, 如JS、JSX、ES 6、SASS、LESS、图片等都作为模块来处理和使用
7.1 安装
npm install webpack -g
npm install webpack-cli -g
测试是否安装成功
配置:创建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.2 使用
-
创建项目:新建目录,然后用IDEA打开
-
创建modules目录,用于放置JS模块等资源文件
-
在modules下创建模块文件
hello.js
//暴露一个方法:sayHi exports.sayHi = function(){ document.write("<div>Hello Webpack</div>"); }
-
在modules下创建一个名为
main.js
的入口文件,用于打包时设置entry属性//require 导入一个模块,就可以调用这个模块中的方法了 var hello = require("./hello"); hello.sayHi();
-
在项目目录下创建
webpack.config.js
配置文件,使用webpack命令打包
-
在项目目录下创建
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>
-
打开
index.html
# 参数--watch 用于监听变化,如果要打包的东西有变化,就重新打包
webpack --watch
8、vue-router路由
8.1 安装
基于第一个vue-cli
进行测试学习; (先查看node modules中是否存在vue-router, vue-router是一个插件包)
npm install vue-router --save-dev
如果在一个模块化工程中使用它,必须要通过Vue.use()明确地安装路由功能
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
8.2 测试
- 删除第一个vue-cli项目中的没用的东西
components
目录下存放我们自己编写的组件- 定义几个自己的组件
Content.vue 、Main.vue、infinite.vue
Content.vue
<template>
<div>
<h1>内容页</h1>
</div>
</template>
<script>
export default {
name:"Content"
}
</script>
Main.vue
<template>
<div>
<h1>首页</h1>
</div>
</template>
<script>
export default {
name:"Main"
}
</script>
infinite.vue
<template>
<div>
<h1>infinite</h1>
</div>
</template>
<script>
export default {
name:"infinite"
}
</script>
在src目录下安装路由。新建目录:router
,专门存放路由,配置路由index.js
import Vue from'vue'
//导入路由插件
import Router from 'vue-router'
//导入上面定义的组件
import Content from '../components/Content'
import Main from '../components/Main'
import cVzhanshi from "../components/infinite";
//安装路由
Vue.use(Router) ;
//配置路由
export default new Router({
routes:[
{
//路由路径
path:'/content',
//路由名称
name:'content',
//跳转到组件
component:Content
},{
//路由路径
path:'/main',
//路由名称
name:'main',
//跳转到组件
component:Main
}
,{
//路由路径
path:'/infinite',
//路由名称
name:'main',
//跳转到组件
component:Infinite
}
]
});
这里的router可能会报错,跟vue-router版本有关,建议不适用4.0+的版本
解决方案参考:https://blog.csdn.net/qq_43814654/article/details/123641317
在main.js
中配置路由
import 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">
<!--
router-link:默认会被渲染成一个<a>标签,to属性为指定链接
router-view:用于渲染路由匹配到的组件
-->
<h1>cVzhanshi</h1>
<router-link to="/main">首页</router-link>
<router-link to="/content">内容</router-link>
<router-link to="/cvzhanshi">cVzhanshi</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
部署到本地:npm run dev
。访问8080端口
9、vue+element实例
9.1 创建工程
创建一个工程:hello-vue
vue init webpack hello-vue
安装依赖, vue-router、element-ui、sass-loader和node-sass
四个插件
#进入工程目录
cd hello-vue
#安装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 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为该命令的缩写
idea打开项目
9.2 创建登录页面
删除src中没用的文件
- assets:用于存放资源文件
- components:用于存放Vue功能组件
- views:用于存放Vue视图组件
- router:用于存放vue-router配置
编写组件
-
在views目录下创建首页视图
Main.vue
组件<template> <div>首页</div> </template> <script> export default { name: "Main.vue" } </script> <style scoped> </style>
-
在views目录下创建登录页面视图
Login.vue
组件<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:"密码不可为空",tigger:"blur"} ] }, //对话框显示和隐藏 dialogVisible:false } }, methods:{ outSubmit(formName){ //为表单绑定验证功能 this.$refs[forName].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 VueRouter from 'vue-router'; //导入组件 import Main from "../views/Main"; import Login from "../views/Login"; //使用 Vue.use(VueRouter); //导出 export default new VueRouter({ 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/> </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.config.productionTip = false Vue.use(router) Vue.use(ElementUI) /* eslint-disable no-new */ new Vue({ el: '#app', router, render:h=>h(App) })
-
测试npm run dev
可能会出现如下错误
跟sass版本过高有关。https://blog.csdn.net/qq_42025798/article/details/122382298
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-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 lang="scss"> .el-header { background-color: #B3C0D1; color: #333; line-height: 60px; } .el-aside { color: #333; } </style>
-
添加了组件,去router修改配置文件
//导入vue import Vue from 'vue'; import VueRouter from 'vue-router'; //导入组件 import Main from "../views/Main"; import Login from "../views/Login"; //导入子模块 import UserList from "../views/user/List"; import UserProfile from "../views/user/Profile"; //使用 Vue.use(VueRouter); //导出 export default new VueRouter({ routes: [ { //登录页 path: '/main', component: Main, //写入子模块 children: [ { path: '/user/profile', component: UserProfile, }, { path: '/user/list', component: UserList, }, ] }, //首页 { path: '/login', component: Login }, ] })
-
测试
9.4 参数传递和重定向
参数传递
方法一
- 修改路由配置, 主要是router下的
index.js
中的 path 属性中增加了:id
占位符
{
path: '/user/profile/:id',
name:'UserProfile',
component: UserProfile
}
- 视图层传递参数(
Main.vue
)
<!--name是组件的名字 params是传的参数 如果要传参数的话就需要用v:bind:来绑定-->
<router-link :to="{name:'UserProfile',params:{id:1}}">个人信息</router-link>
此时在Main.vue
中的route-link位置处 to 改为了 :to,是为了将这一属性当成对象使用,注意 router-link 中的 name 属性名称 一定要和 路由中的 name 属性名称 匹配,因为这样 Vue 才能找到对应的路由路径
- 接收参数(
Profile.vue
)
<template>
<div>
<!-- 所有的元素必须在根节点下-->
<h1>个人信息</h1>
{{$route.params.id}}
</div>
</template>
所有的元素必须在根节点下面,否则会报错。
- 测试
方法二
使用props
减少耦合
-
修改路由配置 , 主要在router下的
index.js
中的路由属性中增加了props: true
属性{ path: '/user/profile/:id', name:'UserProfile', component: UserProfile, props: true }
-
传递参数(和之前一样)
-
在
Profile.vue
接收参数为目标组件增加props
属性<template> <div> <!-- 所有的元素必须在根节点下--> <h1>个人信息</h1> {{id}} </div> </template> <script> export default { props:['id'], name: "UserProfile" } </script> <style scoped> </style>
-
测试
重定向
Vue 中的重定向是作用在路径不同但组件相同的情况
-
在router/index.js配置重定向路径
{ path: '/main', name: 'Main', component: Main }, { path: '/goHome', redirect: '/main' }
-
视图增加(
Main.vue
)<el-menu-item index="1-3"> <!--插入的地方--> <router-link to="/goHome">返回首页</router-link> </el-menu-item>
9.5 路由模式、404、路由钩子
路由模式
路由模式有两种
hash
:路径带 # 符号,如http://localhost/#/login
history
:路径不带 # 符号,如http://localhost/login
修改路由配置:(index.js
,或其他名字的路由的配置文件)
export default new VueRouter({
mode:'history',
routes: []
)}
404
-
创建一个
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 }
-
测试
路由钩子
除了之前的钩子函数还存在两个钩子函数
beforeRouteEnter
:在进入路由前执行
beforeRouteLeave
:在离开路由前执行
在
Profile.vue
使用
<script>
export default {
name: "UserProfile",
beforeRouteEnter: (to, from, next) => {
console.log("准备进入个人信息页");
next();
},
beforeRouteLeave: (to, from, next) => {
console.log("准备离开个人信息页");
next();
}
}
</script>
to
:路由将要跳转的路径信息from
:路径跳转前的路径信息next
:路由的控制参数next()
跳入下一个页面next(’/path’)
改变路由的跳转方向,使其跳到另一个路由next(false)
返回原来的页面next((vm)=>{})
仅在 beforeRouteEnter 中可用,vm 是组件实例
在钩子函数中进行异步请求
- 安装Axios
cnpm install --save vue-axios
-
main.js
引用 Axiosimport axios from 'axios' import VueAxios from 'vue-axios' Vue.use(VueAxios, axios)
-
准备数据
{ "name": "infinite", "url": "https://blog.csdn.net/INFINITE_WAR?type=blog", "page": 1, "isNonProfit": true, "address": { "street": "A街", "city": "B市", "country": "CN" }, "links": [ { "name": "bilibili", "url": "https://www.bilibili.com/" }, { "name": "百度", "url": "https://www.baidu.com/" }, { "name": "wolframalpha", "url": "https://www.wolframalpha.com/" } ] }
只有的
static
目录下的文件是可以被访问到,所以把静态文件放入该目录下 -
在 beforeRouteEnter 中进行异步请求(
Profile.vue
)<script> export default { name: "UserProfile", beforeRouteEnter: (to, from, next) => { console.log("准备进入个人信息页"); next(vm => { //进入路由之前执行getData方法 vm.getData() }); }, beforeRouteLeave: (to, from, next) => { console.log("准备离开个人信息页"); next(); }, //axios methods: { getData: function () { this.axios({ method: 'get', url: 'http://localhost:8080/static/mock/data.json' }).then(function (response) { console.log(response) }) } } } </script>
-
测试