Vue介绍
什么是Vue.js
- vue.js是目前最火的一个框架,React是最流行的一个框架(React除了开发网站,还可以开发手机App)
- Vue.js是前端的主流框架之一,和Angular.js、React.js一起,并称为前端三大主流框架
- Vue.js是一套构建用户界面的框架,只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。(Vue有配套的第三方类库,可以整合起来做大型项目的开发)
- 前端的主要工作?主要负责MVC中的V这一层;主要工作就是和界面打交道
为什么要学习流行框架
- 企业为了提高开发效率;
- Vue.js能够帮助我们减少不必要的DOM操作;提高渲染效率;双向数据绑定的概念【通过框架提供的指令,前端程序员只需要业务逻辑,不再关注DOM是如何渲染的了】
- 增强自己就业时候的竞争力:人无我有,人有我优
框架和库的区别
- 框架:是一套完整的解决方案;对项目的侵入性比较大,项目如果更换框架,则需要重新架构整个项目。如:node中的express
- 库(插件):提供某一个小功能,对项目的侵入性比较小,如果某个库无法完成某些需求,可以很容易切换到其他库实现需求
Node(后端)中的MVC与前端中的MVVC之间的区别
MVC是后端的分层开发概念
MVVM是前端的视图层的概念,主要关注于视图层分离,也就是说:MVVM把前端的视图层,分为了三部分:Model, View, VM ViewModel
-
MVVM
- Model层:职能单一,只负责操作数据库,执行对应的sql语句,进行数据的CRUD(creat、read、update、delete)。M保存的是每个页面中单独的数据
- View层:每当用户操作了界面,如果需要进行业务的处理,都会通过网络请求,去请求后端的服务器,此时,我们的这个请求,就会被后端的项目入口模块监听到。V就是每个页面中的HTML结构
- VM是MVVM思想的核心:因为VM是M和V之间的调度者。每当V层想要获取后保存数据的时候,都要由VM做中间的处理
- 取数据:M->VM->V;存数据:V->VM->M
前端页面中使用MVVC的思想,主要是为了让我们开发更加方便,因为MVVM思想突出贡献了数据的双向绑定。注意:数据的双向绑定是由VM提供的
Vue的使用
简介
<!DOCTYPE html>
<html>
<head>
<title>vue</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-COmpatible" content="ie=edge">
<script type="text/javascript" src="vue.min.js"></script>
</head>
<body>
<div id="app">
<!-- vue控制的区域就是V -->
<!-- 注意:我们new出来的vue对象就是mvvm中的vm调度者 -->
<!-- new的vue实例会控制这个区域的内容 -->
<p>{{msg}}</p>
<!-- 插值表达式,通过此方法,data提供的所有属性都能直接引用了 -->
</div>
<script type="text/javascript">
//创建一个vue的实例
//当我们导入包之后,在浏览器的内容中,就多了一个vue的构造函数
var vm = new Vue({
//当我们new一个vue对象的时候,有几个要配置的属性
el:'#app' //表示要控制id为app的这个元素
//el表示当前我们new的这个vue要控制页面上的哪个区域
data:{//这里的data就是mvvm中的m用来保存每个页面数据的对象
//data属性中,存放的是el中要用到的数据
msg: '欢迎学习vue' //通过vue提供的指令,很方便的就能把数据渲染到页面上
//程序员不再手动操作DOM元素了
}
})
</script>
</body>
</html>
v-cloak v-text v-html
代码实例:
<!DOCTYPE html>
<html>
<head>
<title>vue</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-COmpatible" content="ie=edge">
<script type="text/javascript" src="vue.min.js"></script>
<style type="text/css">
[v-cloak] {
display: none;
/*只要使用了v-cloak这个属性的元素,都让它display为none*/
}
</style>
</head>
<body>
<div id="app">
<p v-cloak>{{msg}}</p>
<!-- 添加v-cloak属性,让vue没有加载完的时候,这个标签隐藏(不然可能会看到{{msg}}到其值的切换过程——即:插值表达式的闪烁问题) -->
<h4 v-text='msg'></h4>
<!-- v-text和插值表达式没有太大区别,但是v-text没有闪烁问题
但是:插值表达式可以在前后放任意内容(即只会替换自己的占位符,不会将整个标签间的内容清空),
而v-text会将其值完全覆盖住标签之间的内容
此二者都会将其内容当作普通内容输出 -->
<div>{{msg2}}</div>
<div v-text='msg2'></div>
<!-- 以上两者的输出一样,都是msg的原格式 -->
<div v-html='msg2'>143</div>
<!-- v-html会将内容输出为html格式,也会覆盖标签间的原本内容 -->
<input type="button" v-bind:title="mytitle+'123'" name="">
<!-- 如果不加v-bind,title的内容就只会是mytitle,而用v-bind进行了绑定后,其内容就是下面data中的myttitle的值。v-bind:可以简写为冒号":" -->
<!-- 可以理解为属性和属性之间需要绑定 -->
<!-- v-bind会将后面的(引号内的)当作js表达式去执行(即认为mytitle是一个变量),所以可以直接对其进行“加”操作 -->
<!-- 亦即:v-bind中可以写合法的js表达式 -->
</div>
<script type="text/javascript">
var vm = new Vue({ //注意:Vue的首字母大写,Vue中,每一语句以逗号分隔
el:'#app',
data: {
msg:'123',
msg2: '<h1>哈哈,我是h1</h1>',
mytitle:'这是一个自己定义的title',
}
})
</script>
</body>
</html>
结果如下:
使用v-on指令定义Vue中的事件
v-on相当于jquery中的click(原生js中的onclick)
-
<input type="button" value="按钮" :title="mytitle + '123'" v-on:click="alert("hello")">
:会弹出hello;如果是自己在methodes中定义的方法,用引号将方法名引起来
<!DOCTYPE html>
<html>
<head>
<title>vue</title>
<meta charset="utf-8">
<script type="text/javascript" src="./vue.min.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<input type="button" v-bind:title="mytitle+'123'" value='按钮' name="" v-on:click="show">
<!-- 当点击的时候,调用show方法,注意:这里的show没有括号 -->
<!-- 浏览器中常见的事件,如:moouseover等,都可以像clikc这样定义 -->
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data: {
mytitle:'这是一个自己定义的title',
},
methods: {
// js中,带s的一般是数组,但是这里比较特殊,是对象
// 在methodes中定义了当前vue实例所有可用的方法
show: function() {
// 定义了一个名字叫show的函数
alert('hello')
}
}
})
</script>
</body>
</html>
ps:v-bind绑定属性(缩写:":");v-on:绑定事件(缩写:"@")
案例:跑马灯效果
暂略
事件修饰符
- .top 阻止冒泡
- .prevent 阻止默认事件,比如a标签的跳转,表单的submit
- .capture 添加事件侦听器时使用事件捕获模式,默认是冒泡机制,从内而外,如果是用捕获机制,从外而内
- .self 只当事件在该元素本身(比如不是子元素,也不是父元素,即:冒泡或捕获不会触发)触发时触发回调
- .once 事件只触发一次
事件修饰符可以串联:@click.prevent.once
:只阻止一次默认行为(代码的先后顺序无关)
.self与.stop的区别:.self只阻止自己的冒泡(比如:子元素产生的冒泡自己不执行,但是依旧将其继续向上冒泡),而.stop阻止所有的冒泡
用法:在事件后面加.修饰符
eg:利用修饰符阻止冒泡
<!DOCTYPE html>
<html>
<head>
<title>阻止冒泡</title>
<meta charset="utf-8">
<script type="text/javascript" src="./vue.min.js"></script>
<style type="text/css">
.inner {
height: 150px;
background-color: darkcyan;
}
</style>
</head>
<body>
<div id="app">
<div class="inner" @click="divHandler">
<input type="button" value="点击" @click.stop='btnHandler' name="">
</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data: {
}, //各平行部分间用逗号分隔
methods: {
divHandler(){
console.log('触发了div的点击事件')
},
btnHandler(){
console.log('触发了btn的点击事件')
}
}
})
</script>
</body>
</html>
v-model实现双向数据绑定
只有这个属性可以实现双向数据绑定
<!DOCTYPE html>
<html>
<head>
<title>数据的双向绑定</title>
<meta charset="utf-8">
<script type="text/javascript" src="./vue.min.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<h4>{{ msg }}</h4>
<input type="text" :value='msg' name="">
<!-- v-bind把msg的值进行了绑定,但是它智能进行单向绑定,从M自动绑定到V -->
<input type="text" v-model='msg' name="">
<!-- 利用v-model实现了表单元素和Model的数据双向绑定 -->
<!-- v-model只能运用在表单元素中,input select radio checkbox text 等-->
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data: {
msg: '大家好',
}, //各平行部分间用逗号分隔
methods: {
}
})
</script>
</body>
</html>
效果图:修改第二个文本框内的内容时,其他的内容会跟着自动修改
案例:计算器
<!DOCTYPE html>
<html>
<head>
<title>数据的双向绑定</title>
<meta charset="utf-8">
<script type="text/javascript" src="./vue.min.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<input type="text" v-model='n1' name="">
<select v-model='opt'>
<!-- 下拉选择框 -->
<option value="+">+</option>
<option value="-">-</option>
<option value="*">*</option>
<option value="/">/</option>
</select>
<input type="text" v-model='n2' name="">
<input type="button" name="" @click="calc" value="=">
<input type="text" name="" v-model="result">
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data: {
n1:0,
n2:0,
result:0,
opt:"+"
}, //各平行部分间用逗号分隔
methods: {
calc(){
switch(this.opt){
case "+":this.result = parseInt(this.n1) + parseInt(this.n2)
break;
case "-":this.result = parseInt(this.n1) - parseInt(this.n2)
break;
case "*":this.result = parseInt(this.n1) * parseInt(this.n2)
break;
case "/":this.result = parseInt(this.n1) / parseInt(this.n2)
break;
}
// 以上代码也可以写成:this.result = eval('parseInt(this.n1)' this.opt 'parseInt(this.n2)'),利用eval将字符串当作代码执行
//但是应注意:这是投机取巧的方式,正式开发中应减少使用
}
}
})
</script>
</body>
</html>
在vue中使用样式
使用class样式
- 数组:
<h1 :class="['red', 'thin']">这是一个h1</h1>
,其中,red是一个样式类,thin也是一个样式类。h1后面的冒号是v-bind的缩写,注意:类名必须用引号,列表也必须用引号 - 数组中使用三元表达式:
<h1 :class="['red', 'thin', isactive?'':'active']">这是一个h1</h1>
,但是代码不易读 - 数组中使用嵌套对象:
<h1 :class="['red','thin', {'active':isactive}]">这是一个h1</h1>
,其中,isactive是一个布尔类型变量 - 直接使用对象:
<h1: class="{'red':true,' italic':true, 'active':true, thin:true}">这是一个h1</h1>
,true表示使用该样式,也可以写成变量的形式,如:<h1: class="{'red':flag,' italic':flag, 'active':flag, thin:flag}">这是一个h1</h1>
,其中,flag是data中的一个属性。注意:类名不用引号也可以。以上代码中也可以将{...}写在data区域中,如:
<h1 :class="classObj">这是一个h1</h1>
......
var vm = new Vue({
el:'#app',
data:{
classObj:{'red':flag,' italic':flag, 'active':flag, thin:flag},
}
})
使用内联样式
- 直接在元素上通过
:style
的形式,书写样式,如:<h1 :style="{color: 'red', 'font-size': '50px'}">这是一个h1</h1>
,如果属性里面有短横线,就不能省略引号,如果要省略,就必须写成驼峰法 - 将样式对象,定义到data中,并直接引用到
:style
中。在data上定义:data:{ h1Style:{color: 'red', 'font-size':'40px'}}
,在元素中,通过属性绑定的形式,将样式对象应用到元素中<h1 :style="h1Style">这是一个h1</h1>
- 在
:style
中通过数组,引用多个data上的样式对象,<h1 :style="[h1Syle1,h2Style2]">这是一个h1</h1>
,其中,列表中的每一个style对象都在data中有定义h1Style:{xxx}
v-for指令的四种使用方式
- 迭代数组(包括对象数组)
<ul>
<li v-for="(item,i) in list">索引:{{i}}---姓名:{{item.name}}---年龄:{{item.age}}</li>
</ul>
以上代码可以创建多个li标签;如果用不到索引值,i可以省略,此时小括号也可以省略
- 迭代对象中的属性
<div v-for="(val, key, i) in userInfo">{{val}}---{{key}}---{{i}}---</div>
ps:迭代对象属性时,第三个参数为索引,第一个为值,第二个为键
- 迭代数字
<p v-for="i in 10">这是弟{{i}}个p标签</p>
注意:用v-for迭代数组,从1开始
v-for的使用注意事项:在2.2.0+的版本里,当在组件中使用v-for时,key是必须的
案例:添加列表项
<!DOCTYPE html>
<html>
<head>
<title>数据的双向绑定</title>
<meta charset="utf-8">
<script type="text/javascript" src="./vue.min.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<label>Id:
<input type="text" v-model="id" name="">
</label>
<label>Name:
<input type="text" v-model="name" name="">
</label>
<input type="button" value="添加" name="" @click="add">
<p v-for='item in list' :key='item'>
<!-- key能够保证数据的唯一性,即:记住该key的选项,而不是单纯根据索引来“记住”一个选项 -->
<!-- 注意:v-for循环的时候key属性只能使用number或者string -->
<!-- 注意:key在使用的时候,必须使用v-bind绑定的形式指定key的值 -->
<!-- 在组件中使用v-for循环的一些特殊情况中,如果v-for的循环出现问题,必须在使用v-for的时候,制定唯一的字符串或数字类型的key值 -->
<input type="checkbox" name="">{{item.id}}:{{item.name}}
</p>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data: {
id:"",
name:"",
list:[
{
id:1, name:'li4'
},
{
id:2, name:"wang5"
},
{
id:3, name:"zhang3"
},
]
}, //各平行部分间用逗号分隔
methods: {
add(){
this.list.push({id:this.id,name:this.name});
}
}
})
</script>
</body>
</html>
效果图如下:
v-if和v-show
二者都是控制元素的显示与隐藏
v-if的特点是每次都会重新删除或创建元素
v-show的特点:每次不会重新进行DOM的删除和创建操作,只是切换了元素的display:none属性;
因此,v-if有较高的切换性能消耗,v-show有较高的初始渲染消耗
如果元素涉及到频繁的切换,最好不要使用v-if;如果元素可能永远都不会给用户显示出来,最好用v-if
代码示例:利用v-if和v-show切换元素的显示与隐藏
<!DOCTYPE html>
<html>
<head>
<title>数据的双向绑定</title>
<meta charset="utf-8">
<script type="text/javascript" src="./vue.min.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<input type="button" @click="toggle" name="" value="按钮">
<h3 v-if="flag">这是用v-if控制的元素</h3>
<h3 v-show="flag">这是用v-show控制的元素</h3>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data: {
flag:true,
}, //各平行部分间用逗号分隔
methods: {
toggle(){
this.flag = !this.flag
}
}
})
</script>
</body>
</html>
品牌管理案例:知识点的综合运用及新增知识点讲解
在vue中,使用事件绑定机制,为元素制定处理函数的时候,如果加了小括号,就可以给函数传参了
过滤器
vue.js允许自定义过滤器,可被用做一些常见的文本格式化,过滤器可以用在两个地方:mustachc插值和v-bind表达式。过滤器应该被添加在javascript表达式的尾部,由“管道”符指示
过滤器的调用时候的格式:{{ name | nameope }} :在使用name的值之前,先调用nameope对其进行处理,并把处理的结果当成内容进行渲染,nameope即过滤器的名称
过滤器中的function,第一个参数已经固定死了,永远是过滤器管道符前面传过来的数据,参数名可以自己随便取,也可以设置默认参数
如果要对过滤器传参,就在过滤器(上面例子的nameope处)加小括号传参即可,但是应注意:过滤器的第一个参数永远是管道符前面的数据,因此传过的来的参数至少在第二个形参位置
过滤器还可以多次连续调用:如:{{msg | msgFormat1 | msgFormat2}}
:把原始数据交给第一个过滤器处理,第一个过滤器处理后的数据再交给第二个过滤器处理,第二个过滤器处理后在拿去使用
ps:字符串的replace方法,第一个参数除了可以是字符串之外,还可以是正则表达式
模板字符串:
`$(y)-$(m)-$(d)` //其中,y,m,d都是变量
全局过滤器
所谓全局过滤器,就是所有的vm实例都能够共享
全局过滤器的定义写在vue对象的同一级别
全局过滤器的定义:Vue.filter("过滤器的名称", function(){}),function表示要对传过来的数据进行什么样的处理
私有过滤器
私有过滤器写在vue对象的内部,与data同级别
定义方法:
filters:{ //在内部定义过滤器
dateFormate:function(data, pattern){
......
}
}
过滤器调用的时候是采用就近原则:如果私有过滤器和全局过滤器名称一致,优先调用私有过滤器(指在管那个标签的vue中定义的私有过滤器)
字符串的padStart方法
字符串.padStart(maxlength, fillstring):第一个参数为指定的宽度,第二个参数为填充字符,如果宽度不足,就在字符串的开头用该字符填充
与padstart方法相匹配的有padEnd()方法,用法与padstart一样,但是在字符串末尾填充
ps:数字.toString(),可以将数字转换成字符串格式
自定义按键修饰符
用法示例:
<input type='text' @keyup='add'>
//任意按键被点击时,调用add方法
如何指定某个固定键?格式如下:
<input type='text' @keyup.enter='add'>
//绑定了enter键,当点击enter键时,才调用add
常用按键别名:
- enter
- tab
- delete:获取删除和退格键
- esc
- up
- down
- left
- right
- space
如果是其他键,直接将对应的键盘码替换别名即可;为了方便,常将键盘码“起别名”
自定义全局按键修饰符
Vue.config.keyCodes.f2=113,
然后就可以将“f2”与enter,esc等按键一样直接用别名,注意:113是f2的键盘码
在vue中,所有的指令都以v-开头
自定义指令
自定义全局指令
使用Vue.directive()定义全局(与vue对象的定义处于同一级)的指令,在使用的时候,直接将v-指令名放在标签中即可
其中:
- 参数一:指令的名称,注意:在定义的时候,指令的前面不需要加v-前缀,但是在调用的时候,必须在指令的前面加上v-前缀进行调用
- 参数二:是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
示例:
Vue.directive('focus',{
bind:function(el){
//el就是绑定到的元素,注意,在每个函数中,第一个餐具是,永远是el,这个el参数,是一个原生的js对象
//在元素刚绑定指令的时候,还没有插入到DOM中去,这时候调用focus方法没有用
el.focus(),
},
inserted:function(el){el.focus()
//由于inserted方法只要父节点存在即可调用,不必插入document,所以可以在此处写focus,让页面一刷新,绑定的元素就自动获取焦点
},
updated:funcion(){}
})
- bind:只调用一次,指令第一次绑定到元素时调用,用这个函数可以定义一个在绑定时执行一次的初始化操作
- inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。通常而言,对于行为,一般写在inserted中,防止行为不生效;而如样式之类的一般写在bind中即可
- updated:做在的组件的VNode更新时调用,但是可能发生在其孩子的VNode更新之前,指令的值可能发生了改变也可能没有,但是你可以通过比较更新前后的值来忽略不必要的模板更新;可能触发多次
- componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
- unbind:只调用一次, 指令与元素解绑时调用。
以上函数均被称为钩子函数,后两者一般用得较少
含参案例:
<input type="text" v-model="keywords" v-focus v-color="'blue'" name="">
其中,v-focus和v-color都是自定义指令,v-focus自动获得焦点,v-color设置字体颜色,可以传参,此处传的参数为“blue”,注意:外面的引号中还有引号,如果只有一个引号,就当变量
钩子函数的参数
el:指令绑定的元素,可以用来直接操作DOM
-
binding:一个对象,包括以下属性
- name: 指令名,不包括 v- 前缀。
- value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2。
- oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression: 绑定值的表达式或变量名(即字符串格式)。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"。
- arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"。
- modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
自定义私有指令
写在vue对象中,格式如下:其他和全局相同
var vm = Vue({
......
directives:{
'fontWeight':{
bind:function(){},
inserted:function(){},
...
}
}
})
函数简写
大多数情况下,我们可能想在bind和update钩子上做重复动作,并且不想关心其他的钩子函数,可以这样写:
Vue.directive('color-switch',function(el,binding){
//注意:这个function等同于将代码写到了bind和update中去
el.stylee.backgroundColor = biding.value
})
Vue实例的生命周期
什么是生命周期:从Vue实例创建、运行、到销毁,总是伴随各种各样的事件,这些事件,统称为生命周期
生命周期钩子:就是生命周期事件的别名,也就是生命周期函数
-
主要的生命周期函数分类:
-
创建期间的生命周期函数:
- beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好data和methods属性
- created:实例已经在内存中创建了,此时data和methods已经创建好,此时还没有开始编译模板。如果要使用各个属性与方法,最早的时间即是此时
- beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中。在beforeMount执行的时候,页面中的元素,还没有被真正的替换过来,只是之前写的一些模板字符串,此时,页面还是旧的
- mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示,此时,页面上的东西就是最新的了。如果要通过某些和的ODM节点,最早要在mounted中运行。只要执行了mounted,就表示这个vue实例已经初始化完毕了,此时,组建已经脱离了创建阶段,进入到了运行阶段
-
运行期间的生命周期函数:
- beforeUpdate:当数据被更新,状态更新之前执行此函数,此时data中的状态值是最新的,但是节目和显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点
- updated:实例更新完毕之后调用此函数,此时data中的和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了
- 以上两事件会根据data数据的改变,有选择的触发,0次到多次
-
销毁期间的生命周期函数:
- beforeDestroy:实例销毁之前调用,在这一步,实例仍然完全可用。还没真正执行销毁过程
- destroyed:Vue实例销毁后调用,调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁
-
这些方法,都可以在methods中重写,但是要注意其各个函数的执行时机(比如beforeCreate函数在调用时,data和methods都还未被初始化好)
主要分为:创建前后、挂载前后、更新前后、销毁前后
vue-resource发起get、post、jsonp请求
<!DOCTYPE html>
<html>
<head>
<title>vue-resource</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 在vue的包后面再导入vue-resource,因为vue-resource依赖于vue -->
<script type="text/javascript" src="https://cdn.bootcss.com/vue-resource/1.5.1/vue-resource.min.js"></script>
<!-- vue-resource在vue上又挂载了 this.$http属性,可以通过this.$http.get/post/jsonp做各种事情 -->
<!-- get请求地址:http://wwww.liulongbin.top:3005/api/getlunbo -->
<!-- post请求地址:http://wwww.liulongbin.top:3005/api/post -->
<!-- jsonp请求地址:http://wwww.liulongbin.top:3005/api/jsonp -->
<!-- 这些服务器的地址均以失效,但是重要的是学会使用,所以笔者也就没去另外找 -->
</head>
<body>
<div id="app">
<input type="button" value="get请求" @click='getInfo' name="">
<input type="button" value="post请求" @click='postInfo' name="">
<input type="button" value="JSONP请求" @click='jsonpInfo' name="">
</div>
<script type="text/javascript">
Vue.component('login', {
template: '<h3>登录组件</h3>'
})
Vue.component('register', {
template:'<h3>注册组件</h3>'
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
getInfo(){ //发起get请求
this.$http.get('http://wwww.liulongbin.top:3005/api/getlunbo').then(function(result){ console.log(result.body)})
// get中的参数:第一个为请求的路径url,后面有可选参数
// 然后通过.then来拿到数据,then中,第一个参数为成功的回调函数,第二个为失败的回调函数
//只要有.then,就表明这个方法是用promise来封装的
// 成功的回调必须要传,失败的回调可选
// 通过result.body拿到服务器返回的数据
},
postInfo(){
this.$http.post('http://wwww.liulongbin.top:3005/api/post', {}, {emulateJSON:true}).then(result => {console.log(result.body)})
// post中的参数:第一个:请求的地址;第二个:要发送的数据;第三部分为可选参数
// 手动发起的请求默认没有表单格式,所以有的服务器处理不了
// 通过post方法的第三个参数,设置提交的内容类型为普通表单数据格式(emulateJSON:true)
},
jsonpInfo(){
// 发起jsonp请求
this.$http.jsonp('http://wwww.liulongbin.top:3005/api/jsonp').then(result=>{console.log(result.body)})
}
},
components:{
}
});
</script>
</body>
</html>
JSONP的实现原理:
由于浏览器的安全级别限制,不允许AJAX访问协议不同、域名不同、端口号不同的数据借口,浏览器认为这种访问不安全
可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,称做JSONP(注意:根据JSONP的实现原理,知晓JSONP只支持Get请求)
-
具体实现过程:
- 先在客户端定义 一个回调方法,预定义对数据的操作
- 再把这个回调方法的名称,通过URL传参的形式,提交到服务器的数据接口
- 服务其数据接口组织好要发送给客户端的数据,再拿着客户端传递过来的回调方法名称,拼接出一 调用这个方法的字符串,发送给客户端去解释执行
- 客户端拿到服务器返回的字符串之后,当作script脚本去解析执行,这样就能够拿到JSONP的数据了
vue中的动画
动画的四个时间点两个阶段:
- 进入阶段:v-enter(此时透明度为0)到v-ener-to(此时透明度为1),称做v-enter-active
- 离开阶段:v-leave(此时透明度为1)到v-leave-to(此时透明度为0),称做v-leave-active
因为v-enter和v-leave-to的透明度一样,所以可以写为一组,同理,中间的两个时间点也可以写做一组
使用过渡类名
<!DOCTYPE html>
<html>
<head>
<title>动画</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style type="text/css">
.v-enter, .v-leave-to { /*为什么一定要写v?因为这是vue里面的特有的样式来实现动画的*/
/*进入之前,元素的起始状态,此时还没有开始进入,这是一个时间点*/
/*v-leave-to,是动画离开之后的终止状态,此时动画已经结束了,*/
opacity: 0;
}
.v-enter-active, .v-leave-active {
/*进入和离开时候的时间段*/
transition: all 0.4s ease;
}
/*用过度类名实现两组动画,就实现两组样式就可以了*/
</style>
</head>
<body>
<!-- 需求:点击按钮,让h3显示,再次点击按钮,让h3隐藏 -->
<!-- 1. 使用transition元素,把需要被动画控制的元素包裹起来 -->
<!-- transition是vue官方提供的元素 -->
<!-- 2. 自定义两组样式,来控制transition内部的元素实现动画 -->
<div id="app">
<input type="button" value="切换" @click='toggle' name="">
<transition>
<h3 v-if='flag'>这是一个h3</h3>
</transition>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data:{
flag:false
},
methods:{
toggle(){
this.flag = !this.flag
}
},
components:{
}
});
</script>
</body>
</html>
效果图:点击时会淡入淡出
如何修改v-前缀:使用transition标签中的name属性,eg:name='my',就表示以后所有的前缀不再是v-而是my-,也可以用此法实现多组动画样式
使用第三方类实现动画
第三方类库:animate.css
视频中的代码是这样的,但是自己的效果没有出来。。。问题尚未解决
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="animate.css">
<!-- 使用animate.css库 -->
<!-- 入场动画:bounceIn 离场动画:bounceOut -->
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<input type="button" value="切换" @click='toggle' name="">
<transition enter-active-class='bounceIn' leave-active-class='bounceOut' :duration='300'>
<!-- 上面的两个animated如果不写,就要在元素上写上class="animated" -->
<!-- duration表明持续时间,单位毫秒;可以省略 -->
<!-- duration还可以写成对象的形式:"duration="{enter:200, leave:500 }"" -->
<h3 v-if='flag' class="animated">这是一个h3</h3>
</transition>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data:{
flag:false
},
methods:{
toggle(){
this.flag = !this.flag
}
},
});
</script>
</body>
</html>
钩子函数实现半场动画
所谓半场动画,就是只有进入的动画,或者只有退出的动画,用之前的两种无法做出这种效果
可以在属性中声明javascript钩子(也可以被称为动画的生命周期函数)
前四个是进入,后四个是离开
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
</transition>
案例:利用钩子函数实现小球的半场动画
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="animate.css">
<!-- 使用animate.css库 -->
<!-- 入场动画:bounceIn 离场动画:bounceOut -->
<style type="text/css">
.ball {
width: 15px;
height: 15px;
border-radius: 50%;
background-color: red;
}
</style>
</head>
<body>
<div id="app">
<input type="button" value="加入购物车" name="" @click="toggle">
<!-- 1. 使用transition元素把小球包裹起来 -->
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter">
<div class="ball" v-show="flag"></div>
</transition>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data:{
flag:false
},
methods:{
toggle(){
this.flag = !this.flag
},
beforeEnter(el){
// 注意:每一个函数上都有一个el属性
// 动画钩子函数的第一个参数表示要执行动画的那个元素是个原生的js dom对象
// 可以认为el是通过document.getElementById('')方式获取到的
// beforeEnter表示动画入场之前,此时动画尚未开始,可以在beforeEnter中设置开始动画之前的起始样式
el.style.transform = "translate(0,0)"//设置小球开始动画之前的起始位置
},
enter(el, done){
// 这句话没有实际的作用,但是如果不写,出不来动画效果
// 可以认为el.offsetWidth是强制动画刷新,当然,也可以是offsetTop,offsetHeight都可以
el.offsetWidth
// enter表示动画开始之后的样式,这里,可以设置小球完成动画之后的结束状态
el.style.transform = "translate(150px, 450px)"
el.style.transition = "all 1s ease"
done() //done其实就是afterEnter函数,此句话的作用是动画完成后立即调用afterEnter,否则小球会停留一会儿
},
afterEnter(el){
// 动画完成之后,会调用afterEnter
//用这句话可以直接跳过后半场动画,当第二次再点击按钮的时候,flag还是从false变为true的过程
this.toggle()
}
},
})
</script>
</body>
</html>
效果图:
明明每次动画后都只是将小球隐藏了,但是为什么每次点击按钮的时候,小球还是从0,0位置出现?
答:因为一轮动画完成后,当我们重新点击按钮,又是一轮新的动画,此时会执行beforeEnter,就会重置小球的位置
使用transition-group元素实现列表动画
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="animate.css">
<!-- 使用animate.css库 -->
<!-- 入场动画:bounceIn 离场动画:bounceOut -->
<style type="text/css">
li {
border: 1px dashed #999;
margin: 5px;
height: 35px;
padding: 5px;
font-size: 12px;
line-height: 35px;
width: 100%;
/*absolute如果不指定宽度,默认是最小值*/
/*所以在此处指定了宽度*/
}
.v-enter,
.v-leave-to {
/*此处是设置enter和leave-to节点的状态*/
opacity: 0;
transform: translateX(100px);
}
.v-enter-active,
.v-leave-active {
/*这里面是动画的过渡效果*/
/*即两个阶段:进入和离开*/
transition: all 0.6s ease;
}
/*下面的v-move和v-leave-active的配合使用,可以实现列表后续的元素渐渐地漂上来的效果*/
.v-move {
/*可以设置元素在位移时候的渐变*/
/*为了让.v-move起作用,还必须在.v-leave-active中设置position:absolute;*/
transition: all 0.6s ease;
}
.v-leave-active {
position: absolute;
/*absolute如果不指定宽度,默认是最小值*/
/*所以在li中指定了宽度*/
}
li:hover {
background-color: hotpink;
transition: all 1s ease;
}
</style>
</head>
<body>
<div id="app">
<div>
<label>
id:<input type="text" v-model='id' name="">
</label>
<label>
name:<input type="text" v-model='name' name="">
</label>
<input type="button" value="添加" @click="add" name="">
</div>
<ul>
<!-- 在实现列表过渡的时候,如果需要过渡的元素是通过v-for玄幻渲染出来的,不能使用transition包裹,需要使用transitionGroup -->
<transition-group>
<!-- 如果要给v-for循环创建的元素添加动画,必须为每个元素定义key属性 -->
<!-- 注意:这里的transition-group不能写成驼峰式 -->
<li v-for="(item,i) in list" :key="item.id" @click='remove(i)'>
{{item.id}}---{{item.name}}
</li>
</transition-group>
</ul>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data:{
id:'',
name:'',
list: [
{id:1, name:'赵高'},
{id:2, name:'秦桧'},
{id:3, name:'严嵩'},
{id:4, name:'魏忠贤'}]
},
methods:{
add(){
this.list.push({id:this.id,name:this.name})
this.id=''
this.name=''
},
remove(i){
this.list.splice(i,1)
// splice方法,从第一个参数处开始删除,删除第二个参数个
}
},
})
</script>
</body>
</html>
效果图:
vue组件
什么是组件:组件的出现,就是为了拆分Vue实例的代码量,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件
组件化和模块化的区别:
- 模块化是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一
- 组件化是从ui界面的角度进行划分的;前端的组件化,方便ui组件 使用
定义vue组件
注意:无论是哪种方式创建的组件,组件的template属性指向的模板内容,必须有且只有一 个根标签
全局组件定义的三种方式
全局组件在任意Vue控制的区域内都可以使用
- 使用Vue.extend配合Vue.componenet方法
var login_obj = Vue.extend({
template:'<h1>登录</h1>'
});
Vue.component('login',login_obj);//将login_obj组件对象注册为名为login的组件
案例:
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 如果要使用组件,直接把组件的名称,以HTML标签的形式,引入到页面中即可 -->
<my-com></my-com>
</div>
<script type="text/javascript">
var mycom = Vue.extend({
template: '<h1>这是使用Vue.extend创建的组件</h1>'
// 通过template属性,指定了组件要战士的HTML结构
})
//1.2 使用Vue.component('组件的名称',创建出来的组件模板对象)
Vue.component('myCom',mycom)//在注册的时候可以使用驼峰,但是在使用的时候,需要写成连字符+小写
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
});
</script>
</body>
</html>
如果取消中间变量,则可以写成:
Vue.component('register',Vue.extend({
template:'<h1>注册</h1>'
}));
第一个参数是组件的名称,将来在引用组件的时候,就是一个标签形式来引入它
第二个参数:Vue.extend创建的组件,其中,template就是组件将来要展示的内容
- 直接使用Vue.component方法,给它传一个字面量的对象
还是使用标签的形式引用自己的组件
Vue.component('register',{
template:'<h1>注册</h1>'
});
- 将模板字符串,定义到script标签中
<script id="tmpl" type="x-template">
<div><a href="#">登录</a> | <a href="#">注册</a></div>
</script>
同时,需要使用Vue.component来定义组件
Vue.component('account',{
template:'#tmpl'
});
案例:
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- 在被控制的app外,使用template元素,定义组件的HTML模板结构 -->
<template id="tmpl">
<!-- 这里面还是只能有一个根标签 -->
<div>
<h1>这是通过template元素,在外部定义的组件结构,这个方式有代码的提示和高亮:个人理解,相当于把已经写好的模板直接当做组件注册</h1>
<h4>相当好用</h4>
</div>
</template>
<div id="app">
<mycom></mycom>
<!-- 在被控制的区域内使用模板 -->
</div>
<script type="text/javascript">
Vue.component('mycom',{
template:'#tmpl' //就是id为tmpl的标签
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
});
</script>
</body>
</html>
效果:
实例内部私有组件的定义
ps:凡是私有的,都只能在自己的控制区域使用
定义:
var vm = new Vue ({
el:'#app',
data:{},
methods:{},
components:{
组件名:{
template:"<h1>这是私有的组件,也是一个HTML字符模板即可</h1>"
}
}
})
案例:
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<login></login>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
components:{
// 定义实例内部的私有组件
login:{
template:"<h1>这是私有的login组件</h1>"
}
}
});
</script>
</body>
</html>
效果图:
当然,也可以将模板提到自己的控制区外单独定义
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<template id="priTmp">
<div>
<h1>这也是私有的组件</h1>
<h2>但是提到了外面进行定义</h2>
</div>
</template>
<div id="app">
<login></login>
</div>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
components:{
// 定义实例内部的私有组件
login:{
template:"#priTmp"
}
}
});
</script>
</body>
</html>
效果图:
其他定义组件的方法
1. 通过对象字面量的形式
var login = { //login就是一个组件模板对象
template:'<h1>23</h1>'
}
Vue.component('mylogin',login) //定义全局
2.也可以用上面的login对象定义私有的组件
var vm=new Vue({
el:...
data:{...}
methods:{...}
components:{
mylogin:login //注意:此处还可以就写一个login,其他的啥都不写,此种方法说明,此处定义了一个变量,这个变量会被解析为属性,它的属性名和变量名相同(即属性值和变量名相同)
}
})
组件中的data
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<mycoml></mycoml>
</div>
<script type="text/javascript">
Vue.component('mycoml',{
template:'<h1>这是一个全局组件,并且在这里引用了自己定义的数据(使用插值表达式):{{msg}}</h1>',
data(){
// 组件可以有自己的data数据
// 组件的data和实例的data不一样,实例中的data可以为一个对象,但是组件中的data必须是一个方法
// 组件中的data除了必须为一个方法之外,这个方法内部还必须返回一个对象
//组件中的data数据,使用方式和实例中的data使用方式完全一样
return {
msg: '这是组件中自己定义的数据'
}
}
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
}
});
</script>
</body>
</html>
效果图:
组件中的data为什么要是一个function
案例:data中返回一个外部定义的对象
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<template id="tmpl">
<div>
<input type="button" value="+1" @click='increment' name="">
<h3>{{count}}</h3>
</div>
</template>
<div id="app">
<counter></counter>
<counter></counter>
<counter></counter>
</div>
<script type="text/javascript">
var dataObj = { count:0}
// 在外面定义一个对象,其中的count值为0
Vue.component('counter', {
// 这是一个计数器的组棬,身上有个按钮,每当点击按钮,让data中 count 值 +1
template: '#tmpl',
data(){
return dataObj
通过此法也实现了返回一个对象
},
methods:{
increment(){
// 定义一个increment方法
this.count++
// 因为在data中定义了dataObj,所以这里可以直接使用this对count进行访问
// 实际上,直接把data对象放在return中是一样的
}
}
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
components:{
// 定义实例内部的私有组件
}
});
</script>
</body>
</html>
效果图:
此种方法的弊端,当有多个模板实例的时候,返回的是同一个对象,所以他们共享这个对象的属性
所以在return时,一定要是一个新的对象,写成形如:return { count: 0}
的形式就能保证每一个实例对象的data都是独有的,不共享
不同组件间的切换
利用v-if和v-else实现组件的切换
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<a href="#" @click.prevent='flag=true'>登录</a>
<a href="#" @click.prevent='flag=false'>注册</a>
<login v-if='flag'></login>
<!-- 当flag为真的时候显示这个组件 -->
<register v-else='flag'></register>
<!-- 否则显示这个组件 -->
</div>
<script type="text/javascript">
Vue.component('login', {
template: '<h3>登录组件</h3>'
})
Vue.component('register', {
template:'<h3>注册组件</h3>'
})
var vm = new Vue({
el:'#app',
data:{
flag:false
},
methods:{
},
components:{
}
});
</script>
</body>
</html>
效果图:
当点击登录或注册文字时,下方的显示文字会切换
利用:is
实现组件的切换
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<a href="#" @click.prevent="comName='login'">登录</a>
<a href="#" @click.prevent="comName='register'">注册</a>
<!-- component元素也是vue提供的,来展示对应名称的组件,is后面的值应该是组件的名称 -->
<!-- component是一个占位符,:is属性,可以用来指定要展示的组件的名称 -->
<component :is='"login"'></component>
<!-- 组件绑定时,后面引号内的内容会当作表达式来解析,而不是单纯的当作字符串,如果要当作字符串,要在加一层引号 -->
<!-- 要实现切换效果,将其用变量指向即可 -->
<div>----------分隔符----------</div>
<component :is='comName'></component>
</div>
<script type="text/javascript">
Vue.component('login', {
template: '<h3>登录组件</h3>'
})
Vue.component('register', {
template:'<h3>注册组件</h3>'
})
var vm = new Vue({
el:'#app',
data:{
comName:'register'
},
methods:{
},
components:{
}
});
</script>
</body>
</html>
效果图:点击最上方的文字是,最下面的文字会切换
组件的切换动画
- 直接使用transition标签将组件包裹起来,如
<transition>
<mycompl></mycompl>
</transition>
- 加上两个节点和两个阶段的样式
.v-enter,.v-leave-to {}
.v-enter-active, .v-leave-active {}
ps:在transition标签中,可以指定模式,例如:<transition mode='out-in“></transition>
,就能够让动画先播放出去的,再播放进来的。比如在如上的组件切换中,就能够让旧的组件先出去,然后新的组件再进来,而不是旧的还没出去,新的就已经在进来了
父子组件的传值
父组件向子组件传值
- 组件实例定义方式,注意:一定要使用props属性来定义父组件传递过来的数据
<script>
var vm = new Vue({
el:'#app',
data:{
msg:'这是父组件中的消息'
},
methods:{},
components:{
son:{
template:'<h2>这是子组件...{{finfo}}</h2>'
props:['下面的自定义属性名']
}
}
})
</script>
- 使用v-bind或简化指令,将数据传递到子组件中
<div id='app'>
<son :自定义属性名='msg'></son>
</div>
案例:
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="animate.css">
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<!-- 父组件可以在引用子组件的时候,通过属性绑定的形式,把需要传递给子组件的数据,以属性绑定的形式,传递给子组件内部,供子组件使用 -->
<com1 v-bind:parentmsg='msg'></com1>
<!-- 为com1组件绑定了一个叫做parentmsg的自定义属性,通过此方法,就通过自定义属性的方法传递到子组件内部去 -->
<!-- 在子组件内部就可以拿到parentmsg -->
<!-- 但是这样子组件还无法直接使用parentmsg -->
<!-- 如果子组件要使用parentmsg这个属性,还需要在自己内部定义一下 -->
</div>
<script type="text/javascript">
var vm = new Vue({//此中,vm是父组件,com1是子组件
// 在js中,vm相当于父级作用域,com1相当于子级作用域,子作用域是可以访问到父作用域中的数据的
el:'#app',
data:{
msg:'123a,父组件中的数据'
},
methods:{
},
components:{
com1:{
data(){
return {
}
},
props:['parentmsg'],// 注意:组件中的所有props中的数据,都是通过父组件传递给子组件的。
// 而子组件中的data中的数据都是自己私有的,不是父组件传过来的。
// data中的数据都是可读可写的,props中的数据都是只读的
// 通常,在vm对象,或者说在组件内部带s的都是对象,比如directives,filters。只有props是一个数组
// 如果子组件要使用父组件中的属性,就需要在props中原封不动的定义一下
template:'<h5>这是子组件,尝试引用父组件中的数据(双大括号内写msg):{{msg}},但是失败了,提示msg未定义,即,默认情况下,子组件是不能访问到父级组件中的数据的。但是通过父组件传值的形式(将msg改写为上面的parentmsg),发现成功了,获得的数据为:{{parentmsg}}</h5>'
}
}
})
</script>
</body>
</html>
子组件通过事件调用向父组件传值
方法也是数据的一种,因此父组件也能够把方法传给子组件,利用父组件把方法传递给子组件,子组件就能够将自己的数据以参数的形式给该方法,从而让父组件获得该数据
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="animate.css">
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<com2 v-on:func="show"></com2>
<!-- 方法要传递给子组件的时候,只能通过事件绑定机制,自定义一个事件属性 -->
<!-- com2就能够拿到一个方法,叫做func,就是show方法的引用传递给了func -->
<!-- ps:v-on可以简写为@ -->
</div>
<template id="tmpl">
<div>
<h1>这是子组件</h1>
<input type="button" value="这是子组件中的按钮,点击它,触发父组件" @click='myclick' name="">
</div>
</template>
<script type="text/javascript">
var com2 = {
// 定义了一个字面量类型的模板对象
template:'#tmpl',
data(){
return {
sonmsg:{
name:'li4',age:34
}
}
},
methods:{
myclick(){
// 当点击子组件的按钮时,如何拿到父组件传递过来的方法?
this.$emit('func',this.sonmsg)
// emit代表触发、调用的意思
// 如果传递过来的方法需要传参,则emit中从第二个参数起都是传递给第一个参数(即:调用的方法)的参数,eg:this.$emit('func',1,"hello")
},
}
}
var vm = new Vue({
el:'#app',
data:{
msg:'123a,父组件中的数据',
dataMsgFromSon:null
},
methods:{
show(data){
console.log("调用了父组件身上的show方法"+data)
this.dataMsgFromSon = data
}
},
components:{
com2
}
})
</script>
</body>
</html>
使用this.$refs来获取元素和组件
<!DOCTYPE html>
<html>
<head>
<title>组件</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="animate.css">
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<input type="button" value="获取元素" @click='getElement' name="">
<h3 id="myH3" ref="myH3">哈哈,这是一个H3</h3>
<!-- 因为h3只是单个的元素,所以这里的ref不带s -->
<login ref='mylogin'></login>
<!-- 组件也可以通过ref引用 -->
</div>
<script type="text/javascript">
var login = {
template:'<h1>这是登录组件</h1>',
data(){
return{
msg:'son msg'
}
},
methods:{
show(){
console.log('调用了子组件的方法')
}
}
}
var vm = new Vue({
el:'#app',
data:{
},
methods:{
getElement(){
// ref是英文单词reference,是引用的意思
console.log(this.$refs.myH3.innerText)
console.log(this.$refs.mylogin.msg)
this.$refs.mylogin.show()
}
},
components:{
login
}
})
</script>
</body>
</html>
效果图:
路由
什么是路由
- 后端路由:对于普通的网站,所有的超链是url地址,所有的url地址都对应服务器上对应的资源
- 前端路由:对于单页面应用程序来说,主要通过url中的hash(#)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现,即不会和服务器有交互
- 在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由(区别于后端路由)
在vue中使用vue-router
使用方法:
- 导包
- 路由是为了组件的切换,所以应该创建子组件
- 创建一个路由对象,定义路由规则
- 路由要展示,在页面上要有容器——router-view.
- 官方建议使用<router-link to=''>去往某个路由
案例:当点击跳转不同的url时,显示不同的组件,并且被激活的链接采用不同的样式类
<!DOCTYPE html>
<html>
<head>
<title>路由的基本使用</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 1. 安装vue-router路由模块 -->
<script type="text/javascript" src="./vue-router.js"></script>
<!-- 也是必须先导入vue,才能使用vue-router -->
<style type="text/css">
.router-link-active {
/*用了router-link的标签都这个样式类。默认:被激活的链接会采用这个样式类*/
/*如果要修改链接激活时使用的样式类名(即不再默认使用router-link-active样式类),可以通过路由的构造选项linkActiveclass来全局设置*/
color: red;
font-weight: bold;
font-size: 30px;
}
.myActive {
/*用于设置链接激活的默认样式类*/
color: yellowgreen;
background-color: cyan;
}
</style>
</head>
<body>
<div id="app">
<!-- <a href="#/login">登录</a> -->
<!-- 如果不加#,就表示跳转到真实的页面 -->
<!-- <a href="#/register">注册</a> -->
<router-link to='/login'>登录</router-link>
<router-link to='/register'>注册</router-link>
<!-- 这两句router-link是vue-router提供的,作用和上面注释掉的两个a标签作用相同 -->
<!-- router-link默认渲染成a标签 -->
<!-- 不管用tag指定其渲染为哪种标签,永远会给它绑定一个点击事件 -->
<!-- router-view是vue-router提供的元素,专门有来当作占位符的,将来路由规则匹配到的组件就会展示到这个router-view中去,所以可以把它当作占位符 -->
<router-view></router-view>
</div>
<script type="text/javascript">
var login={ //组件的模板对象
template:'<h1>登录组件</h1>'
}
var register={ //组件的模板对象
template:'<h1>注册组件</h1>'
}
// 路由是监听不同组件的切换的
// 2. 创建一个路由对象,当导入vue-router包之后,在window全局对象中,就有了一个路由的构造函数,叫做VueRouter
// 在new路由对象的时候,可以为构造函数,传递一个配置对象
var RouterObj = new VueRouter({
// route//这个对象中的route表示路由匹配规则
routes:[//路由匹配规则
// {path:'/', component:login}, //默认显示login组件,但是这种方法不推荐,建议使用下面的这种方法
{path:'/', redirect:'/login'}, //这里的重定向和Node中的redirect是完全不同的。因为node中的redirect是属于服务器端的技术,而这里是客户端通过某种方式修改hash值
// 每个路由匹配规则,都是一个对象,这个规则对象,身上有两个必须的属性
// 属性1:path,表示监听哪个路由链接地址
// 属性2:component,表示如果路由是前面匹配到的path,就展示componenet组件
{path:'/login', component:login}, //如果url地址是指向login,就展示login组件
// 注意:component的属性值,必须是一个组件模板对象,而不能是组件的引用名称
// 组件的引用名称,仅限于在页面的HTML中引用
{path:'/register', component:register}
],
// linkActiveClass:'myActive', 设置自己的链接激活类,当链接激活时,该标签采用myActive样式类,而不再是默认是的router-link-active样式类
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
router:RouterObj //将路由规则对象注册到vm实例上,用来监听url地址的变化,然后展示对应的组件
})
</script>
</body>
</html>
效果图:
在路由规则中定义参数
- 在规则中定义参数:
{path:/register:id, component:register}
,使用query的方式访问
案例:
案例:
```html
<!DOCTYPE html>
<html>
<head>
<title>在路由规则中定义参数</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="./vue-router.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<router-link to='/login?id=10&name=zhang3'>登录</router-link>
<!-- 如果在路由中,使用查询字符串,给路由传递参数,则不需要修改路由规则的path属性 -->
<router-link to='/register'>注册</router-link>
<router-view></router-view>
</div>
<script type="text/javascript">
var login = {
template:"<h1>登录---{{$route.query.name}}:{{$route.query.id}}</h1>",
created(){
// 组件的生命周期钩子函数
console.log(this.$route.query.id)
}
}
var register = {
template:"<h1>注册</h1>"
}
var router = new VueRouter({
routes:[
{path:'/login', component:login},
{path:'/register', component:register},
]
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
router //由于属性名和属性值完全一样,所以可以简写成一个即可,完整写法为:router:router
})
</script>
</body>
</html>
效果图:
我们也可以看到 ,在$route对象的query中,有id和name两个属性,所以我们在login组件中直接取出并展示在了页面上
- 通过
this.$route.params
来获取路由中的参数
var register = Vue.extend({
template:'<h1>注册组件---{{this.$route.params.id}}</h1>'
})
案例:
<!DOCTYPE html>
<html>
<head>
<title>在路由规则传参方式2</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="./vue-router.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<router-link to='/login/223/li4'>登录</router-link>
<!-- 由于在路由匹配规则中使用了占位符:id,因此这里的223会被当作id进行解析 -->
<router-link to='/register'>注册</router-link>
<router-view></router-view>
</div>
<script type="text/javascript">
var login = {
template:"<h1>登录---{{$route.params.name}}---{{$route.params.id}}</h1>",
created(){
console.log(this.$route.params.id)
console.log(this.$route.params.name)
}
}
var register = {
template:"<h1>注册</h1>"
}
var router = new VueRouter({
routes:[
{path:'/login/:id/:name', component:login},
// 冒号表示占位符,意思是将来这个位置的东西当做id解析出来
{path:'/register', component:register},
]
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
router
})
</script>
</body>
</html>
效果图:
使用children属性实现路由嵌套
<!DOCTYPE html>
<html>
<head>
<title>路由的嵌套</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="./vue-router.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<router-link to='/account'>account</router-link>
<router-view></router-view>
</div>
<template id="tmpl">
<div>
<h1>这是Account组件</h1>
<router-link to='/account/login'>登录</router-link>
<router-link to='/account/register'>注册</router-link>
<router-view></router-view>
</div>
</template>
<script type="text/javascript">
var account = {//组件的模板对象
template:'#tmpl'
}
var login = {
template:'<h3>这里是login</h3>'
}
var register = {
template:'<h3>这里是register</h3>'
}
var router = new VueRouter({
routes:[
{path:'/account', component:account,
children:[
{path:'login', component:login},//注意,这里的login前面没有斜线,如果加了斜线,代表以根开始匹配
{path:'register', component:register}
]
},
// 以下这种写法会导致login和account平级,如果点击account后再次点击登录或注册后,“这是Account组件”几个字会消失
// 而我们想要的是登录或注册隶属于account,即不让文字消失
// 所以应该在上面的一个规则中定义子路由
// {path:'/account/login', component:login},
// {path:'/account/register', component:register}
]
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
router
})
</script>
</body>
</html>
效果图:
利用命名视图实现经典布局
<!DOCTYPE html>
<html>
<head>
<title>命名视图-经典布局</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="./vue-router.js"></script>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
.header {
background-color: gold;
}
.container {
display: flex;
}
.left {
flex: 2;
/*意思是在这个盒子内,left占20%*/
background-color: yellowgreen;
height: 1000;
}
.main {
flex: 8;
background-color: hotpink;
height: 1000px;
}
</style>
</head>
<body>
<div id="app">
<router-view></router-view>
<!-- 如果不指定组件的名称,就放default -->
<div class="container">
<!-- 为了实现flex布局,将其包起来 -->
<router-view name='left'></router-view>
<!-- 此处只放名称为left的组件 -->
<router-view name='main'></router-view>
</div>
</div>
<script type="text/javascript">
var header = {
template:'<h1 class="header">Header区域</h1>',
}//在布局时,一个component对应一个“坑”——<router-view></router-view>
var leftBox = {
template:'<h1 class="left">leftBox区域</h1>',
}
var mainBox = {
template:'<h1 class="main">mainBox区域</h1>',
}
var router = new VueRouter({
routes:[
{path:'/', components:{
'default':header, //前面的属性名加不加单引号都可以,如果自己不加,会自动加上
'left':leftBox,
'main':mainBox,
}},
],
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
router
})
</script>
</body>
</html>
效果图局部:
名称案例
使用@keyup监听按键的松开以实现对数据变动的监听
<!DOCTYPE html>
<html>
<head>
<title>名字拼接</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="./vue-router.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="firstname" name="" @keyup="getFullname">+
<input type="text" v-model='lastname' name="" @keyup="getFullname">=
<input type="text" v-model='fullname' name="">
</div>
<script type="text/javascript">
var router = new VueRouter({
routes:[
],
})
var vm = new Vue({
el:'#app',
data:{
firstname:'',
lastname:'',
fullname:'',
},
methods:{
getFullname(){
this.fullname = this.firstname + "-" + this.lastname
}
},
})
</script>
</body>
</html>
使用watch监听文本框数据的变化
<!DOCTYPE html>
<html>
<head>
<title>命名视图-经典布局</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="./vue-router.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="firstname" name="" >+
<input type="text" v-model='lastname' name="" >=
<input type="text" v-model='fullname' name="">
</div>
<script type="text/javascript">
var router = new VueRouter({
routes:[
],
})
var vm = new Vue({
el:'#app',
data:{
firstname:'',
lastname:'',
fullname:'',
},
methods:{
},
watch:{
//使用这个属性,可以监视data中指定数据的变化,然后触发这个watch中对应的函数
firstname:function(newVal, oldVal){
this.fullname = newVal + this.lastname
}, //如果firstname发生了改变,就触发这个函数,属性名除非中间一短线连接,不然可以不加引号
// function中默认提供了两个参数,newVal, oldVal
lastname:function(newVal){
// 因为oldVal无用,所以声明时就不写了
this.fullname = this.firstname + newVal
}
}
})
</script>
</body>
</html>
效果图:
watch的优点:可以监听看不到的数据的改变,比如路由的改变,就无法使用keyup进行监听,而watch就可以
watch监听路由地址的改变
路由地址:$route.path
<!DOCTYPE html>
<html>
<head>
<title>路由的基本使用</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="./vue-router.js"></script>
<style type="text/css">
.router-link-active {
color: red;
font-weight: bold;
font-size: 30px;
}
.myActive {
color: yellowgreen;
background-color: cyan;
}
</style>
</head>
<body>
<div id="app">
<router-link to='/login'>登录</router-link>
<router-link to='/register'>注册</router-link>
<router-view></router-view>
</div>
<script type="text/javascript">
var login={
template:'<h1>登录组件</h1>'
}
var register={
template:'<h1>注册组件</h1>'
}
var router = new VueRouter({
routes:[
{path:'/', redirect:'/login'},
{path:'/login', component:login},
{path:'/register', component:register}
],
})
var vm = new Vue({
el:'#app',
data:{
},
methods:{
},
router,
watch:{
'$route.path':function(newVal, oldVal){
if(newVal == '/login'){
alert('欢迎进入登录界面')
}else if(newVal == '/register'){
alert('欢迎进入注册页面')
}
},
}
})
</script>
</body>
</html>
当路由地址改变的时候,就会弹出弹窗
计算属性
案例:利用计算属性实现名字的拼接
<!DOCTYPE html>
<html>
<head>
<title>命名视图-经典布局</title>
<meta charset="utf-8">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="./vue-router.js"></script>
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="firstname" name="" >+
<input type="text" v-model='lastname' name="" >=
<input type="text" v-model='fullname' name="">
</div>
<script type="text/javascript">
var router = new VueRouter({
routes:[
],
})
var vm = new Vue({
el:'#app',
data:{
firstname:'',
lastname:'',
},
methods:{
},
computed:{ //在computed中,可以定义一些属性,叫做:计算属性,计算属性的本质就是一个方法,只不过我们在使用这些计算属性的时候,是把他们的名称直接当作属性使用的,并不会把计算属性当作方法去调用
'fullname':function(){
return this.firstname + "-" + this.lastname
// 注意:计算属性在引用的时候,一定不要加小括号去调用,直接把它当作普通属性用即可
//只要计算属性这个fucntion内部,所用到的任何data中的数据发生了变化,就会立即重新计算这个计算属性的值
// 计算属性的求值结果会被缓存起来,方便下次直接使用,如果计算属性方法中,所用的任何数据,都没有发生过变化,则,不会重新对计算属性求值
}
}
})
</script>
</body>
</html>
watch、computed、methods的对比
- 计算属性和watch都是function
- 在计算属性内部,无论如何都要return一个值,而watch中没有return值
- methods也是一个function,但是这个fucntion里面更重视业务逻辑的处理,表示一个具体的操作
- watch可以看作是computed和methods的结合体,更方便监听一些虚拟的