1、el:
①类型:string | HTMLElement
②作用:决定管理哪个DOM对象
2、data:
①类型:Object | Function(组件当中data必须是一个函数)
②作用:Vue实例对应的数据对象
3、methods:
①类型:{[key:string]:Function}
②作用:定义属于Vue的方法,可以在其他地方调用,也可以在指令中调用
不仅可以直接写变量,还可以写简单表达式
<div id="app">
<h2>{{firstName + " " + lastName}}h2> // kevin durant
<h2>{{firstName}} {{lastName}}h2> // kevin durant
<h2>{{count * 2}}h2> // 200
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
firstName:"kevin",
lastName:"durant",
count:100
}
})
script>
只渲染一次,不随着数据改变而改变。
后面不跟表达式。
识别 html 标签
<div id="app">
<h2 v-html="url">h2>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
url:"百度一下"
}
})
script>
v-text作用和Mustache比较相似:都是用于将数据显示在界面中
v-text通常情况下,接受一个string类型
<div id="app">
<h2 v-text="message">h2>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊"
}
})
script>
但一般不用,不够灵活。
不跟表达式
直接将文本原封不动显示出来
<div id="app">
<h2>{{message}}h2> // 你好啊
<h2 v-pre>{{message}}h2> // {{message}}
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊"
}
})
script>
<div id='app' v-cloak>div>
// 在vue解析之前,该div有v-cloak属性
// 在vue解析之后,自动删掉v-cloak属性
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值
对一些属性进行动态绑定,比如图片的链接src、网站的链接href、动态绑定一些类、样式等等。
v-bind | Value |
---|---|
作用 | 动态绑定属性 |
缩写 | : (语法糖) |
预期 | any (with argument) |
参数 | attrOrProp (optional) |
<div id="app">
<img v-bind:src="imgURL" alt="">
<a v-bind:href="aHerf">百度一下a>
<img :src="imgURL" alt="">
<a :href="aHerf">百度一下a>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el: "#app",
data: {
imgURL: "https://cn.bing.com/th?id=OIP.NaSKiHPRcquisK2EehUI3gHaE8&pid=Api&rs=1",
aHerf: "http://www.baidu.com"
}
})
script>
:class="{类名1:boolean,类名2:boolean}"
一般不直接赋值布尔值,而是用变量来赋值。
<div id="app">
<h2 :class="{active:isActive}">{{message}}h2>
<button @click="change">点击变色button>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
active:"active",
isActive:true
},
methods: {
change(){
this.isActive = !this.isActive
}
}
})
script>
:class="[类名1,类名2]"
数组中加引号,则表示的是字符串;不加引号则是变量。
一般不太使用。
利用v-bind:style来绑定一些CSS内联样式。
在写CSS属性名的时候,比如font-size
①我们可以使用驼峰式 (camelCase) fontSize
②字符串形式:短横线分隔 (kebab-case,记得用单引号括起来) ‘font-size’
:style="{key(属性名):value(属性值)}"
对象的key是CSS属性名称
对象的value是具体赋的值,值可以来自于data中的属性
<div id="app">
<h2 :style="{fontSize:'50px'}">{{message}}h2>
<h2 :style="{fontSize:fontSize}">{{message}}h2>
<h2 :style="{fontSize:fontSize1 + 'px'}">{{message}}h2>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
fontSize:'100px',
fontSize1:100
}
})
script>
<div id="app">
<h2 :style="[baseStyle1,baseStyle2]">{{message}}h2>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
baseStyle1:{backgroundColor:'red',fontSize:'100px'},
baseStyle2:{backgroundColor:'black',fontSize:'50px'}
}
})
script>
本质上还是属性,但是写法上类似于函数。命名的时候尽量以属性的形式命名,调用时是调用名字,不需要加小括号()
<div id="app">
<h2>{{firstName+ " " + lastName}}h2> // Kevin Durant
<h2>{{getFullName()}}h2> // Kevin Durant
<h2>{{fullName}}h2> // Kevin Durant
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
firstName:"Kevin",
lastName:"Durant"
},
computed: {
fullName:function(){
return this.firstName + " " + this.lastName
}
},
methods: {
getFullName(){
return this.firstName + " " + this.lastName
}
},
})
script>
<div id="app">
<h2>总价格:{{totalPrice}}h2>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
data:{
books:[
{id:110,name:"JavaScript从入门到入土",price:1},
{id:111,name:"Java从入门到放弃",price:2},
{id:112,name:"编码艺术",price:3},
{id:113,name:"代码大全",price:4},
]
},
computed: {
totalPrice(){
let result= 0;
for (let i = 0; i < this.books.length; i++) {
result += this.books[i].price;
}
return result
}
}
})
script>
methods和computed看起来都可以实现我们的功能,
那么为什么还要多一个计算属性这个东西呢?
原因:计算属性会进行缓存,如果多次使用时,计算属性只会调用一次;而方法会使用一次则调用一次,因此计算属性相对而言性能更好。
项目 | Value |
---|---|
作用 | 绑定事件监听器 |
缩写 | @ |
预期 | Function 、 Inline Statement 、 Object |
参数 | event |
当通过methods中定义方法,以供@click调用时,需要注意参数问题:
① 如果该方法不需要额外参数,那么方法后的()可以不添加。
但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
② 如果需要同时传入某个参数,同时需要event时,可以通过 $event 传入事件。
<div id="app">
<button @click="btnClick">按钮1button>
<button @click="btnClick()">按钮2button>
<button @click="btnClick2(123)">按钮3button>
<button @click="btnClick2()">按钮4button>
<button @click="btnClick2">按钮5button>
<button @click="btnClick3($event,123)">按钮6button>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
<script>
const app = new Vue({
el:"#app",
methods:{
btnClick(){
console.log("事件没传参数");
},
btnClick2(value){
console.log(value);
},
btnClick3(event,value){
console.log(event+value);
}
}
})
script>
修饰符 | 作用 |
---|---|
.stop | 调用 event.stopPropagation()。阻止冒泡 |
.prevent | 调用 event.preventDefault()。 阻止默认行为 |
.keyCode 或 .keyAlias | 只当事件是从特定键触发时才触发回调。 |
.native | 监听组件根元素的原生事件。 |
.once | 只触发一次 |
<div id="app">
<div @click="divClick">
aaaaaaa
<button @click.stop="btnClick">按钮1button>
div>
<div @click="divClick">
aaaaaaa
<button @click.stop="btnClick">按钮2button>
div>
<form action="http://www.baidu.com">
<input type="submit" value="提交" @click="submitClick">
form>
<form action="http://www.baidu.com">
<input type="submit" value="提交" @click.prevent="submitClick">
form>
<input type="text" @keyup.enter="keyUp">
<button @click.once="btn2Click">按钮3button>
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
console.log("btn");
},
divClick() {
console.log("div");
},
submitClick() {
console.log('submit');
},
keyUp() {
console.log('keyUp');
},
btn2Click() {
console.log('调用一次');
}
}
})
script>
v-if、v-else-if、v-else
这三个指令与JavaScript的条件语句if、else、else if类似。
Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
v-if的原理:
v-if后面的条件为false时,对应的元素以及其子元素不会渲染,也就是根本没有不会有对应的标签出现在DOM中。
<div id="app">
<p v-if="score>=90">优秀p>
<p v-else-if="score>=80">良好p>
<p v-else-if="score>=60">及格p>
<p v-else>不及格p>
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
score: 99
}
})
script>
v-if当条件为false时,压根不会有对应的元素在DOM中。
v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
当需要在显示与隐藏之间切片很频繁时,使用v-show
当只有一次切换时,使用v-if
(1)直接遍历 item in Arry
(2)包含索引号 (item,index) in Arry
<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: ['Durant', 'Kobe', 'Irving', 'Curry']
}
})
script>
一般我们在js代码中,遍历数组的写法是:
let books = [1,2,3];
for (let i = 0; i < books.length; i++) {
console.log(books[i]); // 1 2 3
}
现有两种简单的写法:for(let i in/of books)
// 1. in
for(let i in books){
console.log(i); // 0 1 2
conlose.log(books[i]); // 1 2 3
// 此时的i为索引值
}
// 2. of
for(let i of books){
console.log(i); // 1 2 3
// 此时的i为数组元素
// 这里还是把i写出item,好认一点
}
获得值的个数:
①在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value
②获取两个值, 那么获取到的是value,key 格式: (value, key)
③获取三个值, 那么获取到的是value,key ,index 格式: (value, key, index)
注意: value、key、index只是一个代号而已,就算是(key,index,value)这种写法,得到信息是(属性值,属性名,索引号),即此时key是属性值,index是属性名,value是索引号。
<div id="app">
<ul>
<li v-for="item in info">{{item}}li>
ul>
<ul>
<li v-for="(value, key) in info">{{value}}-{{key}}li>
ul>
<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: 'why',
age: 18,
height: 1.88
}
}
})
script>
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。
见文章
因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。
Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。
以下方法可以做到响应式
方法 | 作用 |
---|---|
push() | 在数组最后追加数据 |
pop() | 删除数组最后一个数据 |
shift() | 删除数组第一个元素 |
unshift() | 在数组最前面添加元素 |
splice() | 删除元素、插入元素、替换元素 |
sort() | 排序 |
reverse() | 翻转数组 |
// splice作用: 删除元素/插入元素/替换元素
// 第一个参数是索引号,代表起始位置
var arr = ['a','b','c'];
// 删除元素: 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素)
arr.splice(1,1); // ['a','c']
// 插入元素: 第二个参数, 传入0, 并且后面跟上要插入的元素
arr.splice(1,0,'d'); // ['a','b','d','c']
// 替换元素: 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素
arr.splice(1,1,'d'); // ['a','d','c']
arr.splice(1,1,'d','f'); // ['a','d','f','c']
// 替换这个功能可以理解为删除再插入,比如第二个,先把[1]的b删除,再把后面的'd','f'插入
单独使用.sort()时,即没有参数。
判断个位数的大小,可以从小到大排序;
但是数值是多位数的时候,它的判断方式却变了,它先比较第一位数,再比较第二位…
所以下面案例中的排序是 1,13,4,7,77。
var arr1 = [1,4,7,3];
arr1.sort();
console.log(arr1); // [1,3,4,7]
var arr2 = [13,1,7,4,77];
arr2.sort();
console.log(arr2); //[1,13,4,7,77]
要想按照从小到大的排序,有以下模板:
arr.sort(function(a,b){
return a-b;//升序
});
从小到大:
arr.sort(function(a,b){
return b-a;//降序
});
注意:sort的括号是 括 整个函数的,最后记得加分号
var arr1 = [13,1,7,4,77];
arr1.sort(function(a,b){
return a-b;//升序
});
console.log(arr1); //[1,4,7,13,77]
var arr2 = [13,1,7,4,77];
arr2.sort(function(a,b){
return b-a;//降序
});
console.log(arr2); //[77,13,7,4,1]
Vue中使用v-model指令来实现表单元素和数据的双向绑定
<div id="app">
<input type="text" v-model="message">
{{message}}
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
script>
当我们在输入框输入内容时,因为input中的v-model绑定了message,所以会实时将输入的内容传递给message,message发生改变。当message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变。
所以,通过v-model实现了双向的绑定。
当然,我们也可以将v-model用于textarea元素
v-model其实是一个语法糖,它的背后本质上是包含两个操作:
1.v-bind绑定一个value属性
2.v-on指令给当前元素绑定input事件
<input type="text" v-model="message"> {{message}}
等同于:
<input type="text" :value="message" @input="message = $event.target.value">>{{message}}
单选,把选择的东西存入信息。
这个案例就是把选择的性别存进数据里面去
<div id="app">
<label for="male">
<input type="radio" id="male" value="男" v-model="sex">男
label>
<label for="female">
<input type="radio" id="female" value="女" v-model="sex">女
label>
<h2>您选择的性别是: {{sex}}h2>
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
sex: ''
}
})
script>
v-model 绑定同一个变量sex,所以可以互斥。(这个功能等同于两个radio都设置name=“sex”)
复选框分为两种情况:单个勾选框和多个勾选框
(1)单个勾选框:
①v-model即为布尔值。
②此时input的value并不影响v-model的值。
(2)多个复选框:
①当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
②当选中某一个时,就会将input的value添加到数组中。
<div id="app">
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">同意协议
label>
<h2>您选择的是: {{isAgree}}h2>
<button :disabled="!isAgree">下一步button>
<input type="checkbox" value="篮球" v-model="hobbies">篮球
<input type="checkbox" value="足球" v-model="hobbies">足球
<input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球
<input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球
<h2>您的爱好是: {{hobbies}}h2>
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
isAgree: false, // 单选框
hobbies: [] // 多选框,
}
})
script>
不常用
(1)单选:只能选中一个值。
①v-model绑定的是一个值。
②当我们选中option中的一个时,会将它对应的value赋值到mySelect中
(2)多选:可以选中多个值。 multiple
①v-model绑定的是一个数组。
②当选中多个值时,就会将选中的option对应的value添加到数组mySelects中
③多选时要用crtl + 鼠标点击
<div id="app">
<select name="abc" v-model="fruit">
<option value="苹果">苹果option>
<option value="香蕉">香蕉option>
<option value="榴莲">榴莲option>
<option value="葡萄">葡萄option>
select>
<h2>您选择的水果是: {{fruit}}h2>
<select name="abc" v-model="fruits" multiple>
<option value="苹果">苹果option>
<option value="香蕉">香蕉option>
<option value="榴莲">榴莲option>
<option value="葡萄">葡萄option>
select>
<h2>您选择的水果是: {{fruits}}h2>
div>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
fruit: '香蕉',
fruits: []
}
})
script>
就是动态的给value赋值而已。
v-bind:value=""
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
// 当num1和num2改变时,会调用对应的函数
watch:{
num1(newvalue){
console.log(num1改变了);
this.num2 = newvalue * 10; // num2改变,会调用num2(){}
},
num2(newvalue){
console.log(num2改变了);
}
}
(1)lazy修饰符:
默认情况下,v-model默认是在input事件中同步输入框的数据的。也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。
lazy修饰符可以让数据在失去焦点或者按回车时才会更新
(2)number修饰符:
默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
number修饰符可以让在输入框中输入的内容自动转成数字类型
(3)trim修饰符:
如果输入的内容首尾有很多空格,通常我们希望将其去除,trim修饰符可以过滤内容左右两边的空格
可被用于一些常见的文本格式化。
过滤器可以用在两个地方:双花括号插值和 v-bind 表达式
<div id='app'>
{{123 | show}} // ¥123.00
div>
<script>
const app = new Vue({
el:'#app';
filters:{
show(price){
return '¥'+price.toFixed(2)
}
}
})
script>
监听某个变量属性的变化,函数名为该属性名,传入的参数:一个,则为newvalue;两个则为newvalue,oldvalue
尽可能的将页面拆分成一个个小的、可复用的组件。
这样让我们的代码更加方便组织和管理,并且扩展性也更强。
组件的使用分成三个步骤:
①创建组件构造器: Vue.extend()
②注册组件: Vue.component()
③使用组件。在Vue实例范围内使用
<div id="app">
<my-cpn>my-cpn>
<div>
<my-cpn>my-cpn>
div>
div>
<script src="../js/vue.js">script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
我是标题
我是内容, 哈哈哈哈
我是内容, 呵呵呵呵
`
})
// 2.注册组件
Vue.component('my-cpn', cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
script>
①全局组件, 意味着可以在多个Vue的实例下面使用
在new Vue 外部注册
②局部组件,在new Vue内部注册,components
<div id="app">
<cpn>cpn>
div>
<div id="app2">
<cpn>cpn> // 全局时可以使用,局部时,由于没有注册,不能使用
div>
<script src="../js/vue.js">script>
<script>
// 1.创建组件构造器
const cpnC = Vue.extend({
template: `
我是标题
我是内容,哈哈哈哈啊
`
})
// 2.注册组件
// ①全局组件, 意味着可以在多个Vue的实例下面使用
// Vue.component('cpn', cpnC)
const app = new Vue({
el: '#app',
// ②局部组件
components: {
cpn: cpnC // cpn使用组件时的标签名,cpnC组件名
}
}
const app2 = new Vue({
el: '#app2'
})
script>
A组件在B组件内注册,则A组件是B组件的子组件。
子组件只能在父组件内使用,子组件不能在实例或其他地方使用,除非子组件在实例或者全局注册。
<div id="app">
<cpn2>cpn2>
<cpn1>cpn1>
div>
<script src="../js/vue.js">script>
<script>
// 1.创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
我是标题1
我是内容, 哈哈哈哈
`
})
// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template: `
我是标题2
我是内容, 呵呵呵呵
`,
// 注册子组件
components: {
cpn1: cpnC1
}
})
// root组件,实例相当于一个组件
const app = new Vue({
el: '#app',
components: {
cpn2: cpnC2
}
})
script>
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
<div id="app">
<cpn1>cpn1>
<cpn2>cpn2>
div>
<script src="../js/vue.js">script>
<script>
// 1.全局组件注册的语法糖
// 1.创建组件构造器
// const cpn1 = Vue.extend()
// 2.注册组件
Vue.component('cpn1', {
template: `
我是标题1
我是内容, 哈哈哈哈
`
})
// 2.注册局部组件的语法糖
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
'cpn2': {
template: `
我是标题2
我是内容, 呵呵呵
`
}
}
})
script>
①使用 script 标签,text/x-template
②使用 template 标签 (较常用)
记得给模板定个id
script标签:
<div id="app">
<cpn>cpn>
div>
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
script>
<script src="../js/vue.js">script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
const app = new Vue({
el: '#app'
})
script>
template标签:
<template id="cpn">
<div>
<h2>我是标题h2>
<p>我是内容,呵呵呵p>
div>
template>
组件是一个单独功能模块的封装,它有属于自己的HTML模板,也应该有属性自己的数据data。
①组件对象也有一个data属性(也可以有methods等属性)
②data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据
<div id="app">
<cpn>cpn>
div>
<template id="cpn">
<div>
<h2>{{title}}h2>
div>
template>
<script src="../js/vue.js">script>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn',
// data是一个函数,返回一个对象,这个对象存放数据
data() {
return {
title: 'abc'
}
}
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
}
})
script>
组件中data返回对象有两种写法:
(1)返回的对象在data里面定义。(信息不会共享)
(2)返回的对象在外部定义。(信息共享)
第一种写法:
<div id="app">
<cpn>cpn>
<cpn>cpn>
<cpn>cpn>
div>
<template id="cpn">
<div>
<h2>当前计数: {{counter}}h2>
<button @click="increment">+button>
<button @click="decrement">-button>
div>
template>
<script src="../js/vue.js">script>
<script>
Vue.component('cpn', {
template: '#cpn',
data() {
return {
counter: 0
}
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
const app = new Vue({
el: '#app',
})
script>
当点击任意一个模块时,只会改变当前组件的count的值。因为这种写法是调用组件的data时才定义一个对象,则每个对象都有自己的一个内存地址,当调用三次组件的data时,返回的是三个不同内存地址的对象(虽说内容相同,但地址不同)。所以信息不会共享共享。
第二种写法:
<script>
const obj = {
counter: 0
}
Vue.component('cpn', {
template: '#cpn',
data() {
return obj
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
script>
当点击任意一个模块均会改变count的值。因为这种写法是先将obj定义,则obj有一个内存地址,当调用三次组件的data时,返回的都是同一个内存地址的obj。所以导致信息共享。
父组件通过props向子组件传递数据
子组件通过事件向父组件发送消息
真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。
在子组件中使用选项 props 来声明需要从父级接收到的数据。
props的值有两种方式:
方式一:字符串数组,数组中的字符串就是传递时的名称。
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
字符串数组形式:(不常用)
<div id="app">
<cpn :cmessage="message" :cmovies="movies">cpn>
div>
<template id="cpn">
<div>
<p>{{cmovies}}p>
<h2>{{cmessage}}h2>
div>
template>
<script src="../js/vue.js">script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
props: ['cmovies', 'cmessage']
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
script>
对象形式:(当需要对props进行类型等验证时,就需要对象写法了)
验证支持的数据类型:String、Number、Boolean、Array、Object、Date、Function、Symbol
<div id="app">
<cpn :cmessage="message" :cmovies="movies">cpn>
div>
<template id="cpn">
<div>
<p>{{cmovies}}p>
<h2>{{cmessage}}h2>
div>
template>
<script src="../js/vue.js">script>
<script>
// 父传子: props
const cpn = {
template: '#cpn',
props: {
// 1.类型限制
// cmovies: Array,
// cmessage: String,
// 2.提供一些默认值, 以及必传值
cmessage: {
type: String, // 数据类型
default: 'aaaaaaaa', // 默认值
required: true // true表示调用这个组件时,必须调用cmessage这个属性
},
// 类型是对象或者数组时, 默认值必须是一个函数
cmovies: {
type: Array,
default() {
return []
}
}
}
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['海王', '海贼王', '海尔兄弟']
},
components: {
cpn
}
})
script>
当我们在props中声明变量时,采用的驼峰命名法的话,即cMessage,在调用子组件且传递父组件数据时,v-bind绑定属性名不能驼峰标识,需要变换。cMessage 转换成 c-message
<div id="app">
<cpn :cMessage="message">cpn>
<cpn :c-message="message">cpn>
div>
<template id="cpn">
<div>
<h2>{{cMessage}}h2>
div>
template>
<script src="../js/vue.js">script>
<script>
const cpn = {
template: '#cpn',
props: {
cMessage: {
type: String,
default: '很好'
}
}
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn
}
})
script>
子组件传递数据或事件到父组件中需要使用自定义事件来完成。
自定义事件的流程:
在子组件中,通过$emit(‘事件名’,参数)来触发事件。(emit是发射的意思)
在父组件中,通过v-on来监听子组件事件。 @事件名=‘父组件的事件’
<div id="app">
<cpn @item-click="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('item-click', item);
// item-click为父组件的绑定事件名 @item-click='' ,可以传参数
}
}
}
// 2.父组件
const app = new Vue({
el: '#app',
components: {
cpn
},
methods: {
cpnClick(item) {
console.log('cpnClick', item);
}
}
})
script>
父组件访问子组件:使用 children或 refs (reference 引用)
子组件访问父组件:使用 parent
访问根组件:使用 root
最常用的是 refs
children 获取到的是子组件的集合,是一个数组,访问其中的子组件必须通过索引值。
children的缺陷:
当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
refs 相当于key,先给每个子组件定义ref属性,再使用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);
}
console.log(this.$children[1].name);
// 2.$refs => 对象类型, 默认是一个空的对象
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
}
},
}
})
script>
parent:
①尽管在Vue开发中,允许我们通过 parent来访问父组件,但是在真实开发中尽量不要这样做。
②子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
③如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
④另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
组件的插槽也是为了让我们封装的组件更加具有扩展性。让使用者可以决定组件内部的一些内容到底展示什么。
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
在子组件中使用标签 slot
在父组件中直接使用子组件,并在内部使用想要的标签即可。
<div id="app">
<cpn>cpn>
<cpn><i>呵呵呵i>cpn>
<cpn>
<i>呵呵呵i>
<div>我是div元素div>
<p>我是p元素p>
cpn>
<cpn>cpn>
div>
<template id="cpn">
<div>
<h2>我是组件h2>
<p>我是组件, 哈哈哈p>
<slot><button>按钮button>slot>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
script>
当有多个slot插槽时,想替换某个插槽的内容,只需给slot元素一个name属性,再在替换时,使用这个name属性(slot=“name”)即可。
<div id="app">
<cpn><span slot="center">标题span>cpn>
<cpn><button slot="left">返回button>cpn>
div>
<template id="cpn">
<div>
<slot name="left"><span>左边span>slot>
<slot name="center"><span>中间span>slot>
<slot name="right"><span>右边span>slot>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
script>
父组件替换插槽的标签,但是内容由子组件来提供。
最核心就是从子组件获得数据,并可以在父组件中使用。
<div id="app">
<cpn>cpn>
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join(' - ')}}span>
template>
cpn>
div>
<template id="cpn">
<div>
<slot :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: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
}
}
}
}
})
script>
CommonJS、AMD、CMD,也有ES6的Modules
Node用的多,Node会学到
导入module.exports = { }
导出 let { } = require(’./…js’)
先在导入js文件处写入 type=“module”
<script src="aaa.js" type="module">script>
<script src="mmm.js" type="module">script>
export(导出)、inport(导入)
导出方式一:
var flag= true;
function sum(num1,num2){
return num1 + num2
}
class Person {
run() {
console.log('在奔跑');
}
}
export {
flag,
sum,
Person
}
导出方式二:(定义时就先导出)
export var num1 = 1000;
// 导出函数/类:
export function mul(num1, num2) {
return num1 * num2
}
export class Person {
run() {
console.log('在奔跑');
}
}
某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名,使用export default
注:export default在同一个模块中,不允许同时存在多个。
// export default
export default const address = '北京市'
// 导出函数
export default function(argument) {
console.log(argument);
}
// 导入的写法与平常不一样
import addr from "./aaa.js"; // addr为自己命名的,不再需要加{}
导入:
// 部分导入
import {flag,sum} from "./aaa.js";
// 统一全部导入
import * as bbb from './aaa.js' // bbb为自己命名的名字
console.log(bbb.flag); // 调用导入的flag
console.log(bbb.sum(10,20)); // 调用导入的sum()
从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具。
准备工作:
文件和文件夹解析:
1、dist文件夹:用于存放之后打包的文件。dist全写distribution(发布)
2、src文件夹:用于存放我们写的源文件。js代码用模块化开发。
①main.js:项目的入口文件。
②mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。
index.html:浏览器打开展示的首页html
package.json:通过npm init生成的,npm包管理的文件(暂时没有用上,后面才会用上)
webpack打包配置步骤.
plugin是插件,它是对webpack本身的扩展,是一个扩展器。
plugink打包配置步骤.
当我们的内容发生改变时,实时自动在内存帮我们编译,等到我们真正完成代码时,在终端npm run build才把文件放到磁盘。
搭建本地服务器步骤.
根据开发时的配置与发布时的配置进行配置文件分离。
可见视频 P89。目前作用不大。
CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架。
使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.
安装脚手架失败,可以试试:
npm clean cache -force
或者到 C:\Users\Administrator\AppData\Roaming 把npm-cache这个文件夹删了。再安装脚手架。
1、全局安装npm install @vue/cli -g
这个安装的CLI3,如果需要想按照Vue CLI2的方式初始化项目时不可以的。
2-1、Vue CLI2:vue install -g @vue/cli-int (全局安装)
初始化项目:vue init webpack my-project
2-2、Vue CLI3初始化项目:vue create my-project
如果ESLint选择了yes后后悔了,可以在config文件里的index.js中将useEslint改为false。
跑起来:npm run dev
打包:npm run build
可以看package.json里的配置。
vue-cli 3 与 2 版本有很大区别:
①vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
②vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录
③vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
④移除了static文件夹,新增了public文件夹,并且index.html移动到public中
Vue CLI3初始化项目:vue create my-project
跑起来:npm run serve
编译打包:npm run build
可以看package.json里的配置。
路由就是通过互联的网络把信息从源地址传输到目的地址的活动——维基百科。
路由就是决定数据包从来源到目的地的路径.
路由中有一个非常重要的概念叫路由表,本质上就是一个映射表, 决定了数据包的指向.
前后端渲染及前后端路由.
URL的hash也就是锚点(#),我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新。
history.back() 等价于 history.go(-1)
history.forward() 则等价于 history.go(1)
这三个接口等同于浏览器界面的前进后退。
见文章路由hash、history的区别.
webpack安装:npm install vue-router --save
CLI安装:选择vue-router yes即可
CLI安装会自动帮我们配置好,如果是webpack安装,就需要我们自己配置。可按以下进行配置:
src/router/index.js
// 配置路由相关的信息
import Vue from 'vue'
import VueRouter from 'vue-router'
// 1.通过Vue.use(插件), 安装插件
Vue.use(VueRouter)
// 配置路由和组件之间的映射关系(映射表)
const routes = [
]
// 2.创建VueRouter对象
const router = new VueRouter({
routes
})
// 3.将router对象传入到Vue实例
export default router
main.js
import Vue from 'vue'
import App from './App'
import router from './router' // 导入路由 导入的是文件夹的话,会默认导入index文件
Vue.config.productionTip = false
new Vue({
el: '#app',
router, //导入路由
render: h => h(App)
})
src/router/index.js
// 配置路由相关的信息
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../components/Home' // 导入组件
import About from '../components/About' // 导入组件
// 1.通过Vue.use(插件), 安装插件
Vue.use(VueRouter)
// 配置路由和组件之间的映射关系
const routes = [
{
// 路由的默认路径
path: '',
redirect: '/home' // redirect重定向
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
// 2.创建VueRouter对象
const router = new VueRouter({
routes,
mode:'history' // mode:模式,默认情况是hash值,也就是‘#/home’,改用ES5的history就没有#
})
// 3.将router对象传入到Vue实例
export default router
main.js
import Vue from 'vue'
import App from './App'
import router from './router' // 导入路由 导入的是文件夹的话,会默认导入index文件
Vue.config.productionTip = false
new Vue({
el: '#app',
router, //导入路由
render: h => h(App)
})
App.vue
<template>
<div id="app">
<router-link to="/home">首页router-link>
<router-link to="/about">关于router-link>
<router-view>router-view>
div>
template>
router-link:该标签是一个vue-router中已经内置的组件, 它会被默认渲染成一个 a 标签.
router-view:该标签会根据当前的路径, 动态渲染出不同的组件.
网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和router-view处于同一个等级.
在路由切换时, 切换的是router-view挂载的组件, 其他内容不会发生改变
router-link:该标签是一个vue-router中已经内置的组件, 它会被默认渲染成一个 a 标签.
属性:
①to:用于指定跳转的路径.
②tag:可以指定router-link之后渲染成什么标签,比如渲染成一个button标签,tag=“button”
③replace:默认跳转方式是pushState,想要设置成replaceState,可以直接加上replace属性。
④active-class: 当router-link对应的路由匹配成功时,会自动给当前元素设置一个router-link-active的class,设置active-class可以修改默认的名称。
当我们在对进行高亮显示的导航菜单或者底部tabbar时, 会使用到该类。
但是呢,如果有多个router-link就需要修改多次,所以有一种简便写法:
src/router/index.js
const router = new VueRouter({
routes,
mode:'history',
linkActiveClass:'active' // 路由匹配成功时,默认添加的类名为active
})
export default router
之前的跳转是依赖router-link,现在不用router-link来跳转的话,可以这样:
App.vue
<template>
<div id="app">
<button @click="homeClick">首页button>
<button @click="aboutClick">关于button>
<router-view>router-view>
div>
template>
<script>
export default {
name: 'App',
methods: {
homeClick() {
// 通过代码的方式修改路由 vue-router
// this.$router.push('/home')
this.$router.replace('/home') // router其实是index.js中定义的router(Vuerouter对象)
},
aboutClick() {
// this.$router.push('/about') // push=>pushState
this.$router.replace('/about') // replace=>replaceState
}
}
}
script>
在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:
/user/aaaa或/user/bbbb(除了有前面的/user之外,后面还跟上了用户的ID),也可以通过路由获取到信息。
index.js中的路径写法:
{
path: '/user/:id', // id是从App.vue获取到的useId
component: User
}
App.vue
<router-link :to="'/user/'+userId">用户router-link>
<script>
export default {
name: 'App',
data() {
return {
userId: 'zhangsan'
}
}
}
script>
这样就可以在打开组件User.vue时路径显示的是 /user/zhangsan
获取路径里的zhangsan:
User.vue
<template>
<div>
<h2>{{userId}}h2>
<h2>{{$route.params.id}}h2>
div>
template>
<script>
export default {
name: "User",
computed: {
userId() {
return this.$route.params.id
// 这里的route与“另一种跳转方式”的router是不一样的
// route是router里面的routes中活跃的路由
// id是活跃路由中path: '/user/:id'的id
}
}
}
script>
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。
如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块,只有在这个路由被访问到的时候, 才加载对应的组件。
之前的写法:
index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../components/Home' // 导入组件
import About from '../components/About' // 导入组件
Vue.use(VueRouter)
const routes = [
{
// 路由的默认路径
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
路由懒加载写法:
import Vue from 'vue'
import VueRouter from 'vue-router'
const Home = () => import('../components/Home') // 动态导入组件
const About = () => import('../components/About') // 动态导入组件
Vue.use(VueRouter)
const routes = [
{
// 路由的默认路径
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
1、创建对应的子组件(News.vue,Message.vue),并且在路由映射中配置对应的子路由:
import Vue from 'vue'
import VueRouter from 'vue-router'
const Home = () => import('../components/Home')
const News = () => import('../components/News') // 动态导入子组件News
const Message = () => import('../components/Message') // 动态导入子组件Message
const About = () => import('../components/About')
Vue.use(VueRouter)
const routes = [
{
// 路由的默认路径
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home,
children:[
{
path:'',
redirect: News
},
{
path:'news', // 不用加/
component: News
},
{
path:'message', // 不用加/
component: Message
}
]
},
{
path: '/about',
component: About
}
]
2、在两个子组件的父组件内部使用router-link和router-view标签.
Home.vue
<template>
<div>
<router-link to="/home/news">新闻router-link>
<router-link to="/home/message">消息router-link>
<router-view>router-view>
div>
template>
<script>
export default {
name: "Home"
}
script>
URL:
协议://主机:端口/路径?查询#片段
scheme://host:port/path?query#fragment
传递参数主要有两种类型: params和query
配置路由格式:/router/:id
传递的方式:在path后面跟上对应的值
传递后形成的路径:/router/123, /router/abc
见2-4、动态路由
另一种跳转方式,不用router-link跳转(参考2-3(2))
<template>
<div>
<button @click="userClick">用户button>
div>
template>
<script>
export default {
name: 'App',
data(){
return {
userId: 'zhangsan'
}
},
methods:{
userClick(){
this.$route.push('/user'+this.userId)
}
}
script>
配置路由格式:/router, 也就是普通配置
传递的方式:对象中使用query的key作为传递方式
传递后形成的路径:/router?id=123, /router?id=abc
index.js(不用跟params一样设置,正常设置就行)
{
path: '/profile',
component: Profile
}
App.vue
<template>
<div>
<router-link :to="{path:'/profile',query:{name:'wu',age:20}}">我的router-link>
<router-view>router-view>
div>
template>
Profile.vue
<template>
<div>
<h2>{{$route.query}}h2>
<h2>{{$route.query.name}}h2>
<h2>{{$route.query.age}}h2>
div>
template>
此时的url为 …/profile?name=wu&&age=20
另一种跳转方式,不用router-link跳转
<template>
<div>
<button @click="profileClick">我的button>
div>
template>
<script>
export default {
name: 'App',
methods:{
profileClick(){
this.$route.push({
path: '/profile',
qurey:{
name: 'wu',
age: 20
}
})
}
}
script>
router为VueRouter实例,可以导航到不同URL,使用$router.push/replace方法
$route为当前router跳转的对象(route是router里面的routes中活跃的路由),可以获取name、path、query、params等
在一个SPA应用中, 如何改变网页的标题呢?
网页标题是通过 title 标签来显示的, 但是SPA只有一个固定的HTML, 切换不同的页面时, 标题并不会改变。利用导航守卫可解决这一问题。
什么是导航守卫?
①vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
②vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发。
③前置守卫和后置钩子都是全局守卫
④路由独享守卫 beforeEnter,参数与beforeEach一致。
⑤组件内的守卫 beforeRouteEnter(在渲染该组件的对应路由前被调用)、beforeRouteUpdate(在当前路由改变,但是该组件被复用时调用)、beforeRouteLeave(导航离开该组件的对应路由前调用)
改变网页标题:
index.js
const routes = [
{
path: '/home',
component: Home,
meta: { // meta元数据(描述数据的数据)
title: '首页'
}
},
{path: '/about',component: About,meta: {title: '关于'}},
{path: '/user/:id',component: User,meta: {title: '用户'}},
{path: '/profile', component: Profile,meta: {title: '档案'}}
]
// 前置守卫(guard)
router.beforeEach((to, from, next) => {
// 从from跳转到to
document.title = to.matched[0].meta.title
// 路由存在嵌套的话,必须调用matched才能调用到meta,所以干脆都使用
// console.log(to); // 活跃的路由
next() // beforeEach必写,跳转页面
})
导航钩子的三个参数解析:
①to:即将要进入的目标的路由对象.
②from:当前导航即将要离开的路由对象.
③next:调用该方法后, 才能进入下一个钩子.
next(’/路径’) 指定某个路径
后置钩子:
// 后置钩子(hook)
router.afterEach((to, from) => {
// console.log('----');
})
路由独享守卫:
{
path: '/home',
component: Home,
meta: { // meta元数据(描述数据的数据)
title: '首页'
},
beforeEnter:(to,from,next) => {
// ...
next()
}
}
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存。
生命周期回顾.
其中activated函数和deactivated函数均在keep-alive存在时才能使用。
activated(活跃)、deactivated(不活跃)
App.vue
<keep-alive>
<router-view>router-view>
keep-alive>
Home.vue
export default {
name: "Home",
activated(){
console.log('Home activated')
},
deactivated(){
console.log('Home deactivated')
}
}
当我们的页面在Home时,会打印Home activated,当页面离开Home时,会打印Home deactivated。(当然,这是在存在keep-alive的情况下)
调用keep-alive后,组件不会被频繁的创建销毁。它会使其保留状态,或避免重新渲染。
没有使用keep-alive时,组件被调用时就创建,切换时又被销毁。
测试:
App.vue
<keep-alive>
<router-view>router-view>
keep-alive>
Home.vue
export default {
name: "Home",
created(){
console.log('Home created')
},
destroyed(){
console.log('Home destroyed')
}
}
①include(包括) 值为字符串或正则表达,只有匹配的组件会被缓存
②exclude(不包括) 值为字符串或正则表达式,任何匹配的组件都不会被缓存
App.vue
<keep-alive exclude="Home,About">
<router-view>router-view>
keep-alive>
当需要到多个外层文件夹取文件时,…/…/ 就需要很多个了,这在开发中很忌讳,而且如果移动文件所在地,就可能需要改动调用路径代码了。所以起个别名就非常方便。
webpack.base.conf.js
module.exports = {
resolve: {
alias: {
'@': resolve('src'), // 当前项目的src文件夹(第一层)
'assets': resolve('src/assets'),
'components': resolve('src/components'),
'views': resolve('src/views'),
}
}
}
调用:有两种情况:①在html标签内调用(图片的引用)②在import调用
①html标签(加~)
<img slot="item-icon" src="~assets/img/tabbar/home.svg" alt="">
②在import调用
import TabBar from 'components/tabbar/TabBar'
// component是src/component 直接调用就行
Promise是异步编程的一种解决方案。
什么时候我们会来处理异步事件呢?
一种很常见的场景应该就是网络请求了。我们封装一个网络请求的函数,因为不能立即拿到结果,所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。但是,当网络请求非常复杂时,就会出现回调地狱(多个回调函数)。
回调地狱的一般写法,在正常情况下,不会有什么问题,可以正常运行并且获取我们想要的结果。但是,这样的代码难看而且不容易维护。
// Promise的参数是一个函数(resolve,reject)=>{}
// 这个函数的参数有两个:resolve, reject它们本身又是函数(可以传递参数也可以不传)
// reject用不到时可以不传
new Promise((resolve,reject) => {
// 异步事件
setTimeout(() => {
// 请求成功的时候调用resolve
resolve()
// 请求失败的时候调用reject
reject()
}, 1000)
}).then(() => {
// 请求成功时的100行处理代码
// ...
// 另一个异步事件,再来一个Promise
return new Promise((resolve) =>{}) // 用不到reject时可以不传
}).catch(() => {
// 请求失败时的处理代码
// ...
})
案例:setTimeout里面有setTimeout
回调地狱的一般写法:
setTimeout(() => {
console.log('Hello World');
console.log('Hello World');
console.log('Hello World');
setTimeout(() => {
console.log('Hello Vuejs');
console.log('Hello Vuejs');
console.log('Hello Vuejs');
setTimeout(() => {
console.log('Hello Python');
console.log('Hello Python');
console.log('Hello Python');
}, 1000)
}, 1000)
}, 1000)
Promise的写法:
// 链式编程
new Promise((resolve, reject) => { // 用不到reject时可以不传
// 第一个异步事件
setTimeout(() => {
resolve()
}, 1000)
}).then(() => {
// 第一个异步事件的处理代码
console.log('Hello World');
console.log('Hello World');
console.log('Hello World');
// 第二个异步事件
return new Promise((resolve) => { // 用不到reject时可以不传
setTimeout(() => {
resolve()
}, 1000)
})
}).then(() => {
// 第二个异步事件处理的代码
console.log('Hello Vuejs');
console.log('Hello Vuejs');
console.log('Hello Vuejs');
// 第三个异步事件
return new Promise((resolve) => { // 用不到reject时可以不传
setTimeout(() => {
resolve()
},1000)
})
}).then(() => {
// 第三个异步事件处理的代码
console.log('Hello Python');
console.log('Hello Python');
console.log('Hello Python');
})
注: 其实第二个then()是跟在第一个then()后面的,第三个then()是跟在第二个then()后面的。而不是跟在Promise()后面
网络请求到aaa -> 处理成aaa111 -> 再处理成aaa111222 -> 再拒绝处理成aaa111222error->再处理成aaa111222error444 。所以整个过程只有一个异步操作。
常规链式编程:
new Promise(resolve => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(data => {
console.log(data); // => aaa
// 2.对结果进行第一次处理
return new Promise(resolve => {
resovle(data + '111')
})
}).then(data => {
console.log(data); // => aaa111
return new Promise(resolve => {
resovle(data + '222')
})
}).then(data => {
console.log(data); // => aaa111222
return new Promise(reject => {
reject(data + 'error')
})
}).then(data =>{
console.log(data); // 这里没有输出,不会被执行的
return new Promise(resolve => {
resovle(data + '333')
})
}).catch(data => {
console.log(data); // => aaa111222error
return new Promise(resolve => {
resovle(data + '444')
})
}).then(data => {
console.log(data); // => aaa111222error444
})
new Promise(resolve => resolve(结果))简写:
①Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
②Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数
new Promise(resolve => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(data => {
console.log(data); // => aaa
// 2.对结果进行第一次处理
return Promise.resovle(data + '111') // Promise.resolve的简写
}).then(data => {
console.log(data); // => aaa111
return Promise.resovle(data + '222')
}).then(data => {
console.log(data); // => aaa111222
return Promise.reject(data + 'error') // Promise.reject的简写
}).then(data =>{
console.log(data); // 这里没有输出,不会被执行的
return Promise.resovle(data + '333')
}).catch(data => {
console.log(data); // => aaa111222error
return Promise.resovle(data + '444')
}).then(data => {
console.log(data); // => aaa111222error444
})
省略掉Promise.resolve
如果我们希望数据直接包装成Promise.resolve,那么在then中可以直接返回数据
new Promise(resolve => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(data => {
console.log(data); // => aaa
// 2.对结果进行第一次处理
return data + '111' // Promise.resolve的简写
}).then(data => {
console.log(data); // => aaa111
return data + '222'
}).then(data => {
console.log(data); // => aaa111222
throw data + 'error' // Promise.reject的简写
}).then(data =>{
console.log(data); // 这里没有输出,不会被执行的
return data + '333'
}).catch(data => {
console.log(data); // => aaa111222error
return data + '444'
}).then(data => {
console.log(data); // => aaa111222error444
})
某个请求需要两个请求都完成才能继续进行:
Promise.all([ ]) // 数组类型,因为要放多个请求
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'why', age: 18})
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'kobe', age: 19})
}, 1000)
})
]).then(results => { // 两个请求都完成时才调用
console.log(results);
})
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
Vuex是响应式的。
在多个界面间的共享的可以放在Vuex,比如用户的登录状态、用户名称、头像、地理位置信息等等。比如商品的收藏、购物车中的物品等等。
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
安装Vuex插件:npm install vuex --save
在src下建文件夹:store(仓库)
再建index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 安装插件
Vue.use(Vuex)
const store = new Vuex.Store({
state{},
mutations{},
actions{},
getters{},
modules: {}
})
// 导出
export default store
main.js引用(挂载到vue实例中)
这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
import Vue from 'vue'
import App from './App'
import store from './store' // 导入store
Vue.config.productionTip = false
new Vue({
el: '#app',
store, // 导入store
render: h => h(App)
})
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state{
count = 1000
},
mutations{
increment(state){ // 调用state
state.count++
},
decrement(state){
state.count--
}
}
})
export default store
挂载到Vue
import Vue from 'vue'
import App from './App'
import store from './store' // 导入store
Vue.config.productionTip = false
new Vue({
el: '#app',
store, // 导入store
render: h => h(App)
})
使用vuex
<template>
<div id='app'>
<h2>{{$store.state.count}}h2>
<button @click="add">+button>
<button @click="sub">-button>
div>
template>
<script>
export default{
name: 'App',
methods:{
add(){
this.$store.commit('increment') // 通过commit(提交)mutation的方式引用increment方法
},
sub(){
this.$store.commit('decrement')
}
}
}
script>
类似于computed(计算属性)
基本使用:
需要从store中获取一些state变异后的状态,就使用getters。
作为参数:
需要传递参数:
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数
state:{
student:[{
name:'durant',
age:35
},{
name:'curry',
age:30
}]
},
getters:{
more20stu(state){ // 基本使用
return state.students.filter(s => s.age > 20)
},
more20stuNumber(state,getters){ // getters作为参数
return getters.more20stu.length
},
moreAgeStu(state) { // 传递参数的方式:返回一个函数
return age => {
return state.students.filter(s => s.age > age)
}
}
}
引用:
<template>
<div>
<h2>{{$store.getters.more20stu}}h2>
<h2>{{$store.getters.more20stuNumber}}h2>
<h2>{{$store.getters.moreAgeStu(32)}}h2>
div>
template>
Vuex的store状态的更新唯一方式:提交Mutation
mutations{
increment(state){ // 第一个参数必须为state
state.count++
},
decrement(state){
state.count--
}
}
必须用commit调用
methods:{
add(){
this.$store.commit('increment') // 通过commit(提交)mutation的方式引用increment方法
},
sub(){
this.$store.commit('decrement')
}
}
事件名(state,payload)
payload(负载),可以是一个参数,也可以是一个对象(需要传递多个参数时,可以包装在对象里传递)。
mutations{
incrementCount(state,counter){ // 第一个参数必须为state
state.count += counter
}
}
调用
methods:{
addCounter(counter){
this.$store.commit('increment',counter)
}
}
①普通:commit(‘事件名’,参数)
②commit({type:‘事件名’,参数}) 对象
methods:{
addCounter(counter){
this.$store.commit({
type:'increment',
counter
})
}
}
mutations{
incrementCount(state,payload){ // 此时的payload是一个对象,而之前的提交风格payload是counter
state.count += payload.counter
}
}
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
Vuex对应的响应式规则:提前在store中初始化好所需的属性.
state:{
info:{
name:'durant',
number:7,
height:2.11
}
},
mutations{
change(state){
state.info.name = 'curry' // 响应式
state.info['address'] = '布鲁克林' // 非响应式,但是info会添加 address:'布鲁克林'
Vue.set(state.info,'address','华盛顿') // 响应式
delete state.info.number // 非响应式,但是info会删除 number:7
Vue.delete(state.info,'number') // 响应式
// Vue.set和Vue.delete 的第二个参数只能是string或者number
}
}
<template>
<div>
<h2>{{$store.state.info}}h2>
<button @click='change'>改变<button>
div>
template>
<script>
export default{
name: 'App',
methods:{
change(){
this.$store.commit('change')
}
}
}
script>
在mutation中, 我们定义了很多事件类型(也就是其中的方法名称).
当我们的项目增大时,Vuex管理的Mutation中的方法越来越多,方法过多,使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
官方推荐: 使用常量替代Mutation事件的类型,我们可以将这些常量放在一个单独的文件中,方便管理以及让整个app所有的事件类型一目了然。
新建mutations-type.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import {INCREMENT,DECREMENT} from "./mutations-types" // 导入常量类型
Vue.use(Vuex)
const store = new Vuex.Store({
state{
count = 1000
},
mutations{
[INCREMENT](state){ // [常量](){}
state.count++
},
[DECREMENT](state){
state.count--
}
}
})
export default store
使用vuex
<template>
<div id='app'>
<h2>{{$store.state.count}}h2>
<button @click="add">+button>
<button @click="sub">-button>
div>
template>
<script>
import {INCREMENT,DECREMENT} from "./mutations-types" // 导入常量类型
export default{
name: 'App',
methods:{
add(){
this.$store.commit(INCREMENT) // 不再是字符串形式
},
sub(){
this.$store.commit(DECREMENT)
}
}
}
script>
Vuex要求我们Mutation中的方法必须是同步方法.
主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.
Action类似于Mutation, 但是是用来代替Mutation进行异步操作的。
第一个参数context(上下文),它是和store对象具有相同方法和属性的对象。
也可以传递payload。
调用:是dispatch。
当我们的异步操作或者说网络请求成功或者失败的时候执行一些代码时,我们可以利用Promise。
将异步操作或者网络请求放在Promise里,但是then()和catch()是跟在dispatch后面的。
Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutations、actions、getters等。但是模块最好最多两个。
功能特点:在浏览器中发送 XMLHttpRequests 请求;在 node.js 中发送 http请求;支持 Promise API;拦截请求和响应;转换请求和响应数据等等
安装axios:npm install axios --save
main.js
// 导入axios
import axios from 'axios'
// axios(config),config可以是对象
// 1.没有请求数据
axios({
url: 'http://123.207.32.32:8000/home/multidata'
}).then(res => {
console.log(res) // res 为该接口的数据
})
// axios内部会自动使用Promise、resolve,所以可以直接使用then()
// 2.请求数据
axios({
url: 'http://123.207.32.32:8000/home/date',
// 专门针对get请求的参数拼接
params:{
type: 'pop',
page: 1
}
}).then(res => {
console.log(res)
})
// 等同于
axios({
url: 'http://123.207.32.32:8000/home/date?type=pop&page=1'
}).then(res => {
console.log(res)
})
httpbin.org/ 可以用来网络请求测试
// axios.all([axios({}),axios({})]).then(results => {})
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]);
})
// axios.spread((res1,res2) => {})
.then(axios.spread((res1,res2) => {
console.log(res1); // 直接获得第一次请求的数据
console.loh(res2); // 直接获得第二次请求的数据
}))
axios.defaults.baseURL = 'http://123.207.32.32:8000'
axios.defaults.timeout = 5000 // 请求时间在5s内,超出则报错
axios({
url: '/home/multidata' // 等于 http://123.207.32.32:8000/home/multidata
})
常见的配置选项(用到时再查也行)
作用 | 代码 |
---|---|
请求地址 | url: ‘/user’, |
请求类型 | method: ‘get’,(对应params),‘post’(对应request body) |
请根路径 | baseURL: ‘http://www.mt.com/api’, |
请求前的数据处理 | transformRequest:[function(data){}], |
请求后的数据处理 | transformResponse: [function(data){}], |
自定义的请求头 | headers:{‘x-Requested-With’:‘XMLHttpRequest’}, |
URL查询对象 | params:{ id: 12 }, |
查询对象序列化函数 | paramsSerializer: function(params){ } |
request body | data: { key: ‘aa’}, |
超时设置 | timeout: 1000, |
跨域是否带Token | withCredentials: false, |
自定义请求处理 | adapter: function(resolve, reject, config){}, |
身份验证信息 | auth: { uname: ‘’, pwd: ‘12’}, |
响应的数据格式 json / blob /document /arraybuffer / text / stream | responseType: ‘json’, |
… | … |
因为可能存在网络请求的ip地址不同,所以使用全局配置的时候很不友好。
最好创建axios实例。
const instance1 = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance1({
url: '/home/multidata' // http://123.207.32.32:8000/home/multidata
}).then(res => {
console.log(res);
})
instance1({
url: '/home/data', // http://123.207.32.32:8000/home/data?type=pop&page=1
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res);
})
const instance2 = axios.create({
baseURL: 'http://222.111.33.33:8000',
timeout: 10000,
})
instance2({
url: '/category' // http://222.111.33.33:8000/category
}).then(res => {
console.log(res);
})
防止未来axios这个框架不再使用时,修改代码很麻烦。
在建src下一个文件夹network,再建request.js
import axios from 'axios'
export function request(config) {
// 创建axios的实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
return instance(config) // 因为axios本身会返回一个Promise
}
调用时:
// 封装request模块
import {request} from "./network/request";
request({
url: '/home/multidata'
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
如果修改换框架:
只需要改动request.js的代码,而调用的代码不需要改动
import Android from 'Android'
export function request(config) {
// Android的代码
return new Promise() // 包装一个Promise
}
四个拦截:请求成功、请求失败、响应成功、响应失败
拦截请求是instance.interceptors.request
什么时候需要拦截:
①比如config中的一些信息不符合服务器的要求
②比如每次发送网络请求时, 都希望在界面中显示一个请求的图标
③某些网络请求(比如登录(token)), 必须携带一些特殊的信息
拦截响应是instance.interceptors.response
import axios from 'axios'
export function request(config) {
// 1、创建axios的实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 2、拦截
// 2-1、网络请求拦截
// use(config => {},err => {}) 第一个是成功时调用,第二个是失败时调用
instance.interceptors.request.use(config => {
// 网络请求时的一些操作
return config // 一定要返回出去,不然被拦截下来了,就接收不到网络请求的数据
}, err => {
console.log(err);
})
// 2-2、请求响应的拦截
instance.interceptors.response.use(res => {
return res.data // 返回网络请求的data即可,其他的数据对我们来说没用
}, err => {
console.log(err);
})
// 3、发送真正的网络请求
return instance(config) // 因为axios本身会返回一个Promise
}