vue.js
vue简介
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的JavaScript框架。封装了原生的DOM操作,无需进行任何手动的DOM编码即可完成页面数据的渲染。
MVVM模式
前端页面开发需要解决的核心问题是:数据(模型)和页面(视图)的绑定问题。如何将数据展示到页面中,以及当页面的数据发生变化时,如何同步变化到数据?
传统的DOM操作:
手动编码将Model中的数据绑定到View上; //dom对象.属性=数据
当View的发生变化时,也需要手动编码获取变化。//数据 = dom对象.属性
存在着如下的问题:
- 开发者需要手动编码DOM操作,开发繁琐
- 当Model频繁发生变化时,开发者需要手动编码更新到View中;当View发生变化时,也需要同步到Model中。这样不仅开发繁琐,而且很难维护复杂多变的数据状态。
MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是通过 ViewModel 把Model和View关联起来。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model,即所谓的数据双向绑定。
- M:即Model,模型,主要指的就是要展示在页面的数据
- V:即View,视图,负责页面效果展示,就是html+css
- VM:即View-Model,模型与视图间的双向操作(通常由框架负责实现)
MVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用再关心Model和View之间是如何互相影响,把开发者从繁琐的DOM操作中解放出来,把关注点放在如何操作Model上。
Vue.js 是一个提供了 MVVM 风格的双向数据绑定的 Javascript 框架,使得我们无需关注底层DOM操作,专注于业务数据操作,提高页面开发效率。
第1个Vue示例
- 新建页面,并引入
vue.js
<script src="js/vue-2.6.12.js"></script>
- 定义一个视图标签
<body>
<div id="app"></div>
</body>
- 创建一个Vue实例(ViewModel)
<body>
<!--{{}}插值表达式,可以将vm对象的数据插入到视图中 -->
<div id="app"><h1>{{msg}}</h1></div>
<script>
const vm = new Vue({
el:"#app",//通过选择器,选则要挂载的标签
data:{//数据
msg:"hello "
}
})
</script>
</body>
- 总结
- 页面要有一个用于挂载数据的标签
- 创建Vue实例(ViewModel对象)
- el:通过选择器选择标签
- data:定义可以渲染到视图的数据
Vue框架要解决的核心问题
Vue作为构建页面的JavaScript框架,简化了页面操作的方式,但其和原生JS在操作页面时要解决的问题是相同的。原生JS操作页面需要解决的问题,也必是Vue需要解决的,而学习Vue其实就是在学习它对这些问题的解决方法。
操作页面需要解决的问题:
- 渲染数据到页面
- 为标签属性赋值
- 事件绑定
当然Vue在解决这些基本问题的同时,也提供了更多的功能,简化开发。下面就让我们开始学习Vue的具体语法。
渲染数据到页面
数据绑定最常见的形式就是使用 {{值}}
(双大括号)的文本插值:
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
msg:"hello vue"
}
})
</script>
说明:
**{{}}**中通常是变量,但也可以是表达式(比如 a+b)、有返回值的函数调用。
v-text和v-html指令
v-text
如果数据不是预先定义好,而是通过网络获取时,使用
{{}}
方式在网速较慢时会出现问题。在数据未加载完成时,页面会显示出原始的{{}}
,加载完毕后才显示正确数据,称为插值闪烁。此时,可以使用v-text代替插值表达式:
<div id="app">
<h1 v-text="msg"></h1>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
msg:"hello vue"
}
})
</script>
v-html指令
如果数据中包含有HTML标签,双大括号和
v-text指令
会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用v-html
指令:
<div id="app">
<h1>{{msg}}</h1>
<h1 v-text="msg"></h1>
<h1 v-html="msg"></h1>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
msg:"<span style='color:red'>hello vue</span>"
}
})
</script>
v-if和v-show指令
v-if指令
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true 值的时候被渲染。
<div id="app">
<h1 v-if="show">
<span style="color:green">show=true</span>
</h1>
<h1 v-if="!show">
<span style="color:red">show=false</span>
</h1>
<button onclick="handleClick()">点我</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
show:true
}
})
function handleClick(){
vm.show = !vm.show;
}
</script>
v-if
后还可以添加v-else
指令来表示 v-if
的“else 块”,上面的示例还可以写出如下代码
<div id="app">
<h1 v-if="show">
<span style="color:green">show=true</span>
</h1>
<h1 v-else>
<span style="color:red">show=false</span>
</h1>
<button onclick="handleClick()">点我</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
show:true
}
})
function handleClick(){
vm.show = !vm.show;
}
</script>
v-show指令
另一个用于根据条件展示元素的指令是 v-show
指令。和v-if
用法大致一样,不过v-show
后不能跟v-else
:
<div id="app">
<h1 v-show="show">
<span style="color:green">show=true</span>
</h1>
<h1 v-show="!show">
<span style="color:red">show=false</span>
</h1>
<button onclick="handleClick()">点我</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
show:true
}
})
function handleClick(){
vm.show = !vm.show;
}
</script>
v-if 和 v-show 的区别:
<div id="app">
<h1 v-if="show">
<span style="color:green">v-if指令</span>
</h1>
<h1 v-show="show">
<span style="color:green">v-show指令</span>
</h1>
<button onclick="handleClick()">点我</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
show:true
}
})
function handleClick(){
vm.show = !vm.show;
}
</script>
v-if 和 v-show 指令展示效果相同,但是打开开发者工具(F12或者ctrl+shift+i)查看Element面板,会发现2者的区别
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的子组件适当地被销毁和重建。当初始条件为false时,其内部组件不会渲染。v-show
就简单得多,它在切换过程中,只是简单地基于 CSS 进行切换。当初始条件为false时,其内部组件也会预先渲染。
一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
v-for指令
遍历数据渲染页面是非常常用的需求,Vue中通过v-for
指令来实现。
遍历数组
v-for="(item,index) in items"
items
:要遍历的数组,需要在vue的data中定义好。item
:数组元素index:
元素下标,可以省略
示例:
<div id="app">
<ul>
<li v-for="(item,index) in favorites">{{index+ ":"+item}}</li>
</ul>
</div>
<script>
var vm = new Vue({
el:"#app",
data:{
favorites:['唱','跳','Rap','打篮球']
}
});
</script>
再看一个对象数组的示例:
<div id="app">
<table>
<thead>
<tr>
<th>name</th>
<th>age</th>
<th>gender</th>
</tr>
</thead>
<tbody>
<tr v-for="u in users">
<td>{{u.name}}</td>
<td>{{u.age}}</td>
<td>{{u.gender}}</td>
</tr>
</tbody>
</table>
<button onclick="addPerson()">点我</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
users: [
{"name": "小明", "age": 13, "gender": "男"},
{"name": "小红", "age": 13, "gender": "女"},
{"name": "小绿", "age": 4, "gender": "男"}
]
}
});
function addPerson(){
vm.users.push({"name": "小翠", "age": 14, "gender": "女"})
}
</script>
遍历对象
v-for`除了可以迭代数组,也可以迭代对象。语法基本类似
语法:
v-for="value in object"
v-for="(value,name) in object"
v-for="(value,name,index) in object"
- 1个参数时,得到的是对象的属性值
- 2个参数时,第一个是属性值,第二个是属性名
- 3个参数时,第三个是索引,从0开始
示例:
<div id="app">
<ul>
<li v-for="(value,name,index) in person">{{index}} - {{name}}={{value}}</li>
</ul>
<button onclick="changePerson()">点我</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
person:{
"name": "zhangsan",
"age": 13,
"gender": "男",
"address": "中国"
}
}
})
function changePerson(){
vm.person = {
"name": "lisi",
"age": 16,
"gender": "女",
"address": "中国"
}
}
</script>
注意:由于 v-for
优先级比 v-if
高,用在同1个元素上,一定会先执行数组遍历,然后再执行判断,出于性能的考虑,官方并不建议我们连用。
事件绑定:v-on指令
基本使用
在Vue中通过v-on
指令给页面元素绑定事件。
语法:
v-on:事件名="js代码片段或函数" //事件名=事件属性-on 比如:click=onclick-on
//函数必须定义在Vue示例的methods属性中。
const vm = new Vue({
el:"选择器",
methods:{
//定义事件处理函数
函数名:function(){
//函数实现
}
}
})
示例:
<div id="app">
<h1 v-if="show">
<span style="color:green">show=true</span>
</h1>
<h1 v-else>
<span style="color:red">show=false</span>
</h1>
<!-- 直接使用函数名即可 -->
<button v-on:click="handleClick">点我</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
show:true
},
methods:{//注意事件处理函数必须定义在methods中
handleClick:function(){
this.show=!this.show;//data中的属性,在函数中可以直接this.访问
}
}
})
</script>
事件绑定还有简写语法:
@事件名="js代码片段或函数"
const vm = new Vue({
el:"选择器",
methods:{
//定义事件处理函数:直接定义函数,甚至无需function关键字
函数名(){
...
}
}
})
再看一个案例:
<div id="app">
<h2>num={{num}}</h2>
<!-- 事件处理函数代码简单时,可以直接在事件绑定处写代码 -->
<button @click="num++">自增</button>
<button @click="decrement">自减</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
num:1
},
methods:{
/*
increment:function(){
this.num--;
}*/
//methods定义函数还可以有如下简写形式 函数名(){}
increment(){
this.num--;
}
}
})
</script>
事件修饰符
在事件处理函数中调用
event.preventDefault()
禁用标签的默认行为或event.stopPropagation()
停止事件冒泡是非常常见的需求。
<div id="app">
<a href="https://www.baidu.com" @click="handleClick">点我</a>
</div>
<script>
const vm = new Vue({
el:"#app",
methods:{
handleClick(event){
alert("被单击了");
event.preventDefault();//禁用超链接的默认点击跳转
}
}
})
</script>
尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。为了解决这个问题,Vue.js 为
v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
:阻止事件冒泡.prevent
:阻止默认事件发生,比如超链接的默认跳转、表单的提交(重点).self
:只有元素自身触发事件才执行。(子元素的事件冒泡来的不执行).once
:只执行一次
<div id="app">
<a href="https://www.baidu.com" @click.prevent="handleClick">点我</a>
</div>
<script>
const vm = new Vue({
el:"#app",
methods:{
handleClick(event){
alert("被单击了");
// event.preventDefault();
}
}
})
</script>
标签属性的赋值:v-bind指令
基本使用
有时,我们需要动态的修改标签的属性值,看如下需求:动态修改div的class属性值
<head>
<style type="text/css">
.red_bg{
background-color: red;
}
.green_bg{
background-color: green;
}
</style>
</head>
<body>
<div id="app">
<!-- 这里意图通过插值表达式动态获取class_name的值,但是失败了 -->
<div class="{{class_name}}">点击按钮改变背景样式:{{class_name}}</div>
<button @click="class_name='red_bg'">红色</button>
<button @click="class_name='green_bg'">绿色</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
class_name:""
}
})
</script>
</body>
无法直接在标签的属性中使用插值表达式动态的设置标签的属性值,这时候就需要使用
v-bind
指令。语法如下:
v-bind:属性名="Vue中的变量"
例如,在这里我们可以写成:
<div v-bind:class="class_name"></div>
不过,v-bind
太麻烦,因此可以省略,直接写成:
,:属性名="属性值"
,即:
<div :class="color"></div>
完整示例
<head>
<style type="text/css">
.red_bg{
background-color: red;
}
.green_bg{
background-color: green;
}
</style>
</head>
<body>
<div id="app">
<div class="{{class_name}}">点击按钮改变背景样式:{{class_name}}</div>
<div v-bind:class="class_name">点击按钮改变背景样式:{{class_name}}</div>
<div :class="class_name">点击按钮改变背景样式:{{class_name}}</div>
<button @click="class_name='red_bg'">红色</button>
<button @click="class_name='green_bg'">绿色</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
class_name:""
}
})
</script>
</body>
绑定class属性的对象语法
我们可以传给在这里插入代码片 v-bind:class
一个对象,以动态地切换 class:
<div id="app">
<div v-bind:class="{red_bg:showRed,green_bg:!showRed}">点击按钮改变背景样式</div>
<button @click="showRed=true">红色</button>
<button @click="shoRed=false">绿色</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
showRed:true
}
})
</script>
v-for时的key属性
当我们需要更新 v-for 渲染过的元素列表时,比如向数组中添加一个新的元素。为了保证Vue正确并高效的渲染页面,官方建议我们为每项提供一个唯一
key
属性,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素。key一般使用在遍历完后,又增、减数组的元素的时候更有意义。
示例:
<ul>
<li v-for="(item,index) in items" :key="index"></li>
</ul>
示例:
- 这里使用了属性赋值语法:
:key="index"
为每一个生成的元素设置一个唯一的key属性 - 这里绑定的key是数组的索引,应该是唯一的
表单的双向数据绑定:v-model指令
之前我们演示的数据绑定都是单向绑定,数据影响了视图渲染,但是反过来就不行(视图的变化不会影响模型数据)。如下例所示:
<div id="app">
<input type="text" :value="name"> <br>
<button @click="setName">点我设置value</button>
<button @click="getName">点我获取value</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
name:""
},
methods:{
setName(){
this.name="xiaohei";
},
getName(){
alert(this.name);
}
}
})
</script>
而对于表单中的控件而言,我们需要数据的绑定是双向的,即:模型数据的变化会影响视图,同时视图发生变化也会同步到模型数据。
接下来学习的
v-model
是双向绑定,视图(View
)和模型(Model
)之间会互相影响。既然是双向绑定,一定是在视图中可以修改数据的组件,这样就限定了视图的元素类型。
目前v-model
的可使用元素有:
- input
- radio
- checkbox
- select
- textarea
- components(Vue中的自定义组件)
基本上除了最后一项,其它都是表单的输入项。
首先我们先看一个简单的示例:
<div id="app">
<input type="text" v-model="name"> <br>
<button @click="setName">点我设置value</button>
<button @click="getName">点我获取value</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
name:""
},
methods:{
setName(){
this.name="xiaohei";
},
getName(){
alert(this.name);
}
}
})
</script>
接下来,我们演示一个完整的示例:
<div id="app">
<form action="">
姓名: <input type="text" name="name" v-model="person.name"> <br>
性别: <input type="radio" name="sex" value="1" v-model="person.sex"> 男
<input type="radio" name="sex" value="0" v-model="person.sex"> 女 <br>
爱好:<input type="checkbox" name="favorites" value="0" v-model="person.favorites"> 唱
<input type="checkbox" name="favorites" value="1" v-model="person.favorites"> 跳
<input type="checkbox" name="favorites" value="2" v-model="person.favorites"> Rap
<input type="checkbox" name="favorites" value="3" v-model="person.favorites"> 打篮球 <br>
学历:<select name="education" v-model="person.education">
<option value="0">小学</option>
<option value="1">中学</option>
<option value="2">大学</option>
<option value="3">Java练习生</option>
</select> <br>
个性签名: <textarea name="signature" v-model="person.signature" cols="30" rows="10"></textarea> <br>
<input type="submit" value="提交">
</form>
<hr>
<button @click="updatePerson">点我修改person改变表单</button>
<button @click="getPerson">点我获取表单同步后的person</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
person:{}
},
methods:{
updatePerson(){
this.person = {
name:"xiaohei",
sex:1,
favorites:[0,1,2,3],
education:3,
signature:"小黑不是一般的黑"
};
},
getPerson(){
console.log(this.person);
}
}
})
</script>
input
和textarea
默认对应是字符串radio
对应的input的value值是预先定义好的,model中对应的值匹配哪个就选中哪个- 多个
checkbox
对应的类型是一个数组 select
根据option
子元素的value属性进行匹配选择
实战案例:todo-list
<div id="app">
<form action="" @submit.prevent="addNewTodo">
Add a todo
<input type="text" v-model="newTodoText">
<button type="submit">Add</button>
</form>
<ul v-if="todos.length > 0">
<li v-for="(todo,index) in todos">{{todo}} <button @click="removeTodo(index)">Remove</button></li>
</ul>
<h4 v-else>
当前没有待办事项
</h4>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
newTodoText:"",
todos:[]
},
methods:{
addNewTodo(){
if(this.newTodoText.trim().length > 0){
this.todos.push(this.newTodoText);
this.newTodoText="";
}
},
removeTodo(index){
//从指定下标开始,删除1个元素
this.todos.splice(index,1);
}
}
})
</script>
计算属性和过滤器
计算属性
在插值表达式中可以直接使用js表达式,这是非常方便的。但是如果表达式的内容很复杂,就会影响代码可读性,而且后期维护起来也不方便,例如下面的场景:需要统计多个商品的总价。
<div id="app">
<h2>总价:{{products.reduce((total,p)=>{return total+p.price*p.count;},0)}}</h2>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
products:[
{
name: 'iPhone 7',
price: 7199,
count: 2
},
{
name: 'iPad',
price: 2888,
count: 1
}
]
}
})
</script>
Vue中提供了计算属性,来替代复杂的表达式。通过给Vue实例添加选项computed来定义计算属性:
<div id="app">
<h2>总价:{{products.reduce((total,p)=>{return total+p.price*p.count;},0)}}</h2>
<h2>总价:{{totalPrice}}</h2>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
products:[
{
name: 'iPhone 7',
price: 7199,
count: 2
},
{
name: 'iPad',
price: 2888,
count: 1
}
]
},
computed:{
totalPrice(){
return this.products.reduce((total,p)=>{return total+p.price*p.count;},0);
}
}
})
</script>
计算属性本质就是方法,但是一定要返回数据。然后页面渲染时,可以把这个方法当成一个变量来使用。
过滤器
过滤器的作用是对数据进行格式化,比如字母全部大写、日期格式化输出、货币前添加货币符号等场景。Vue.js支持在{{ }}插值的尾部添加一个管道符“(|)”对数据进行过滤。过滤的规则,通过给Vue实例添加选项filters来设置。
<div id="app">
<!-- 过滤器只有1个参数时,过滤器不需要写出(),默认传入|前的数据 -->
<h2>使用过滤器字母全大写:{{str | toUpperCase}}</h2>
<h2>使用过滤器日期格式化:{{date | dateFormat}}</h2>
<!-- 过滤器有多个参数时,第1个参数仍然是|前的数据,后续的参数需要显式给出 -->
<h2>货币格式化输出:{{money | moneyFormat("¥")}}</h2>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
str:"hello filter",
date:new Date(),
money:1000.0
},
filters:{
toUpperCase(value){
return value.toUpperCase();
},
dateFormat(value){
let year = value.getFullYear();
let month =value.getMonth()+1;
let day = value.getDate();
let hours = value.getHours();
let minutes = value.getMinutes();
let seconds = value.getSeconds();
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}dateFormat(value){
let year = value.getFullYear();
let month =value.getMonth()+1;
let day = value.getDate();
let hours = value.getHours();
let minutes = value.getMinutes();
let seconds = value.getSeconds();
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
moneyFormat(value,symbol){
return symbol+" "+value;
}
}
})
</script>
说明:还可以将过滤器定义为全局过滤器。
Vue.filter('过滤器名称', function (value[,param1,...] ) {
//逻辑代码
})
示例:(将上面的进行修改)
<html>
<head>
<meta charset="utf-8"/>
<script type="text/javascript" src="js/vue-2.6.12.js"></script>
</head>
<body>
<div id= "app">
<h1>{{str | toUpper}}</h1>
<h2>{{date | dateFormt}}</h1>
<h1>{{money | moneyFormt("¥")}}</h1>
</div>
<script>
Vue.filter("toUpper",function(value){
return value.toUpperCase();
});
Vue.filter("moneyFormt",function(value,sign){
return `${sign} ${value}`;
})
Vue.filter("dateFormt",function(value){
let year = value.getFullYear();
let month =value.getMonth()+1;
let day = value.getDate();
let hours = value.getHours();
let minutes = value.getMinutes();
let seconds = value.getSeconds();
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
});
const vm = new Vue({
el:"#app",
data:{
str:"baizhi",
date:new Date(),
money:10000.0
}
});
</script>
</body>
</html>
计算属性和过滤器的异同
相同点:
- 都基于原始属性值进行变换
- 都可以在{{}}中调用
不同点:
- 计算属性可以基于多个原始属性,而过滤器只对一个原始属性操作
- 计算属性通常用于复杂的逻辑计算,而过滤器用于简单的数据格式化输出
实战案例:购物车
示例代码:
<body>
<div id="app">
<div style="text-align: center;">
<button @click="addFormShow = true;">添加</button>
</div>
<hr>
<div v-if="shoppingList.length > 0">
<h2 style="text-align:center">购物列表</h2>
<table align="center" border="1">
<thead>
<tr>
<th>编号</th>
<th>商品</th>
<th>价格</th>
<th>数量</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in shoppingList">
<td>{{index+1}}</td>
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td>
<button @click="increment(index)">+</button>
<span>{{item.count}}</span>
<button @click="decrement(index)">-</button>
</td>
</tr>
</tbody>
</table>
<h2 style="text-align: center;">总价:{{totalPrice | priceFormat}}</h2>
</div>
<div v-else>
<h2 style="text-align: center;">购物车空空如也</h2>
</div>
<hr>
<div style="text-align: center;" v-show="addFormShow">
<form action="" @submit.prevent="addItem">
商品: <input type="text" v-model="shoppingItem.name"> <br>
价格:<input type="number" v-model="shoppingItem.price"> <br>
数量:<input type="number" v-model="shoppingItem.count"> <br>
<input type="submit" value="添加">
</form>
</div>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
addFormShow:false,
shoppingItem:{},
shoppingList:[
{
name:"macbookair",
price:7999.0,
count:1
}
]
},
methods:{
increment(i){
this.shoppingList[i].count++;
},
decrement(i){
if(this.shoppingList[i].count <= 1){
this.shoppingList.splice(i,1);
}else{
this.shoppingList[i].count--;
}
},
addItem(){
let item = JSON.parse(JSON.stringify(this.shoppingItem));
this.shoppingList.push(item);
this.shoppingItem={};
}
},
computed:{
totalPrice(){
return this.shoppingList.reduce((total,item)=>total+item.price*item.count,0);
}
},
filters:{
priceFormat(value){
return "¥ "+value;
}
}
})
</script>
</body>
Vue的生命周期
每个 Vue 实例在被创建时都要经过一系列的初始化过程 :创建实例,装载模板,渲染模板、销毁等。Vue为生命周期中的每个状态都设置了钩子函数(监听函数)。当Vue实例处于不同的生命周期时,对应的函数就会被触发调用。
生命周期图示
下图展示了实例的生命周期。你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
钩子函数
每个 Vue 实例在被创建时都要经过一系列的初始化过程。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这些函数在生命周期的不同阶段自动触发执行,这给了用户在不同阶段添加自己的代码的机会。
生命周期钩子 | 含义 |
---|---|
beforeCreate(vue对象创建前) | 组件实例刚被创建,组件属性计算之前,此时不能访问属性 |
created(创建后) | 组件实例创建完,属性可以访问,但是还不能通过 $el 访问DOM |
beforeMount(加载前) | 模板编译、挂载之前,可以通过 $el 访问渲染前的DOM |
mounted(载入后) | 模板编译、挂载之后,可以通过 $el 访问渲染前的DOM |
beforeUpdate(更新前) | 组件更新之前,可以获取组件更新前的DOM |
updated(更新后) | 组件更新之后,可以获取组件更新后的DOM |
beforeDestroy(销毁前) | 组件销毁前调用 |
destroyed(销毁后) | 组件销毁后调用 |
通过以下案例,演示下各个钩子函数的使用:
<div id="app">
<h1>{{msg}}</h1>
<button @click="changeMsg">点我修改msg</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
msg:"hell vue"
},
methods:{
changeMsg(){
this.msg = "hello baizhi";
}
},
beforeCreate:function(){
// alert("beforeCreate...");
console.log("beforeCreate...");
console.log(this.$el);//undefined
console.log(this.msg);//undefined
},
created:function(){
// alert("created...");
console.log("created...");
console.log(this.$el);//undefined
console.log(this.msg);//hello vue
},
beforeMount:function(){
// alert("beforeMount...");
console.log("beforeMount...");
console.log(this.$el);//加载前的标签,就是原始代码,插值表达式、事件绑定都还没解析
console.log(this.msg);//hello vue
},
mounted:function(){
// alert("mounted...");
console.log("mounted...");
console.log(this.$el);//加载后的标签,插值表达式、事件绑定均已解析
console.log(this.msg);//hello vue
},
beforeUpdate:function(){
// alert("beforeUpdated...");
console.log("beforeUpdated...");
console.log(this.$el.innerHTML);//更新前的DOM
console.log(this.msg);//hello baizhi
},
updated:function(){
// alert("updated...");
console.log("updated...");
console.log(this.$el.innerHTML);//更新后的DOM
console.log(this.msg);//hello baizhi
},
beforeDestroy:function(){//在console中执行vm.$destroy()触发,开发时很少关注
// alert("destroyed...");
console.log("beforeDestroy...");
console.log(this.$el);
console.log(this.msg);
},
destroyed:function(){
// alert("destroyed...");
console.log("destroyed...");
console.log(this.$el);
console.log(this.msg);
}
})
</script>
说明:
一般地,我们会在 created
钩子函数中,从服务端获取数据,并对数据进行初始化。
一般用来发起ajax请求
组件基础
在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。
但是如果每个页面都独自开发,这无疑增加了我们开发的成本。所以为了提高代码复用,我们会把页面的可复用部分拆分成独立的组件,然后在不同页面就可以共享这些组件,避免重复开发。
第1个组件案例
有如下需求:在页面上要定义多个记录点击次数的按钮
,我们使用之前的所学,可以这么做:
<div id="app">
<button @click="increment1">你点了我{{count1}}次,我记住了</button>
<button @click="increment2">你点了我{{count2}}次,我记住了</button>
</div>
<script >
const vm = new Vue({
el:"#app",
data:{
count1:0,
count2:0
},
methods:{
increment1(){
this.count1++;
},
increment2(){
this.count2++;
}
}
})
</script>
注意:不同的按钮,必须操作不同的属性,如果使用同1个属性,多个按钮的点击次数会累加到一起。如果按钮的数量要求更多,按照这种方式,无法代码复用,影响开发效率而且代码也不易维护。
解决方案:可以将需要复用的代码定义成组件,通过组件进行复用。
- 注册组件
<script >
Vue.component("button-counter",{
template:"<button @click='increment'>你点了我{{count}}次,我记住了</button>",
data:function(){
return {
count:0
}
},
methods:{
increment(){
this.count++;
}
}
})
</script>
- 通过
component
方法注册组件- 第1个参数表示组件名
- 第2个参数表示组件的组成,组件本质上也是一个Vue实例,也就可以定义:
data
、methods
等等 - 组件在定义时,不会和任何页面元素绑定,因此没有
el
属性。但多了template
属性抽取html片段 - data必须是一个函数,后面解释
- 使用组件
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
</div>
- 把组件名当作标签直接使用就可以
3.创建vue实例
<div id="app">
<!-- 2 使用组件 -->
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script >
//1 注册组件
Vue.component("button-counter",{
template:"<button @click='increment'>你点了我{{count}}次,我记住了</button>",
data:function(){
return {
count:0
}
},
methods:{
increment(){
this.count++;
}
}
})
//3 创建vue实例
const vm = new Vue({
el:"#app"
})
</script>
- 因为组件会被多次使用,所以定义组件时,语法上要求data必须是一个函数,每次使用时返回一个新的对象。
- 组件要在Vue实例中使用,所以最后一定要创建Vue实例。
还有2点需要注意:
- 组件名如果由多个单词组成,按照规范多个单词全小写,并使用
-
连接,比如button-counter
。如果使用驼峰命令法,比如buttonCounter
,那么在使用组件时,也需要在单词间添加-
。
- 组件中
template
只能有一个根标签,比如实例中只有一个div根标签。如果又有一个同级的div就会报错。
template:"<div><button>按钮</button><br></div>" //对
template:"<button @click='increment'>你点了我{{count}}次,我记住了</button>" //对
template:"<div><button>按钮<button></div><div></div>" //错误,2个div根标签
组件的注册方式
组件的注册方式有2种:全局注册 和 局部注册
全局注册
全局注册的组件,可以在不同的Vue实例中使用。语法如下:
<script>
Vue.component("组件名",{
template:"复用的html片段",
data:function(){
return {...}//return的对象,类似于创建Vue实例时的data
},
methods:{
//和定义Vue实例时一样,用于定义函数
}
})
</script>
第1个案例就是全局注册,这里不再演示。
局部注册
一旦全局注册,就意味着即便以后你不再使用这个组件,它依然会随着Vue的加载而加载。因此,对于一些并不频繁使用的组件,我们会采用局部注册。在Vue实例中添加选项 components
语法如下:
<script>
const vm = new Vue({
el:"选择器",
data:{
//属性
},
components:{
//注册局部组件
"组件名":{
template:"复用的html片段",
data:function(){
return {...}//return的对象,类似于创建Vue实例时的data
},
methods:{
//和定义Vue实例时一样,用于定义函数
}
}
}
});
</script>
示例:
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
const vm = new Vue({
el:"#app",
components:{
"button-counter":{
template:"<button @click='increment'>你点了我{{count}}次,我记住了</button>",
data:function(){
return {
count:0
}
},
methods:{
increment(){
this.count++;
}
}
}
}
})
</script>
注册优化
优化1:将组件参数单独抽取成变量
当Vue实例中注册的组件比较多时,Vue实例的代码就会变得非常臃肿,不利于代码管理,此时可以将Vue实例参数抽取成变量。
<script>
const buttonCounter = {
template:"<button @click='increment'>你点了我{{count}}次,我记住了</button>",
data:function(){
return {
count:0
}
},
methods:{
increment(){
this.count++;
}
}
};
const vm = new Vue({
el:"#app",
components:{
"button-counter":buttonCounter
}
})
</script>
说明:组件注册时,如果组件名和组件变量名同名,还可以简写:
components:{
buttonCounter:buttonCounter
}
// ==> 直接只写一个 buttonCounter即可
components:{
buttonCounter
}
优化2:将html片段抽取到template标签中
当
template
中的html片段比较复杂时,在组件参数中直接定义html片段非常麻烦,此时可以将html片段抽取到特殊的template标签中。
<template id="bc">
<!-- 再次提示:template标签中也必须只有一个根标签 -->
<button @click='increment'>你点了我{{count}}次,我记住了</button>
</template>
<script>
const buttonCounter = {
template:"#bc",
data:function(){
return {
count:0
}
},
methods:{
increment(){
this.count++;
}
}
};
const vm = new Vue({
el:"#app",
components:{
"button-counter":buttonCounter
}
})
</script>
组件和is属性
在html标签中使用组件时,受到 HTML 本身的一些限制。比如table、ul、select内部只能出现特定的子标签,如果在这些标签中使用组件,组件无法正确显示。解决方案:使用is属性
<div id="app">
<h4>直接在table中使用table-body组件</h4>
<table border="1">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>gender</th>
</tr>
</thead>
<!-- 直接使用组件-->
<table-body></table-body>
</table>
<h4>在table中通过is属性使用table-body组件</h4>
<table border="1">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>gender</th>
</tr>
</thead>
<!-- 通过is属性使用组件 -->
<tbody is="table-body"></tbody>
</table>
</div>
<template id="table-body">
<tbody>
<tr v-for="u in users" :key="u.id">
<td>{{u.id}}</td>
<td>{{u.name}}</td>
<td>{{u.age}}</td>
<td>{{u.gender}}</td>
</tr>
</tbody>
</template>
<script>
const tableBody = {
template:"#table-body",
data(){
return {
users: [
{"id":1, "name": "小明", "age": 13, "gender": "男"},
{"id":2, "name": "小红", "age": 13, "gender": "女"},
{"id":3, "name": "小绿", "age": 4, "gender": "男"}
]
}
}
}
const vm = new Vue({
el:"#app",
components:{
"table-body":tableBody
}
})
</script>
组件的嵌套
在Vue的设计中,一切都可以看作组件。整个页面可以看作是一个根组件,内部的各块组件可以看作子组件。组件之间自然的会发生嵌套关系。
接下来,我们看一下组件嵌套的示例:
<div id="app">
<my-table></my-table>
</div>
<template id="table-head">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>gender</th>
</tr>
</thead>
</template>
<template id="table-body">
<tbody>
<tr v-for="u in users" :key="u.id">
<td>{{u.id}}</td>
<td>{{u.name}}</td>
<td>{{u.age}}</td>
<td>{{u.gender}}</td>
</tr>
</tbody>
</template>
<template id="my-table">
<table border="1" align="center">
<thead is="table-head"></thead>
<tbody is="table-body"></tbody>
</table>
</template>
<script>
var tableHead = {
template:"#table-head"
}
var tableBody = {
template:"#table-body",
data:function(){
return {
users: [
{"id":1, "name": "小明", "age": 13, "gender": "男"},
{"id":2, "name": "小红", "age": 13, "gender": "女"},
{"id":3, "name": "小绿", "age": 4, "gender": "男"}
]
}
}
}
//使用局部注册,需要将子组件注册到父组件中
var myTable ={
template:"#my-table",
components:{
"table-head":tableHead,
"table-body":tableBody
}
}
const vm = new Vue({
el:"#app",
components:{
"my-table":myTable
}
});
</script>
说明:
事实上,虽然 new Vue()
没有显式的使用组件的语法,但它本质上也是一个父组件。
组件通信
通常一个较为复杂的页面,一定会出现组件的嵌套。各个组件之间以嵌套的关系组合在一起,那么这个时候不可避免的会有组件间通信的需求。
父传子:props
通常父组件的模板中包含子组件,父组件要正向地向子组件传递数据或参数,子组件接收到后根据参数的不同来渲染不同的内容或执行操作。这个正向传递数据的过程就是通过props来实现的。
比如在之前的表格案例中,
table-body
子组件展示的数据是定义在子组件自个身上的,这么做虽有效果,但降低了该组件的复用价值,更好的做法:子组件中不定义数据,而是由使用它的父组件传递。此时,需要使用 props完成父组件向子组件的数据传递。
语法:
//1.定义子组件中添加props属性
const 组件 = {
template:"html片段",
props:["自定义参数名",...]
}
//2 使用组件时,为组件添加 自定义参数名 同名的属性
<组件 :自定义参数名=”值"></组件>
示例:
<div id="app">
<my-table :us="users"></my-table>
</div>
<template id="my-table">
<table border="1" align="center">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>gender</th>
</tr>
</thead>
<tbody>
<tr v-for="u in us" :key="u.id">
<td>{{u.id}}</td>
<td>{{u.name}}</td>
<td>{{u.age}}</td>
<td>{{u.gender}}</td>
</tr>
</tbody>
</table>
</template>
<script>
var myTable = {
template:"#my-table",
props:["us"]
}
const vm = new Vue({
el:"#app",
data:{
users: [
{"id":1, "name": "小明", "age": 13, "gender": "男"},
{"id":2, "name": "小红", "age": 13, "gender": "女"},
{"id":3, "name": "小绿", "age": 4, "gender": "男"}
]
},
components:{
"myTable":myTable
}
});
</script>
子传父:$emit
父组件的模板中包含子组件,那么经常会出现子组件的状态发生变化时,要通知到父组件。所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。如下所示:
<div id="app">
<h2>{{num}}</h2>
<!--
父组件向子组件传递数据 count=num
子组件中修改count值,不能修改父组件的num
-->
<counter :count="num"></counter>
</div>
<script>
Vue.component("counter",{
template:`
<div>
<button @click="increment">点我自增</button>
<span>{{count}}</span>
<button @click="decrement">点我自减</button>
</div>`,
props:["count"],
methods:{
increment(){
this.count++;
},
decrement(){
this.count--;
}
}
})
const vm = new Vue({
el:"#app",
data:{
num:0
}
})
</script>
所以,当需要子传父的时候,Vue采用事件放射的方式完成。
- 在子组件中执行 $emit(“父组件的自定义事件”)通知父组件,并发送数据
- 父组件中定义自定义事件处理函数,并接收数据
<div id="app">
<h2>{{num}}</h2>
<!-- 2 绑定自定义事件处理函数,监听子组件的事件触发 change-count
当子组件触发事件后,执行handleChange事件处理函数,
$emit发送的数据将成为handleChange的实参
-->
<counter :count="num" @change-count="handleChange"></counter>
</div>
<script>
Vue.component("counter",{
template:`
<div>
<button @click="increment">点我自增</button>
<span>{{count}}</span>
<button @click="decrement">点我自减</button>
</div>`,
props:["count"],
methods:{
increment(){
this.count++;
//1 子组件中通过$emit触发父组件中的自定义事件,并发送数据
this.$emit("change-count",this.count);
},
decrement(){
this.count--;
//1 子组件中通过$emit触发父组件中的自定义事件,并发送数据
this.$emit("change-count",this.count);
}
}
})
const vm = new Vue({
el:"#app",
data:{
num:0
},
methods:{
handleChange(value){
this.num = value;
}
}
})
</script>
实战案例
示例:
<div id="app">
<div>
<my-table :us="users" @show-user="handleShowUser"></my-table>
<hr>
<update-form :u="user" @update-user="handleUpdateUser"></update-form>
</div>
</div>
<template id="my-table">
<table border="1">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>operation</th>
</tr>
</thead>
<tbody>
<tr v-for="(u,index) in us" :key="u.id">
<td>{{u.id}}</td>
<td>{{u.name}}</td>
<td>{{u.age}}</td>
<td>
<button @click="emitShowUser(u)">更新</button>
</td>
</tr>
</tbody>
</table>
</template>
<template id="update-form">
<form action="" @submit.prevent="emitUpdateUser">
<input type="hidden" v-model="u.id" >
用户名: <input type="text" v-model="u.name"> <br>
年龄: <input type="number" v-model="u.age"> <br>
<input type="submit" value="更新">
</form>
</template>
<script>
const updateForm = {
template:"#update-form",
props:["u"],
methods:{
emitUpdateUser(){
this.$emit("update-user",this.u);
}
}
};
const myTable = {
template:"#my-table",
props:["us"],
methods:{
emitShowUser(u){
this.$emit("show-user",u);
}
}
};
const vm = new Vue({
el:"#app",
data:{
users:[
{id:1,name:"xiao1hei",age:18},
{id:2,name:"xiao2hei",age:20},
{id:3,name:"xiao3hei",age:22}
],
user:{}
},
components:{
myTable,updateForm
},
methods:{
handleShowUser(u){
this.user = JSON.parse(JSON.stringify(u));
},
handleUpdateUser(u){
this.users.forEach((item,i,users)=>{
if(item.id == u.id){
//由于Vue2.x无法直接检测到数组中元素的变化,可以使用vue提供的$set方法
this.$set(this.users,i,JSON.parse(JSON.stringify(u)));
}
})
}
}
})
</script>
路由 VueRouter
5.1 路由解决什么问题?
什么是路由(这里不要死抠字眼,尝试从字面上理解,只是一个名词而已,重点是理解名词代表的技术解决什么问题)?生活中最能体现路由功能的是路由器,路由器的作用:互联网中的计算机,必须要通过路由器根据目标机器ip地址,将数据转发到目标机器。
路由的作用:根据请求的地址,将请求转发到匹配的组件上。
后端路由
传统的网站大多数是多页面的,当我们点击一个超链接时,请求发送到服务端,由服务端根据请求路径匹配到不同的Controller,进行各种操作,最终将html或数据返回给前端。当然,目前绝大多数的网站都是这种后端路由,也就是多页面的。这样的好处有很多,比如页面可以在服务端渲染好直接返回给浏览器,不用等待前端加载任何js和css就可以直接显示网页内容,再比如对SEO的友好等。后端路由的缺点也是很明显的,就是模板是由后端来维护或改写的。前端开发者需要安装整套的后端服务,必要时还得学习像PHP或Java这些非前端语言来改写html结构,所以html和数据、逻辑混为一谈,维护起来既臃肿又麻烦。
前端路由
然后就有了前后端分离的开发模式,后端不再返回页面只提供API来返回数据,前端通过Ajax获取数据后,再用一定的方式渲染到页面里,这么做的优点就是前后端做的事情分得很清楚,后端专注在数据上,前端专注在交互和可视化上,如果今后再开发移动App,那就正好能使用一套API了。
随着前端技术的发展,前端功能越来越丰富,出现了大前端的概念,有了不少的单页面富应用。也就是在一个页面,可以使用到应用的所有功能。功能视图之前的切换,不再通过服务端完成,而是通过前端控制,这就是前端路由。
Vue中路由的作用
在Vue中一个功能视图就是一个组件,Vue中路由解决的就是在单页面中组件的显示切换问题。比如有这个一个页面,页面上有2个超链接(登录和注册),点击登录链接显示登录的表单,点击注册连接显示注册的表单。
第1个路由示例
1.页面引入 vue-router.js
<script type="text/javascript" src="js/vue-2.6.12.js"></script>
<!--注意引入顺序: vue-router.js 必须在vue.js之后引入 -->
<script type="text/javascript" src="js/vue-router-3.4.9.js"></script>
2.注册组件
const loginForm = {
template:"#login-form"
}
const registerForm = {
template:"#register-form"
}
const vm = new Vue({
el:"#app",
components:{
loginForm,
registerForm
}
})
3.创建 VueRouter对象
,配置路由匹配规则
//router对象
var router = new VueRouter({
routes:[
{
name:"login",//路由名称
path:"/login",//路由路径
component:loginForm//要路由到的组件
},
{name:"register",path:"/register",component:registerForm}
]
});
4.向Vue实例中配置router
const vm = new Vue({
el:"#app",
components:{
loginForm,
registerForm
},
//router:router //如果路由对象名也叫router,则可以省略:router
router
})
5.在页面上使用路由
<div id="app">
<!-- 超链接的href必须以#开头 -->
<a href="#/login">登录</a>
<a href="#/register">注册</a>
<hr>
<!-- router-view是路由的锚点,也就是路由到的组件显示的地方 -->
<router-view></router-view>
</div>
router-link和redirect
router-link
router-link标签用于代替a标签,定义跳转超链接
<div id="app">
<!-- 超链接的href必须以#开头 -->
<!--<a href="#/login">登录</a>
<a href="#/register">注册</a>-->
<!-- to属性配置跳转路径,不用添加#前缀 -->
<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>
<hr>
<!-- router-view是路由的锚点,也就是路由到的组件显示的地方 -->
<router-view></router-view>
</div>
redirect
redirect属性用在路由上,比如在a路由上配置
redirect:b路由的路径
,当访问a时会自动重定向到b。通常用于配置页面的默认路由地址
var router = new VueRouter({
routes:[
{name:"default",path:"/",redirect:"/login"},
{
name:"login",//路由名称
path:"/login",//路由路径
component:loginForm//要路由到的组件
},
{name:"register",path:"/register",component:registerForm}
]
});
嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。当我们路由到某一个组件后,还经常需要在该组件中继续路由到其子组件,这就需要使用Vue的嵌套路由。比如:当我们路由到登录组件后,又分为
手机验证码登录
和账户名密码登录
2个子组件。
语法:
const 子组件 = {
template:"子组件标签"
}
const 父组件 = {
tempalate:`
<router-link to="/父组件路径/子组件的路径">链接1</router-link>
<router-view></router-view>
`
}
const router = new VueRouter({
routes:[
{
path:"/组件路径",
component:父组件对象,
children:[//设置子路由信息
{
path:"子组件路径",// 不以/开头
component:子组件
}
]
}
]
})
示例:
<body>
<div id="app">
<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>
<hr>
<router-view></router-view>
</div>
<template id="phone-login-form">
<form action="">
手机号: <input type="number" name="phone" > <br>
验证码: <input type="password" name="code"> <br>
<input type="submit" value="登录">
</form>
</template>
<template id="account-login-form">
<form action="">
用户名: <input type="text" name="username" > <br>
密码: <input type="password" name="pwd"> <br>
<input type="submit" value="登录">
</form>
</template>
<template id="register-form">
<form action="">
用户名: <input type="text" name="username" > <br>
密码: <input type="password" name="pwd1"> <br>
确认密码: <input type="password" name="pwd2" > <br>
<input type="submit" value="注册">
</form>
</template>
<script>
const registerForm = {
template:"#register-form"
}
const phoneLogin = {
template:"#phone-login-form"
}
const accountLogin = {
template:"#account-login-form"
}
const loginForm = {
template:`
<div>
<router-link to="/login/phone">手机验证码登录</router-link>
<router-link to="/login/account">用户名密码登录</router-link> <br> <br>
<router-view></router-view>
</div>
`
}
const router = new VueRouter({
routes:[
{path:"/",redirect:"/register"},
{path:"/register",component:registerForm},
{
path:"/login",
component:loginForm,
children:[
{path:"phone",component:phoneLogin},
{path:"account",component:accountLogin}
]
}
]
})
const vm = new Vue({
el:"#app",
router
})
</script>
</body>
路由组件传参
query传参
<div id="app">
<router-link to="/user?name=李栓蛋&age=38">用户管理</router-link>
<router-link :to="{path:'/user',query:{name:'王花花',age:28}}">用户管理</router-link>
<router-view></router-view>
</div>
<script>
const user = {
template:`<div>
<div>我叫{{$route.query.name}}</div>
<div>今年{{$route.query.age}}</div>
</div>`
}
const router = new VueRouter({
routes:[
{path:"/user",component:user}
]
})
const vm = new Vue({
el:"#app",
router
})
param传参
<div id="app">
<router-link to="/product/macbookair">商品1</router-link>
<router-link :to="{name:'product',params:{name:'macbookpro'}}">商品2</router-link> <router-view></router-view>
</div>
<script>
const product = {
template:`<div>
<div>商品名:{{$route.params.name}}</div>
</div>`
}
//通过:参数名的方式匹配数据
const router = new VueRouter({
routes:[
{name:"product",path:"/product/:name",component:product}
]
})
const vm = new Vue({
el:"#app",
router
})
</script>
编程式的路由导航
除了使用
<router-link>
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。
语法:router.push(location, onComplete?, onAbort?)
注意:在 Vue 实例内部,你可以通过 $router
访问路由实例。因此你可以调用 this.$router.push
。
声明式 | 编程式 |
---|---|
<router-link :to="..."> | router.push(...) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串
router.push('/login')
// 对象
router.push({ path: '/login' })
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
示例:在注册后,立刻导航到登录
<div id="app">
<router-link to="/login">登录</router-link>
<router-link to="/register">注册</router-link>
<hr>
<router-view></router-view>
</div>
<template id="login-form">
<form action="">
用户名: <input type="text" name="username" > <br>
密码: <input type="password" name="pwd"> <br>
<input type="submit" value="登录">
</form>
</template>
<template id="register-form">
<form action="" @submit.prevent="handleRegister">
用户名: <input type="text" name="username" > <br>
密码: <input type="password" name="pwd1"> <br>
确认密码: <input type="password" name="pwd2" > <br>
<input type="submit" value="注册">
</form>
</template>
<script>
const loginForm = {
template:"#login-form"
}
const registerForm = {
template:"#register-form",
methods:{
handleRegister(){
alert("添加成功");
this.$router.push({path:"/login"})
}
}
}
var router = new VueRouter({
routes:[
{name:"default",path:"/",redirect:"/login"},
{
name:"login",//路由名称
path:"/login",//路由路径
component:loginForm//要路由到的组件
},
{name:"register",path:"/register",component:registerForm}
]
});
const vm = new Vue({
el:"#app",
components:{
loginForm,
registerForm
},
//router:router //如果路由变量名也叫router,则可以省略:router
router
})
</script>
实战案例
登录和注册切换
案例效果:
示例代码:
<div id="app">
<router-view></router-view>
</div>
<template id="login-form">
<form action="" @submit.prevent="handleLogin">
用户名: <input type="text" > <br>
密码: <input type="password"> <br>
<input type="submit" value="登录">
<router-link to="/register">注册</router-link>
</form>
</template>
<template id="register-form">
<form action="" @submit.prevent="handleRegister">
用户名: <input type="text" > <br>
密码: <input type="password" > <br>
确认密码: <input type="password" > <br>
<input type="submit" value="注册">
<router-link to="/login">登录</router-link>
</form>
</template>
<script>
const loginForm = {
template:"#login-form",
methods:{
handleLogin(){
alert("登录成功");
location.href="https://www.baidu.com";
}
}
};
const registerForm = {
template:"#register-form",
methods:{
handleRegister(){
alert("注册成功");
this.$router.push("/login");
}
}
};
const router = new VueRouter({
routes:[
{path:"/",redirect:"/login"},
{path:"/login",component:loginForm},
{path:"/register",component:registerForm}
]
});
const vm = new Vue({
el:"#app",
router
})
</script>
增删改查的切换
案例效果:
示例代码:
<div id="app" >
<div >
<router-link to="/book/show"><button >查询</button></router-link>
<router-link to="/book/add"><button >添加</button></router-link>
<hr>
<router-view></router-view>
</div>
</div>
<template id="my-table">
<table border="1">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>price</th>
<th>stock</th>
<th>operation</th>
</tr>
</thead>
<tbody>
<tr v-for="b in books" :key="b.id">
<td>{{b.id}}</td>
<td>{{b.name}}</td>
<td>{{b.price}}</td>
<td>{{b.stock}}</td>
<td>
<button>删除</button>
<router-link :to="{path:'/book/update',query:{id:b.id}}"><button>更新</button></router-link>
</td>
</tr>
</tbody>
</table>
</template>
<template id="add-form">
<form action="">
name: <input type="text" name="" id=""> <br>
price: <input type="number" name="" id=""> <br>
stock: <input type="number" name="" id=""> <br>
<input type="submit" value="添加">
</form>
</template>
<template id="update-form">
<form action="">
<input type="hidden" name="">
name: <input type="text" name="" id=""> <br>
price: <input type="number" name="" id=""> <br>
stock: <input type="number" name="" id=""> <br>
<input type="submit" value="更新">
</form>
</template>
<script>
const addForm = {
template:"#add-form"
}
const updateForm = {
template:"#update-form"
};
const myTable = {
template:"#my-table",
data(){
return {
books:[
{id:1,name:"十万个为什么",price:100000.0,stock:1},
{id:2,name:"vue从入门到放弃",price:19800.0,stock:2}
]
}
}
}
const router = new VueRouter({
routes:[
{path:"/book/show",component:myTable},
{path:"/book/add",component:addForm},
{path:"/book/update",component:updateForm},
{name:"book",path:"/book/:id",component:updateForm}
]
});
const vm = new Vue({
el:"#app",
router
})
</script>