基础知识整理
一、邂逅Vue.js
1.1. 认识Vue.js
- Vue的读音:(/vju:/,类似于view)
- Vue的渐进式
- 渐进式意味着你可能将Vue作为你应用的一部分嵌入其中,带来更丰富的交互体验
- 或者如果你希望将更多的业务逻辑使用Vue实现,那么Vue的核心库以及其生态系统比如Core+Vue-router+Vuex,也可以满足你各种各样的需求
- Vue的特点
- 解耦视图和数据
- 可复用的组件:一个组件可以在多个页面 里面用
- 前端路由技术
- 状态管理
- 虚拟DOM
1.2. 安装Vue
- CDN引入
- 下载引入
- 下载引入
- NPM安装
1.3.Vue初体验
1.3.1 Hello Vue.js
01-HelloVuejs.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
Hello {{message}}
</div>
<script src="./js/vue.js"></script>
/*
数据和界面编写分离
*/
<script>
//编程范式:声明式编程
const app = new Vue ({
//用于挂载要管理的元素
el: '#app',
//定义数据
data :{
message : 'Vuejs'
}
})
</script>
//元素js的做法(编程范式:命令式编程)
/*
1.创建div元素,设置id属性
2.定义一个变量叫message
3.将message变量防止前面的div元素中显示
*/
</body>
</html>

</script>
</body>
</html>
运行结果
-
mustache 体验Vue响应式
-
Vue列表展示
-
movies:[‘大话西游’,‘星际穿越’,‘少年派’,‘违背’]
-
v-for
-
{{movies}}
- [ “大话西游”, “星际穿越”, “少年派”, “违背” ]
-
<li v-for="item in movies">{{item}}</li>
- 大话西游
- 星际穿越
- 少年派
- 违背
-
-
后面的数组追加元素的时候,新的元素也可以在界面上渲染出来,并且是响应式的
- app.movies.push(‘同心难改’);
-
1.3.2 Vue计数器小案例
03-vue案例-计数器.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>当前计数: {{counter}}</h2>
<!-- <button v-on:click="counter++">+</button>
<button v-on:click="counter--">-</button>-->
//v-on:click 等价于 @click
<button v-on:click="increment">+</button>
<button v-on:click="decrement">-</button>
</div>
<script src="./js/vue.js"></script>
<script>
//proxy
const obj = {
counter: 0
}
const app = new Vue({
el: '#app',
data: obj,
methods: {
increment: function () {
this.counter++;
},
decrement: function () {
this.counter--;
}
},
beforeCreate: function(){
},
created: function(){//一般进行网络请求
console.log("created");
},
mounted: function(){
console.log("mounted");
}
})
//1.拿button元素
//2.添加监听事件
</script>
</body>
</html>
运行结果
- 事件监听:v-on:click=“” 或者@click
- 该指令用于监听某个元素的点击事件,并且需要指定当发生点击时,执行的方法,方法通常是methods中定义的方法
- 语法糖:@click是v-on:click的语法糖
1.4.MVVM
-
Model(JS)+View(DOM) +ViewModel(VUE)
-
View层
- 视图层
- 在我们前端开发中,通常就是DOM层
- 主要的作用是给用户展示各种信息
-
Model层
- 数据层
- 数据可能是我们固定的数据,更多的是来自我们服务器,从网络上请求下来的数据
- 在我们计数器的案例中,就是后面抽取出来的obj,当然,里面的数据可能没有这么简单
-
ViewModel层
- 视图模型层
- 视图模型层是view和Model沟通的桥梁
- 一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时的反应到View中
- 另一个方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data
1.5.创建Vue时,options中的要素
-
el:
document.querySelector(HTMLElement)
类型:String | HTMLElement
作用:决定之后Vue实例会管理哪一个DOM
-
data:
类型:Object | Function(组件中必须是一个函数)
作用:Vue实例对应的数据对象
-
methods:
类型:{[key:string]: Function}
作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用
-
方法和函数
- 方法:methods,和某一个实例相挂钩的
- 函数:funcation
-
生命周期函数
- 生命周期:事物从诞生到消亡的整个过程
- git: debug:开发版本 release:发布版本
beforeCreate create mounted
callHook() 钩子函数
二、插值语法
- 插值操作:定义了一个数据,希望把这个数据C插入到DOM里面
2.1 mustache语法
-
mustache语法中不仅仅可以直接写变量,也可以写简单的表达式
01-Mustache语法.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <h2>{{ message }} </h2> <h2>{{ message }},李银河 </h2> <!-- mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式--> <h2>{{ firstName + lastName }}</h2> //字符串拼接 <h2>{{ firstName + ' ' + lastName }}</h2> <h2>{{ firstName}} {{lastName}}</h2> //算术表达式 <h2>{{ counter * 2}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!', firstName: 'Kobe', lastName: 'kim', counter: 100 } }) </script> </body> </html>
运行结果
2.2 v-once
-
在开发中,有时候只需要第一次的时候去改变,之后在改变
-
该指令后面不需要跟任何表达式(比如v-for后面是要跟表达式的)
-
该指令表示元素和组件只渲染一次,不会随着数据的改变而改变
02- v-once的使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <h2>{{ message }}</h2> <h2 v-once>{{ message }}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!' } }) </script> </body> </html>
运行结果
响应式的数据 加了v-once之后 不会发生改变
2.3 v-html
-
某些情况下,我们从服务器请求到的数据本身就是一个HTML代码,如果我们直接通过{{}}来输出,会将HTML代码也一起输出
-
此时,我们可以使用v-htm指令
- 该指令后面往往会跟上一个String类型
- 会将String的html格式进行解析,并且显示对应的内容
03-v-html的使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <h2 v-html="url"></h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!', url: '<a href ="http://www.baidu.com">百度一下</a>' } }) </script> </body> </html>
2.4 v-text
-
v-text作用和Mustache比较相似:都是用于将数据显示在页面中
-
v-text通常情况下,接受一个string类型
04-v-text的使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> //v-text的使用方式没有mustache语法灵活 <h2>{{ message }},李银河</h2> <h2 v-text="message">,李银河</h2> /*前端显示 你好!,李银河 你好! */ </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!' } }) </script> </body> </html>
2.5 v-pre
-
v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> //mustache语法会解析大括号中的内容 <h2>{{ message }}</h2> //v-pre 可以把标签中的内容原封不动的展示 <h2 v-pre>{{ message }}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!' } }) </script> </body> </html>
运行结果
2.6 v-cloak
-
在某些情况下(可能页面卡顿 mustache标签未解析,可能直接显示{{XXX}}文本),我们浏览器可能会直接显示出未编译的Mustache标签
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> [v-cloak]{ display :none; } </style> </head> <body> <div id="app" v-cloak> {{ message }} </div> <script src="../js/vue.js"></script> <!-- 在vue解析之前,div中有一个v-cloak属性 在vue解析之后,div中没有v-cloak属性 --> <script> setTimeout(function () { const app = new Vue({ el: '#app', data: { message: '你好!' } }) },1000) </script> </body> </html>
三、动态绑定属性
mustache语法用于标签的内容
<h1>{{XXX}}</h1>
若图片标签需要动态的修改路径 需要动态绑定
<img src="imgurl" alt="">
改成 此时src是动态绑定的
<img v-bind:src="imgurl" alt="">
v-bind:class=“{}” {}表示是对象 {key1:value1, key2:value2}
key可以不加单引号 value如果不加单引号会解析成变量,如果value是字符需要加单引号
<h2 :style="{fontSize:'50px'}"></h2> 另一种写法 <h2 :style="{'font-size':'50px'}"></h2> <h2 class="title" v-bind:class="{active: isActive, line :isLine}">
3.1 v-bind绑定基本属性
-
前面学习的指令主要作用是将值插入到我们模板的内容当中
-
但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定
- 比如动态绑定a元素的href属性
- 比如动态绑定img元素的src属性
-
这个时候,我们可以使用v-bind指令:
- 作用:动态绑定属性
-
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值,在开发中,图片的链接src、网站的链接href,动态绑定的一些类、样式等等
语法糖简写 v-bind: 缩写 :
-
v-bind:str可简写为:str
-
v-bind:href可简写为: href
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <img v-bind:src="imgurl" alt=""> <a v-bind:href= "aHref">百度一下</a> //语法糖 <img :src="imgurl" alt=""> <a :href= "aHref">百度一下</a> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!', imgurl:'http://bpic.588ku.com/back_list_pic/21/08/04/407487b62b3857e7281d7cde47df4b35.jpg!/fh/300/quality/90/unsharp/true/compress/true', aHref:'http://www.baidu.com' } }) </script> </body> </html>
运行结果
3.2 v-bind动态绑定class
3.2.1 对象语法:Class后面跟的是一个对象
-
基本绑定
<h2 class="active">{{message}}</h2>
-
动态绑定(多此一举)
<h2 :class="active">{{message}}</h2>
-
后面跟对象(title是固定的,active和line是不固定的)
v-bind:class="{}" {}表示是对象 {key1:value1, key2:value2} <h2 v-bind:class="{类名1: true, 类名2 :boolean}">{{message}}</h2> //title是固定样式 active是可能修改的样式 <h2 class="title" v-bind:class="{active: isActive, line :isLine}">{{message}}</h2> <button @click="btnClick">按钮</button> ... btnClick:function(){ this.isActive = !this.isActive }
-
如果过于复杂,可以放在一个methods或者computed中
<h2 class="title" v-bind:class="getClasses()">{{message}}</h2> getClasses : function() { return {active: this.isActive, line :this.isLine}; }
02-v-bind动态绑定class属性(对象语法).html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .active{ color: red; } </style> </head> <body> <div id="app"> <!-- <h2>{{ message }}</h2>--> <!-- <h2 class="active">{{message}}</h2>--> <!-- <h2 :class="active">{{message}}</h2>--> <!-- <h2 v-bind:class="{key1: value1, key2 :value2}">{{message}}</h2> <h2 v-bind:class="{类名1: true, 类名2 :boolean}">{{message}}</h2>--> <h2 class="title" v-bind:class="{active: isActive, line :isLine}">{{message}}</h2> <h2 class="title" v-bind:class="getClasses()">{{message}}</h2> <button v-on:click="btnClick">按钮</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!', isActive:'active', isLine: true }, methods:{ btnClick : function(){ this.isActive=!this.isActive; }, getClasses : function(){ return {active: this.isActive, line :this.isLine}; } } }) </script> </body> </html>
3.2.2 数组语法:Class后面跟的是一个数组
-
可以传入多个值 (样式较多时使用)
<h2 class="title" :class ="[active, line]">{{ message }}</h2>
-
如果过于复杂,可以放在一个methods或者computed中
<h2 class="title" :class ="getClasses()">{{ message }}</h2> methods: { getClasses: function () { return [this.active, this.line]; } }
03-v-bind动态绑定class属性(数组语法).html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .active{ color: red; } </style> </head> <body> <div id="app"> //active不加单引号表示是变量 若加上单引号就是字符串 字符串文字为active <h2 :class="[active, isLine]">{{message}}</h2> <h2 class="title" v-bind:class="[active, line]">{{message}}</h2> <h2 class="title" v-bind:class="getClasses()">{{message}}</h2> <button v-on:click="btnClick">按钮</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!', active:'aaaa', line:'bbbb' }, methods:{ getClasses : function(){ return [this.active, this.line]; } } }) </script> </body> </html>
3.3 v-bind动态绑定style
3.3.1 对象语法
- style后面跟的是一个对象类型,对象的key四CSS属性名称;对象的value是具体赋的值,值可以来自于data中的属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--<h2 :style="{key(css属性名):value(属性值)}">{{ message }}</h2>-->
<!--'50px'必须加上单引号,否则是当做一个变量去解析-->
<!--<h2 :style="{fontSize: '50px'}">{{ message }}</h2>-->
<!--finalSize当做一个变量使用-->
<h2 :style="{fontSize: finalSize+'px',color: finalColor}">{{ message }}</h2>
<h2 :style="getStyles()">{{ message }}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data:{
message: '你好!',
finalSize: 100,
finalColor: 'red'
},
methods: {
getStyles : function(){
return {fontSize: this.finalSize+'px',color: this.finalColor};
}
}
})
</script>
</body>
</html>
运行结果
3.3.2 数组语法
-
style后面跟的是一个数组类型,多个值以","分割即可
<h2 :style="[baseStyle,baseStyle1]">{{ message }}</h2> data:{ message: '你好!', baseStyle: { backgroundColor: 'red'}, baseStyle1: {fontSize : '100px'} }
06-v-bind动态绑定style(数组语法).html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--finalSize当做一个变量使用--> <h2 :style="[baseStyle,baseStyle1]">{{ message }}</h2> <h2 :style="getStyles()">{{ message }}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!', baseStyle: { backgroundColor: 'red'}, baseStyle1: {fontSize : '100px'} }, methods: { getStyles : function(){ return [baseStyle: this.baseStyle,baseStyle1: this.baseStyle1]; } } }) </script> </body> </html>
四、计算属性
4.1 计算属性的基本属性
-
在模板中可以直接通过插值语法显示一些data中的数据,但是在某些情况下,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示,比如我们有firstName和lastName两个变量,我们需要显示完整的名称,但是如果多个地方都需要显示完整的名称,我们就需要写多个{{firstName}}{{lastName}},此时,我们便可以使用计算属性
01-计算属性的基本使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <h2>{{ firstName + ' ' +lastName }}</h2> <h2>{{ firstName}} {{ lastName }}</h2> //h2尖括号之间放的是内容,所以调用函数不太适合,属性更加合适 <h2>{{ getFullName() }}</h2> <h2>{{ fullName }}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ firstName: 'Kobe', lastName: 'Kim' }, //computed: 计算属性 computed: { //此时的fullName是一个属性名 本质是实现了属性的get方法 fullName :function(){ return this.firstName + ' ' +this.lastName; } }, methods: { getFullName(){ return this.firstName + ' ' +this.lastName; } } }) </script> </body> </html>
运行结果
4.2 计算属性的复杂操作
02-计算属性的复杂操作.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
{{ totalPrice }}
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data:{
books: [
{id :110, name: 'Unix编程艺术',price :119},
{id :111, name: '代码大全',price :105},
{id :112, name: '深入理解计算机原理',price :98},
{id :113, name: '现代操作系统',price :87},
]
},
computed:{
totalPrice : function (){
let result =0;
for (let i=0; i<this.books.length;i++){
result += this.books[i].price;
}
return result;
}
//ES6
/* for (let book in this.books){
result += book.price;
}
for (let book of this.books){
result += book.price;
}*/
}
})
</script>
</body>
</html>
运行结果
-
补充ES6
for (let i in this.books) for (let book of this.books)
4.3 计算属性的getter和setter
-
每一个计算属性都包含一个getter和setter
03-计算属性的setter和getter.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> {{ fullName }} </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ firstName: 'Jennie', lastName: 'Kim' }, computed: { /* fullName本质是一个属性,不是一个函数 此时的function(){}是一种简写模式,等价于fullName属性的get方法 fullName :function (){ return this.firstName + ' ' + this.lastName } */ fullName : { //计算属性一般是没有set方法的, 一般只具备 只读属性 /* 可以在cosole中输入 app.fullName='Lebron James' */ set : function (newValue){ console.log("-----------"+newValue); const names = newValue.split(' '); this.firstName = names[0]; this.lastName = names [1]; }, get : function () { return this.firstName + ' ' + this.lastName } } } }) </script> </body> </html>
运行结果
4.2 计算属性和methods对比
-
计算属性在多次使用时,只会调一次,它是有缓存的,methods在多次使用时,会调用多次,是没有缓存的,性能较低
-
优先使用计算属性,‘空间换了时间’
04-计算属性和methods的对比.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--1.直接拼接:语法过于繁琐--> <h2>{{ firstName }} {{lastName}}</h2> <!--2.通过定义methods 调用4次,性能低--> <h2>{{ getFullName() }}</h2> <h2>{{ getFullName() }}</h2> <h2>{{ getFullName() }}</h2> <h2>{{ getFullName() }}</h2> <!--3.通过计算属性 调用1次,性能高--> <h2>{{ fullName }}</h2> <h2>{{ fullName }}</h2> <h2>{{ fullName }}</h2> <h2>{{ fullName }}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ firstName: 'Jennie', lastName: 'Kim' }, methods: { getFullName() { console.log('getFullName'); return this.firstName + ' ' + this.lastName } }, computed: { fullName: function () { console.log('fullName'); return this.firstName + ' ' + this.lastName } } }) </script> </body> </html>
运行结果
五、ES6补充
5.1 let/var
- ES5中的var是没有作用域的,ES6中的var是有块级作用域的
- ES5之前因为 if 和 for 都没有块级作用域的概念,所以在很多时候,我们必须借助于funcation(函数)的作用域来解决应用外面变量的问题,ES6中,加入了let,他是有 if 和 for 的块级作用域的
//没有块级作用域
if(true)
{
var name = 'wlf';
func = function(){
console.log(name);
}
}
//块级作用域失效,name在外面还能继续使用
name = 'kobe'
//func本来只是想打印if中的name信息 但是由于没有块级作用域 name在外面被修改了
func()
//var变量在for块级中没有块级作用域引起的问题
var btns = document.getElementsByTagName('button');
for(var i = 0; i<btns.length; ++i)
{
btns[i].addEventListener('click',function(){
console.log('第'+i+'个按钮被点击');
})
}
/*
只会显示第5个按钮被点击
因为var没有作用域,所以最后一次var赋值为5 btn的i被for循环修改了
*/
//var在函数中有作用域,使用闭包来解决问题,修改为
for(var i = 0; i<btns.length; ++i)
{
(function(i){
btns[i].addEventListener('click',function(){
console.log('第'+i+'个按钮被点击');
})
})(i)
}
5.2 Const
在ES6开发中,优先使用const,只有需要改变某一个标识符的时候才使用let
-
- 一旦给const修饰的标识符被赋值后,不能修改
-
- 在使用const定义标识符,必须进行赋值
const name; //× 必须初始化
-
- 常量的含义是指向的对象不能修改,但是可以改变对象内部的属性
const obj = { name : 'Kobe', age : 18, height :1.88 } console.log(obj); obj.name ='Kobe'; obj.age = 10, obj.height='1.88'
5.3 对象字面量的增强写法
-
属性的增强写法
const name = 'why'; const age = '18'; const height = '1.88'; //ES5 的写法 const obj = { name : name, age : age , height : height } //ES6的写法 /* ES6会自动 以name字符作为key 以name对应的值作为value */ const obj = { name, age , height } console.log(obj);
-
函数的增强写法
//ES5的增强写法 const obj = { name : name, ... run : function () { console .log ('在本派'); }, eat : function () { console.log ("在吃东西"); } } //ES6的增强写法 const obj = { name, run () { }, eat () { }
六、事件监听
前端开发需要经常和用户交互
6.1 v-on的基本使用
-
v-on介绍
-
作用:绑定事件监听器
-
缩写:@
-
预期:Function | Inline Statement | Object
-
参数:event
-
语法糖 v-on: 等价于 @; v-bind: 等价于 :
01-v-on的基本使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> {{ counter }} <!-- <button v-on:click="increment">+</button> <button v-on:click="decrement">-</button>--> <button @click="increment">+</button> <button @click="decrement">-</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ counter : 0 }, methods :{ //ES6函数增强写法 increment () { this.counter++; }, decrement (){ this.counter--; } } }) </script> </body> </html>
-
6.2 v-on的参数问题
02-v-on的参数问题.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--1. 事件调用的方法没有参数-->
<button @click="btn1Click()">按钮1</button>
<button @click="btn1Click">按钮1</button>
<!--2. 在事件定义时,写方法时省略了小括号,但是方法本身是需要一个参数的,
这个时候,Vue会默认将浏览器生产的event事件对象作为参数传入到方法-->
<!--button @click="btn1Click(123)>按钮2</button>-->
<!--<button @click="btn2Click()">按钮2</button>-->
<button @click="btn2Click">按钮2</button>
<!-- 3. 方法定义时,我们需要event对象,同时又需要其他参数-->
<!-- 在调用方法,如何手动的获取到浏览器参数的event对象 :$event-->
<button @click="btn3Click('abc',$event)">按钮3</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data:{
message: '你好!'
},
methods:{
btn1Click (){
console.log("btnClick");
},
btn2Click (event){
console.log("----------"+event);
},
btn3Click (abc,event){
console.log("----------"+event+"+++++++++++++++"+abc);
}
}
})
//如果函数需要参数,但是没有传入,那么函数的形参位undefined
function abc(name){
console.log(name);
}
abc();
</script>
</body>
</html>
-
事件调用的方法没有参数参数传递进去
- 如果该方法不需要额外参数,那么方法后的()可以不添加,如果方法本身有一个参数,那么会默认将原生事件event传递进去
<button @click="btn1Click()">按钮1</button> <button @click="btn1Click">按钮1</button>
-
事件调用的方法有event事件参数
<!--在事件定义时,写方法时省略了小括号,但是方法本身是需要一个参数的,这个时候,Vue会默认将浏览器生产的event事件对象作为参数传入到方法--> <!--button @click="btn1Click(123)>按钮2</button>--> <!--<button @click="btn2Click()">按钮2</button>--> <button @click="btn2Click">按钮2</button>
-
事件调用的方法有event对象和其他参数
<!-- 方法定义时,我们需要event对象,同时又需要其他参数--> <!-- 在调用方法,如何手动的获取到浏览器参数的event对象 :$event--> <button @click="btn3Click('abc',$event)">按钮3</button>
6.3 v-on修饰符
-
stop:阻止冒泡 调用event.stopPropagation()
-
prevent:阻止默认行为 调用event.preventDefault()
-
enter:监听键盘的键帽的点击
-
once:只触发一次回调
-
native:监听组件根元素的原生事件
03-v-on修饰符.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!-- 1.stop修饰符的使用,阻止冒泡 若是没有加stop修饰,点击 div时 divClick和btnClick都会触发 实际应用场景,希望点击按钮以外的div触发divClick,点击按钮触发btnClick --> <div @click="divClick"> aaaaaaaa <button @click.stop="btnClick">按钮</button> </div> <!--2.prevent修饰符的使用,阻止默认行为--> <br> <form action="baidu"> <input type="submit" value="提交" @click.prevent="submitClick"></input> </form> <!--3.监听键盘的键帽的点击 @keyup是监听所有的按键 .enter是监听按下enter键的事件--> <input type="text" @keyup.enter ="keyUp"></input> <!--4.once修饰符的使用 只触发一次回调--> <button @click.once="btn2Click">按钮</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!' }, methods:{ divClick(){ console.log("divClick"); }, btnClick(){ console.log("btnClick"); }, submitClick(){ console.log("submitClick"); }, keyUp (){ console.log("keyUp"); }, btn2Click () { console.log("btn2Click"); } } }) </script> </body> </html>
七、条件判断
7.1 v-if的使用.html
01-v-if的使用.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2 v-if="isShow">{{ message }}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data:{
message: '你好!',
isShow : 'true'
}
})
</script>
</body>
</html>
运行结果
7.2 v-if和v-else结合使用.html
02-v-if和v-else结合使用.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2 v-if="isShow">
<div>abc</div>
<div>abc</div>
<div>abc</div>
<div>abc</div>
<div>abc</div>
{{ message }}
</h2>
<h2 v-else>
<div>isShow为fasle时,显示</div>
</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data:{
message: '你好!',
isShow : false
}
})
</script>
</body>
</html>
7.3 v-if/v-else-if/v-else的使用
-
Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
-
v-if的原理
- v-if后面的条件的为false时,对应的元素以及其子元素不会渲染,也就是根本没有不会有对应的标签出现在DOM中
03-v-if和v-else-if和v-else的使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <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> <h2>{{result}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ score:99 }, computed:{ //推荐使用这种方式 result (){ let showMessage=''; if(this.score>=90){ showMessage="优秀" }else if(this.score>=80){ showMessage="良好" }else if(this.score>=60){ showMessage="及格" }else{ showMessage="不及格" } return showMessage } } }) </script> </body> </html>
运行结果
登录小案例
-
如果我们在有输入内容的情况下,切换了类型,我们会发现文字依然显示之前的输入内容
-
这是因为Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素
-
如果我们不希望Vue出现类似重复利用的问题,可以给对应的input添加key,并且我们需要保证Key的不同
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> /* input 默认情况下会复用,输入了账户跳转到邮箱,之前输入的账户信息还在 如果不想复用可以指定key key不同的话就会有两个输入框 */ <div id="app"> <span v-if="isUser"> <label for="username">用户账号</label> <input type="text" id="username" placeholder="用户账号" key="username"> </span> <span v-else> <label for="Email">用户邮箱</label> <input type="text" id="Email" placeholder="用户邮箱" key="email"> </span> <button @click="isUser =!isUser">切换类型</button> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ isUser: true } }) </script> </body> </html>
运行结果
7.4 v-show的使用
-
v-show和v-if 的区别
- v-if当条件为false时,压根不会有对应的元素在DOM中
- v-show当条件为false时,仅仅是将元素的display属性设置为none而已
- 当需要在显示和隐藏之间切换很频繁时,使用v-show;当只切换一次时,通过使用v-if
06-v-show的使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--v-if:当条件为fasle时,包含v-if指令的元素,根本不会存在dom中--> <h2 v-if="isShow" id="aaa">{{ message }}</h2> <!--v-show:当条件为fasle时,v-show只是给我们的元素添加一个行内样式,display:none--> <h2 v-show="isShow" id="bbb">{{ message }}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!', isShow : true } }) </script> </body> </html>
运行结果
八、循环遍历
8.1 v-for遍历数组
-
v-for的语法类似于JavaScript中的for循环,格式:item in items的形式
-
如果在遍历的过程中不需要使用索引值
- v-for =“movie in movies”,依次从movies中取出movie,并且在元素的内容中,我们可以使用Mustache语法,来使用movies
-
如果在遍历的过程中,我们需要拿到元素在数组的索引
- v-for=(item,index) in items,其中index就代表了取出的item在原数组的索引值
01-v-for遍历数组.html
index索引使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--在遍历过程中,没有使用下标值--> <ul> <li v-for="item in names">{{item}}</li> </ul> <!--在遍历过程中,获取下标值--> <ul> <li v-for="(item ,index) in names">{{index+1}}.{{item}}</li> </ul> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ names:['why','no','yes','key'] } }) </script> </body> </html>
运行结果
8.2 v-for遍历对象
-
在遍历对象的过程中,如果只是获取一个值,那么获取的是value
<ul> <li v-for ="item in info">{{item}}</li> </ul>
-
获取key和value,格式:(value,key)
<ul> <li v-for ="(value,key) in info">{{value}}-{{key}}</li> </ul>
-
获取key、value和index,格式:(value, key, index)
<ul> <li v-for ="(value,key,index) in info">{{value}}-{{key}}-{{index}}</li> </ul>
02-v-for遍历对象.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--在遍历对象的过程中,如果只是获取一个值,那么获取的是value-->
<ul>
<li v-for ="item in info">{{item}}</li>
</ul>
<!--2.获取key和value,格式:(value,key)-->
<ul>
<li v-for ="(value,key) in info">{{value}}-{{key}}</li>
</ul>
<!--3.获取key和value和index,格式:(value,key,index)-->
<ul>
<li v-for ="(value,key,index) in info">{{value}}-{{key}}-{{index}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data:{
info:{
name: 'kk',
age:18,
height:168
}
}
})
</script>
</body>
</html>
运行结果
8.3 组件key属性
数组渲染之后,需要在中间插入元素
有了key,虚拟dom会先把key和元素本身内容对比有没有改变,如果没有改变,不需要修改,否则就插入新的到对应位置。少了位移操作
### 8.4 数组的方法是响应式的
响应式是指 数组发生变化的时候,界面也会发生对应的修改
-
通过索引值修改数组的元素不是响应式的
//通过索引修改 数组会发生改变,但是界面不会相应 this.letters[0] = 'bbbb' //若想要界面响应可以修改为 this.letters.splice(0,1,'bbb'); Vue.set(this.letters, 0, 'bbb'); //Vue内部提供的方法
-
push():数组最后添加元素
this.letters.push('aaa') this.letters.push('aaa','bbb','vvv')
-
pop():删除数组中的最后一个元素
this.letters.pop();
-
shift():删除数组中的第一个元素
this.letters.shift();
-
unshift():给数组最前面添加元素
this.letters.unshift('aaa'); this.letters.unshift('aaa','bbb','vvv')
-
splice()
-
删除元素:第二个参数传入你要删除几个元素(如果没有传,就删除所有的元素)
this.letters.splice(1,2) //从index为1的位置开始,删除2个元素
-
替换元素:第二个参数,表示我们要替换几个元素,后面是用于替换前面的元素
this.letters.splice(1, 3, 'a','b','c') //从index为1的位置开始, 替换3个元素为 a,b,c
-
插入元素:第二个参数,传入0,并且要跟上插入的元素
this.letters.splice(1,0, 'a','b','c') //从index为1的位置开始,插入3个元素a,b,c
-
-
sort():排序
this.letters.sort();
-
reverse():反转
this.letters.reverse();
九、书籍购物车案例
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<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>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for ="(item ,index) in books">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.data}}</td>
<!--
<td>{{getFinalPrice(item.price)}}</td>
-->
//过滤器的使用
<td>{{item.price| showPrice}}</td>
<td>
//v-bind:disable="true"
<button @click="decrement(index)" v-bind:disabled="item.count<=1">-</button>
{{item.count}}
<button @click="increment(index)">+</button>
</td>
<td>
<button @click ="removeHandle(index)">移除</button>
</td>
</tr>
</tbody>
</table>
<h2>总价格:{{totalPrice | showPrice}}</h2>
</div>
<h2 v-else>购物车为空</h2>
</div>
<script src="../js/vue.js"></script>
<script src="main.js"></script>
</body>
</html>
main.js
const app = new Vue({
el: '#app',
data:{
books:[
{
id:1,
name :'《算法导论》',
data:'2006-9',
price:85.00,
count:1
},
{
id:2,
name :'《UNIX编程艺术》',
data:'2006-2',
price:59.00,
count:1
},
{
id:3,
name :'《编程艺术》',
data:'2008-10',
price:39.00,
count:1
},
{
id:4,
name :'《代码大全》',
data:'2006-3',
price:128.00,
count:1
},
]
},
methods:{
getFinalPrice(price){
return '¥' + price.toFixed(2)
},
increment(index){
this.books[index].count++
},
decrement(index){
this.books[index].count--
},
removeHandle(index){
this.books.splice(index,1)
}
},
filters:{
showPrice(price){
return '¥' + price.toFixed(2)
}
},
computed:{
totalPrice(){
//1.普通的for循环
/*
let totalPrice=0;
for(let i =0;i<this.books.length;i++){
totalPrice+=this.books[i].price*this.books[i].count
}
return totalPrice;
*/
//2. for (let i in this.books)
/*
let totalPrice=0;
for(let i in this.books){
const book = this.books[i]
totalPrice+=book.price*book.count
}
*/
/* 3.for ( let i of this.books)*/
/*
let totalPrice=0;
for(let item of this.books){
totalPrice += item.price*item.count
}
return totalPrice
*/
return this.books.reduce( function (prevValue,books){
return prevValue + books.price * books.count
},0)
}
}
}
)
style.css
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: #f7f7f7;
color:#5c6b77;
font-weight: 600;
}
运行结果
高阶函数.js
//编程范式 :编程式编程/声明式编程
//编程范式 :面向对象编程(第一公民是对象)/函数式编程(第一公民是函数)
//高阶函数 filer/map/reduce
//filter中的回调函数有一个要求:必须返回一个bollean值
//true:当返回true时,函数内部会自动将这次回调的n加入到新的数组中
//false:当返回false时,函数内部会过滤掉这次的n
const nums = [10,20,111,222,444,40,50]
//数组中小于100的加入到新数组中
let newNums = nums.filter(function (n){ //n是依次遍历数组的值
return n<100
})
console.log(newNums);
/**2.map函数的使用**/
//新数组×2
let new2Nums =newNums.map(function (n){
return n * 2;
})
console.log(new2Nums)
//3.reduce函数的使用
//reduce函数的作用:对数组中所有内容汇总 reduce(func, initValue)
let total = new2Nums.reduce(function (preValue , n){
return preValue + n
},0)
//第一次 : preValue:0 n:20
//第二次 : preValue:20 n:40
//第三次 : preValue:60 n:80
//第四次 : preValue:140 n:100
//240
//函数式编程
/*let total = nums.filter(function (n){
return n<100
}).map(function (n){
return n*2
}).reduce(function (prevValue , n){
return prevValue+n
},0)*/
let total = nums.filter(n => n< 100).map(n =>n * 2).reduce((pre , n) => pre + n);
console.log(total);
/*
//需求:1.取出所有小于100的数字
let newNums =[]
for (let n of nums){
if(n<100){
newNums.push(n);
}
}
//需求:2.将所有小于100的数字进行转化:全部 *2
let new2Nums = []
for (let n of newNums){
new2Nums.push(n*2)
}
//需求:3.将所有需求new2Nums中的数字相加,得到最终结果
let total = 0
for (let n of new2Nums){
}*/
十、v-model的使用
v-model实现了双向绑定,通过app.message=xxx修改,页面会自动渲染;通过页面修改>message,data中的message也会发生改变
v-bind: 能够实现数据发生变化时,页面自动渲染
<input type="text" v-bind:value="message"/> <h2> {{message}} </h2>
v:on 手动实现页面数据发生改变时,数据对应发生改变
<input type="text" v-bind:value="message" v-on:input="valueChange"/> <h2> {{message}} </h2> .... methods:{ valueChange(event){ this.message = event.target.value; } }
v-bind:value 可简写为 :value
v-on:input 可简写为 @input
10.1 v-model 的基本使用
-
表单控件在实际开发中是非常常见的,特别是对于用户信息的提交,vue中使用v-model指令来实现表单元素和数据的双向绑定
-
当我们在输入框输入内容时,因为input中的v-model绑定了message,所以会实时将输入的内容传递给message,message 发生改变;当message发生改变时,因为上面使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变
-
所以,通过v-model实现了双向的绑定
-
v-model =>v-bind:value v-on: input
-
v-model其实是一个语法糖,它的背后本质是包含两个操作
-
v-bind绑定一个value属性
-
v-on指令给当前元素绑定input事件
<input type="text" v-model="message"> <!--等同于--> <input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
-
也可以将v-model用于textarea元素
10.2 v-model和radio/checkbox/select
-
radio
<!-- name: 两个radio的name都为'sex'标签,这两个radio只能选择一个 实现互斥,有了v-model后可删 value: 表示的是这个radio对应的值 v-model: 将点击的radio对应的值与数据中的sex变量绑定 --> <label for='male'> <input type='radio' id='male' name='sex' value="男" v-model="sex"/>男 </label> <label for='female'> <input type='radio' id='male' name='sex' value="女" v-model="sex"/>女 </label> ... data:{ sex:'' }
-
checkbox
-
单个勾选框
- v-model即为布尔值,此时input的value并不影响v-model的值
-
多个复选框
- 当是多个复选框时,因为可以选中多个,所以对应的data中的属性是一个数组。当选中某一个时,就会将input的value添加到数组中
<!-- 有了label标签后,在文字上点击也能触发 --> <label for='license'> <input type='checkbox' id='license' v-model="isAgree"/>同意协议 </label> <button :disable="!isAgree"> 下一步 </button> ... data:{ isAgree:false }
-
-
select
- 单选
- v-model绑定的是一个值,当我们选中option中的一个时,会将它对应的value赋值到mySelect中
- 多选
- v-model绑定的是一个数组,当选中多个值时,就会将选中的option对应的value添加到数组mySelects中
- 单选
10.3 修饰符
-
lazy
-
默认情况下,v-model默认是在input事件中同步输入框的数据。
也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。
lazy修饰符可以让数据在失去焦点或者回车时才会更新<input type="text" v-model.lazy="message"> <h2>{{message}}</h2>
-
-
number
-
默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
number修饰符可以让在输入框中输入的内容自动转成数字类型<input type="text" v-model.number="age"> <h2>{{age}}--{{typeof(age)}}</h2>
-
-
trim
- 如果输入的内容首尾有很多空格,通常我们希望将其去除
trim修饰符可以过滤内容左右两边的空格
06-v-model修饰符的使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <input type="text" v-model="message"> <!--等同于--> <input type="text" v-bind:value="message" v-on:input="message = $event.target.value"> <!--1.修饰符:lazy 回车键和输入框失去焦点的时候会绑定,普通情况下会实时绑定--> <input type="text" v-model.lazy="message"> <h2>{{message}}</h2> <!--2.修饰符;number,v-model默认是string类型--> <input type="text" v-model.number="age"> <h2>{{age}}--{{typeof(age)}}</h2> <!--2.修饰符;trim 删除多余的空格--> <input type="text" v-model.trim="name"> <h2>您输入的名字是:{{name}}</h2> </div> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data:{ message: '你好!', age:'' } }) </script> </body> </html>
- 如果输入的内容首尾有很多空格,通常我们希望将其去除
运行结果
十一、组件化开发
11.1 认识组件化
- 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。但是如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
- 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用,任何的应用都会被抽象成一棵组件树。
- 有了组件化的思想,我们在之后的开发中就要充分的利用它,尽可能的将页面拆分成一个个小的,可复用的组件,这样让我们的代码更加方便组织和管理,并且扩展性也很强
11.2 组件的基本使用
-
注册组件的基本步骤
- 创建组件构造器
- 调用Vue.extend()方法创建组件构造器
- 注册组件
- 调用Vue.component()方法注册组件
- 使用组件
- 在Vue实例的作用范围内使用组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>this is a title</title> </head> <body> <div id="app"> <!-- 3.组件使用 --> <my-cpn></my-cpn> </div> <script src="./js/vue.js"></script> <script> // 1.创建组件构造器 const myCpn = Vue.extend({ template: ` <div> <h2>我是标题</h2> <p>我是内容1</p> <p>我是内容2</p> </div> `, }) // 2.组件注册 Vue.component('my-cpn', myCpn); const app = new Vue({ el: '#app', data: { message: 'Vuejs' } }) </script> </body> </html>
- 创建组件构造器
-
Vue.extend()
- 调用Vue.extend()创建的是一个组件构造器,通常在创建组件构造器时,传入template代表我们自定义组件的模板,该模板就是在使用到组件的地方,要显示的HTML代码,事实上,这种写法在Vue2.x的文档中几乎看不到了,他会直接使用语法糖
-
Vue.component()
- 调用Vue.component()是将刚才的组件的组件构造器注册为一个组件,并且给他起一个组件的标签名称。所以需要传递两个参数:1、注册组件的标签名2、组件构造器
-
组件必须挂载在某个Vue实例下,否则他不会生效
11.3 全局组件和局部组件
-
全局组件
-
意味着可以在多个Vue的实例下面使用
-
注册方式
-
Vue.component('my-cpn',cpnC)
-
-
-
局部组件
-
意味着只能在该Vue的实例下面使用
-
注册方式
-
const app = new Vue({ el: '#app', data:{ message: '你好!' }, components :{ //con使用组件时的标签名 使用时候对应的标签 //cpnC组件的构造器 创建组件时对应的名称 myCpn :cpnC } })
-
-
11.4 父组件和子组件
//子组件
const cpnCS = Vue.extend({
template :`<div>
<h2>我是标题1-1</h2>
<p>我是内容,哈哈哈哈哈</p>
</div>`
}
)
//父组件
const cpnCF = Vue.extend({
template :`<div>
<h2>我是标题1</h2>
<p>我是内容,呵呵呵呵呵</p>
<cpnS></cpnS>
</div>`
,
components :{
cpnS : cpnCS,
}
}
)
//root 根组件
const app = new Vue({
el: '#app',
data:{
message: '你好!'
},
components :{
cpnF: cpnCF
}
})
11.5 注册的语法糖
//全局组件的语法糖
Vue.component('cnp1',{
template :`<div>
<h2>我是标题1</h2>
<p>我是内容,哈哈哈哈哈</p>
</div>`
})
const app = new Vue({
el: '#app',
data:{
message: '你好!'
},
//局部组件的注册语法糖
components:{
'cnp2':{
template :`<div>
<h2>我是标题2</h2>
<p>我是内容,哈哈哈哈哈</p>
</div>`
}
}
})
11.6 模板的分类写法
-
script
<!--1.script标签,类型是:text/x-template--> <script type="text/x-template" id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,哈哈哈哈哈</p> </div> </script> <script> Vue.component('cpn',{ template:'#cpn' }) </script>
-
template
<template id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,哈哈哈哈哈</p> </div> </template> <script> Vue.component('cpn',{ template:'#cpn' }) </script>
11.7 数据的存放
-
组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板,也应该有属于自己的数据data
-
组件不能直接访问Vue实例中的data,而且即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变得非常臃肿
-
组件数据的存放
- 组件对象也有一个data属性(也可以有methods等属性),只是这个data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存数据
-
组件中的data为什么必须是一个函数
如果data是一个对象的话,多个组件就公用一份数据了
data是函数,通过这个函数返回一个对象,每个组件都有属于自己的对象,多个组件之间的数据不会共享
Vue.component('cpn',{ template:'#cpn', //是函数,不会相互影响,每一个组件都用的不同的对象,不会引起连锁反应 //data:function(){} data(){ return{ counter :0 } }, //不是函数,会相互影响,因为每一个组件都用的一个对象,会引起连锁反应 /* 如果想要组件之间公用,可以用这种方式实现 */ const obj = { counter :0 } data(){ return obj },
11.8 父子组件的通信
-
子组件是不能引用父组件或者Vue实例的数据的,但是在开发中,往往一些数据确实需要从上层传递到下层,比如我们从服务器请求到了很多数据,其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示,这个时候,并不会让子组件再发一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)(数据通过父组件传送给子组件)
-
真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。
-
父传子 : props (通过props向子组件传送数据)
<div id="app"> //v-bind:cInfo="info" 因为不支持驼峰命名,所以需要写成c-info形式 <cpn :c-info="info" :child-my-message="message"></cpn> </div> <!--Vue组件如果需要传多个标签的话,需要有一个跟--> <template id="cpn"> <div> <h2>{{cInfo}}</h2> <h2>{{childMyMessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> const cpn = { template :'#cpn', //props:['cInfo','childMyMessage'] props: { //这种写法可以指定类型 还可以提供默认类型 cInfo: { type: Object, //对象或者数组不能直接用{}或[]初始化, 需要使用 return{}或者return[] default(){ return {} }, required:true //父组件必须向子组件传递数据 }, childMyMessage :{ type : String , default : '' } } } const app = new Vue({ el: '#app', data:{ info :{ name :'why', age:18, height:1.88 }, message: '你好!' }, components:{ cpn } }) </script>
-
子传父:$emit (通过事件向父组件发送消息)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--父组件模板--> <div id="app"> <cpn @itemclick="cpnClick"></cpn> </div> <!--子组件模板--> <template id="cpn"> <div> <button v-for="item in categories" @click="btnClick(item)" >{{item.name}}</button> </div> </template> <script src="../js/vue.js"></script> <script> //1.子组件 const cpn = { template: '#cpn', data(){ return { categories:[ {id : 'aaa',name:'热门推荐'}, {id : 'bbb',name:'手机数码'}, {id : 'ccc',name:'家用电器'}, {id : 'ddd',name:'电脑办公'}, ] } }, methods:{ btnClick(item){ //发射事件:自定义事件 this.$emit('itemclick',item) } } } //2. 父组件 const app = new Vue({ el: '#app', data:{ message: '你好!' }, components:{ cpn }, methods:{ cpnClick(item) { console.log("cpnclick",item); } } }) </script> </body> </html>
11.9 小案例
父子组件之间的通信,结合双向绑定的案例
父子之间的数据能够相互绑定,且number1和number2之间的变化有倍数关系
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>this is a title</title>
</head>
<body>
<div id="app">
<cpn v-bind:number1="num1" v-bind:number2="num2" @num1chageemit="num1Change" @num2chageemit="num2Change" />
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<input type="text" v-bind:value="dnumber1" @input="num1Input" />
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<input type="text" v-bind:value="dnumber2" v-on:input="num2Input" />
</div>
</template>
<script src="./js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
num1Change(num) {
this.num1 = parseInt(num);
},
num2Change(num) {
this.num2 = parseInt(num);
}
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2
}
},
<!-- methods中的方法可以放在watch中-->
methods: {
num1Input(event) {
this.dnumber1 = event.target.value;
this.$emit('num1chageemit', this.dnumber1);
//同时修改num2的值为num1的100倍
this.dnumber2 = this.dnumber1 * 100;
this.$emit('num2chageemit', this.dnumber2);
},
num2Input(event) {
this.dnumber2 = event.target.value;
this.$emit('num2chageemit', this.dnumber2);
this.dnumber1 = this.dnumber2 / 100;
this.$emit('num1chageemit', this.dnumber1);
},
},
}
}
})
</script>
</body>
</html>
11.10 父子组件的访问方式
-
有时候需要父组件直接访问子组件,子组件需要直接访问父组件,或者子组件访问跟组件
-
父组件访问子组件:使用 children或refs
<div id="app"> <cpn></cpn> <cpn></cpn> <cpn ref="aaa"></cpn> <button @click="btnClick">按钮</button> </div> <template id="cpn"> <div>我是子组件</div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好!' }, methods:{ btnClick(){ /* 1.$children console.log(this.$children); for (let c of this.$children){ console.log(c.name); c.showMessage(); }*/ //console.log(this.$children[3].name); //2.$refs =>对象类型,默认是空的对象 组件上需要加上ref="bbb" console.log(this.$refs.aaa.name); } }, components:{ cpn:{ template : '#cpn', data(){ return { name : '我是子组件的name' } }, methods :{ showMessage(){ console.log('showMessage'); } } } } }) </script>
-
子组件访问父组件:使用$parent
<div id="app"> <cpn></cpn> </div> <template id="cpn"> <div> <ccpn></ccpn> </div> </template> <template id="ccpn"> <div> <!--子组件中访问父组件--> <h2>我是子组件</h2> <button @click="btnClick">按钮</button> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好!' }, components:{ cpn:{ template : '#cpn', data(){ return { name: '我是cpn组件的name' } }, components:{ ccpn:{ template :'#ccpn', methods:{ btnClick(){ //1.访问父组件 $parent console.log(this.$parent) console.log(this.$parent.name) //2.访问跟组件$root console.log(this.$root) console.log(this.$root.name) } } } } }, } }) </script>
-
十二、组件化高级
12.1 slot插槽
-
插槽的目的是让我们封装的组件更加具有扩展性
-
封装插槽的方法:抽取共性,保留不同
-
插槽的基本使用
<slot></slot>
-
插槽的默认值
<slot><button>按钮</button></slot>
-
如果有多个值,同时放入到组件进行替换时,一起作为替换元素
01-插槽的基本使用.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <cpn><button>按钮</button></cpn> <cpn><span>哈哈哈哈哈哈</span></cpn> <cpn><i>呵呵呵呵呵</i></cpn> <cpn></cpn> </div> <template id="cpn"> <div> <h2>我是组件</h2> <p>我是组件,哈哈哈哈哈哈</p> <slot><button>按钮</button></slot> <!--<button>按钮</button>--> </div>> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好!' }, components: { cpn: { template: '#cpn' } } }) </script> </body> </html>
运行结果
12.2 具名插槽
02-具名插槽.html
’
<cpn><span slot="center">标题</span></cpn>
<slot name="right"><span>右边</span></slot>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!--替换中间为标题-->
<cpn><span slot="center">标题</span></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
<slot></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好!'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</body>
</html>
运行结果
12.3 编译的作用域
-
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
03-编译的作用域.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <!--会使用实例中的isShow--> <cpn v-show="isShow"></cpn> </div> <template id="cpn"> <div> <h2>我是子组件</h2> <p>我是内容,哈哈哈哈</p> <!--会使用组件的中的isShow--> <button v-show="isShow"></button> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好!', isShow: true }, components:{ cpn: { template: '#cpn', data(){ return { isShow :false } } } } }) </script> </body> </html>
12.4 作用域插槽
-
父组件替换插槽的标签,但是内容是由子组件来提供的
04-作用域插槽的案例.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <cpn></cpn> <cpn> <!--目的是获取子组件的pLanguages--> <template slot-scope="slot"> <!--<span v-for ="item in slot.data">{{item}} - </span>--> <!--删除前后的最后一个符号--> <span>{{slot.abc.join(' - ')}}</span> </template> </cpn> <cpn> <!--目的是获取子组件的pLanguages 以作用域插槽方式得到子组件中的数据 --> <template slot-scope="slot"> <!--<span v-for ="item in slot.data">{{item}} * </span>--> <span>{{slot.abc.join(' * ')}}</span> </template> </cpn> </div> <template id="cpn"> <div> <slot :abc="pLanguages"> //data可以是随意的变量名 通过data来得到pLanguages <ul> <li v-for="item in pLanguages">{{item}}</li> </ul> </slot> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好!', isShow: true }, components:{ cpn: { template: '#cpn', data(){ return { pLanguages:['JavaScript','C++','Java','Python','Go','Swift'] } } } } }) </script> </body> </html>
运行结果
十三、前端模块化
多个js文件中,存在全局变量同名问题
13.1 CommonJS
-
模块化有两个核心:导出和导入
-
CommonJS的导出
module.exports = { flag: true, test(a,b){ return a+b }, demo(a,b){ return a*b } }
-
CommonJS的导入
//CommonJS模块 let {test,demo,flag} = require('moduleA'); //文件路径 //等同于 let _mA = required('moduleA'); let test = _mA.test; let demo = _mA.demo; let flag = _mA.flag;
13.2 export基本使用
-
export指令用于导出变量
//info.js export let name ='why' export let age = 18 export let height = 1.88
上面的代码还有另外一种写法:
//info.js let name ='why' let age = 18 let height = 1.88 export{name,age,height}
-
export指令用于导出函数/类
export function test(content){ console.log(content) } export class Person{ constructor(name,age){ this.name = name; this.age = age; } run(){ console.log(this.name +'在奔跑'); } }
上面的代码还有另外一种写法:
function test(content){ console.log(content) } class Person{ constructor(name,age){ this.name = name; this.age = age; } run(){ console.log(this.name +'在奔跑'); } } export{test,Person};
-
export default
-
某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名,这个时候就要用export default
//导出 //info.js export default function(){ console.log('default funtion'); }
导入 import myFunc form './info.js' myFunc()
另外,需要注意:
export default在同一模块中,不允许同时存在多个
-
13.3 import使用
-
首先,我们需要在HTML代码中引入两个js文件,并且类型需要设置为module
<script src="info.js" type="module"></script> <script src="main.js" type="module"></script>
-
import指令用于导入模块的内容,比如main.js的代码
import{name,age,height} from "./info.js" console.log(name,age,height);
-
如果我们希望某个模块中所有的信息都导入,一个导入显然麻烦:
- 通过*可以导入模块中所有的export变量
- 但是通常情况下我们需要给*起一个别名,方便后续的使用
import * as info from './info.js' console.log(info.name,info,age,info.height,info.friends);
13.4 Webpack ?
13.4.1 Webpack的定义
-
Webpack是一个现代的JavaScript应用的静态模块打包工具(处理模块间的依赖关系)
-
前端模块化
- 目前使用前端模块:AMD、CMD、CommJS、ES6
- 在ES6之前,我们想要进行模块化开发,就必须借助其他的工具,让我们可以进行模块化开发,并且在通过模块化开发完成了项目后,还需处理模块建的各种依赖,并且将其进行整合打包。
- 而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系,而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack都可以被当作模块来使用。
13.4.2 Webpack和grunt/gulp的对比
-
grunt/gulp的核心是Task
-
我们可以配置一系列的task ,并且定义task要处理的事务(例如ES6、ts转化、图片压缩、scss转化css),之后让grunt/gulp来一次执行这些task,并且让整个流程自动化,所以grunt/gulp也被称为前端自动化任务管理工具。
-
下面的task就是将src下面的所有js文件转成ES5的语法,并且最终输出到disk文件中
const gulp = require('gulp'); const babel = require('gulp-babel'); gulp.task('js',() => gulp.src('src/*js')) .pipe(babel({ presets :['es2015'] })) .pipe(gulp.dest('dist')) );
- 如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念。只需要进行简单的合并、压缩,就用grunt/gulp即可,但是如果整个项目使用了模块化管理,而且相互依赖性非常强,我们就可以使用更加强大的webpack了。
-
-
grunt/gulp和webpack有什么不同
- grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。
- webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是它附带的功能。
13.4.3 Webpack安装
-
webpack模块化打包,webpack为了可以正常运行,必须依赖node环境,node为了可以正常的执行很多代码,必须其中各种依赖的包
-
npm工具(node packages manager)(方便管理node的各种包)
-
安装webpack首先需要安装Node.js
- 查看自己的node版本:
node -v
-
全局安装webpack
npm install webpack@3.6.0 -g
-
局部安装webpack
- –save-dev 是开发时依赖,项目打包后不需要继续使用的。
cd 对应目录
npm install webpack@3.6.0 --save -dev
-
为什么全局安装后,还需要局部安装?
- 在终端直接执行webpack命令,使用的全局安装的webpack
- 当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
13.4.4 js文件的打包
-
现在的js文件中使用了模块化的方式进行开发,他们不可以直接用,因为如果直接在index.html引入这两个js文件,浏览器并不识别其中的模块化代码。另外,在真实项目中当有许多这样的js文件时,我们一个个引用非常麻烦,并且后期非常不方便对他们进行管理。
-
我们可以使用webpack工具,对多个js文件进行打包,webpack就是一个模块化的打包工具,所以它支持我们代码中写模块化,可以对模块化的代码进行处理。另外,如果在处理完所有模块之间的关系后,将多个js打包到一个js文件中,引入时就变得非常方便了。
-
打包的命令
webpack src/main.js dist/bundle.js
-
使用打包后的文件
- 打包后会在dist文件下,生成一个bundle.js文件
- bundle.js文件,是webpack处理了项目直接文件依赖后生成的一个js文件,我们只需要将这个js文件在index.html中引入即可
- 打包后会在dist文件下,生成一个bundle.js文件
13.4.5 配置打包的入口和出口
-
如果每次使用webpack的命令都需要写上入口和出口作为参数,就非常麻烦,webpack.config.js文件可以将这两个参数写到配置中,在运行时,可以直接读取。 直接输入webpack就可以进行打包
const path = require('path') module.exports = { //入口:可以是字符串/数组/对象,这里我们数组只有一个,所以写一个字符串即可 entry:'./src/main.js', //出口:通常是一个对象,里面至少包含两个重要属性,path 和 filename output :{ path : path.resolve(__dirname,'dist'),//注意:path是一个绝对路径 ./dist filename : 'bundle.js' } }
-
package.json中定义启动
npm init会自动生成package.json文件(npm包管理文件) node所需要的文件
-
在终端执行的都是全局的
-
package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置
{ "name": "meetwebpack", "version": "1.0.0", "description": "index.js", "main": "webpack.config.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" //npm run build会执行webpack指令 }, "author": "", "license": "ISC", "devDependencies": { "webpack": "^3.6.0" } }
-
首先,会寻找本地的node_modules/.bin路径中对应的命令。
-
如果没有找到,会去全局的环境变量中去找
如何执行bulid指令
npm run bulid
-
-
如果需要使用css等相关文件该如何处理呢?
13.4.6 Loader
-
loader是webpack中一个非常核心的概念
-
webpack的作用
- 我们主要是用webpack来处理我们写的js代码,并且webpack会自动处理js之间相关的依赖,但是,在我们开发中,我们不仅仅有基本的Js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5的代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。对于webpack本身的能力来说,对于这些转化是不支持的,这个时候,将webpack扩展对应的loader就可以了。
-
load使用过程:
- 通过npm安装需要使用的loader
- 在webpack.config.js中的modules关键字下进行配置
13.4.6.1 css文件处理-准备工作
- 项目开发过程中,我们必然需要添加很多的样式,而样式我们往往写到一个单独文件中。
- 在src目录中,创建一个css文件,其中创建一个normal.css文件
- 我们也可以重新组织文件的目录结构,将零散的js文件放在一个js文件夹中
- normal.css的代码非常简单,就是将body设置red,但是,这个时候的normal.css的样式不会生效,我们没有引用他,webpack也不可能找到它,因为我们只有一个入口,webpack会从入口开始查找其他依赖的文件
13.4.6.2 css文件处理-打包报错信息
-
重新打包,会出现如下错误:
bundle.js 4.33 kB 0 [emitted] main [0] ./src/main.js 299 bytes {0} [built] [1] ./src/js/mathUtils.js 149 bytes {0} [built] [2] ./src/js/info.js 81 bytes {0} [built] [3] ./src/css/normal.css 623 bytes {0} [built] [failed] [1 error] ERROR in ./src/css/normal.css Module build failed: CssSyntaxError (1:1) Unknown word
-
第一步:安装 npm install --save-dev css-loader npm install --save-dev style-loader
-
第二步:配置 此处应该将webpack.config.js文件修改为
const path = require('path')//用node里面的,通过npm init module.exports = { entry: './src/main.js', //main.js中依赖的会逐个打包,会嵌套式的打包main.js依赖的东西 output: { path : path.resolve(__dirname,'dist'),//绝对路径 filename:'bundle.js' }, module: { rules: [ { test: /\.css$/, //css-loader只负责将css文件进行加载 //style-loader 负责将样式添加到DOM中 //webpack在使用多个loader时,是从右向左的 先加载后添加 use: ['style-loader','css-loader'] } ] } }
-
第三步:npm run build
-
13.4.6.3 图片文件处理
安装 npm install --save-dev url-loader
-
图片小于8kb
- 安装url-loader,运行成功,会发现背景图是通过base64显示出来的,这是limit属性的作用,当图片小于8kb时,对图片进行base64编码
-
图片大于8kb
-
安装file-loader,运行成功,再次打包,就会发现dist文件夹下多了一个图片文件,这是webpack自动生成的名称,这是一个32位的hash值,目的是防止名称重复,但是,真实开发中,我们可能对打包的图片名字有一个的要求,比如,将所有的图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复,但是,我们发现图片并没有显示出来,这是因为图片使用的路径不正确,默认情况下,webpack会将生成的路径直接返回给使用者,但是我们整个程序是打包在dist文件夹下的,所以需要在路径下再添加一个dist/
-
我们可以在options中添加上如下选项
- img: 文件要打包到的文件夹
- name:获取图片原来的名字,放在该位置
- hash8: 为了防止图片名称冲突,依然使用hash,但是我们只保留8位
- ext:使用图片的扩展名
const path = require('path')//用node里面的,通过npm init module.exports = { entry: './src/main.js', output: { path : path.resolve(__dirname,'dist'),//绝对路径 filename:'bundle.js', publicPath: 'dist/' // }, module: { rules: [ { test: /\.css$/, //css-loader只负责将css文件进行加载 //style-loader 负责将样式添加到DOM中 //webpack在使用多个loader时,是从右向左的 use: ['style-loader','css-loader'] }, { test: /\.less$/, use: [{ loader: "style-loader" // creates style nodes from JS strings }, { loader: "css-loader" // translates CSS into CommonJS }, { loader: "less-loader" // compiles Less to CSS }] }, { test: /\.(png|jpg|gif|jpeg)$/, use: [ { loader: 'url-loader', options: { /*当加载的图片,小于limit时,会将图片编译成base64字符串形式*/ /*当加载的图片,大于limit时,需要使用file-load模块进行加载, 需要打包到dist文件夹中 对应到dist下面需要publicPath字段中指定*/ limit: 73000, /*默认是32位hash值,img/name.hash:8.extension xxx.12345678.jpg ext原先文件的扩展名 */ name: 'img/[name].[hash:8].[ext]' } } ] } ] } }
-
13.4.6.4 ES6语法处理
ES6并不是所有浏览器都支持,一般需要打包成ES5
-
Webpack打包的js文件写的ES6语法并没有转成ES5,那么就意味着可能一些对ES6还不支持的浏览器没有办法很好的运行我们的代码
-
如果希望将ES6的语法转成ES5,那么就需要babel,而在webpack中,我们直接使用babel对应的loader就可以了。
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
配置webpack.config.js
//rules内 { test: /\.js$/, //排除 exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['es2015'] } } }
重新打包就可以
13.4.6.5 webpack配置vue
-
我们希望在项目中使用Vuejs,那么必然需要对其有依赖,所以需要先安装
注:后续是在实际项目中使用vue,所以并不是开始时依赖 去掉-dev
npm install vue --save
-
Vue构建的两个版本
-
runtime-only
- 代码中,不可以有任何的template
-
runtime-compiler
- 代码中,可以有template,因为有compiler可以用于编译template
-
-
打包项目-错误信息
- 修改完成后,重新打包,运行程序,打包过程没有任何错误,但是在运行的时候,没有达到想要的效果,而且浏览器报错
-
修改webpack的配置 webpack.config.js
resolve:{ alias:{ //设置import Vue的时候Vue默认使用的版本 'vue$': 'vue/dist/vue.esm.js' } }
-
el和template区别
- 如果同时有el和template,el会替换template
-
.vue文件封装处理
-
一个组件以一个js对象的形式进行组织和使用的时候是非常不方便的,一方面编写template模块非常的麻烦,另一方面如果有样式的话不知在哪里写合适,此时,我们以一种全新的方式来组织一个vue组件
-
安装vue-loader和vue-template-compiler
-
注:vue-template-compiler的版本必须与所安装的vue版本保持一致
npm install vue-loader@13.0.0 vue-template-compiler@2.5.21 --save-dev --force
-
配置webpack.config.js文件
{ test :/\.vue$/, use :['vue-loader'] }
-
-
main.js
import Vue from 'vue'
const App={
template:`
<div>
<h2>{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
</div>
`,
data(){
return{
message:"Hello webpack",
name:"wlf"
}
},
methods:{
btnClick(){
}
}
}
new Vue({
el:'#app',
template:'<App/>',
components:{
App
}
})
改进
创建app.js
export default{
template:`
<div>
<h2>{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
</div>
`,
data(){
return{
message:"Hello webpack",
name:"wlf"
}
},
methods:{
btnClick(){
}
}
}
main.js
import Vue from 'vue'
import App from './vue/app'
new Vue({
el:'#app',
template:'<App/>',
components:{
App
}
})
index.html
...
<div id="app">
</div>
<script src="./dst/bundle.js"></script>
13.4.6.6 横幅plugin的使用
-
plugin的定义
- plugin是插件的意思,通常是用于对某个现有的架构进行扩展,webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等
-
loader和plugin的区别
- loader主要用于转换某些类型的模块,它是一个转换器
- plugin是插件,它是对webpack本身的扩展,是一个扩展器
-
plugin的使用过程
- 通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
- 在webpack.config.js中的plugins配置插件
-
添加版权的plugin
-
BannerPlugin,属于webpack自带的插件,为打包的文件添加版权声明
-
修改webpack.config.js文件:
const path = require('path') const webpack = require('webpack') module.exports = { ... plugins: [ new webpack.BannerPlugin('最终版权归aaa所有') ] }
-
重新打包程序:查看bundle.js文件的头部,可以看到
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xffscHno-1644828620589)(C:\Users\JMS\AppData\Roaming\Typora\typora-user-images\image-20220203152747651.png)]
-
-
打包html的plugin
-
目前,我们index.html存放在项目的根目录下的,我们知道,在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也没有意义了,所以我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用HtmlWebpackPlugin插件
-
HtmlWebpackPlugin插件的作用
-
自动生成一个index.html文件(可以指定模板来生成),将打包的js文件,自动通过script标签插入到body中
-
安装HtmlWebpackPlugin插件
npm install html-webpack-plugin@3.2.0 --save-dev --force
-
需要删除之前在output中添加的publicPath属性,否则插入的script标签中的src可能会有问题
-
const HtmlWebpackPlugin = require('html-webpack-plugin') // plugins的配置 plugins:[ // 详细plugins的配置 // 因为是直接引的,所以 new 就好了 // 功能:默认会创建一个空的HTML文件,自动引入打包输出的所有资源(JS/CSS) new HtmlWebpackPlugin({ // 复制 路径下'./XX/index.html'文件,并自动引入打包输出的所有资源(JS/CSS) template:'index.html' }) ]
-
-
js压缩的Plugin
-
在项目发布之前,我们必然需要对js等文件进行压缩处理,我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
-
修改webpack.config.js文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3nJiRh8B-1644828620589)(C:\Users\JMS\AppData\Roaming\Typora\typora-user-images\image-20220204133632973.png)]
-
查看bundle.js,已经是被压缩过了的
13.4.6.7 搭建本地服务器
-
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果,不过它是一个单独的模块,在webpack中使用之前需要先安装它
npm install --save-dev webpack-dev-server@2.9.1
-
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
- contentBase;为哪一个文件夹提供本地服务,默认是跟文件夹,我们这里要填写./dist
- port:端口号,默认8080
- inline: 页面实时刷新
- historyApiFallback:在SPA页面中,依赖HTML5的history模式
-
webpack.config.js文件配置修改如下
-
我们可以再配置另外一个scripts:
- –open参数表示直接打开浏览器
-
-
webpack-配置文件的分离
npm install webpack-merge@4.1.5 --save-dev
13.5. Vue CLI
13.5.1 简述Vue CLI
-
如果只是简单写几个Vue的Demo程序,那么你不需要Vue CLI,如果你在开发大型项目,那么你需要,并且必然需要使用Vue CLI
-
使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。如果每个项目都要手动完成这些工作,那无疑效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。
-
CLI的定义
- CLI是Command-Line Interface,翻译为命令行页面,但是俗称脚手架。
- Vue CLI是一个官方发布vue.js项目脚手架
- 使用vue-cli可以快速搭建Vue开发环境以及对应的webpack配置
-
Vue CLI使用前提 -Node,Node环境要求8.9以上或者更高的版本
-
Vue CLI使用前提 -WebPack
- Vue.js官方脚手架工具就使用了webpack模板
- 对所有的资源会压缩等优化操作
- 它在开发过程中提供了一套完整的功能,能够使得我们开发过程中变得高效
template->ast->render->vdom->UI
- Vue.js官方脚手架工具就使用了webpack模板
13.5.2 Vue CLI的使用 ?
-
安装Vue脚手架
npm install -g @vue/cli-init
旧版本2.Xnpm install -g @vue/cli
注意:安装的是Vue CLI3的版本,如果需要按照Vue CLI2的方式初始化项目需拉取2.x模板
-
Vue CLI2初始化项目
vue init webpack my-project
-
Vue CLI3初始化项目
vue create my-project
-
runtime+compiler 和runtime-only的区别
- 运行过程
- runtime+compiler
- template -> ast -> render -> vdom ->UI
- runtime-only
- render -> vdom ->UI
- …vue文件里面的template是由vue-template-compiler来解析的
- 总结
- runtime-only性能更多
- runtime-only代码量更少(轻6kb)
- 如果在之后的开发中,你依然使用template,就需要选择Runtime-Compiler
- 如果在之后的开发中,使用的.vur=e文件夹,那么可以选择Runtime-Only
- runtime+compiler
- 运行过程
-
createElement的使用
/* 1.普通用法: createElement('标签',{标签的属性},['']) return createElement('h2', {class: 'box'}, ['Hello World', createElement('button', ['按钮'])]) } */ // 2.传入组件对象 return createElement(App)
-
Vue CLI3
- Vue CLI3和Vue CLI2的区别
- vue-cli是基于webpack4打造,vue -cli还是webpack3
- vue-cli的设计原则是"0配置",移除的配置文件根目录下的,build和config等目录
- vue-cli3提供了vue ui 命令,提供了可视化配置,更加人性化,移除了static文件夹,新增了public文件夹,并且index.html移动到public中
- Vue CLI3和Vue CLI2的区别
13.6 箭头函数
-
箭头函数的基本使用
//箭头函数: 也是一种定义函数的方式 //1.定义函数的方式: function const aaa = function () { } //2.对象字面量中定义函数 const obj = { bbb: function () { }, bbb(){ } } //3.ES6中的箭头函数 /* const ccc = (参数列表) =>{ } */ //aaa用ccc表示 const ccc = () =>{ }
-
箭头函数的参数和返回值
//1.参数问题 //1.1放入两个参数 const sum =(num1, num2) =>{ return num1 + num2 } //1.2放入一个参数 *** const power = num =>{ return num * num } //2.函数中的 //2.1函数代码块中有多行代码时 const test = () =>{ //1.打印Hello World console.log("Hello World"); //2.打印Hello Vuejs console.log("Hello Vuejs"); } //2.1函数代码块中只有一行代码时 *** /* const mul = (num1, num2 ) => { return num1 * num2 } */ const mul = (num1, num2) => num1 * num2 /* const demo = () => { console.log("Hello Demo"); } */ const demo = () => console.log("Hello Demo") console.log(demo()); //一个参数 一行代码 (num)=>{ return num*num;} const power = num=>num*num
-
箭头函数中this的使用
//当把一个函数作为参数传到另一个函数中的时候,用箭头参数较多 /* setTimeout(function (){ console.log(this)//window },1000)*/ setTimeout(() => { console.log(this)//window },1000) //结论: 箭头函数中的this引用的就是向外层(最近)作用域,一层层查找this,直到有this的定义 const obj = { aaa() { setTimeout(function () { console.log(this);//window }) setTimeout(()=>{ console.log(this);//obj对象 }) } } const obj = { aaa() { setTimeout(function () { setTimeout(function () { console.log(this);//window }) setTimeout(() => { console.log(this);//window }) }) setTimeout(() => { setTimeout(function () { console.log(this);//window }) setTimeout(() => { console.log(this);//obj对象 }) }) } }
13.7 路由
13.7.1 路由的定义
- 路由就是通过互联的网路把信息从源地址传输到目的地址的活动
- 路由器提供了两种机制:路由和传送,路由是决定数据包从来源到目的地的路径,转送将输入端的数据转移到合适的输出端,路由中有一个非常重要的概念叫路由表,路由表本质上就是一个映射表,决定了数据包的指向。
13.7.2 后端渲染和后端路由阶段
-
早期的网站开发整个html页面是由服务器来渲染的,服务器直接生成渲染好对应的html页面,返回给客户端进行展示
-
一个页面有自己对应的网址,也就是url,url会发送到服务器,服务器会通过正则对该URL进行匹配,并且最后交给一个Controller进行处理,**Controller(控制器)**进行各种处理,最终生成HTML或者数据,返回给前端,这就完成了一个IO操作
-
当我们页面中需要请求不同的路径内容时,交给服务器来进行处理,雾浮起渲染好整个页面,并且将页面返回给客户端,这种情况下渲染好的页面,不需要单独加载任何js和css,可以直接交给浏览器展示,这样也有利于SEO的优化。
-
后端渲染
- jsp :java server page
-
后端路由
- 后端处理url和页面之间的映射关系
-
后端路由的缺点
- 整个页面的模块由后端人员来编写和维护的,前端开发人员如果要开发页面,需要通过PHP和Java来编写页面代码,而且通常情况下html代码和数据以及对应的逻辑会混在一起,编写和维护都是非常棘手的。
13.7.3 前后端分离阶段
-
随着Ajax的出现,有了前后端分离的开发模式,后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中,这样做最大的优点就是前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上。并且当移动端(ios/android)出现后,后端不需要进行任何处理,依然使用之前的一套API即可,目前很多的网站依然采用这种模式开发。
-
后端只负责提供数据,不负责任何阶段的内容
-
前端渲染
- 浏览器中显示的网页中的大部分内容,都是由前端写的js代码在浏览器中执行,最终渲染出来的网页。
- 浏览器中显示的网页中的大部分内容,都是由前端写的js代码在浏览器中执行,最终渲染出来的网页。
13.7.4 单页面富应用阶段
前端路由管理url于组件的对应关系
-
其实SPA最主要的特点就是在前后端分离的基础上加上了一层前端路由,也就是前端来维护一套路由规则。
-
SPA:simple page web application(单页面富应用),整个网页只有一个html页面
-
前端路由的核心:改变URL,但是页面不进行整体的刷新(不会去服务器请求资源)
-
修改url
location.hash = 'aaa'//修改url的hash值,不会重新请求数据 history.pushState({},'','home')//利用栈结构push,会保存历史记录 history.replaceState({},'','home')//只会替换,不会保存历史记录 history.go(-1) //等价于 history.back()//栈中弹出一个元素 history.go(1) //等价于 history.forward()//栈中压入一个元素
-
改变URL,但是页面不进行整体的刷新
13.7.5 vue-router
-
目前流行的三大框架,都有自己的路由实现
- Angular的ngRouter
- React的ReactRouter
- Vue的vue-router
- vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。
- vue-router是基于路由和组件的,路由用于设定访问路径,将路径和组件映射起来,在vue-router的单页面应用中,页面的路径的改变就是组件的切换。
//router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../components/Home'
import About from '../components/About'
//1.通过Vue.use(插件) 安装插件
/*
Vue.use是Vue.js中用来安装插件的方法。它接收一个插件作为参数,并将其安装到Vue实例中。
*/
Vue.use(Router)
//2.创建VueRouter(路由)对象
const routes = [
{
path:'',
redirect:'/home' //重定向 让url XXX路径变成 XXX/home自动显示/home对应的组件
},
{
path:'/home',
component:Home
},{
path:'/about',
component:About
}
]
const router = new VueRouter({
//配置路由和组件之间的映射关系
routes,
mode:'history'//默认是哈希方式会有#号
})
//3.将router对象导出
export default router
//main.js
//将router对象挂载在Vue实例上
new Vue({
el: '#app',
router, //router:router
render: h => h(App)
})
两个组件 Home.vue 以及About.vue
<template>
<div>
<h2>我是首页</h2>
<p>我是首页内容</p>
</div>
</template>
//App.vue
<template>
<div id="app">
//类似于a标签
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
//对应组件的占位符
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
13.7.5.1 安装和使用vue-router
-
安装vue-router
npm install vue-router --save
-
在模块化工程中使用它(因为是一个插件,所以可以通过Vue.use()来安装路由功能)
- 导入路由对象,并且调用Vue.use(VueRouter)
- 创建路由实例,并且传入路由映射配置
- 在Vue实例中挂载创建的路由实例
-
使用vue-router的步骤
-
创建路由组件
-
配置路由映射:组件和路径映射关系
-
使用路由:
<router-link>和<router-view>
-
<router-link>:该标签是一个vue-router中已经内置的组件,它会被渲染成一个<a>标签。 <router-view>:该标签会根据当前的路径,动态渲染出不同的组件,网页的其他内容,比如顶部的标题/导航,或者底部的一些版权信息等会和<router-view>处于同一个等级,在路由切换时,切换的是<router-view>挂载的组件,其他内容不会发生改变。
-
路由的默认路径
{ path: '', redirect:'/home' },
- 配置解析:我们在routes中又配置了一个映射,path配置的是根路径:/,redirect是重定向,也就是我们将根路径重新定向到/home的路径下
-
HTML5的history模式
-
改变路径的方式有两种:URL的hash和HTML5的history,默认情况下,路径的改变使用URL的hash
-
如果希望使用HTML5的history
const router = new Router({ //配置路由和组件之间的应用关系 routes, mode: 'history'//将hash模式修改为html5中的history })
-
-
router-link的其他属性
-
tag: tag可以指定渲染成什么组件 默认是渲染成a标签
-
replace: replace不会留下history记录,所以指定replace的情况下,后退键返回不能返回到上一个页面 默认是pushState可以保存历史操作,replace 改为 replaceState不能返回上一级
-
active-class:当对应的路由匹配成功时,会自动给当前元素设置一个router-link-active的class,设置active-class可以修改默认的名称,在进行高亮显示的导航菜单或者底部tabbar时,会使用到该类,但是通常不会修改类的属性,会直接使用默认的router-link-active即可
-
该class具体的名称也可以通过router实例的属性进行修改
const router = new Router({ //配置路由和组件之间的应用关系 routes, mode: 'history',//将hash模式修改为html5中的history linkActiveClass: 'active' })
-
路由代码跳转
<template> <div id="app"> <!-- <router-link to="/home" tag="button" replace active-class="active">首页</router-link> | <router-link to="/about" tag="button" replace active-class="active">关于</router-link>--> <!-- <router-link to="/home" tag="button" replace>首页</router-link> | <router-link to="/about" tag="button" replace>关于</router-link>--> <button @click="homeClick">首页</button> <button @click="aboutClick">关于</button> <router-view/> </div> </template> <script> export default { name: 'App', methods:{ homeClick() { //不能使用history.pushState 通过代码的方式修改路径 vue-router //$router vue-router源码往所有组件中中加入了$router //push --> pushState replace--->replaceState /*this.$router.push('/home')*/ this.$router.replace('/home') console.log("homeClick"); }, aboutClick() { this.$router.replace('/about') console.log("aboutClick"); } } } </script> <style> /*.router-link-active{ color: #42b983; }*/ .active{ color: coral; } </style>
-
-
13.7.5.2 动态路由
-
在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望是如下路径
- /user/aaaa或/user/bbbb user/zhangsan
除了有前面的/user之外,后面还跟上了用户的ID,这种path和Component的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式)。
{
path:'/user/:userId',
component:User
}
User.vue组件中如何获取userId呢
<template>
<div>
<h2>{{userID}}</h2>
</div>
</template>
<script>
export default{
computed:{
userID(){
/*
this.$route vue-router中那个组件处于活跃状态,拿到的就是这个对象
userId 对应vue-router中的参数
*/
return this.$route.params.userId
}
}
}
</script>
//App.vue
<router-link :to="'/user/'+userId">用户</router-link>
data(){
return{
userId:'lisi'
}
}
13.7.5.3 路由的懒加载
-
当打包构建应用时,Javascript包会变得非常大,影响页面加载(bundle.js过大,首次加载时可能页面会出现短暂的空白)。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
-
路由中通常会定义很多不同的页面,一般情况下,是放在一个js文件中,但是,页面这么多放在一个js文件中,必然会造成这个页面非常大。如果我们一次性从服务器请求下来这个页面,可能需要花费一定的时间,甚至用户的电脑上还出现了短暂空白的情况,为了避免这种情况,使用路由懒加载。
-
路由加载的主要作用就是将路由对应的组件打包成一个个js代码块。只有在这个路由被访问到的时候,才加载对应的组件。
-
懒加载的方式
-
在ES6中,有更加简单的写法来组织Vue一步组件和Webpack的代码分割
const Home = () => import('../components/Home.vue')
路由嵌套
- 嵌套路由是一个很常见的功能,比如在home页面中,我们希望通过/home/news和/home/message访问一些内容,一个路径映射了一个组件,访问这两个路径也会分别渲染两个组件。
- 实现嵌套路由的步骤
- 创建对应的子组件,并且在路由映射中配置对应的子路由
- 在组件内部使用
<router-view>
标签
-
传递参数的方式
-
传递参数主要有两种类型: params和query
-
params的类型(示例见上)
-
配置路由的格式:/rouer/:id
-
传递的方式:在path后面跟上对应的值
-
传递后形成的路径:/router/123,/router/abc
$route.params.userId
-
-
query的类型(传递大量数据时使用)
-
配置路由格式:/router,也就是普通配置
-
传递的方式:对象中使用query的key作为传递方式
-
传递后形成的路径:/router?id=123,/router?id=abc
//profile.vue组件 <h2>{{$route.query.name}}</h2> <h2>{{$route.query.age}}</h2>
//App.vue <router-link :to="{path:'/profile',query:{name:'why',age:18,height:1.88}}"></router-link>
-
-
URL: 协议://主机:端口/路径?查询(query)
scheme://host:port/path?query#fragment
所有的组件都继承Vue类的原型
//main.js Vue.prototype.test = function(){ console.log("test"); } Vue.prototype.name = "codeWhy" //组件中就可使用 this.test(); this.name
13.7.5.4 导航守卫
配置meta
//router/index.js
{
path: '/about',//about 前端路由地址
component: About,
meta: {
title: '关于'
}
}
添加beforeEach/afterEach方法
//导航守卫被称作全局守卫,除了全局守卫之外,还有路由独享的守卫,组件内的守卫
//前置守卫(guard)是在跳转前回调的
//前置钩子 路由跳转之前进行回调
router.beforeEach((to, from , next ) => {
//从from跳到to
document.title = to.matched[0].meta.title;
console.log(to);
next();
})
//后置钩子(hook)是在跳转后回调的,不需要调用next()函数
router.afterEach((to , from ) => {
})
路由守卫
meta: 描述数据的数据
-
keep-alive
-
keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
-
include -字符串或正则表达,只有匹配的组件会被缓存
-
exclude-字符串或正则表达式,任何匹配的组件都不会被缓存
//,前后不能加空格,正如正则表达式也不能随便加空格 <keep-alive exclude="Profile,User"> <router-view/> </keep-alive>
-
-
router-view 也是一个组件,如果直接被包在keep-alive里面,所有路径匹配到的视图组件都会被缓存
-
activated和deactived两个生命周期,只有该组件被保持了状态使用了keep-alive时,才是有效的
-
13.8 TabBar
13.8.1 TabBar实现思路
- 下方封装一个单独的TabBar组件
- 自定义TabBar组件,在APP中使用,让TabBar出于底部,并且设置相关的样式
- TabBar中显示的内容由外界决定
- 定义插槽,flex布局平分TabBar
- 自定义TabBarItem,可以传入图片和文字
- 定义TabBarItem,并且定义两个插槽:图片、文字
- 给两个插槽外层包装div,用于设置样式
- 填充插槽,实现底部TabBar的效果
14 Promise
promise是什么?
- 主要用于异步计算。
- 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
- 可以在对象之间传递和操作promise,帮助我们处理队列。
为什么会有promise?
- 同步:假设你去了一家饭店,找个位置,叫来服务员,这个时候服务员对你说,对不起我是“同步”服务员,我要服务完这张桌子才能招呼你。那桌客人明明已经吃上了,你只是想要个菜单,这么小的动作,服务员却要你等到别人的一个大动作完成之后,才能再来招呼你,这个便是同步的问题:也就是“顺序交付的工作1234,必须按照1234的顺序完成”。
- 异步:则是将耗时很长的A交付的工作交给系统之后,就去继续做B交付的工作,等到系统完成了前面的工作之后,再通过回调或者事件,继续做A剩下的工作。
AB工作的完成顺序,和交付他们的时间顺序无关,所以叫“异步”。
异步操作的常见语法
- 事件监听
document.getElementById('#start').addEventListener('click', start, false);
function start() {
// 响应事件,进行相应的操作
}
// jquery on 监听
$('#start').on('click', start)
- 回调
// 比较常见的有ajax
$.ajax('http://www.wyunfei.com/', {
success (res) {
// 这里可以监听res返回的数据做回调逻辑的处理
}
})
// 或者在页面加载完毕后回调
$(function() {
// 页面结构加载完成,做回调逻辑处理
})
nodeJS之后,对异步的依赖进一步加剧
大家都知道在nodeJS出来之前PHP、Java、python等后台语言已经很成熟了,nodejs要想能够有自己的一片天,那就得拿出点自己的绝活:
无阻塞高并发,是nodeJS的招牌,要达到无阻塞高并发异步是其基本保障
举例:查询数据从数据库,PHP第一个任务查询数据,后面有了新任务,那么后面任务会被挂起排队;而nodeJS是第一个任务挂起交给数据库去跑,然后去接待第二个任务交给对应的系统组件去处理挂起,接着去接待第三个任务…那这样子的处理必然要依赖于异步操作
异步回调的问题
-
之前处理异步是通过纯粹的回调函数的形式进行处理
-
很容易进入到回调地狱中,剥夺了函数return的能力
-
问题可以解决,但是难以读懂,维护困难
-
稍有不慎就会踏入回调地狱 - 嵌套层次深,不好维护
回调地狱
一般情况我们一次性调用API就可以完成请求。
有些情况需要多次调用服务器API,就会形成一个链式调用,比如为了完成一个功能,我们需要调用API1、
API2、API3,依次按照顺序进行调用,这个时候就会出现回调地狱的问题
promise
- promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
- 并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
- 代码风格,容易理解,便于维护
- 多个异步等待合并便于解决
promise详解
new Promise(
function (resolve, reject) {
// 一段耗时的异步操作
resolve('成功') // 数据处理完成
// reject('失败') // 数据处理出错
}
).then(
(res) => {console.log(res)}, // 成功
(err) => {console.log(err)} // 失败
)
- resolve作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- promise有三个状态:
1、pending[待定] 初始状态
2、fulfilled[实现] 操作成功
3、rejected[被否决] 操作失败
当promise状态发生改变,就会触发then()里的响应函数处理后续步骤; promise状态一经改变,不会再变。
- Promise对象的状态改变,只有两种可能:
从pending变为fulfilled
从pending变为rejected。
这两种情况只要发生,状态就凝固了,不会再变了。
最简单示例:
new Promise(resolve => {
setTimeout(() => {
resolve('hello')
}, 2000)
}).then(res => {
console.log(res)
}).catch((err)=>{
console.log(err);
})
//方式二
new Promise((resolve,reject) => {
setTimeout(() => {
resolve('hello');
//reject('error message');
}, 2000)
//then传入两个函数 then(func1, func2)
}).then(res => {
console.log(res);
},err=>{
console.log(err);
})
分两次,顺序执行
new Promise(resolve => {
setTimeout(() => {
resolve('hello')
}, 2000)
}).then(val => {
//第一次方法调用的处理
console.log(val) // 参数val = 'hello'
return new Promise(resolve => {
setTimeout(() => {
resolve('world')
}, 2000)
})
}).then(val => {
//第二次方法调用的处理
console.log(val) // 参数val = 'world'
})
promise完成后then()
let pro = new Promise(resolve => {
setTimeout(() => {
resolve('hello world')
}, 2000)
})
setTimeout(() => {
pro.then(value => {
console.log(value) // hello world
})
}, 2000)
结论:promise作为队列最为重要的特性,我们在任何一个地方生成了一个promise队列之后,我们可以把他作为一个变量传递到其他地方。
假如在.then()的函数里面不返回新的promise,会怎样?
.then()
1、接收两个函数作为参数,分别代表fulfilled(成功)和rejected(失败)
2、.then()返回一个新的Promise实例,所以它可以链式调用
3、当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行
4、状态响应函数可以返回新的promise,或其他值,不返回值也可以我们可以认为它返回了一个null;
5、如果返回新的promise,那么下一级.then()会在新的promise状态改变之后执行
6、如果返回其他任何值,则会立即执行下一级.then()
.then()里面有.then()的情况
1、因为.then()返回的还是Promise实例
2、会等里面的then()执行完,再执行外面的
then嵌套
- 对于我们来说,此时最好将其展开,也是一样的结果,而且会更好读:
展开增加可读性
错误处理
Promise会自动捕获内部异常,并交给rejected响应函数处理。
- 第一种错误处理
2. 第二种错误处理
- 错误处理两种做法:
第一种:reject(‘错误信息’).then(() => {}, () => {错误处理逻辑})
第二种:throw new Error(‘错误信息’).catch( () => {错误处理逻辑})
推荐使用第二种方式,更加清晰好读,并且可以捕获前面所有的错误(可以捕获N个then回调错误)
catch() + then()
- 第一种情况:
第一种情况 -结果
结论:catch也会返回一个promise实例,并且是resolved状态
- 第二种情况:
第二种情况结果
结论:抛出错误变为rejected状态,所以绕过两个then直接跑到最下面的catch
Promise.all() 批量执行
Promise.all([p1, p2, p3])用于将多个promise实例,包装成一个新的Promise实例,返回的实例就是普通的promise
它接收一个数组作为参数
数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态改变
当所有的子Promise都完成,该Promise完成,返回值是全部值得数组
有任何一个失败,该Promise失败,返回值是第一个失败的子Promise结果
//切菜
function cutUp(){
console.log('开始切菜。');
var p = new Promise(function(resolve, reject){ //做一些异步操作
setTimeout(function(){
console.log('切菜完毕!');
resolve('切好的菜');
}, 1000);
});
return p;
}
//烧水
function boil(){
console.log('开始烧水。');
var p = new Promise(function(resolve, reject){ //做一些异步操作
setTimeout(function(){
console.log('烧水完毕!');
resolve('烧好的水');
}, 1000);
});
return p;
}
Promise.all([cutUp(), boil()])
.then((result) => {
//result[0] result[1]
console.log('准备工作完毕');
console.log(result);
})
Promise.race() 类似于Promise.all() ,区别在于它有任意一个完成就算完成
let p1 = new Promise(resolve => {
setTimeout(() => {
resolve('I\`m p1 ')
}, 1000)
});
let p2 = new Promise(resolve => {
setTimeout(() => {
resolve('I\`m p2 ')
}, 2000)
});
Promise.race([p1, p2])
.then(value => {
console.log(value)
})
- 常见用法:
异步操作和定时器放在一起,,如果定时器先触发,就认为超时,告知用户;
例如我们要从远程的服务家在资源如果5000ms还没有加载过来我们就告知用户加载失败 - 现实中的用法
回调包装成Promise,他有两个显而易见的好处:
1、可读性好
2、返回 的结果可以加入任何Promise队列
实战示例,回调地狱和promise对比:
/***
第一步:找到北京的id
第二步:根据北京的id -> 找到北京公司的id
第三步:根据北京公司的id -> 找到北京公司的详情
目的:模拟链式调用、回调地狱
***/
// 回调地狱
// 请求第一个API: 地址在北京的公司的id
$.ajax({
url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/city',
success (resCity) {
let findCityId = resCity.filter(item => {
if (item.id == 'c1') {
return item
}
})[0].id
$.ajax({
// 请求第二个API: 根据上一个返回的在北京公司的id “findCityId”,找到北京公司的第一家公司的id
url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/position-list',
success (resPosition) {
let findPostionId = resPosition.filter(item => {
if(item.cityId == findCityId) {
return item
}
})[0].id
// 请求第三个API: 根据上一个API的id(findPostionId)找到具体公司,然后返回公司详情
$.ajax({
url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/company',
success (resCom) {
let comInfo = resCom.filter(item => {
if (findPostionId == item.id) {
return item
}
})[0]
console.log(comInfo)
}
})
}
})
}
})
// Promise 写法
// 第一步:获取城市列表
const cityList = new Promise((resolve, reject) => {
$.ajax({
url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/city',
success (res) {
resolve(res)
}
})
})s
// 第二步:找到城市是北京的id
cityList.then(res => {
let findCityId = res.filter(item => {
if (item.id == 'c1') {
return item
}
})[0].id
findCompanyId().then(res => {
// 第三步(2):根据北京的id -> 找到北京公司的id
let findPostionId = res.filter(item => {
if(item.cityId == findCityId) {
return item
}
})[0].id
// 第四步(2):传入公司的id
companyInfo(findPostionId)
})
})
// 第三步(1):根据北京的id -> 找到北京公司的id
function findCompanyId () {
let aaa = new Promise((resolve, reject) => {
$.ajax({
url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/position-list',
success (res) {
resolve(res)
}
})
})
return aaa
}
// 第四步:根据上一个API的id(findPostionId)找到具体公司,然后返回公司详情
function companyInfo (id) {
let companyList = new Promise((resolve, reject) => {
$.ajax({
url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/company',
success (res) {
let comInfo = res.filter(item => {
if (id == item.id) {
return item
}
})[0]
console.log(comInfo)
}
})
})
}
Promise链式调用
我们在看Promise的流程图时,发现无论是then还是catch都可以返回一个Promise对象。所以,我们的代码其实是可以进行链式调用的。
我们直接通过Promise包装了一下新的数据,将Promise对象返回
Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
简写
如果我们希望数据直接包装成Promise.resolve,那么在then中可以直接返回数据。
注意下面的代码中,将return Promise.resovle(data)改成了return data,结果依然是一样的
15 Vuex
Vuex是做什么的
官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
它采用集中式存储管理应用所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
状态管理是什么
状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。你可以简单地将其看成把需要多个组件共享的变量全部存储在一个对象里面。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?是响应式。
如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
但是,有什么状态时需要我们在多个组件间共享的呢?如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。比如用户的登录状态、用户名称、头像、地理位置信息等等。比如商品的收藏、购物车中的物品等等。
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
单界面的状态管理
State
:状态(可以当做就是data中的属性)
View
:视图层,可以针对State变化显示不同的信息。
Actions
:主要是用户的各种操作,点击、输入等等会导致状态的改变。
在这个案例中,counter便是需要管理的状态,counter需要某种方式被记录下来,也就是我们的State。
counter目前的值需要被显示在界面中,也就是我们的View部分。
界面发生某些操作时(这里是用户的点击,也可以是用户的input等),需要更新状态,也就是Actions
多界面状态管理
Vue已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
**多个视图都依赖同一个状态(一个状态改了,多个界面需要进行更新),**不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)。也就是说对于某些状态(状态1/状态2/状态3)来说只属于某一个视图,但是也有一些状态(状态a/状态b/状态c)属于多个视图共同想要维护的
状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理。Vuex就是为我们提供这个大管家的工具。
我们现在要做的就是将共享的状态抽取出来,交给大管家进行统一管理。之后,每个视图按照规定好的规定,进行访问和修改等操作。这就是Vuex的基本思想。
Vuex基本使用
先创建一个文件夹store存放Vuex代码,并且在其中创建一个index.js文件
//index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store = Vuex.Store({
state:{
count:0
},
mutations:{
increment(state){
state.count++
},
decrement(state){
state.count--
}
}
})
export default store
然后,让所有的Vue组件都可以使用这个store对象
切换到main.js,导入store对象,并且放在new Vue中。这样,在其他Vue组件中就可以通过this.$store的方式,获取到这个store对象了
//main.js
import Vue from 'vue'
import App from './App'
import store from './store'
new Vue({
el:'#app',
store,
render: h => h(App)
})
这就是使用Vuex最简单的方式,接下来对使用步骤做一个简单的小节
- 提取出一个公共的store对象,用于保存在多个组件中共享的状态
- 将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
- 在其他组件中使用store对象中保存的状态即可
- 通过this.$store.state.属性的方式来访问状态
- 通过this.$store.commit(‘mutation中方法’)来修改状态
注意事项
通过提交mutation的方式,而非直接改变store.state.count。
因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值。
Vuex核心概念
Vuex有几个比较核心的概念,它们分别是
- State
- Getters
- Mutations
- Action
- Module
State 单一状态树
Vuex提出使用单一状态树,英文名称是Single Source of Truth,也可以翻译成单一数据源。那么什么是单一状态树呢? 我用一个生活中的例子做一个简单的类比。
我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等。这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。
这个和我们在应用开发中比较类似
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难,所以Vuex也使用了单一状态树来管理应用层级的全部状态。单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
使用一个store
Getters基本使用
有时候,我们需要从store中获取一些state变异后的状态,比如下面的Store中获取学生年龄大于20的个数。
const store = new Vuex.Store({
state:{
students:[
{id:10010,name:'shiguang',age:20},
{id:10011,name:'zhangsan',age:22},
{id:10012,name:'lisi',age:21},
{id:10013,name:'wangwu',age:19},
]
}
})
我们可以在Store中定义getters
computed:{
getGreaterAgesCount(){
return this.$store.state.students.filter(age => age >= 20).length
}
}
getters:{
greaterAgesCount: state => {
return state.students.filter(s => s.age >= 20).length
}
}
如果我们已经有了一个获取所有年龄大于20岁学生列表的getters, 那么代码可以这样来写
getters:{
greaterAgesStus: state => {
return state.students.filter(s => s.age >=20)
},
greaterAgesCount: (state,getters) => {
return getters.greaterAgesStus.length
}
}
getters默认不能传递参数, 如果希望传递参数, 只能让getters本身返回另一个函数。比如根据ID获取用户的信息
getters:{
stuById: state => {
//return id => {
//return state.students.find(s => s.id === id)
//}
return function(id){
return state.students.find(s => s.id === id)
}
}
}
Mutations状态更新
Vuex的store状态的唯一更新方式: 提交Mutations
Mutation主要包括两部分:
-
字符串的 事件类型(type)
-
一个回调函数(handler),该回调函数的第一个参数就是state。
mutations的定义方式
mutations:{
increment(state){
state.count++
}
}
通过mutation更新
addition:function(){
this.$store.commit('increment')
}
Mutations传递参数
在通过mutations更新数据的时候, 有可能希望携带一些额外的参数,参数被称为是mutations的载荷(Payload)
Mutations中的代码
decrement(state,n){
state.count -= n
}
sub:function(){
this.$store.commit('decrement',2)
}
但是如果参数不是一个呢?比如我们有很多参数需要传递.这个时候,通常会以对象的形式传递, 也就是payload是一个对象。这个时候可以再从对象中取出相关的信息。
//changeCount(state,payload){
// state.count = payload.count
//},
addStudent(state, stu){
state.students.push(stu)
}
changeCount : function(){
//this.$store.commit('changeCount',{count:0})
const stu = {id:114, name:'wlf'}
this.$store.commit('addStudent',stu)
}
Mutations提交风格
Vue还提供了另外一种风格, 它是一个包含type属性的对象
this.$store.commit({
type:'changeCount',
count:100
})
Mutations中的处理方式是将整个commit的对象作为payload使用, 所以代码没有改变, 依然如下
changeCount(state,payload){
state.count = payload.count
}
Mutations响应规则
Vuex的store中的state是响应式的, 当state中的数据发生改变时,Vue组件会自动更新。
这就要求我们必须遵守一些Vuex对应的规则
-
提前在store中初始化好所需的属性
-
当给state中的对象添加新属性时, 使用下面的方式:
方式一: 使用Vue.set(obj, ‘newProp’, 123)
方式二: 用新对象给旧对象重新赋值
我们来看一个例子,当我们点击更新信息时, 界面并没有发生对应改变。
//响应式是 数据方式改变并且页面也会对应渲染
updateInfo(state){
state.info.name='codewhy'//响应式的 因为name之前就存在
state.info['address'] = '北京' //非响应式 address属性之前不存在
Vue.set(state.info,'address','北京') //响应式
//删除对象中的属性
delete state.info.age //非响应式
Vue.delete(state.info, 'age')
}
如何才能让它改变呢?查看下面代码的方式一和方式二,都可以让state中的属性是响应式的。
Mutations常量类型
在mutations中,定义了很多事件类型(也就是其中的方法名称)。当项目增大时,Vuex管理的状态越来越多,需要更新状态的情况越来越多,那么意味着Mutations中的方法越来越多。方法过多,使用者需要花费大量的精力记住这些方法,甚至是多个文件间来回切换,查看方法名称,如果不是复制,可能还会出现写错的情况。
如何避免上述的问题呢?在各种Flux实现中,一种很常见的方案就是使用常量替代Mutations事件的类型,可以将这些常量放在一个单独的文件中,方便管理以及让整个app所有的事件类型一目了然。具体怎么做呢?我们可以创建一个文件: mutation-types.js,并且在其中定义我们的常量。定义常量时,可以使用ES2015中的风格,使用一个常量来作为函数的名称。
Mutation同步函数
通常情况下,Vuex要求我们Mutations中的方法必须是同步方法,主要原因是当使用devtools时,devtools可以帮助我们捕捉mutation的快照。
但是如果是异步操作,那么devtools将不能很好的追踪这个操作什么时候会被完成。比如之前的代码,当执行更新时,devtools中会有如下信息
但是,如果Vuex中的代码,我们使用了异步函数,state中的info数据一直没有被改变,因为他无法追踪到.
所以通常情况下,不要在mutations中进行异步的操作
Actions 基本定义
我们强调,不要在Mutations中进行异步操作,但是某些情况,确实希望在Vuex中进行一些异步操作,比如网络请求,必然是异步的. 这个时候怎么处理呢?
Actions类似于Mutations,但是是用来代替Mutations进行异步操作的。Action的基本使用代码如下
context是和store对象具有相同方法和属性的对象, 我们可以通过context进行commit相关操作,也可以获取context.state等。但是需要注意,它们并不是同一个对象。
我们定义了actions,然后又在actions中去进行commit,这样的代码是否多此一举呢?事实上并不是这样,如果在Vuex中有异步操作,那么我们就可以在actions中完成了。
Actions的分发
在Vue组件(App.Vue)中,如果我们调用actions中的方法,那么就需要使用dispatch
同样地,也是支持传递payload
Actions返回Promise
Promise经常用于异步操作,在Actions中,可以将异步操作放在一个Promise中,并且在成功或者失败后,调用对应的resolve或reject。
Module模块
Module是模块的意思,Vue使用单一状态树也意味着很多状态都会交给Vuex来管理。当应用变得非常复杂时,store对象就有可能变得相当臃肿,为了解决这个问题,Vuex允许我们将store分割成模块(Module),每个模块拥有自己的state、mutations、actions、getters等
Module局部状态
在moduleA中添加state、mutations、getters,mutations和getters接收的第一个参数是局部状态对象
注意:虽然 doubleCount和increment是定义在对象内部,但在调用时,依然通过this.$store直接调用。
Actions的写法
actions的写法接收一个context参数对象,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
如果getters中也需要使用全局的状态,可以接受更多的参数。
项目结构
Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰。
16、网络封装请求
a、网络模块的选择
Vue中发送网络请求有非常多的方式, 那么, 在开发中, 如何选择呢?
- 选择 Ajax
传统的Ajax是基于XMLHttpRequest(XHR)
为什么不用它呢?
非常好解释, 配置和调用方式等非常混乱.
编码起来看起来就非常蛋疼.
所以真实开发中很少直接使用, 而是使用jQuery-Ajax - 选择 jQuery-Ajax
在前面的学习中, 我们经常会使用jQuery-Ajax,相对于传统的Ajax非常好用.
为什么不用它呢?
首先, 我们先明确一点: 在Vue的整个开发中都是不需要使用jQuery了.
那么, 就意味着为了方便我们进行一个网络请求, 特意引用一个jQuery, 你觉得合理吗?
jQuery的代码1w+行.
Vue的代码才1w+行.
完全没有必要为了用网络请求就引用这个重量级的框架. - 选择 Vue-resource
官方在Vue1.x的时候, 推出了Vue-resource.
Vue-resource的体积相对于jQuery小很多.
另外Vue-resource是官方推出的.
为什么不选择它呢?
在Vue2.0退出后, Vue作者就在GitHub的Issues中说明了去掉vue-resource, 并且以后也不会再更新.
那么意味着以后vue-reource不再支持新的版本时, 也不会再继续更新和维护.
对以后的项目开发和维护都存在很大的隐患. - 选择 axios
在说明不再继续更新和维护vue-resource的同时, 作者还推荐了一个框架: axios
axios有非常多的优点, 并且用起来也非常方便.
b、axios的使用
1. 认识 axios
-
为什么选择
axios
- 功能特点:
- 在浏览器中发送
XMLHttpRequests
请求 - 在
node.js
中发送 http请求 - 支持
Promise API
拦截请求和响应
转换请求和响应数据
- 等等
- 在浏览器中发送
- 功能特点:
-
axios
请求方式-
支持多种请求方式:
-
get,获取数据的方式 post,提交数据的方式(表单提交以及文件上传) put,更新数据的方式(提交所有的数据) patch,提交数据的方式 (提交修改的数据) delete,删除数据的方式
-
2. 发送基本请求
-
发送
get
请求演示,默认的就是get请求方式 -
//1.axios的基本使用 //不带参数 axios({ url: 'http://123.207.32.32:8000/home/multidata', }).then(res => { console.log(res); }) //携带参数 axios({ url: 'http://123.207.32.32:8000/home/data', // 专门针对get请求的参数拼接,若用post则用data params: { type: 'pop', page: 1 } }).then(res => { console.log(res); })
发送
并发请求
演示- 有时候, 我们可能需求同时发送两个请求
- 使用
axios.all
, 可以放入多个请求
的数组
. axios.all([x,x,x])
返回的结果是一个数组,使用axios.spread
可将数组[res1,res2]
展开为res1, res2
- 使用
//2.axios发送并发请求 axios.all([axios({ url: 'http://123.207.32.32:8000/home/multidata' }), axios({ url: 'http://123.207.32.32:8000/home/data', params: { type: 'sell', page: 5 } })]).then(results => { console.log(results); console.log(results[0]); console.log(results[1]); }) //或者 .then(axios.spreda((res1, res2)=>{ console.log(res1); console.log(res2); }))
- 有时候, 我们可能需求同时发送两个请求
3. 全局配置
在上面的示例中, 我们的BaseURL
是固定
的
- 事实上, 在开发中可能很多参数都是
固定
的. - 这个时候我们可以进行一些抽取, 也可以利用
axios
的全局配置
//3.使用全局的axios和对应的配置在进行网络请求
axios.defaults.baseURL = 'http://123.207.32.32:8000'
axios.defaults.timeout = 5000
axios.all([axios({
url: '/home/multidata'
}), axios({
url: '/home/data',
params: {
type: 'sell',
page: 5
}
})]).then(axios.spread((res1, res2) => {
console.log(res1);
console.log(res2);
}))
常见的配置选项:
4. axios的实例
为什么要创建axios的实例呢?
- 当我们从axios模块中导入对象时, 使用的实例是默认的实例.
- 当给该实例设置一些默认配置时, 这些配置就被固定下来了.
- 但是后续开发中, 某些配置可能会不太一样.
- 比如某些请求需要使用特定的baseURL或者timeout或者content-Type等.
- 这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息.
//4.创建对应的axios的实例
const instance1 = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance1({
url: '/home/multidata'
}).then(res => {
console.log(res);
})
instance1({
url: '/home/data',
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res);
})
const instance2 = axios.create({
baseURL: 'http://222.111.33.33:8000',
timeout: 10000,
// headers: {}
})
5. axios的封装
还可以直接 return instance,因为instance的返回类型就为 Promise
c、拦截器的使用
import axios from 'axios'
export function request(config) {
// 1.创建axios的实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 2.axios的拦截器
// 2.1.请求拦截的作用
instance.interceptors.request.use(config => {
// console.log(config);
// 1.比如config中的一些信息不符合服务器的要求
// 2.比如每次发送网络请求时, 都希望在界面中显示一个请求的图标
// 3.某些网络请求(比如登录(token)), 必须携带一些特殊的信息
//拦截到并修改为想要的数据之后,需要将 config 返回出去
return config
}, err => {
console.log(err);
})
// 2.2.响应拦截
//服务器已经响应过了,传入结果result: res
instance.interceptors.response.use(res => {
// console.log(res);
return res.data
}, err => {
console.log(err);
})
// 3.发送真正的网络请求
return instance(config)
}
二商城项目
代码链接 https://gitee.com/wlf1224/codewhy_-vue.git