vue学习笔记(b站王红元老师网课笔记)
在b站学习王红元老师的vue课程时的随笔笔记,仅供学习参考
1.vue的template模板
可以把一整块的代码块放入template中,这样就可以方便以后调用。
<div id="app">
{{message}}
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
}
})
</script>
以上即为一个模板,把它复制后进入设置,找到editor->live templates->vue,再点击右侧加号,把内容复制进去,同时给这个模板取个名字。记得要在界面下方的这里设置勾选html,再点击apply即可。
2. vue的mustache语法
如何把data,methods中的文本数据,插入到HTML代码中?我们可以用到mustache语法,也就是这样双大括号的形式:
<div id="app">
{{message}}
</div>
这样的数据是响应式的。在mustache里还可以进行字符串的拼接,数值的计算等操作。
要注意的是,mustache语法是写在内容中的,而不能写在标签里。
3. vue插值操作的其他语法
3.1 v-once
如上的数据是响应式的,但是有时,我们不希望数据随着用户输入而改变。这时,就需要在代码里加入“v-once”。如下:
<div id="app">
<h2 v-once>
{{message}}
</h2>
</div>
3.2 v-html
有时,我们从服务器请求到的数据是html代码,这时候用mustache语法解析会把html代码输出(比如下图里会把a标签也展示出来),但是我们希望得到的是按照html格式来进行解析的内容,这时,就要用到v-html。如下:
<div>
<h2 v-html="url">
{{message}}
</h2>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
url:'<a href="...">xxx</a>'
}
})
</script>
3.3 v-text
<div>
<h2 v-text="message"></h2>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
url:'<a href="...">xxx</a>'
}
})
</script>
展示效果与mustache相同,但是一般不用,不如mustache灵活,无法进行拼接。
3.4 v-pre
v-pre类似于
标签,里面的东西可以原封不动地表现出来,用于跳过这个元素和这个元素里的内容。如,我们想在页面上显示一个双括号:
<div>
<h2 v-pre>{{message}}</h2>
</div>
3.5 v-cloak(斗篷)
html的代码是从上至下解析的,所以当之后的js代码卡住时,前面未被js解释的mustache语法就会显示出来。一般情况下,我们不希望用户看到这种信息,所以就有了v-cloak,用法如下:
<div id="app" v-cloak>
{{message}}
</div>
我们可以把v-cloak当成一个属性,加入到div里。在vue解析之前,div里有一个属性v-cloak,解析之后就没了。所以,可以根据有无v-cloak属性来判断js代码是否执行了。我们用style来实现这个功能:
<style>
[v-cloak] {
display:none;
}
</style>
4. v-bind
4.1 基本使用
网页中某些属性我们希望动态决定,如商城里的商品轮播图等。一些网址等,在开发中也很少会写死,大多都是从服务器请求得来的。我们把这样的数据在vue里暂存中转,在html里利用v-bind来实现动态绑定。
<div id="app">
<img v-bind:src="imgURL">
<a v-bind:href="ahref"></a>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
imgURL:'website1,website2……'
ahref:'website1,website2……'
}
})
</script>
v-bind有个语法糖(简写),只留下一个冒号,格式如下:
<div id="app">
<img :src="imgURL">
<a :href="ahref"></a>
</div>
4.2 v-bind动态绑定class
4.2.1 对象语法
v-bind和class的对象用法如下:
<style>
.active {
color:red;
}
</style>
<div id="app">
<h2 :class="active">
{{message}}
</h2>
<h2 v-bind:class="{key1:value1, key2:value2}"><!--大括号里存放对象-->
{{message}}
</h2>
<h2 v-bind:class="{类名1:boolean, 类名2:boolean……}"><!--当boolean的值为true时,是元素拥有该类名,否则没有-->
{{message}}
</h2>
<h2 v-bind:class="{active:isActive, line:isLine}"><!--这是常用情况,即把boolean的值存入vue中-->
{{message}}
</h2>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
active:'active'
isActive:true,
isLine:true,
}
})
</script>
假设有这样的一个需求:有一个button,按下后使文字变成红色,再按一下又变回黑色,如此往复,怎么实现?
<style>
.active {
color:red;
}
</style>
<div>
<h2 v-bind:class="{active:isActive, line:isLine}">
{{message}}
</h2>
<button v-on:click="Click"></button>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
active:'active'
isActive:true,
isLine:true,
}
methods:{
Click: function(){
this.isActive=!this.isActive
}
}
})
</script>
如上代码,我们可以在button里定义一个点击事件click=”Click“,再在vue里定义方法Click。这样,每当我们按下button,就等于在vue里让isActive的值取反,从而实现message反复拥有、不拥有active类——即颜色变化的效果。
此外,v-bind决定的class不与直接定义的class(固定的)冲突,可以共存。如:
<h2 class="title" v-bind:class="{active:isActive, line:isLine}">
{{message}}
</h2>
这里,title类是固定不变的,其余两个active和line类都是可变的。
4.2.2 数组语法
v-bind和class的数组用法如下(不常用):
<h2 class="title" v-bind:class="['active','line']">
{{message}}
</h2>
4.3 v-bind动态绑定style
什么时候会用到动态绑定style?比如说,我们在做网站时,我们会把一个搜索栏做成一个组件。而这个搜索栏再主页、分页里样式不同,所以需要动态绑定。
4.3.1 对象语法
v-bind和style的对象用法如下:
<div id="app">
<h2 :style="{key(css属性名):value(属性值)}">
{{message}}
</h2>
<h2 :style="{fontSize:'50px'}"><!--记得加单引号,不加的话会被vue认为是变量,但是类名不用加-->
{{message}}
</h2>
<h2 :style="{fontSize: finalSize}">
{{message}}
</h2>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
finalSize: '100px'
}
})
</script>
4.3.2 数组语法
<h2 :style="[baseStyle,baseStyle2]">
{{message}}
</h2>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
baseStyle:{backgroundColor:'red'},
baseStyle:{fontSize:'100px'}
}
})
</script>
5. 计算属性
5.1 计算属性的基本使用
当我们需要对data里的数据进行处理并显示时,需要用到computed(计算属性),它虽然在vue里通过函数实现,但在html里要当作属性来使用(不用加括号)。
计算属性的基本使用:
<h2>
{{getFullName()}}<!--通过方法获取-->
{{fullName}}<!--计算属性,不用加括号-->
</h2>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
firstName:'Lebron',
lastName:'James'
},
methods:{
getFullName(){
return this.firstName+' '+this.lastName
}
},
computed:{<!--按照属性名取函数的名字-->
fullName: function(){
return this.firstName+' '+this.lastName
}
}
})
</script>
computed还可以实现一些不用计算属性,只用mustache语法难以实现的功能:
<h2>
{{totalPrice}}
</h2>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
books:[
{id:100,name:'unix',price:120},
{id:101,name:'c++',price:110},
{id:102,name:'java',price:100},
]
},
computed:{
totalPrice: function(){
let result=0;
for(let i=0;i<this.books.length;i++)
{
result+=books[i].price
}
return result
}
}
})
</script>
5.2 计算属性的getter和setter方法
每个计算属性都包含一个getter和setter方法。这是计算属性的完整版本(5.1里是简写版本)。一般情况下,我们不用写set方法。
<div id="app">
<h2>
{{fullName}}
</h2>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
firstName:'Kobe',
lastName:'Bryant'
},
computed:{
fullName:{
set:function(newValue){
const names = newValue.split(' ');
this.firstName=names[0];
this.lastName=names[1];
},
get:function(){
return this.firstName+' '+this.lastName
}
}
}
})
</script>
5.3 计算属性和methods的对比
为什么我们更多使用计算属性而不是methods方法?因为当我们使用(调用)属性时,methods每次调用属性都要调用methods方法,而计算属性只调用一次。所以计算属性性能更好。
<h2>
{{getFullName()}}<!--通过方法获取-->
{{fullName}}<!--计算属性-->
</h2>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
firstName:'Lebron',
lastName:'James'
},
methods:{
getFullName(){
return this.firstName+' '+this.lastName
}
},
computed:{<!--按照属性名取函数的名字-->
fullName: function(){
return this.firstName+' '+this.lastName
}
}
})
</script>
6. ES6基本语法补充
6.1 块级作用域 let var
变量作用域:变量在什么范围内可用。
ES5之前,由于if和for都没有块级作用域,必须借助function来解决引用外部变量的问题。ES6中加入了let,它拥有if和for块级作用域。
下面是var和let的区别,方便我们理解块级作用域:
const btns = document.getElementsByTagName('button')
for(var i = 0; i < btns.length; i++){
btns[i].addEventListener('click',function(){
console.log("第"+i+"个元素被打印");
})
}
for(let i = 0; i < btns.length; i++){
btns[i].addEventListener('click',function(){
console.log("第"+i+"个元素被打印");
})
}
当我们用var指定i时,点击第一个按键,console会打印“第5个元素被打印”,这是因为在执行console.log语句之前,由于var没有for的块级作用域,var已经被加到了5,再去进入执行console.log语句,而let则不会。let每个循环语句里有独立的i,var的情况下,每当进入下一个循环,由于没有块级作用域,改变i值(i++)会把i赋给之前所有的循环里的i,所以最终所有的i都变成了最后一个i值。当我们点击时,回调console.log,自然得不到正确的结果。
6.2 const的使用
const用以保证数据安全性。在开发中,优先使用const,只在需要改变标识符时才使用let.
需要注意的是:const指向的对象不能修改,但是其内部属性可以。如:
const obj = {
name:'fyk'
}
obj.name = 'james';
这时,obj对象的属性已被改变。
6.3 对象字面量的增强写法
什么是字面量?当我们创建一个对象时,可以new一个对象,也可以用大括号代替。这就是字面量。
const obj = new Object();
const obj = {} <!--字面量-->
字面量的增强写法是一个十分方便的功能。在ES5及之前的版本里,当我们想要对一个对象的属性赋以一个对象外的值时,我们需要这样操作:
const name= 'fyk';
const height= 1.83
const obj = {
name: name,
height: height
}
ES6里可以采取增强写法:
const obj = {
name,
height
}
除了对属性的增强写法,我们还可以对函数进行增强写法。下面是对比:
const obj = {
run : function(){
}
}
<!--ES5-->
const obj = {
run(){
}
}
<!--ES6-->
7. v-on
7.1 v-on基本使用和语法糖
前端开发需要经常和用户交互。这时就要监听用户事件,比如点击、拖拽等。在vue中,我们使用v-on指令来监听事件。
<div id="app">
<h2>
{{counter}}
</h2>
<button v-on:click="increasement">
+
</button>
<button v-on:click="sub">
-
</button>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
counter: 0
},
methods: {
increasement(){
this.counter++
},
sub(){
this.counter--
}
}
})
</script>
v-on的语法糖:
@click <!--v-on:click,直接把v-on:变成@-->
7.2 v-on的参数传递
当事件监听时使用的方法没有参数,那么不用加括号,直接写方法名调用即可。
但是除这种情况外,还有很多情况:如有参数但无括号
<div id="app">
<button v-on:click="Click1"><!--这种情况下,写方法时省略了小括号,但是方法是需要参数的,那么此时vue会默认把浏览器产生的事件作为参数传入方法-->
按钮1
</button>
<button v-on:click="Click2(123,$event)"><!--调用方法时,要手动获取浏览器产生的事件对象:$event-->
按钮2
</button>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
counter: 0
},
methods: {
Click1(event){
console.log(event);
} ,
Click2(abc,event){
}
})
</script>
7.3 v-on的修饰符
<div @click="divClick">
<button @click="buttonClick">
按钮
</button>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
counter: 0
},
methods: {
divClick(){
},
buttonClick(){
}
})
</script>
当上述代码块执行后,若我们点击div块(非按钮区域),则divClick和buttonClick都会被执行。有时,我们在点击一块区域时并不想让其中的子区域的点击事件也被触发,那么我们就可以使用stop修饰符,如:
<div @click="divClick">
<button @click.stop="buttonClick">
按钮
</button>
</div>
当我们想要监听某个键盘的键的键入时,我们可以用修饰符来帮助我们:
<div>
<input type="text" @keyup.enter="keyUp">
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
counter: 0
},
methods: {
keyUp(){
}
})
</script>
上述代码块执行时,当我们按下并松开键盘的键,不会执行keyUp方法,但是当我们按下并松开了回车键,就会执行keyUp方法。在keyup或keydown后加特定的修饰符可以用来监听特定按键的键入。
8. v-if,v-else-if和v-else
8.1 v-if
当我们想要根据一些条件来判断是否显示一些内容时,可以使用v-if.
<div>
<h2 v-if="isShow">
abc
</h2>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
isShow: true
}
})
</script>
8.2 v-if和v-else结合使用
<div>
<h2 v-if="isShow">
abc
</h2>
<h1 v-else>
isShow为false时显示此句
</h1>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
isShow: true
}
})
</script>
8.3 v-else-if
<div id="app">
<h2 v-if="score>=90">
优秀
</h2>
<h2 v-else-if="score>=80">
良好
</h2>
<h2 v-else-if="score>=60">
及格
</h2>
<h2 v-else>
不及格
</h2>
<h1> <!--计算属性的写法-->
{{result}}
</h1>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
score:99
},
computed:{ <!--复杂情况更推荐写在计算属性里-->
result(){
let show = '';
if(this.score>=90){
show='优秀'
}
else if(...)
return show
}
}
})
</script>
下面是依靠v-if实现的一个登录切换的小案例:
<div id="app">
<span v-if="isUser">
用户账号
<input type="text" id="username">
</span>
<span v-else>
用户邮箱
<input type="text" id="email">
</span>
<button @click="change">
切换登录类型
</button>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
isUser: true
},
methods:{
change(){
this.isUser=!this.isUser
}
}
})
</script>
这里有个小问题:如果我们在有输入内容的情况下,切换了类型,我们会发现此时文本框内依然存在着我们之前输入的内容。但是按理来说,我们应该切换到另一个input元素中了。而在另一个input元素中,我们并没有输入内容,为什么会出现这个问题?
这是因为vue的虚拟dom出于性能考虑,遇到互斥条件时,会复用input。为了解决这个问题,可以在input里加入key,当key值不同时,不会复用:
<div id="app">
<span v-if="isUser">
用户账号
<input type="text" id="username" key="username">
</span>
<span v-else>
用户邮箱
<input type="text" id="email" key="email">
</span>
<button @click="change">
切换登录类型
</button>
</div>
8.4 v-show
v-show和v-if很类似,都能决定元素是否显示。区别在于条件为false时,v-if修饰的元素根本就不会存在dom中,而v-show只是新增一个行内样式display,并把display属性设为none.条件为true时,v-if会再创建一个元素,v-show只用把display属性删去即可。
开发时,当需要频繁切换时,使用v-show更好。反之,v-if常用。
9. v-for
9.1 v-for遍历数组和对象
v-for遍历数组:
<div id="app">
<ul><!--不加索引的遍历-->
<li v-for="item in names">{{item}}</li>
</ul>
<ul><!--加上索引的遍历-->
<li v-for="(item,index) in names">{{index}}.{{item}}</li>
</ul>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
names:['harden','westbrook','tucker']
},
})
</script>
v-for遍历对象:
<div id="app">
<ul><!--只获取一个值的遍历获取到的是value-->
<li v-for="item in info">{{item}}</li>
</ul>
<ul><!--加上key值的遍历-->
<li v-for="(value, key) in info">{{key}}.{{value}}</li>
</ul>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
info:{
name:'kevin',
height:1.83,
weight:63
}
},
})
</script>
9.2 v-for绑定和非绑定key区别
<div id="app">
<ul>
<li v-for="item in letters" :key="item">{{item}}</li><!--注意不要把key的value写成index,因为当新数据插入后,index和item不是一一对应的-->
</ul>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
letters:['a','b','c']
},
})
</script>
9.3 数组中哪些方法是响应式的
push():从末尾加入数组元素(可一次加入多个元素)
pop():删除数组末尾元素
shift():删除数组第一个元素
unshift():从最前面加入数组元素(可一次加入多个元素)
splice(arg1,arg2,…):删除、插入、替换元素
删除:从第arg1位置开始,删除arg2个参数。如果第二个参数未定义,则默认从arg1开始全部删除。
插入:arg2=0,从arg1位置开始,插入n个元素(第三个参数及以后)
替换:从arg1位置开始,删除arg2个参数,再加入n个元素(第三个参数及以后)
sort():排序
reverse():反转数组
这些方法都是响应式的方法。
要注意的是,直接通过索引值改变数组元素不是响应式的。要改动指定位置的元素,可以使用splice()来实现。也可以使用Vue的set方法。
Vue.set(this.letters,0,'abc')<!--arg1是数组名,arg2是改变的元素位置,arg3是改变后的值-->
9.4 购物车案例(综合运用)
以下是一个购物车的案例,综合运用了v-if,v-for,v-bind,v-on等知识,同时用到了过滤器。目标是这样的:
<head>
...
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<div v-if="books.length"><!--当购物车为空时,不显示这些,而是显示字符串“购物车为空”-->
<table>
<thead>
<tr>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in books">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.date}}</td>
<td>{{item.price|showPrice}}</td>
<td>
<button @click="increasment(index)">
+
</button>
{{item.quantity}}
<button @click="sub(index)" v-bind:disabled="item.count<=1">
-<!--当数量等于1时无法再减少-->
</button>
</td>
<td>
<button @click="remove(index)">
移除
</button>
</td>
</tr>
</tbody>
</table>
<h2>
{{totalPrice|showPrice}}
</h2>
</div>
<h2 v-else>
购物车为空
</h2>
</div>
<script src="vue.js"></script>
<script src="main.js"></script>
</body>
const app = new Vue({
el:'#app',
data:{
books:[
{
id:1,
name:'algorithm introduction',
date:'2006-9',
price:85.00,
count:1
},
{
id:2,
name:'unix',
date:'2006-2',
price:59.00,
count:1
}
]
}
methods:{
increasment(index){
this.books[index].count++
},
sub(index){
this.books[index].count--
},
remove(index){
this.books.splice(index,1)
}
}
computed:{
totalPrice(){<!--利用计算属性计算总价格-->
let totalPrice=0;
for(let i=0;i<this.books.length;i++){
totalPrice+=this.books.[i].price*this.books[i].quantity;
}
return totalPrice
}
}
filters:{
showPrice(price){
return '$'+price.toFixed(2);<!--保留两位小数,使用过滤器,可以自动传参,比methods方便-->
}
}
})
table{
border:1px solid #e9e9e9;
border-collapse: collapse;
border-spacing :0;
}
th, td{
padding: 8px 16px;
border:1px solid #e9e9e9;
text-align: left
}
th{
background-color:;
color:;
font-weight:;
}
10. JavaScript高阶函数(对于数组的方法)
10.1 filter
filter需要回调函数,它要求必须返回一个布尔值。当返回的是true,函数内部会自动把这次回调的n(数组里的每一个值或对象)加入到一个新的数组里。当返回的是false,函数内部不会把n加入新数组(过滤)。如果我们想把一个数组里小于100的值提取出来,可以利用filter这样写,比写for循环方便许多:
const nums = [10,20,50,110,220]
let newNums = nums.filter(function(n)({
return n < 100
}))
10.2 map
如果我们想对数组里的每一个元素进行操作,可以使用map函数。map函数也需要一个回调函数,与filter类似,它会把每一个回调的n按照我们的指令进行操作并加入到新的数组里。如果我们想要把上一个例子里的数组的元素值都乘以两倍,那么可以利用map这样写,比for循环方便许多:
let newNums2 = newNums.map(function(n){
return n * 2
})
10.3 reduce
reduce函数的作用是对数组中的所有内容进行汇总。
a.reduce(arg1,arg2)<!--arg2是初始值initialValue-->
a.reduce(function(preValue,n){<!--n就是数组里代表元素的变量-->
return 1
},0)
<!--第一次:preValue=0,n=20-->
<!--第一次:preValue=1,n=40-->
<!--第一次:preValue=1,n=80-->
let total = newNums2.reduce(function(preValue,n){
return preValue+n
},0)
<!--初始值设为0,每循环一次加一次n,而n刚好是数组里每一个值,从而实现累加操作-->
let total = this.books.reduce(function(preValue,book){
return preValue+book.price*book.quantity
},0)
<!--购物车案例里总价格的reduce函数实现-->
这几个函数,就体现了函数式编程的思想。
11. v-model
11.1 v-model基本使用
v-model用以实现表单元素和数据的双向绑定。如下的代码运行后,我们可以在网页中看到,虽然我们没有输入内容,但此时表单里已经有默认的值”hello“,当我们改动data里的message,表单里的内容也会随着改变。同时,当我们改动表单里的内容(输入我们想输入的内容),data里的message也会随之改变。这就是双向绑定,而不是v-bind那种单向绑定——单向绑定的情况下,只有改动data里的数据才会让网页元素改变,改变网页元素无法改变data里的数据。
<div id="app">
<input type="text" v-model="message">
{{message}}
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
}
})
</script>
v-model其实就是这两个语法的结合:1.v-bind绑定一个value属性。2.v-on指令给当前元素绑定input事件
<div id="app">
<input type="text" :value="message" @input="valueChange"><!--这里的valueChange不用加参数,遇到event事件会自动传参-->
{{message}}
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
methods:{
valueChange(event){
this.message = event.target.value
}
}
})
</script>
11.2 v-model结合radio类型
当我们在radio里想要让选项互斥,就要把input的name属性设置为相同的。而如果我们只是单纯地设两个相同的name,我们就无法获取其值(后端要用),所以就需要再在input标签里加入value属性。v-model就可以把name和value放到一起,比较便捷。
<div id="app">
<label for="male"><!--注意label for的值与input的id对应-->
<input type="radio" id="male" v-model="sex">男
</label>
<label for="female">
<input type="radio" id="female" v-model="sex">女
</label>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
sex:'男'<!--默认选择是男-->
}
})
</script>
11.3 v-model结合checkbox类型
checkbox分为单选框和多选框。我们分别举例:
单选框,比如说同意协议,且同意了才能进行下一步:(v-model具备了获得input的value属性的能力,理解时要注意这个)
<div id="app">
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">
</label>
<button :disabled="!isAgree">
next step
</button>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
isAgree:false//单选框对应布尔类型
}
})
</script>
多选框:
<div id="app">
<input type="checkbox" value="basketball" v-model="hobbies">basketball
<input type="checkbox" value="football" v-model="hobbies">football
<input type="checkbox" value="badminton" v-model="hobbies">badminton
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
hobbies:[]//多选框对应数组类型
}
})
</script>
11.4 v-model结合select类型
select下拉选择框也分为单选和多选。我们分别举例:
单选:
<div id="app">
<select v-model="fruit">
<option value="apple">apple</option>
<option value="orange">orange</option>
<option value="banana">banana</option>
</select>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
fruit:'apple'
}
})
</script>
多选:
<div id="app">
<select v-model="fruit" mutiple><!--多选只要加上mutiple-->
<option value="apple">apple</option>
<option value="orange">orange</option>
<option value="banana">banana</option>
</select>
</div>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
fruit:[]
}
})
</script>
11.5 v-model修饰符
11.5.1 lazy
默认情况下,v-model是在input事件中同步输入框的数据的。一旦有数据改变,data里的数据就会自动随之改变。但有时,我们不想让data里的数据这么快改变,那么就可以添加lazy修饰符,从而让数据在失去焦点(点击其他地方)或回车时才会更新。
<div id="app">
<input type="text" v-model.lazy="message">
</div>
11.5.2 number
当我们往input里输入数字,Vue会自动把这些数字当成是字符串。如果想要以数字的格式存储它,那就要添加number修饰符。
<div id="app">
<input type="text" v-model.number="age">
</div>
11.5.3 trim
当我们往input里输入内容时,有时会输入一些空格。当我们想要删去其中的空格时,就要添加trim修饰符。
<div id="app">
<input type="text" v-model.trim="message">
</div>
12. 组件化
如果我们把一个页面里所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,且不利于后期的管理、扩展。但如果我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
12.1 组件化实现和使用步骤
组件使用分成三个步骤:
1.创建组件构造器 Vue.extend()
2.注册组件 Vue.component()
3.使用组件
12.2 组件化的基本使用过程
首先创建组件构造器对象(注意这里template用的是tab键上方的引号来包含字符串):
<script src="../vue.js"></script>
<script>
const cpnConstructor = Vue.extend({
template:`
<div>
<h2></h2>
</div>`
})
</script>
然后注册组件:
Vue.component('my_cpn',cpnConstructor)
<!--第一个参数是组件名,第二个参数是我们之前创建的组件构造器对象名-->
注册完成之后,我们就可以这样使用组件:
<div id="app"><!--注意组件要放在挂载的实例之下-->
<my_cpn></my_cpn>
</div>
12.3 全局组件和局部组件
像12.2里的组件注册完成后就是全局组件。局部组件定义如下,它只能在对应的实例下(下例中的app)起作用:
<script src="../vue.js"></script>
<script>
const app = new Vue({
el:'#app',
components:{
cpn:cpnConstructor<!--组件名:构造器名-->
}
})
</script>
在开发里,局部组件比较常用。
12.4 父组件和子组件的区分
我们可以把一个组件b放到另一个组件a的构造器里去,这样组件a在构造时同时会构造组件b。而如果要使用这个组件,只需要注册一个组件a即可,因为组件b已被包含在其中。
但是要注意的是,如果这里我们在id为app的div下使用,网页会报错并不予显示。这是因为,一个组件要想被使用,要么是一个全局组件(在全局被注册),要么在局部的Vue里被注册。父组件里的子组件无法直接使用,要想使用可以在局部Vue的components里再加入子组件的注册。
<div id="app">
<cpn2></cpn2><!--会显示cpn1和cpn2里的内容-->
</div>
<script src="../vue.js"></script>
<script>
const cpnC1 = Vue.extends({
template:`
<div>
<h2>headline</h2>
</div>`
})
const cpnC2 = Vue.extend({
template:`
<div>
<h2>headline</h2>
</div>`,
components:{
cpn1:cpnC1
}
})
const app = new Vue({
el:'#app',
components:{
cpn2:cpnC2
}
})
</script>
12.5 注册组件的语法糖写法
注册组件的过程可能有些繁琐,为了简化这个过程,Vue提供了注册的语法糖,省去了Vue.extend()的步骤,直接用一个对象来代替。
<script src="../vue.js"></script>
<script>
const cpnC1 = Vue.extend({
template:`
<div>
<h2>headline</h2>
</div>`
})
Vue.components('cpn1',cpnC1)<!--普通写法-->
Vue.components('cpn1',{
template:`
<div>
<h2>headline</h2>
</div>`
})<!--全局组件语法糖,省去extend语句,把template直接写到第二个参数的位置,即直接把构造器的内容写到构造器的位置处-->
const app = new Vue({
el:'#app',
components:{
cpn2: {
template:`
<div>
<h2>headline</h2>
</div>`
}
}
})<!--局部组件的语法糖-->
</script>
12.6 组件模板的抽离写法
每次在Vue里写模板很麻烦,所以可以把模板抽离出去写,再在全局组件处注册即可,不过记得挂载template
<!--第一种写法-->
<script type="text/x-template" id="cpn">
<div>
...
</div>
</script>
<script src="../vue.js"></script>
<script>
Vue.component('cpn',{
template:'#cpn'
})
</script>
<!--第二种写法,个人感觉比较方便-->
<template id="cpn">
<div>
...
</div>
</template>
<script src="../vue.js"></script>
<script>
Vue.component('cpn',{
template:'#cpn'
})
</script>
12.7 组件中的数据存放问题
组件是一个单独功能模块的封装,这个模块有它自己的html模板,也有自己的data属性,组件中是不能直接访问Vue实例中(如挂载app的Vue)的data的。
其实,组件对象也有一个data属性,(也有methods等属性)只是它的data属性是个函数,而且这个函数返回一个对象,对象的内部保存着我们组件里要用到的数据。
<template id="cpn">
<div>
{{message}}
</div>
</template>
<script src="../vue.js"></script>
<script>
Vue.component('cpn',{
template:'#cpn',
data(){
return {
message:'hello'
}
}
})
</script>
为什么组件里的data必须是个函数呢?因为如果data是一个对象,那么每次调用组件,这些组件实例的数据就会相互影响,因为事实上这些实例的data对象都是同一个。改动一个组件实例里的数据会让其他组件实例里的数据跟着改变。而如果我们使用的是函数,函数每次返回一个对象,实质上是在每次调用这个data函数的时候,data函数都会创建一个新的对象并返回,所以实例之间相互不会影响。
12.8 父子组件的通信(难点)
子组件不能引用父组件或者Vue实例的数据。但是在开发中,往往一些数据确实需要从上层传递到下层。比如说在一个页面中,我们从服务器请求到了很多数据,其中一部分书不是由大组件来展示,而是由小组件展示(比如淘宝的商品列表等),这时,不会让子组件再次发送请求,而是让大组件(父组件)把数据传递给小组件(子组件)。
父子组件间的通信方式有两种:一是父组件通过props向子组件传递,二是子组件通过(自定义)事件向父组件发送消息。
12.8.1 父组件向子组件传递数据
下面是父组件向子组件通过props传递数据的一个例子:
<div id="app"><!--3.在要使用子组件的地方,往子组件里加入如下通过v-bind实现的代码,把子组件props里定义的属性和父组件里的data动态绑定-->
<cpn :childrenmovies="movies"></cpn>
</div>
<template id="cpn">
<div><!--4.最后就可以在子组件里使用父组件的数据了-->
{{childrenmovies}}
</div>
</template>
<script src="../vue.js"></script>
<script>
Vue.component('cpn',{
template:'#cpn',
props:['childrenmovies']<!--2.再在子组件里加入props属性,变量名字自定义,这里设置为字符串数组,方便传入多个父组件的data-->
data(){
return {
message:'hello'
}
}
})
const app = new Vue({
el:'#app',
data:{
movies:['海贼王','火影忍者']
}<!--1.先在父组件里写好要传送的数据,注意Vue实例可以算是所有组件的父组件(root)-->
})
</script>
props写成字符串形式可能有些复杂,我们还有更清晰的对象写法:
<script>
Vue.component('cpn',{
template:'#cpn',
props:['childrenmessages']<!--第一种写法,字符串形式-->
props:{
childrenmessages:{
type:String,
default:'aaa',
required:true<!--在使用子组件时必传的值,不传会报错-->
}
childrenmovies:{
type:Array,
default(){
return []
}
}<!--当传递的类型时数组或者对象时,我们的默认值不能直接写成数组或是对象,而是要写一个函数,返回对应的类型-->
}<!--第二种形式,对象形式,在对象里我们还能定义传入数据的类型和默认值,比较常用-->
data(){
return {
message:'hello'
}
}
})
</script>
要注意的是,我们在写子组件的props的属性时,不能直接用驼峰标识的形式写,而是要转换成”-“。比如:childMessage要写成child-message.
12.8.2 子组件访问父组件
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<button @click="btnClick">
button1
</button>
</div>
</template>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
components:{
cpn:{<!--子组件-->
template:'#cpn',
methods:{
btnClick(){
console.log(this.$parent);
console.log(this.$root);<!--直接访问根组件-->
}
}
}
}
})
</script>
13. 插槽(slot)——组件化高级运用
13.1 slot的基本使用
为每一次组件的使用定义更多的拓展性(每一次使用组件都在组件template的模板上拥有自己独特的地方)。
<div id="app">
<cpn><button>按钮</button></cpn><!--此时,slot的位置被一个按钮代替(不管你写多少东西都会代替到这个slot的位置)-->
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>
</h2>
</div>
<slot></slot><!--插槽-->
<slot><span>默认值</span></slot><!--可以直接往slot里加默认值,则使用组件时默认显示该默认值-->
</template>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
components:{
cpn:{
template:'#cpn'
}
}
})
</script>
比如,我们在写网页的导航栏时,通常把导航栏封装成为一个组件。但是,每个网页的导航栏又有所不同。这时,我们只需要把这个导航栏组件分成几个插槽即可。如下图:
总的来说,就是把共性的东西写入组件,不同的东西所在的位置,就预留为插槽。
13.2 具名插槽
<div id="app">
<cpn><span slot="left">左</span></cpn><!--可以通过具名插槽来指定替换哪一个插槽,不然会引起混乱-->
<cpn><button slot="middle">中</button></cpn>
</div>
<template id="cpn">
<slot name="left"></slot>
<slot name="center"></slot>
<slot name="right"></slot>
</template>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
components:{
cpn:{
template:'#cpn'
}
}
})
</script>
13.3 作用域
<div id="app">
<cpn v-show="isShow"></cpn><!--此时isShow为true,因为它在大的vue实例里(父组件)-->
</div>
<template id="cpn">
<p>
abc
</p>
<span v-show="isShow">def</span><!--此时isShow为false,因为它在小的组件里-->
</template>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
isShow:true<!--父级作用域-->
},
components:{
cpn:{
template:'#cpn',
data(){
return{
isShow:false
}
}
}
}
})
</script>
只能在自己的作用域里查找变量名。
13.4 作用域插槽
一句话总结作用域插槽:父组件替换插槽标签,内容由子组件决定。
我们可以看这个例子:子组件里有一个Language属性,子组件中默认以列表形式展示这个数组。但是当我们想要在父组件(大的div里)换一种方式展示数组时,我们不能直接用另一种方式替换slot,因为父组件无法直接使用子组件数据。所以要把子组件的数据绑定一个属性A(下面这个属性命名为data),然后再在父组件里用slot-scope这个属性,写{{slot.A}}就可以使用到子组件的数据。
<div id="app">
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join('*')}}</span><!--这里要求这个数组以*号分隔,而不是以列表形式呈现-->
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="Languages">
<ul>
<li v-for="item in Languages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="..."></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello',
},
components:{
cpn:{
template:'#cpn',
data(){
return{
Languages:['js','c++','python']
}
}
}
}
})
</script>
14. 模块化
14.1 闭包
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。
而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为模块模式(module pattern):
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter
的变量和名为 changeBy
的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter
变量和 changeBy
函数。
我们可以把它看成类似于Java的语法。Counter是一个类,而三个公共函数是它的private方法,privateCounter是它的private属性。
14.2 模块化原始雏形
由上面一小节可知,我们可以使用模块化来避免变量的冲突,具体操作如下:
var moduleA = (function(){
var age = 20
var flag = true
function sum(num1,num2){
return num1 + num2
}
var obj = {}//1.创建一个对象
obj.age=age//2.把这个模块里的属性和方法(函数)都赋值给这个对象
obj.flag=flag
obj.sum=sum
return obj//3.将这个对象返回,赋值给一个var变量moduleA,此时moduleA就是obj的一个引用,属性完全相同
})()
console.log(moduleA.age)//20
console.log(moduleA.sum(10,20))//30
14.3 模块化规范
幸运的是,前端模块化开发已经有了许多的规范以及对应的实现方案。比如commonJS、AMD、CMD、ES6的Modules
14.4 ES6的模块化实现
ES6中,最简单的导入导出这样实现:
假设同一个html文件有多个js文件需要引入:
html文件:
<script src="a.js" type="module"></script><!--type="module"说明把js文件当成模块化-->
<script src="b.js" type="module"></script>
js文件:
//a.js
var flag = true
function sum(num1,num2){
return num1 + num2
}
//第一种导出方式
export{ //export用以导出
flag,sum
}
//第二种导出方式,定义时直接导出
export var name = 'james';
export function mul(num1,num2){
return num1 * num2
}
export class Person(){
run(){
...
}
}
//b.js
import {flag} from "./a.js" //import用以导入
import {Person} from "./a.js"
import * as aa from "./a.js"//当需要导入的东西很多时,可以使用*,并为它取个名字(aa),这里其实实质上是把这些属性统合成了一个对象,aa就是它的名字,当需要用到属性的时候,用aa.+属性名就可以调用了
console.log(aa.name)
if(flag){
...
}
const p = new Person()
p.run()
15. Webpack
15.1 Webpack简介
Webpack简单来说,就是把我们项目里一些浏览器无法识别的文件(比如commonJS)打包,转换成浏览器可以识别的文件。
15.2 Webpack基本使用
我们项目的文件夹下有两个子文件夹:src(源码)和dist(发布),我们把使用模块化开发的js文件写好后,不能直接发布,因为浏览器无法解析我们的模块化(比如commonJS)。
所以,要通过
webpack ./src/main.js ./dist/bundle.js
这条命令,把src下的js文件打包成为dist下的bundle.js文件,然后再在html文件里引入bundle.js,即可实现模块化开发。
要注意的是,webpack会自动识别js文件之间的依赖,所以我们不必把每一个js文件都打包。(比如上面的这行命令,main.js里引入了a.js的导出,但我们不必在命令里写上a.js)。
15.3 其他准备工作
在创建完src、dist、index.html后,再要写一个webpack.config.js文件,最后通过npm init 命令创建package.json
webpack.config.js文件内容如下:
const path = require('path')//commonJS的语法
module.exports={
entry:'./src/main.js'
output:{
path:path.resolve(__dirname,'dist')//dirname是node里的一个全局变量
filename:'bundle.js'
}
}
这样,我们就不用每次都在命令里指定输出的文件,只要用webpack这一个简单的命令或者”npm run build“(项目中更常用,在package.json里的script里加一个build:webpack)就可以实现打包。
16. Vue CLI(脚手架)
如果要开发大型项目,那么必须要用到Vue CLI
使用脚手架,要先安装node、npm、webpack
16.1 Vue CLI3创建项目和目录结构
创建项目:vue create 项目名称
目录结构:
node modules:存放node的一些包(通过npm安装的)
public:类似于VueCLI2里的static,最后会原封不动地放入dist文件夹
src:源代码
跑项目:npm run serve
16.2 路由 vue-router
16.2.1 前端路由
前端路由中,整个项目只有一个index.html,通过router来决定选择哪些组件渲染,就不用写多个html文件,直接在index后加上/…即可。
前端路由的核心就是:改变url,但是页面不进行整体的刷新。
16.2.2 vue-router的安装配置
用以访问设定路径,将路径和组件映射起来。在vue-router的单页面应用中,页面路径的改变就是组件的切换。
安装vue-router: npm install vue-router --save
我们在脚手架中可以勾选router,这样在创建完项目后自动会生成一个router文件夹(里面有一个index.js)
为了后续方便自己加入组件,我们要自己学会配置router(在index.js里):
import VueRouter from 'vue-router'
import Vue from 'vue'
//配置路由相关信息
Vue.use(VueRouter)//1.通过Vue.use(插件)来安装插件,这里的VueRouter是一个插件
//2.创建VueRouter对象
const routes=[
{
},
]
const router = new VueRouter({
routes
})//配置路由和组件之间的关系
16.2.3 vue-router路由映射配置
首先要知道,写路由对应的组件,都是通过.vue文件来写的:
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home'
import Home from '../components/About'
Vue.use(VueRouter)//1.通过Vue.use(插件)来安装插件,这里的VueRouter是一个插件
const routes=[
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]//2.建立路径和组件联系
const router = new VueRouter({
routes
})//3.创建router实例
自己创建.vue文件(模板参见Helloworld.vue),比如这个Home.vue:
<template>
<div>
<h2>
homepage
</h2>
</div>
</template>
<script>
export default{
name:"Home"
}
</script>
<style scoped></style>
这样,我们就把router和对应的组件联系起来了。那么怎么跳转到对应的url,从而显示不同的组件呢?我们需要在App.vue里写上以下内容:
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<!--router-link类似于a标签-->
<router-view></router-view>
<!--router-view用于占位,表示切换过去的组件在页面的哪里显示出来-->
总结来说,使用vue-router的步骤有三步:
第一步:创建路由组件(.vue)
第二步:配置路由映射(组件和路径)(index.js)
第三步:使用路由:通过和
16.2.4 vue-router路由的默认值
默认情况下,我们刚进入一个网站时,总是希望显示网站首页,但是按照之前的写法,还得再点击一个a标签才行,默认没有显示首页组件。所以,我们需要重定向:
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home'
import About from '../components/About'
Vue.use(VueRouter)
const routes=[
{
path:'/',//path配置的是根路径: /
redirect:'/home'
},//redirect就是指:当进入这个网页时,默认重定向至某某组件
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]
const router = new VueRouter({
routes
})
17. axios
前端需要发送网络请求到服务器请求后端数据,这时就需要用到axios框架。
17.1 axios基本使用
首先,在vue项目里安装axios:npm install axios --save
然后,在main.js中,引入axios:
import axios from 'axios'
new Vue({...})
引入axios后,就可以直接引用它。只要往axios里传入相关的网络配置即可,而config一般是对象类型:
axios(config)
axios({
url:'http://39.97.183.73:8000/home/data'//默认get方式,也可以用methods来换其他方式(下图)
}).then(res => {
console.log(res)
})//单一参数函数的简便写法,相当于function(res){console.log(res)}
axios.request(config)
axios.get(url[,config])
axios.delete(url[,config])
axios.head(url[,config])
axios.post(url[,data[,config]])
17.2 axios发送并发请求
使用axios.all,可以放入多个请求的数组。axios.all([])返回的是一个数组,使用axios.spread可以把[res1,res2]展开为res1,res2
axios.all([axios({
url:''
}),axios({
url:''
})]).then(results => {
console.log(results[0]);
console.log(results[1])
})//第一种写法
axios.all([axios({
url:''
}),axios({
url:''
})]).then(axios.spread((res1,res2) =>{
console.log(res1);
console.log(res2)
}))//第一种写法
17.3 axios实例
在工作中,一个项目的不同页面往往被放在不同的服务器上。所以,使用全局的axios会有所不便。这时就需要用到axios的实例:
const instance1 = axios.create({
baseURL:'',
timeout:5000
})
const instance2 = axios.create({
baseURL:'',
timeout:1000
})//不同的实例可以设置独立的配置
instance1({
url:'/home'
}).then(res=>{
console.log(res)
})
instance({
url:''
}).then(res=>{
console.log(res)
})
17.4 模块封装
当我们的组件(.vue)文件多起来后,我们最好不要在每个.vue文件里都import axios,这样对第三方框架的依赖太大了——当这个框架不维护后,我们要一个个文件改动,很麻烦。
为了解决这个可能出现的问题,我们不妨在项目的src文件夹里新创建一个network文件夹,并在里面新建一个request.js,用来当作框架和我们项目的一个“中介”,内容如下:
import axios from 'axios'
export function request(config){
const instance = axios.create({//1.创建axios实例
baseURL:'',
timeout:1000
})
//2.发送网络请求
return instance(config)
}
在.vue文件里这样写:
import {request} from './network/request'
request({
url:'/home',
}).then(res=>{
...
})//把res返回到要处理的地方
17.5 拦截器
用于每次发送请求或得到响应后,做出相应处理。
instance.interceptors.request.use(config=>{//请求拦截
console.log()
return config//必须返回,不然就彻底拦下来了传不下去了,我们只是为了处理
},err=>{
})
instance.interceptors.reponse.use(config=>{//响应拦截
console.log()
},err=>{
})
new Vue({...})
引入axios后,就可以直接引用它。只要往axios里传入相关的网络配置即可,而config一般是对象类型:
axios(config)
axios({
url:'http://39.97.183.73:8000/home/data'//默认get方式,也可以用methods来换其他方式(下图)
}).then(res => {
console.log(res)
})//单一参数函数的简便写法,相当于function(res){console.log(res)}
axios.request(config)
axios.get(url[,config])
axios.delete(url[,config])
axios.head(url[,config])
axios.post(url[,data[,config]])
更多推荐
所有评论(0)