Vue基础语法
插值操作
Mustache语法
可以直接写变量,也可以写简单的表达式
{{firstName + lastName}}
’
{{firstName + ' ' +lastName}}
{{firstName}} {{lastName}}
其他指令使用
- v-noce:
<h2 v-once>{{message}}</h2>
某些情况下,我们可能不希望界面随意的跟随改变,这时可以使用该指令
该指令后面不需要跟任何表达式
该指令表示元素和组件只渲染一次,不会随着数据的改变而改变 - v-html
某些情况下,我们从服务器请求到的数据本身就是一个html代码
如果我们直接通过{{}}来输出,会将html代码一起输出,但我们希望的是能对代码进行解析,并显示相应内容。
该指令后面往往会跟上一个string类型,会将string的gtml解析出来并进行渲染
<div id="app">
<h2 v-html="url"></h2>
</div>
<script>
let app = new Vue({
el:"#app",
data:{
url:"<a href='http://www.baidu.com'>百度一下</a>"
}
})
</script>
-
v-test :
<h2 v-text="message"></h2>
作用和Mustache相似:都是用于将数据显示在界面中
通常情况下,接受一个string类型 -
v-pre:
<h2 v-pre>{{message}}</h2>
用于跳过这个元素和他子元素的编译过程,用于显示原本的Mustache语法 -
v-cloak
在某些情况下,我们浏览器可能会直接显示出来未编译的Mustache标签。
绑定属性
v-bind:动态绑定属性(可简写成:)
之前学习的指令主要作用是将值插入到我们模板的内容当中
但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值
v-bind绑定class
绑定方式
对象语法:class后面跟的是一个对象
用法:
1、直接通过{}绑定一个类<h2 :class="{'active': isActive}">Hello World!</h2>
2、也可以通过判断,传入多个值<h2 :class="{'active': isActive,'line':isLine}">Hello World!</h2>
3、和普通的类同时存在,并不冲突<h2 class="title" :class="{'active': isActive,'line':isLine}">Hello World!</h2>
注:如果两者都为true,会有三个类
4、如果过于复杂,可以放在一个method或者computed中<h2 class="title" :class="classes">Hello World!</h2>
数组语法:class后面跟的是一个数组
用法:
1、直接通过{}绑定一个类<h2 :class="['active']">Hello World!</h2>
2、可以传入多个值<h2 :class="['active','line']">Hello World!</h2>
3、和普通的类同时存在,并不冲突<h2 class="title" :class="['active','line']">Hello World!</h2>
注:如果两者都为true,会有三个类
4、如果过于复杂,可以放在一个method或者computed中<h2 class="title" :class="classes">Hello World!</h2>
v-bind绑定style
对象语法::style="color:currentColor,fontsize:fontSize + 'px'}"
style后面跟的是 一个对象类型。对象的key是css属性的名称,value是具体赋的值,值可以来自于data中的属性。
数组语法:<div v-bind:style="[baseStyles,overridingStyles]"></div>
style后面跟的是 一个数组类型,多个值 分割即可
计算属性
我们知道,在模板中可以直接通过插值语法显示一些data中的数据。
但是在某些情况,我们可能需要对数据进行一些转化后再显示。或者需要将多个数据结合起来进行显示。
比如我们有firstName和lastName两个变量,我们需要显示完整的名称。
但是如果多个地方都需要显示完整的名称,我们就需要写多个{{firstName}}{{lastName}}
计算属性是写在实例computed选项中的
基本使用
<div id="app">
<h2>书籍总价值:{{totalPrice}}</h2>
</div>
<script src="../vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data: {
books: [
{name: '编程思想', price: 50, count: 3},
{name: '编程艺术', price: 100, count: 1},
{name: '程序设计', price: 50, count: 2}
]
},
computed:{
totalPrice() {
//这里使用了一个高阶函数
return this.books.reduce((total,b) =>{
return total + b.price * b.count
},0)
}
}
})
</script>
set和get方法
计算属性一般是没有set方法,是只读属性
和methods的对比
计算属性会进行缓存,如果多次使用时,计算属性只会调用一次
ES6补充知识
var、let、const
let有块级作用域,var没有块级作用域
const主要的作用是将某个变量修饰为常量。当我们修饰的标识符不会被再次赋值时,就可以使用const来保证数据的安全性。
建议:在ES6开发中,优先使用const,只有需要改变某一个标识符的时候才使用let。
const指向的变量不能修改,但是可以改变对象内部的属性。
const不能被修改,修饰的标识符必须被赋值
对象的增强写法
属性的增强写法
const name = "why";
const age = 18;
const height = 1.88;
//ES5写法
const obj={
name:name,
age:age,
height:height,
}
//ES6写法
const obj={
name,
age,
height,
}
函数的增强写法
//ES5写法
const obj = {
run:function(){},
eat:function(){}
}
//ES6写法
const obj = {
run(){}
}
事件监听
在前端开发中,我们需要经常和用于交互。这个时候,就必须监听用户发生的时间,比如点击、拖拽、键盘事件等。
v-on (v-on:click可以写成@click)
在vue中,使用v-on指令监听事件
作用:绑定事件监听器
参数
当通过methods中定义方法,以供@click调用时,需要注意参数问题
1、如果该方法不需要额外参数,那么方法后的()可以不添加
注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
2、如果需要同时传入某个参数,同时需要event时,可以通过$event传入时事件。
<div id="app">
<h2>点击次数{{counter}}</h2>
<button @click="handleAdd">+1</button>
<button @click="handleAddTen(10,$event)">+10</button>
</div>
methods:{
handleAdd(event){
console.log(event);
this.counter++;
},
handleAddTen(count,event){
console.log(event);
this.counter += 10;
}
}
修饰符
在某些情况下,我们拿到event的目的可能是进行一些事件处理。
vue提供了修饰符来帮助我们方便的处理一些事件 :
.stop—调用event.stopPropagation()
.prevent—调用event.preventDefault()
.{keyCode|keyAlias}—只当事件是从特定键触发时才触发回调
.native—监听组件根元素的原生事件
.once—只触发一次回调
<!-- 停止冒泡-->
<button @click.stop="doThis"></button>
<!-- 阻止默认行为-->
<button @click.prevent = "doThis"></button>
<!-- 阻止默认行为,没有表达式-->
<button @click.prevent></button>
<!-- 串联修饰符-->
<button @click.stop.prevent = "doThis"></button>
<!-- 键修饰符,键别名-->
<button @click.enter = "onEnter"></button>
<!-- 键修饰符,键代码-->
<button @click.13 = "onEnter"></button>
<!-- 点击回调只会触发一次-->
<button @click.once = "doThis"></button>
条件判断
v-if、v-else-if、v-else
这三个指令与JavaScript的条件语句if、else、else if类似。
Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
v-if原理:
v-if后面的条件为false时,对应的元素以及其子元素不会渲染,也就是根本没有不会有对应的标签出现在DOM中。
演示:
<div id="app">
<div v-if="score>=90">优秀</div>
<div v-else-if="score>=80">良好</div>
<div v-else-if="score>=60">及格</div>
<div v-else>不及格</div>
</div>
<script src="../vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data: {
score: 98
}
})
</script>
v-if和v-show之间的区别
v-if当条件为false时,压根不会有对应的元素在DOM中。
v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
开发中如何选择呢?
当需要在显示与隐藏之间切片很频繁时,使用v-show;当只有一次切换时,通过使用v-if
循环遍历
v-for遍历数组
当有一组数据需要进行渲染时,就可以使用v-for。类似于js中的for循环
在遍历的过程中不需要使用索引值:v-for="movie in movies"
依次从movies中取出movie,并且在元素的内容中,可以使用Mustache语法,来使用movie
反之,需要拿到元素在数组中的索引值:v-for=(item, index) in items
其中的index就代表了取出的item在原数组的索引值。
v-for遍历对象
v-for可以用户遍历对象:
比如某个对象中存储着你的个人信息,我们希望以列表的形式显示出来。
<div id="app">
<ul>
<li v-for="(value,key,index) in info">
{{value}}--{{key}}--{{index}}
</li>
</ul>
</div>
<script src="../vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data: {
info:{
name:'ac',
age:18,
height:199
}
}
})
</script>
组件的key属性
官方推荐我们在使用v-for时,给对应的元组或组件添加上一个:key属性。
key的作用主要是为了高效的更新虚拟DOM。
响应式的数组方法
Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。
Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。
pop():删除数组中的最后一个元素
shift():删除数组中的第一个元素
unshift():在数组最前面添加元素
splice() :删除元素、插入元素、替换元素
删除元素:第二个参数传入要删除几个元素(如果没有传参数,就默认删除后面的所有元素)
替换元素:第二个参数,表示我们要替换几个元素,后面是用于替换前面的元素
插入元素:第二个参数传入0,并且后面跟上要插入的元素
sort():排序
reverse():反转
图书案例
表单绑定v-model
表单控件在实际开发中时非常常见的。特别是对于用户信息的提交,需要大量的表单。
使用v-model指令来实现表单元素和数据的双向绑定。
v-model其实是一个语法糖,它的背后本质上是包含两个操作:
1.v-bind绑定一个value属性
2.v-on指令给当前元素绑定input事件
也就是说下面的代码:等同于下面的代码:
<input type="text" v-model="message">
等同于
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
基本使用
案例的解析:
当我们在输入框输入内容时
因为input中的v-model绑定了message,所以会实时将输入的内容传递给message,message发生改变。
当message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变。
所以,通过v-model实现了双向的绑定。
<div id="app">
<input type="text" v-model="message">
<h2>{{message}}</h2>
</div>
<script src="../vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data: {
message:''
}
})
</script>
当然,我们也可以将v-model用于textarea元素
<!--2.绑定rextarea元素-->
<textarea v-model="message"></textarea>
<p>输入的内容是:{{message}}</p>
结合radio使用
当存在多个单选框时,
<div id="app">
<label for="male">
<input type="radio" :value="abc" v-model="gender" id="male">男
</label>
<label for="female">
<input type="radio" :value="female" v-model="gender" id="female">女
</label>
<p>您的选择:{{gender}}</p>
</div>
<script src="../vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data: {
gender:'',
abc:'male'
}
})
</script>
结合checkbox的使用
复选框分为两种情况:单个勾选框和多个勾选框
单个勾选框:
v-model即为布尔值。
此时input的value并不影响v-model的值。
多个复选框:
当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
当选中某一个时,就会将input的value添加到数组中。
<div id="app">
<!-- 单个复选框-->
<label for="check">
<input type="checkbox" v-model="checked" id="male">同意协议
</label>
<p>是否选中:{{ckecked}}</p>
<!-- 多个复选框-->
<label><input type="checkbox" v-model="hobbies" value="l篮球">篮球</label>
<label><input type="checkbox" v-model="hobbies" value="足球">足球</label>
<label><input type="checkbox" v-model="hobbies" value="乒乓球">乒乓球</label>
<p>您选中的爱好:{{hobbies}}</p>
</div>
<script src="../vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data: {
checked:false,
hobbies:[]
}
})
</script>
结合select使用
和checkbox一样,select也分单选和多选两种情况
单选:只能选中一个值
当我们选中option中的一个时,会将它对应的value赋值到mySelect中
<select v-model="mySelect">
<option value="1">苹果</option>
<option value="2">香蕉</option>
<option value="3">橘子</option>
<option value="4">火龙果</option>
</select>
<p>您最喜欢的水果:{{mySelect}}</p>
多选:可以选中多个值
当选中多个值时,就会将选中的option对应的value添加到数组mySelect中
<select v-model="mySelects" multiple>
<option value="1">苹果</option>
<option value="2">香蕉</option>
<option value="3">橘子</option>
<option value="4">火龙果</option>
</select>
<p>您最喜欢的水果:{{mySelects}}</p>
let app = new Vue({
el:'#app',
data:{
mySelect:'apple',
mySelects:[]
}
})
修饰符的使用
-
lazy修饰符
默认情况下,v-model默认是在input事件中同步输入框的数据的
也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变
该修饰符可以让数据在失去焦点或者回车时才会更新。 -
number修饰符
默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当作字符串类型进行处理
但是如果我们希望处理的是数字类型,那么最好直接将内容当作数字处理
number修饰符可以让在输入框中输入的内容自动转成数字类型。 -
trim修饰符
如果输入的内容首尾有很多空格,通常我们希望将其去除
该修饰符可以过滤内容左右两边的空格
js高阶函数的使用
filter map reduce
组件化开发
注册组件的基本步骤
创建组件构造器:调用Vue.extend()方法
注册组件:调用Vue.component()方法
使用组件:在Vue实例的作用范围内使用
这里的步骤都代表什么含义呢?
1、Vue.extend()
调用Vue.extend()创建的是一个组件构造器
通常在创建组件走早期时,传入template代表我们自定义组件的模板
该模板就是使用到组件的地方,要显示html代码
2、Vue.component()
调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给他起一个组件的标签名称
需要传递两个参数:①注册组件的标签名②组件构造器
3、组件必须挂载在某个Vue实例下,否则不会生效
<body>
<div id="app">
<my-con></my-con>
<my-con></my-con>
<my-con></my-con>
</div>
<script src="../vue.js"></script>
<script>
// 创建组件构造器对象
const cpnC = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>内容</p>
<p>啦啦啦</p>
</div>`
})
//注册组件
Vue.component('my-con',cpnC);
//简化:
/*Vue.component("my-con",{
template:`
<div>
<h2>我是标题</h2>
<p>内容</p>
<p>啦啦啦</p>
</div>`
}*/
let app = new Vue({
el:"#app",
data:{
message:"你好啊"
},
})
</script>
</body>
全局组件和局部组件
当我们通过调用Vue.component()注册组件时,组件的注册时全局的,这意味着该组件可以在任意Vue示例下使用
如果我们注册的组件时挂载在某个实例中,那么就是一个局部组件
components:{
cpm:cpnC
}
父组件和子组件
父子组件错误用法:以子标签的形式在Vue实例中使用
因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
<child-cpn></child-cpn>
是只能在父组件中被识别的。
类似这种用法,<child-cpn></child-cpn>
是会被浏览器忽略的。
vue为了简化流程,提供了注册的语法糖,省去了调用Vue.extend()的步骤,可以直接使用一个对象来代替。
组件模板的分离写法
虽然通过语法糖简化了vue组件的注册过程,但template模块写法变得复杂。如果能将其中的html分离出来写,然后挂载到对应的组件上,结构就会变得更清晰。
Vue提供了两种方案来定义HTML模块内容:
使用<script>
标签
使用<template>
标签
组件数据存放
组件是一个单独功能模块的封装:
这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
组件中的数据是保存在哪里呢?顶层的Vue实例中吗?
我们先来测试一下,组件中能不能直接访问Vue实例中的data?
发现不能访问,而且就算可以访问,如果将所有的数据都放在Vue实例中,它就会变的非常臃肿。
结论:Vue组件应该有自己保存数据的地方。
组件自己的数据存放在哪里呢?
组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
只是这个data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据
为什么data在组件中必须是一个函数呢?
首先,如果不是一个函数,Vue直接就会报错。
其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
父子组件的通信
真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。
props的基本用法
在组件中,使用选项props来声明需要从父级接收到的数据。
props的值有两种方式:
方式一:字符串数组,数组中的字符串就是传递时的名称。
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
实例:
<body>
<div id="app">
<cpn v-bind:cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="../vue.js"></script>
<script>
//父传子props
const cpn = {
template:"#cpn",
props:["cmovies","cmessage"],
data(){
return{}
},
methods:{
}
}
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
movies:['lalal','嘿嘿嘿','kekeke']
},
components:{
cpn
}
})
</script>
</body>
props数据验证
props选项是使用一个数组,除此以外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法
验证都支持的数据类型:String,Number,Boolean,Array,Object,Date,Function,Symbol
当我们有自定义构造函数时,验证也支持自定义的类型
Vue.component('my-con',{
props:{
// 基础的类型检查(null匹配任何类型)
propA:Number,
// 多个可能的类型
propB:[String,Number],
// 必填的字符串
propC:{
type:String,
require:true
},
// 带有默认值的数字
propD:{
type:Number,
default:100
},
// 带有默认值的对象
propE:{
type:Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function (){
return {
message:'hello'
}
}
},
// 自定义验证函数
propF:{
validator:function (value){
// 这个值必须匹配下列字符串中的一个
return['success','warning','danger'].indexOf(value) !== -1
}
}
}
})
子级向父级传递
props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
这个时候,我们需要使用自定义事件来完成。
什么时候需要自定义事件呢?
当子组件需要向父组件传递数据时,就要用到自定义事件了。
我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
自定义事件的流程:
在子组件中,通过$emit()来触发事件。
在父组件中,通过v-on来监听子组件事件。
<body>
<div id="app">
<cpn @item-click="cpnClick"></cpn>
</div>
<template id="cpn">
<div>
<button v-for="item in categories"
@click="btnClick(item)">
</button>
</div>
</template>
<script src="../vue.js"></script>
<script>
//父传子props
const cpn = {
template:"#cpn",
data(){
return{
categories:[
{id:'aaa',name:'aaa1'},
{id:'bbb',name:'bbb1'},
{id:'ccc',name:'ccc1'},
{id:'ddd',name:'ddd1'},
]
}
},
methods:{
btnClick(item){
//发射事件,自定义事件
this.$emit('item-click')
}
}
}
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
},
components:{
cpn
},
methods: {
cpnClick(){
console.log('cpnClick');
}
}
})
</script>
</body>
双向绑定案例
父子组件的访问方式
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件。
父访问子-children-refs
this.$children是一个数组类型,它包含所有子组件对象。
我们这里通过一个遍历,取出所有子组件的message状态。
$children
的缺陷:
通过$children
访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
$refs
的使用:
$refs
和ref指令通常是一起使用的。
首先,我们通过ref给某一个子组件绑定一个特定的ID。
其次,通过this.$refs.ID就可以访问到该组件了。
<child-cpn1 ref="child1"></child-cpn1>
<child-cpn2 ref="child2"></child-cpn2>
<button @click="showRefsCpn">通过refs访问子组件</button>
showRefsCpn(){
console.log(this.$refs.child1.message);
console.log(this.$refs.child2.message);
}
子访问父-parent-root
如果我们想在子组件中直接访问父组件,可以通过$parent
注意事项:
尽管在Vue开发中,我们允许通过$parent
来访问父组件,但是在真实开发中尽量不要这样做。
子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
另外,更不好做的是通过$parent
直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于调试和维护。
非父子组件通信
非父子组件关系包括多个层级的组件,也包括兄弟组件的关系。
在Vue1.x的时候,可以通过
d
i
s
p
a
t
c
h
和
dispatch和
dispatch和broadcast完成
$dispatch用于向上级派发事件
$broadcast用于向下级广播事件
但是在Vue2.x都被取消了
在Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。
但是这种方案和直接使用Vuex的状态管理方案还是逊色很多。
并且Vuex提供了更多好用的功能,以后使用Vuex。
插槽slot
组件的插槽是为了让我们封装的组件更加具有扩展性,让使用者可以决定组件内部的一些内容到底展示什么。
如何封装?:抽取共性,保留不同
slot的基本使用
slot中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容
<template id="myCpn">
<div>
<slot>我是一个插槽中的默认内容</slot>
</div>
</template>
<script src="../learn/dist/bundle.js"></script>
<script>
Vue.component('my-cpn',{
template:'#myCpn'
})
let app = new Vue({
el:'#app'
})
</script>
具名插槽slot
当子组件的功能复杂时,子组件的插槽可能并非是一个。
比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
这个时候,我们就需要给插槽起一个名字,给slot元素一个name属性
<slot name='myslot'></slot>
作用域插槽
前端模块化开发
常见的模块化规范:
CommonJS、AMD、CMD,也有ES6的Modules
CommonJS
模块化有两个核心:导入和导出
导出:
module.exports = {
flag:true,
test(a,b){
return a+b;
},
demo(a,b){
return a*b;
}
}
导入:
l'l
export基本使用
export指令用于导出变量:
export const name = "ac";
export const age = 18;
export const height = 1.88;
还有另外一种写法:
const name = "ac";
const age = 18;
const height = 1.88;
export{name,age,height}
导出函数或类和上述一致
export default
某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名
这个时候就可以使用export default
//info.js
export default function (){
console.log('default function');
}
在main.js中,这样使用:
(这里的myFunc是我自己命名的,你可以根据需要命名它对应的名字)
import myFunc from './info.js'
myFunc()
另外,需要注意!!!
export default在同一个模块中,不允许同时存在多个。
import的使用
我们使用export指令导出了模块对外提供的接口,下面就可以通过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);
webpack
webpack是一个模块化的打包工具,支持我们代码中写模块化,可以对模块化的代码进行处理。
在处理完所有模块之间的关系后,将多个js打包到一个js文件中,引入时就方便的多。
webpack模块化的概念
目前我们使用前端模块化的一些方案:AMD、CMD、CommonJS、ES6
在ES6之前,我们要想进行模块化开发,就必须借助其他的工具,让我们可以进行模块化开发
并且在通过模块化开发完成项目之后,还需要处理模块间的各种依赖,并将其进行整合打包
而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。
而且不仅是js问津,我们的css、图片、json文件等等在webpack中都可以被当做模块来使用
这就是webpack中模块化的概念。
如何理解打包?
就是将webpack中的各种资源模块进行打包合并成一个或多个包(bundle)。
并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成js等等操作。但是打包的操作grunt/gulp也可以完成。
二者区别
grunt/gulp的核心是Task
我们可以配置一系列的task,并且定义task要处理的事务
之后让grunt/gulp来依次执行这些task,而且让整个流程自动化
所以grunt/gulp也被成为前端自动化任务管理工具
我们来看一个gulp的task:
下面的task就是将src,下面的所有js文件转成ES5语法,并且最终输出到dist文件夹中
什么时候使用?
如果工程模块依赖非常简单,甚至没有用到模块化的概念,只需要进行简单的合并压缩,就可以使用。
但如果整个项目使用了模块化管理,而且互相依赖非常强,我们就可以使用更强大的webpack了。
所以两者间的不同?
grunt/gulp更加强调的是前端流程的自动化,模块化不是他的核心
webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。
const glup = require('gulp');
const babel = require('gulp-babel');
.task('js',()=>
gulp.src('src/*.js')
.pipe(babel({
presets:['es2015']
}))
.pipe(gulp.dest('dist'))
);
基本使用过程:
aa.js
function add(num1,num2){
return num1+num2;
}
function mul(num1,num2){
return num1*num2;
}
module.exports = {
add,mul
}
main.js
//使用commonjs的模块化规范
const {add,mul} = require('./aa.js')
console.log(add(20, 30));
console.log(mul(20, 30));
//使用ES6的模块化规范
//import {name,age,height} from "./info";
通过webpack ./src/main.js ./dist/bundle.js
打包成bundle.js文件,在index.html引入
创建webpack.config.js文件,配置入口和出口参数,运行时可以直接读取。
局部安装webpack
因为一个项目往往依赖待定的webpack版本,全局的版本可能跟这个项目的webpack版本不一致,导致打包出现问题
所以通常一个项目,都有自己局部的webpack。
第一步、项目中需要安装自己局部的webpack npm install webpack@版本号 --save-dev
第二步、通过node_modules/.bin/webpack启动webpack打包
loader?
loader是webpack中一个非常核心的概念。
在我们之前的实例中,我们主要是用webpack来处理我们写的js代码,并且webpack会自动处理js之间相关的依赖。
但是,在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。
对于webpack本身的能力来说,对于这些转化是不支持的。
那怎么办呢?给webpack扩展对应的loader就可以啦。
loader使用过程:
1、通过npm安装需要使用的loader
2、在webpack.config.js中的modules关键字下进行配置
大部分loader我们都可以在webpack的官网中找到,并且学习对应的用法。
css文件处理、less文件处理、图片文件处理、ES6转ES5的babel
main.js
const {add,mul} = require('./js/aa.js')
console.log(add(20, 30));
console.log(mul(20, 30));
import {name,age,height} from "./js/info.js";
console.log(name);
console.log(age);
console.log(height);
require('./css/normal.css')
require('./css/special.less')
document.writeln("<h2>你好啊</h2>")
special.less
@fontSize:50px;
@fontColor:orange;
body{
font-size: @fontSize;
color: @fontColor;
}
normal.css
body{
/*background-color: red;*/
background: url("../img/2022-12-02_153836.png");
}
package.json
{
"name": "learn",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",//ES6语法处理
"babel-loader": "^7.1.5",//ES6语法处理
"babel-preset-es2015": "^6.24.1",//ES6语法处理
"css-loader": "^2.0.2",//css文件处理
"file-loader": "^3.0.1", //图片文件处理
"less": "^3.9.0",//less文件处理
"less-loader": "^4.1.0",//less文件处理
"style-loader": "^0.23.1",//css文件处理
"url-loader": "^1.1.2",//图片文件处理
"webpack": "^3.5.3"
}
}
webpack.config.js
const path = require('path');
module.exports = {
entry:'./src/main.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'bundle.js',
publicPath:'dist/'
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
},
{
test: /\.less$/,
use:[
{loader:"style-loader"},
{loader: "css-loader"},
{loader: "less-loader"}
]
},
{
test:/\.(png|jpg|gif|jpeg)$/,
use:[
{
loader: 'url-loader',
options:{
//当加载的图片小于limit时,会将图片编译成base64字符串形式
//当加载的图片大于limit时,需要使用file-loader模块进行加载
limit:15000,
name:'img/[name].[hash:8].[ext]'
}
}
]
},
{
test: /\.js$/,
exclude:/(node_modules|bower_components)/,
use:{
loader:'babel-loader',
options:{
presets:['es2015']
}
}
}
]
}
}
webpack中使用vue的配置过程
在项目终端安装vue,这里vue使用的版本是2.5.21.
//使用vue进行开发
import Vue from 'vue'
new Vue({
el:'#app',
data:{
message:'hello'
}
})
template和el的区别
el用于指定Vue要管理的DOM,可以帮助解析其中的指令、事件监听等等。
而如果Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应el的模板。
.vue文件封装处理
但是一个组件以一个js对象的形式进行组织和使用的时候是非常不方便的
现在,我们以一种全新的方式来组织一个vue的组件
但是,这个时候这个文件可以被正确的加载吗?
必然不可以,这种特殊的文件以及特殊的格式,必须有人帮助我们处理。
谁来处理呢?vue-loader以及vue-template-compiler。
安装:
npm install vue-loader vue-template-compiler --save-dev
plugin
plugin是什么?
plugin是插件的意思,通常是用于对某个现有的架构进行扩展
webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化、文件压缩等等
loader和plugin区别
loader主要用于转换某些类型的模块,它是一个转换器。
plugin是插件,它是对webpack本身的扩展,是一个扩展器。
plugin的使用过程:
步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
步骤二:在webpack.config.js中的plugins中配置插件。
添加版权的plugin
plugins:[
new webpack.BannerPlugin('最终版权归Haier所有')
]
打包html的plugin
将index.html文件打包到dist文件夹中,这时候就可以使用HtmlWebpackPlugin插件
HtmlWebpackPlugin插件作用:
自动生成一个index.html文件(可以指定模板来生成),将打包的js文件,自动通过script标签插入到body中。
安装HtmlWebpackPlugin插件:
npm install html-webpack-plugin --save-dev
实现代码:
plugins:[
new webpack.BannerPlugin('最终版权归Haier所有'),
new HtmlWebpackPlugin({
template:'index.html'
})
]
js压缩的plugin
在项目发布之前,我们必然需要对js等文件进行压缩处理
这里,我们就对打包的js文件进行压缩
我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
搭建本地服务器
npm install --save-dev webpack-dev-server@2.9.1
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
port:端口号
inline:页面实时刷新
historyApiFallback:在SPA页面中,依赖HTML5的history模式
我们可以再配置另外一个scripts:
–open参数表示直接打开浏览器
最终:
package.js
{
"name": "learn",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server --open"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-es2015": "^6.24.1",
"css-loader": "^2.0.2",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"style-loader": "^0.23.1",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^1.1.2",
"vue-loader": "^13.0.0",
"vue-template-compiler": "^2.5.21",
"webpack": "^3.5.3",
"webpack-cli": "^5.0.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"vue": "^2.5.21"
}
}
webpack.config.js
const path = require('path');
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry:'./src/main.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'bundle.js',
publicPath:'dist/'
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
},
{
test: /\.less$/,
use:[
{loader:"style-loader"},
{loader: "css-loader"},
{loader: "less-loader"}
]
},
{
test:/\.(png|jpg|gif|jpeg)$/,
use:[
{
loader: 'url-loader',
options:{
//当加载的图片小于limit时,会将图片编译成base64字符串形式
//当加载的图片大于limit时,需要使用file-loader模块进行加载
limit:15000,
name:'img/[name].[hash:8].[ext]'
}
}
]
},
{
test: /\.js$/,
exclude:/(node_modules|bower_components)/,
use:{
loader:'babel-loader',
options:{
presets:['es2015']
}
}
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader'
}
},
]
},
resolve:{
alias:{
'vue':'vue/dist/vue.esm.js'
}
},
plugins:[
new webpack.BannerPlugin('最终版权归Haier所有'),
new HtmlWebpackPlugin({
template:'index.html'
}),
//new UglifyjsWebpackPlugin()
],
// devServer:{
// contentBase:'./dist',
// inline:true
// }
}
App.vue
<template>
<div>
<h2 class="title">{{message}}</h2>
<button @click="btnClick">点我hhhhhhhhhh</button>
<h2>{{name}}</h2>
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
message:'hello',
name:'sxx'
}
},
methods:{
btnClick(){
console.log('hhh');
}
}
}
</script>
<style scoped>
.title{
color:green;
}
</style>
main.js
const {add,mul} = require('./js/aa.js')
console.log(add(20, 30));
console.log(mul(20, 30));
import {name,age,height} from "./js/info.js";
console.log(name);
console.log(age);
console.log(height);
require('./css/normal.css')
require('./css/special.less')
document.writeln("<h2>你好啊</h2>")
//使用vue进行开发
import Vue from 'vue'
import App from "../vue/App.vue";
new Vue({
el:'#app',
template:'<App/>',
components:{
App
}
})
Vue CLI详解 (ok)
什么是Vue CLI?
使用vue.js开发大型应用时,我们需要考虑代码目录结构,项目结构和部署,热加载,代码单元测试等事情。
如果每个项目都要手动的完成这些工作,效率低效,所以通常会使用一些脚手架工具来帮助完成这些事情。
CLI翻译为命令行界面,俗称脚手架
使用vue-cli可以快速搭建Vue开发环境以及对应的webpack配置
Vue CLI的使用
安装:npm install -g @vue/cli
创建项目:
Vue CLI2初始化项目:vue init webpack my-project
Vue CLI3初始化项目:vue create my-project
vue程序运行过程:
npm run build实现流程:
npm run dev实现流程:
vue-router(ok)
路由就是通过互联的网络把信息从源地址传输到目的地址的活动。
路由决定数据包从来源到目的地的路径,转送将输入端的数据转移到合适的输出端。
路由中有一个非常重要的概念叫路由表。路由表本质上就是一个映射表,决定了数据包的指向。
vue-router适合用于构建单页面应用
vue-router是基于路由和组件的,路由用于设定访问路径,将路径和组件映射起来,
在vue-router的单页面应用中,页面的路径的改变就是组件的切换。
后端路由阶段-前后端分离-前端路由阶段
后端路由的缺点:
一种情况是整个页面的模块由后端人员来编写和维护的。
另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码。
通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情.
前后端分离阶段:
随着Ajax的出现, 有了前后端分离的开发模式。
后端只提供API来返回数据, 前端通过Ajax获取数据, 并且可以通过JavaScript将数据渲染到页面中。
这样做最大的优点就是前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上。
并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可。
目前很多的网站依然采用这种模式开发。
单页面富应用阶段:
最主要的特点就是在前后端分离的基础上加了一层前端路由,通过前端来维护一套路由规则。
**前端路由的核心是什么呢?**改变URL,但是页面不进行整体的刷新。
hash和history
URL的hash
URL的hash也就是锚点(#), 本质上是改变window.location的href属性.
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
HTML5的history模式:
history接口是HTML5新增的, 它有五种模式改变URL而不刷新页面.
history.pushState()
history.replaceState()
history.go()
history.back() 等价于 history.go(-1)
history.forward() 等价于 history.go(1)
这三个接口等同于浏览器界面的前进后退。
安装和基本使用
安装步骤:
1、使用npm安装:npm install vue-router --save
2、在模块化工程中使用它(因为他是一个插件,所以可以通过vue.use()来安装路由功能)
①导入路由对象,并调用Vue.use(VueRouter)
②创建路由实例,并且传入路由映射配置
③在Vue实例中挂载创建的路由实例。
使用vue-router的步骤:
1、创建路由组件
2、配置路由映射:组件和路径映射关系
3、使用路由:通过<router-link>
和<router-view>
<router-link>
: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个<a>
标签。
<router-view>
: 该标签会根据当前的路径, 动态渲染出不同的组件。
网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和<router-view>
处于同一个等级。在路由切换时, 切换的是<router-view>
挂载的组件, 其他内容不会发生改变。
路由的默认路径:
默认情况下,进入网站的首页,我们希望<router-view>
渲染首页内容,但实际上,默认没有显示首页组件,必须让用户点击才可以。
这时,我们只需要在routes中多配置一个映射就可以了
{
path:'/', //配置的是根路径
redirect:'/home' //重定向,也就是我们将根路径重定向到/home的路径下,这样就可以得到我们想要的结果了
}
router-link其他属性:
tag:可以指定link之后渲染成什么组件,比如a,button,li等
replace:不会留下history记录,所以指定replace的情况下,后退键返回不能返回到上一个页面中
active-class:当link对应的路由匹配成功时,会自动给当前元素设置一个router-link-active的class,设置active-class可以修改默认的名称。
在进行高亮显示的导航菜单或者底部tabbar时,会使用到该类。但是通常不会修改类的属性,会直接使用默认的router-link-active。
该class具体的名称也可以通过router实例的属性进行修改:linkActiveClass:'active'
路由代码跳转:
<button @click="ClickHome">首页</button>
<button @click="ClickAbout">关于</button>
<script>
export default {
name: "App",
methods:{
ClickHome(){
//通过代码的方式修改路由vue-router
//this.$router.push('/home')
this.$router.replace('/home')
console.log("homeClick");
},
ClickAbout(){
//this.$router.push('/about')
this.$router.replace('/about')
console.log("AboutClick");
}
}
}
</script>
动态路由
在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,可能希望路径是/user/aaa。
这种path和Component的匹配关系,称之为动态路由
{
path:'/user/:id',
component: User
}
<h2>{{$route.params.id}}</h2>
<router-link to="/user/123">用户</router-link>
路由的懒加载
将路由对应的组件打包成一个个的js代码块。只有在这个路由被访问到的时候,才加载对应的组件。
使用方式:
方式一: 结合Vue的异步组件和Webpack的代码分析.
const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
方式二: AMD写法
const About = resolve => require(['../components/About.vue'], resolve);
方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
const Home = () => import('../components/Home.vue')
嵌套路由
嵌套路由是一个很常见的功能
比如在home页面中, 我们希望通过/home/news和/home/message访问一些内容。
一个路径映射一个组件, 访问这两个路径也会分别渲染两个组件。
路径和组件的关系如下:
实现嵌套路由有两个步骤:
创建对应的子组件, 并且在路由映射中配置对应的子路由。
在组件内部使用<router-view>
标签。
传递参数
传递参数主要有两种类型: params和query
params的类型:
配置路由格式: /router/:id
传递的方式: 在path后面跟上对应的值
传递后形成的路径: /router/123, /router/abc
query的类型:
配置路由格式: /router, 也就是普通配置
传递的方式: 对象中使用query的key作为传递方式
传递后形成的路径: /router?id=123, /router?id=abc
获取参数通过$route
对象获取的.
在使用了 vue-router 的应用中,路由对象会被注入每个组件中,赋值为 this.$route ,并且当路由切换时,路由对象会被更新。
$route
和$router
是有区别的
$router
为VueRouter实例,想要导航到不同URL,则使用$router.push
方法
$route
为当前router跳转对象里面可以获取name、path、query、params等
导航守卫
在一个SPA应用中, 如何改变网页的标题呢?
网页标题是通过<title>
来显示的, 但是SPA只有一个固定的HTML, 切换不同的页面时, 标题并不会改变.
但是我们可以通过JavaScript来修改<title>
的内容.window.document.title = ‘新的标题’.
那么在Vue项目中, 在哪里修改? 什么时候修改比较合适呢?
普通的修改方式:
我们比较容易想到的修改标题的位置是每一个路由对应的组件.vue文件中.
通过mounted声明周期函数, 执行对应的代码进行修改即可.
但是当页面比较多时, 这种方式不容易维护(因为需要在多个页面执行类似的代码).
有没有更好的办法呢? 使用导航守卫即可.
什么是导航守卫?
vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
我们可以利用beforeEach来完成标题的修改.
首先, 我们可以在钩子当中定义一些标题, 可以利用meta来定义
其次, 利用导航守卫,修改我们的标题.
导航钩子的三个参数解析:
to: 即将要进入的目标的路由对象.
from: 当前导航即将要离开的路由对象.
next: 调用该方法后, 才能进入下一个钩子.
如果是后置钩子, 也就是afterEach, 不需要主动调用next()函数.
keep-alive遇见vue-router
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
有两个非常重要的属性:
include - 字符串或正则表达,只有匹配的组件会被缓存
exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
TabBar练习
- 如果在下方有一个单独的TabBar组件,你如何封装
自定义TabBar组件,在APP中使用
让TabBar出于底部,并且设置相关的样式 - TabBar中显示的内容由外界决定
定义插槽
flex布局平分TabBar - 自定义TabBarItem,可以传入 图片和文字
定义TabBarItem,并且定义两个插槽:图片、文字。
给两个插槽外层包装div,用于设置样式。
填充插槽,实现底部TabBar的效果 - 传入 高亮图片
定义另外一个插槽,插入active-icon的数据
定义一个变量isActive,通过v-show来决定是否显示对应的icon - TabBarItem绑定路由数据
安装路由:npm install vue-router —save
完成router/index.js的内容,以及创建对应的组件
main.js中注册router
APP中加入<router-view>
组件 - 点击item跳转到对应路由,并且动态决定isActive
监听item的点击,通过this.$router.replace()
替换路由路径
通过
this.$route.path.indexOf(this.link) !== -1
来判断是否是active
- 动态计算active样式
封装新的计算属性:this.isActive ? {‘color’: ‘red’} : {}
Promise
Promise是异步编程的一种解决方案。
Promise 三种状态
当我们开发中有异步操作时,就可以给异步操作包装一个Promise。
异步操作之后会有三种状态:
pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
Vuex详解
Vuex状态管理图例
Vuex是做什么的?
Vuex是一个专为Vue.js应用程序开发的状态管理模式。
他采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
那么状态管理到底是什么呢?
状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
其实,可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
但是,有什么状态时需要我们在多个组件间共享的呢?
如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
比如用户的登录状态、用户名称、头像、地理位置信息等等。
比如商品的收藏、购物车中的物品等等。
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的。
Vuex基本思想: 将共享的状态抽取出来,交给vuex统一进行管理,之后每个视图按照规定好的规定,进行访问和修改等操作。
Vue核心概念
State 单一状态树
Vuex提出使用单一状态树, 什么是单一状态树呢?
英文名称是Single Source of Truth,也可以翻译成单一数据源。
这个和我们在应用开发中比较类似:
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
所以Vuex也使用了单一状态树来管理应用层级的全部状态。
单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
Getters基本使用
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数
Mutation
状态更新
Vuex的store状态的更新唯一方式:提交Mutation
Mutation主要包括两部分:
1.字符串的事件类型(type)
2.一个回调函数(handler),该回调函数的第一个参数就是state
mutation的定义方式:
mutation:{
incrament(state){
state.count++
}
}
通过mutation更新:
increment:function (){
this.$store.commit('increment')
}
传递参数
在通过mutation更新数据的时候,有可能我们希望携带一些额外的参数。参数被称为是mutation的载荷。
Mutation中的代码:
decrement(state,n){
state.count -= n
}
decrement:function (){
this.$store.commit('decrement',2)
}
当有很多参数需要传递时,通常会以对象的形式传递,也就是payload是一个对象。
这个时候可以再从对象中取出相关的信息。
changeCount(state,payload){
state.count = payload.count
}
changeCount:function(){
this.$store.commit('changeCount',{count:0})
}
提交风格
上面通过commit进行提交是一种普通的方式,Vue还提供了另外一种风格,他是一个包含type属性的对象。
this.$store.commit({
type:'changeCount',
count:100
})
Mutation中的处理方式是将整个commit的对象作为payload的使用,所以代码没有改变,依然如下:
changeCount(state,payload){
state.count = payload.count
}
响应规则
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新。
这就要求我们必须遵守一些Vuex对应的规则:
提前在store中初始化好所需的属性。
当给state中的对象添加新属性时,使用下面的方式:
方式一:使用Vue.set(obj, ‘newProp’, 123)
方式二: 用心对象给旧对象重新赋值
如示例,当我们点击更新信息时,界面并没有发生对应改变。
使用如图两种方式,都可以让state中的属性是响应式的。
常量类型
在mutation中,我们定义了很多事件类型(也就是其中的方法名称)。
当我们的项目增大时,Vuex管理的状态越来越多,需要更新状态的情况越来越多,那么意味着Mutation中的方法越来越多。
方法过多,使用者需要花费大量的经历去记住这些方法,甚至是多个文件间来回切换, 查看方法名称,甚至如果不是复制的时候,可能还会出现写错的情况。
如何避免上述的问题呢?
在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型。
可以将这些常量放在一个单独的文件中,方便管理以及让整个app所有的事件类型一目了然。
具体方式:
可以创建一个文件:mutation-types.js, 并且在其中定义我们的常量。
定义常量时,可以使用ES2015中的风格,使用一个常量来作为函数的名称。
同步函数
通常情况下,Vuex要求我们Mutation中的方法必须是同步方法。
主要的原因是当我们使用devtools时,可以devtools可以帮助我们捕捉mutation的快照。
但是如果是异步操作,那么devtools将不能很好的追踪这个操作什么时候会被完成。
Action
基本定义
我们强调,不要在Mutation中进行异步操作。但是在某些情况下,确实希望在Vuex中进行一些异步操作,比如网络请求,必然是异步的,这时就需要Action,他类似于Mutation,可以代替其进行异步操作。
const store = new Vuex.Store({
state:{
count:0
},
mutations:{
increment(state){
state.count++
}
},
actions:{
increment(context) {
context.commit('increment')
}
}
})
context是什么?
context是和store对象具有相同方法和属性的对象。
也就是说,我们可以通过context去进行commit相关的操作,也可以获取context.state等。
但是注意,这里它们并不是同一个对象
分发
在Vue组件中,如果调用action中的方法,就需要使用dispatch
methods:{
increment(){
this.$store.dispatch('increment')
}
}
同样的,也支持传递payload
methods:{
increment(){
this.$store.dispatch('increment',{Ccount:5})
}
}
mutations:{
increment(this.state.,payload){
state.count += payload.cCount
}
}
actions:{
increment(context,payload){
setTimeout(() => {
context.commit('increment',payload)
},5000)
}
}
返回的Promise
前面学习ES6语法的时候提到过,Promise经常用于异步操作。
在Action中,我们可以将异步操作放在一个Promise中,并且在成功或者失败后,调用对应的resolve或reject。
actions:{
increment(context){
return new Promise((resolve) => {
setTimeout(() => {
context.commit('increment')
resolve()
},1000)
})
}
}
methods:{
increment(){
this.$store.dispatch('increment').then(res =>{
console.log('完成了更新操作');
})
}
}
Module
Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理。
当应用变得非常复杂时,store对象就有可能变得相当臃肿。
为了解决这个问题,Vuex允许我们将store分割成模块(Module),而每个模块拥有自己的state、mutations、actions、getters等
网络封装
常见的网络请求模块
基于XMLHttpRequest(XHR)
jQuery-Ajax
Vue-resource.
axios
JSONP的原理和封装
JSONP原理回顾
使用JSONP最主要的原因是为了解决跨域访问的问题
原理:
JSONP的核心在于通过<script>
标签的src来帮助我们请求数据。
原因是我们的项目部署在domain1.com服务器上时,是不能直接访问domain2.com服务器上的资料的。
这个时候, 我们利用<script>
标签的src帮助我们去服务器请求到数据,将数据当做一个javascript的函数来执行,并且执行的过程中传入我们需要的json。
所以,封装jsonp的核心就在于我们监听window上的jsonp进行回调时的名称。
axios的内容详解
发送基本请求
支持多种请求方式:
axios(config)
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
常见的配置选项:
请求地址
url: '/user',
请求类型
method: 'get',
请根路径
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'},
超时设置s
timeout: 1000,
跨域是否带Token
withCredentials: false,
自定义请求处理
adapter: function(resolve, reject, config){},
身份验证信息
auth: { uname: '', pwd: '12'},
响应的数据格式 json / blob /document /arraybuffer / text / stream
responseType: 'json',
axios创建实例
为什么要创建axios的实例呢?
当我们从axios模块中导入对象时, 使用的实例是默认的实例。
当给该实例设置一些默认配置时,这些配置就被固定下来了,
但是后续开发中,某些配置可能会不太一样,
比如某些请求需要使用特定的baseURL或者timeout或者content-Type等,
这个时候,我们就可以创建新的实例, 并且传入属于该实例的配置信息。
axios拦截器的作用
如何使用?
请求拦截可以做到的事情:
请求拦截中错误拦截较少,通常都是配置相关的拦截
可能的错误比如请求超时,可以将页面跳转到一个错误页面中。
响应拦截中完成的事情:
响应的成功拦截中,主要是对数据进行过滤。
响应的失败拦截中,可以根据status判断报错的错误码,跳转到不同的错误提示页面。