一、什么是Vue.js
1. vue是一种数据驱动的前端框架
this.msg="我爱你"
,通过改变数据,然后自动渲染到绑定的DOM节点上
2. jQuery就是一种结构驱动的前端框架
$(#app).text('你真好')
,先获取结构,然后在修改数据来更新结构
二、搭建环境
首先保证你的电脑上有node和npm,版本越新越好
-
npm install -g vue-cli
全局安装脚手架工具,安装的时候可以指定版本 -
vue init webpack myProject
用webpack工具初始化vue项目,myProject是项目名称,可以自己命名 -
cd myProject
进入创建的vue项目目录 -
npm install
安装项目所需要的依赖,也就是安装一些必要的插件 -
npm run dev
开始运行项目
三、Vue核心知识点
1. Vue实例
1.1
vue实例被创建时,它会自动的将data中的属性,methods中的方法绑定到Vue实例对象上,也就是Vue实例代理了data对象上的所有属性
调用时:vm.msg = "hello world"
1.2
data中存在的属性才是响应式的,新添加的不算
比如在浏览器端添加:vm.b = 1
,那么b的变化不会对页面进行渲染
1.3
Object.freeze(),会阻止修改现有的属性,响应系统无法再追踪变化
var data = {msg: 1} ; Object.freeze(data)
1.4
Vue实例还暴露了一些有用的实例属性和方法,有前缀el
vm.$data
vm.$props……
3. Vue模板语法
2.1 动态参数
<a v-bind:[attrName] = 'url'></a>
如果data中有一个属性attrName值为 href,那么这个绑定v-bind:href = 'url'
v-on:[eventName] = 'doSomething'></a>
同样,在data中有eventName的值为focus函数时,v-on:focus = 'doSomething'
注意:
2.1.1. 动态参数值是字符串类型,值为null,用于解除绑定,其他任何非字符串类型的值都会触发一个警告
2.1.2<a v-bind:['foo' + bar] = 'value'></a>
无效,要使用没有空格和引号的表达式,或者用计算属性代替
2.2 修饰符
.prevent
告诉v-on指令对于触发的事件调用event.preventDefault()
<form v-on:submit.prevent = 'onSubmit'></form>
3. 计算属性、方法和侦听器
3.1 计算属性
计算属性有缓存,依赖改变才会重新计算
<div id="app">
{{fullName}}
{{age}}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
firstName: 'Dell',
lastName: 'Lee',
age: 28
},
computed: {
//这个计算属性依赖于this.firstName this.firstName这两个变量,只要他们不发生
//改变,那么这个计算属性就不会进行重新计算
//vm.age = 27,页面重新渲染,但计算属性不会重新计算
//vm.firstName = 'Mike',会重新计算
fullName () {
console.log('打印了一次')
return this.firstName + ' ' + this.firstName
}
}
</script>
3.1.1 计算属性中的 getter和setter
computed: {
fullName: {
//自动执行,获取 fullName 的值
//并且 get 依赖的变量发生改变时,get就会重新进行计算
get () {
return this.firstName + " " + this.lastName
},
//当重新设置fullName的值的时候,set函数就会执行 vm.fullName = 'Mike Wang'
//value就是 vm.fullName = 'Mike Wang'
//set函数里,重新设置 fullName 改变了firstName lastName,触发了get 进行计算
set (value) {
var arr = value.split(' ')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
3.2 方法
methods: {
//没有缓存,只要页面上有内容变动,就会执行
fullName () {
console.log('打印了一次')
return this.firstName + ' ' + this.lastName
}
},
3.3 侦听器
watch: {
//有缓存,只有监听的属性变量发生改变,才会执行函数内的内容
firstName () {
console.log('打印了一次')
return this.fullName = this.firstName + ' ' + this.lastName
},
lastName () {
console.log('打印了一次')
return this.fullName = this.firstName + ' ' + this.lastName
}
},
注意: 计算属性和侦听器
- 计算属性的键名是计算出来的,所以键名是一个新的名称,在data和props中都是不存在的,计算属性一般可以监听多个值的变化
- watch侦听器就是已有值发生变化的时候执行的操作,所以侦听器的键名是data或者props中已经存在的
4.class与style的绑定
4.1 class的绑定
<style>
.divStyle{
background-color: red;
widows: 100px;
height: 100px;
}
.borderStyle{
border: 10px solid black;
}
.btnBackground{
background: red;
}
.active{
background-color: yellow;
widows: 100px;
height: 100px;
}
.error{
border: 10px solid red;
}
</style>
<div id="app">
绑定class对象语法:对象键是类名,值是布尔值 <br>
意思就是isActive是true,那么就绑定类名为divStyle的这个类
<div v-bind:class= '{divStyle:isActive,borderStyle:isBorder}'>1223454</div>
<button v-bind:class = '{btnBackground:isBackground}' v-on:click='changeColor'>哈哈,快点我</button>
绑定class数组语法:数组中的成员直接对应类名
意思是绑定的类由 activeClass 这个变量来决定,若为空,就绑定空,若是active,就绑定类名为active的这个类
<div v-bind:class='[activeClass,errorClass]'>hhhhhh</div>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
isActive: true,
isBorder: true,
isBackground: true,
activeClass: 'active',
errorClass: 'error'
},
methods:{
changeColor(){
this.isBackground = !this.isBackground
}
}
})
</script>
4.2 style的绑定
绑定内联样式:键代表的是style的属性(color,size属性等),值就是属性值啦
切记: 在vue中,只要是大写字母,就会转变为 -小写
fontSize ----> font-size
<div id="app">
<div v-bind:style="{'color':color,'fontSize':fontSize}">你丫真傻啊</div>
</div>
<script src="https://cdn.bootcss.com/vue/2.6.6/vue.min.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
color:'red',
fontSize: '20px'
},
})
</script>
5. 条件渲染
5.1 v-if
v-if绑定的变量若为true,则该元素就会出现在DOM中,并且在页面中渲染出来;若为false,则将该元素从DOM中移除
5.1.1 v-if使用
<div id="app">
<div v-if='show'>{{msg}}</div>
</div>
<script>
var app = new Vue({
el: '#app',
data:{
show: false,
msg: 'hello world'
},
methods: {
}
})
</script>
5.1.2 v-if v-else-if v-else的使用
<div id="app">
v-if v-else-if v-else一定要连在一起写,中间不能有其他标签分隔开
<div v-if='this.show === "a"'>this is A</div>
<div v-else-if='this.show === "b"'>this is B</div>
<div v-else>this is others</div>
</div>
<script>
var app = new Vue({
el: '#app',
data:{
show: 'a',
// msg: 'hello world'
},
methods: {
}
})
</script>
5.1.3 key值的使用
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染,所以如果是完全一样的文本框,vue会完全借用上一个文本框,如果不想复用,可用不同的key值区分。
注意:元素复用并不局限与文本框,其他元素都会复用的,这会极大的提高Vue的渲染效率
<div id="app">
<div v-if='show'>
用户名:<input type="text" key="userName">
</div>
<div v-else>
lalala: <input type="text" key="userName"> //会复用
密码名:<input type="text" key="passwordName"> //不会复用
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data:{
show: true
},
})
</script>
5.2 v-show
v-show绑定的变量无论是true还是false,它只是控制这个DOM元素的display:'none'这个style属性而已,这个元素一直在DOM中
6.列表渲染
6.1数组渲染
这种是工作中常用的循环,要加上key值,可以提高vue的性能,理想的 key 值是每项都有的唯一 id。key不建议使用index
注意:
- Vue有一组观察数组的变异方法,所以他们也会触发视图更新
方法如下:
- push()----在数组末尾加上一项
vm.list.push({id:'004',text:'啦啦啦'})
- pop()----将数组的最后一个元素移除
- shift()----删除数组的第一个元素
- unshift()----在数组的第一个元素位置添加一个元素
- splice()----可以添加或者删除数组中的一个或多个元素—返回删除的元素
三个参数:
- 表示开始操作的位置
- 要操作的长度,为0,就是不删除
- 为可选参数,可以把要添加的元素或者数组对象写在这里
arr.splice(4,1,{ll:true})
- sort()----排序
- reverse()----数组反转
当我们想要修改数组中的元素的时候,不能通过下标的方式进行改变,只能通过Vue提供的几个数组变异方法来实现。
比如想要在数组添加一项内容:
vm.list[4]={id: '005',text:'hello world'}
其实list数组中已经添加上了这一项,只是无法在页面中渲染出来
比如在数组第二项后面添加一项内容:
vm.list.splice(1,0,{id:'001',text:'第三项'})
当我们想要修改数组中的元素的时候,还可以通过改变数组的引用,就是对数组进行重新赋值
当我们想要修改数组中的元素的时候,还可以通过set语法
Vue.set(vm.list,1,{id:'007',text: '你真傻'})
或者vm.$set(vm.list,1,{id:'007',text: '你真傻'})
,中间数字为操作元素的index值
<div id="app">
<div v-for='(item,index) of list' :key = 'item.id'>{{item.text}}----{{index}}</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
list: [{
id: '001',
text: 'xiaoliu'
},{
id: '002',
text: 'xiaoxv'
},{
id: '003',
text: 'xiaofang'
}]
}
})
</script>
如果我们想要通过一个list循环两项,比如div和span
两个循环完全独立,不符合预期
<div id="app">
<div v-for='(item,index) of list' :key='item.id'>{{item.text}}----{{index}}</div>
<span v-for='(item,index) of list' :key='item.id'>{{item.text}}----{{index}}</span>
</div>
外面包一层div,效果可以,但我们并不想要包裹的div出现在DOM中
<div id="app">
<div v-for='(item,index) of list' :key='item.id'>
<div>{{item.text}}----{{index}}</div>
<span>{{item.text}}----{{index}}</span>
</div>
</div>
满足预期,且在DOM中不会出现template标签
<div id="app">
<template v-for='(item,index) of list' :key='item.id'>
<div>{{item.text}}----{{index}}</div>
<span>{{item.text}}----{{index}}</span>
</template>
</div>
6.2 对象渲染
参数顺序:拿到value key index 的写法 v---k---i 外开
<div id="app">
<div v-for = '(item,key,index) of userInfo'>{{key}}: {{item}}--{{index}}</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
userInfo: {
name: 'Dell',
sex: 'man',
age: 18
}
}
})
</script>
注意:
对象里面要想添加某一项,用点语法是行不通的
比如:vm.userInfo.address = 'ZhengZhou'
,在页面中渲染不出来
- 通过改变对象引用,就是为对象重新赋值的方式来改变对象内容
- 通过set语法
Vue.set(vm.userInfo,'address', 'beijing')
或者vm.$set(vm.userInfo,'adress','beijing')
7.组件使用中的一些细节
7.1 table中的问题
正常情况下如此
<div id="root">
<table>
<tbody>
<tr><td>1</td></tr>
<tr><td>2</td></tr>
<tr><td>3</td></tr>
</tbody>
</table>
</div>
每一行都引入一个组件,但是渲染的时候发现渲染出来的 row 并不在 tbody 标签里面
因为 tbody 标签里面只能写 tr,其他标签他识别不了
<div id="root">
<table>
<tbody>
<row></row>
<row></row>
<row></row>
</tbody>
</table>
</div>
<script>
Vue.component('row',{
template: '<tr><td>this is a row</td></tr>'
})
new Vue({
el: '#root',
})
</script>
使用is属性可以解决这个问题
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
比如:这几种情况最好也不要直接写组件,用is属性来实现
<ul>
<li is="row"></li>
</ul>
<ol>
<li is="row"></li>
</ol>
<section>
<option is="row"></option>
</section>
7.2 组件中的data
组件都是可以复用的,所以组件中data也得是一个独立的对象,那么组件之间的data才不会相互干扰
<div id="root">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
<script>
Vue.component('row',{
data () {
return {
content: 'this is a row'
}
},
template: '<tr><td>{{content}}</td></tr>'
})
new Vue({
el: '#root',
data: {
}
})
</script>
7.3 ref的使用
ref在dom标签上使用,指向的是这个dom节点
<div id="root">
<div ref="hello"
@click = 'handleClick'
>
hello world
</div>
</div>
<script>
new Vue({
el: '#root',
data: {
},
methods: {
handleClick () {
console.log(this.$refs.hello)//就是指向的上面的div节点
console.log(this.$refs.hello.innerHTML)//hello world
}
}
})
</script>
ref在一个组件上使用,实际上是对这个组件的一个引用
<div id="root">
<counter ref="one" @change = 'sumNumber'></counter>
<counter ref="two" @change = 'sumNumber'></counter>
<div>{{sum}}</div>
</div>
<script>
Vue.component('counter',{
template: '<div @click="handleClick">{{number}}</div>',
data () {
return {
number: 0
}
},
methods: {
handleClick () {
this.number ++
this.$emit('change',this.number)
}
}
})
new Vue({
el: '#root',
data: {
sum: 0
},
methods: {
sumNumber (number) {
this.sum = this.$refs.one.number + this.$refs.two.number
}
}
})
</script>
8. 父子组件间数据传递
8.1 父组件向子组件传递数据
注意:
- 父组件向子组件传值时,传值参数前最好加上v-bind,以下面为例:
count="1"
就是传递的字符串1
:count="1"
就是数字1,
不加:,那么传递参数就是参数后的那个值
加:,那么传递参数就是引号里面的js表达式- 子组件接收到父组件传过来的值,是不能修改的,不要在子组件中修改props中的值,vue中有单向数据流的概念,即父组件向子组件传值时,父组件中可以随意修改所要传递地数据,子组件不能反过来修改父组件传递来的数据
如果你在子组件中修改了props:
- 如果传过来的是基本类型,那么会有警告,但在页面中正常渲染
- 如果是引用类型(对象,数组),vue会检测不到变化,页面中也不会渲染
如何修改传递过来的值?
- 把传递过来的值赋值给子组件data的一个属性里,就可以修改啦
<div id="root">
<counter :count = "1"></counter>
<counter :count = "2"></counter>
</div>
<script>
var counter = { //局部组件,需要在父组件中注册
props: ['count'],
template: '<div @click = "handleClick">{{this.count}}</div>',
methods: {
handleClick () {
this.count ++
}
}
}
var vm = new Vue({
el: '#root',
data: {
},
components: {
counter
}
})
</script>
//正确写法
var counter = { //局部组件,需要在父组件中注册
props: ['count'],
data () {
return {
number: this.count
}
},
template: '<div @click = "handleClick">{{number}}</div>',
methods: {
handleClick () {
this.number ++
}
}
}
8.2 子组件向父组件传递数据
<div id="root">
<counter :count = "1" @change = 'numberChange'></counter>
<counter :count = "2" @change = 'numberChange'></counter>
<div>{{total}}</div>
</div>
<script>
var counter = { //局部组件,需要在父组件中注册
props: ['count'],
data () {
return {
number: this.count
}
},
template: '<div @click = "handleClick">{{number}}</div>',
methods: {
handleClick () {
this.number = this.number + 2
this.$emit('change',2) // 2是步长
}
}
}
var vm = new Vue({
el: '#root',
data: {
total: 3
},
components: {
counter,
},
methods: {
numberChange (step) {
this.total += step
}
}
})
</script>
8.3 组件参数校验与非props特性
以下是props的参数校验的几种写法,以及参数意思
props特性和非props特性
- props特性就是父组件传递过去,子组件有接收,接收后这个特性就不会出现在DOM结构中
- 非props特性就是父组件传递过去,子组件没有接收,会在DOM结构中出现(了解)
<div id="root">
<child content="hello world"></child>
</div>
<script>
Vue.component('child',{
// props: ['content'],
props: {
// content: [ String, Number ]
content: {
type: String,
required: true,//要求父组件必须向子组件传content,不传会有提示
default: 'default value',//如果不传内容,那么这个内容就默认显示
validator (value) {//校验器,value就是传过来的数据
return (value.length < 5)
}
}
},
template: '<div>{{content}}</div>'
})
var vm = new Vue({
el: '#root',
data: {
},
})
</script>
8.3 自定义事件和原生事件
- 自定义事件:子组件在父组件中使用时,直接绑定在子组件上的事件就是自定义事件,必须经过子组件的触发才能执行
- 原生事件:直接在子组件里的模板上绑定的事件,子组件引入后是可以直接使用的
- 怎么在父组件的子组件里直接绑定原生事件,不用子组件的再次触发呢?
直接在绑定的自定义事件后加上修饰符
.native
<div id="root">
<child @click = 'handleClick'></child> //在这里绑定的是自定义事件,必须经过子组件的触发才能执行
</div>
<script>
Vue.component('child', {
template: '<div @click = "handleChildClick">click</div>',//这里绑定的是原生事件
methods: {
handleChildClick () {
alert('child click')
this.$emit('click')//触发自定义事件
}
}
})
var vm = new Vue({
el: '#root',
methods: {
handleClick () {
alert('click')
}
}
})
</script>
<div id="root">
<child @click.native = 'handleClick'></child>
</div>
<script>
Vue.component('child', {
template: '<div @click = "handleChildClick">click</div>'//这里绑定的是原生事件
})
var vm = new Vue({
el: '#root',
methods: {
handleClick () {
alert('click')
}
}
})
</script>
9. 非父子组件之间的传值
bus/总线/发布订阅模式/观察者模式
步骤:
- 建立总线
Vue.prototype.bus = new Vue()
- 在子组件中触发
this.bus.$emit('change',this.selfContent)
- 在子组件mounted生命周期钩子函数中监听
this.bus.$on('change',(value)=>{})
value就是传过来的值,可以进行操作
<div id="root">
<child content="Dell" ></child>
<child content="Lee"></child>
</div>
<script>
Vue.prototype.bus = new Vue()
Vue.component('child',{
props: {
content: String
},
data () {
return {
selfContent: this.content
}
},
template: '<div @click = "handleClick">{{selfContent}}</div>',
methods: {
handleClick () {
this.bus.$emit('change',this.selfContent)
}
},
mounted () {
this.bus.$on('change',(value)=>{
// alert(this.content)
this.selfContent = value
})
}
})
var vm = new Vue({
el: '#root'
})
</script>
10. 插槽
10.1 没有插槽前的传值方式
父组件向子组件传值,采用props传值,可以传少量的值,但如果数值较多的话,代码可读性会很低
<div id="root">
<child content="<p>Dell</p>"></child>
</div>
<script>
Vue.component('child',{
props: ['content'],
// template: "<div><p>hello</p>{{content}}</div>"//content无法转义
template: `<div>
<p>hello</p>
<div v-html='this.content'></div> //可以转义,但是content外包了一个div
</div>`
})
var vm = new Vue({
el: '#root'
})
</script>
插槽
<div id="root">
<child>
<p>Dell</p>
</child>
</div>
<script>
Vue.component('child',{
template: `<div>
<p>hello</p>
<slot>默认数据</slot>//父组件中间没有传值的时候默认显示
</div>`
})
var vm = new Vue({
el: '#root'
})
</script>
具名插槽,可以在父组件中插入多个模块的内容
<div id="root">
<child>
<div class="header" slot="header">header</div>
<div class="footer" slot="footer">footer</div>
</child>
</div>
<script>
Vue.component('child',{
template: `<div>
<slot name="header"></slot>
<p>hello</p>
<slot name="footer"></slot>
</div>`
})
var vm = new Vue({
el: '#root'
})
</script>
作用域插槽
- 执行逻辑:
- 父组件调用子组件时,向子组件传递了一个插槽
- 子组件通过slot向父组件传递数据,比如:
:item = item
- 插槽是作用域插槽,插槽必须写在template里面,同时声明从子组件接收的数据都放在props里面
- 在template里面写上模板的信息,以什么方式进行展示
- 什么时候用作用域插槽?
子组件做循环或者有一部分的DOM结构要由外部传过来的时候
<div id="root">
<child></child>
</div>
<script>
Vue.component('child',{
data () {
return {
list: [1,2,3,4]
}
},
template: `<div>
<ul>
<li v-for='item of list'>{{item}}</li>
</ul>
</div>`
})
var vm = new Vue({
el: '#root'
})
</script>
作用域插槽改写
<div id="root">
<child>
<template slot-scope='props'>
<h1>{{props.item}}</h1>
</template>
</child>
</div>
<script>
Vue.component('child',{
data () {
return {
list: [1,2,3,4]
}
},
template: `<div>
<ul>
<slot v-for='item of list' :item = item></slot>
</ul>
</div>`
})
var vm = new Vue({
el: '#root'
})
</script>
11. 动态组件和v-once指令
- 动态组件
动态组件中is属性根据绑定的组件名的不同会动态的切换组件
- v-once 使用
子组件中加上v-once,也就是第一次执行的时候就把子组件放入内存,下次直接复用即可,所以如果子组件内容不变的话,加上v-once会提高vue性能
<div id="root">
<component :is = 'type'></component>
<child-one v-if='type === "child-one"'></child-one>
<child-two v-if='type === "child-two"'></child-two>
<button @click='handleClick'>change</button>
</div>
<script>
Vue.component('child-one',{
template: '<div v-once>child-one</div>'
})
Vue.component('child-two',{
template: '<div v-once>child-two</div>'
})
var vm = new Vue({
el: '#root',
data: {
type: 'child-one'
},
methods: {
handleClick () {
this.type = this.type === 'child-one'? 'child-two':'child-one'
}
}
})
</script>
12.Vue动画
12.1 css过渡动画
过渡动画原理:
- 在需要过渡的元素外面包裹上transition标签,那么vue执行代码的时候就会对被包裹的元素进行解析
- 以缓动出现为例:
- 动画开始前,vue刚开始解析代码,就会给transition包裹的标签(div)加上
v-enter , v-enter-active
两个类(注意可以给transition加name,那么v就换成name名称)- 动画开始时,去掉
fade-enter
这个类,添加fade-enter-to
这个类- 动画结束时,所有的类都去掉
- 我们可以根据这些类的添加和删除为这些类添加一些样式,来做出动画效果
代码如下:
<style>
/*缓动出现动画 */
.v-enter {
opacity: 0; /*将初始状态的透明度设为0*/
}
.v-enter-active {
transition: opacity 1s; /*transition检测到opacity的变化,1s内完成*/
}
/*缓慢消失动画 */
.v-leave-to {
opacity: 0;/*将最终状态变为0*/
}
.v-leave-active {
transition: opacity 1s;
}
</style>
<div id="root">
<transition>
<div v-if='show'>hello world</div>
</transition>
<button @click='handleButton'>切换</button>
</div>
<script>
var vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleButton() {
this.show = !this.show
}
}
})
</script>
12.2 在Vue中使用animate.css库
Vue中使用keyframes
v-enter-active, v-leave-active
在动画整个过程都是存在的,所以可以在这里面写效果vue提供的原生类名太长,想换类名怎么办?
可以在
transition
里面自己设置类名 比如:enter-active-class='enter'
leave-active-class='leave'
,那么这两个类就可以这样简写啦
<style>
@keyframes bounce-in {
0% {
transform: scale(0)
}
50% {
transform: scale(1.5)
}
100% {
transform: scale(1)
}
}
.v-enter-active {
transform-origin: left center;/*设置元素变形的原点,左边线的中点*/
animation: bounce-in 1s;
}
.v-leave-active {
transform-origin: left center;
animation: bounce-in 1s reverse;
}
</style>
<div id="root">
<transition>
<div v-if='show'>hello world</div>
</transition>
<button @click='handleButton'>切换</button>
</div>
<script>
var vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleButton() {
this.show = !this.show
}
}
})
</script>
使用animate.css库
如何使页面出现以及刷新的时候也出现动画呢?在transition中加入属性
appear
,类appear-active-class='animated swing'
<link rel="stylesheet" href="../animate.css">
<div id="root">
<transition
appear
enter-active-class = 'animated swing'
leave-active-class='animated shake'
appear-active-class= 'animated swing'
>
<div v-if='show'>hello world</div>
</transition>
<button @click='handleButton'>切换</button>
</div>
<script>
var vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleButton() {
this.show = !this.show
}
}
})
</script>
12.3 在vue中同时使用animate.css库和transition
注意: animate.css动画默认时间是1s,所以如果transition和animate时间不一致?
怎么办?
- 加
type="transition"
用来指定以谁的时间为准- 自定义时间
:duration="5000"
<style>
.v-enter,.v-leave-to{
opacity: 0;
}
.v-enter-active,
.v-leave-active{
transition: opacity 3s;
}
</style>
<div id="root">
<transition
//type="transition"
//:duration="5000" 动画执行时间为5s
:duration="{enter: 5000,leave: 10000}" //可以设置进出场动画的时间
appear
enter-active-class = 'animated swing v-enter-active'
leave-active-class='animated shake v-leave-active'
appear-active-class='animated swing'
>
<div v-if='show'>hello world</div>
</transition>
<button @click='handleButton'>切换</button>
</div>
12.4 js 动画
js动画钩子:
- before-enter: 动画开始执行前就执行啦
- enter: 动画开始的时候执行
- after-enter: 动画结束时执行
离开动画与进入动画一样before-leave,leave,after-leave
<div id="root">
<transition
name="fade"
@before-enter="handleBeforeEnter"
@enter = "handleEnter"
@after-enter = "handleAfterEnter"
>
<div v-if='show'>hello world</div>
</transition>
<button @click='handleButton'>切换</button>
</div>
<script>
var vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleButton() {
this.show = !this.show
},
handleBeforeEnter (el) {//动画开始执行前调用
// alert('beforeenter')
el.style.color="red"//el就是指transition里面包裹的元素
},
handleEnter (el,done) {//动画开始执行时调用
// alert('enter')
setTimeout(() => {
el.style.color="green"
},2000)
setTimeout(()=>{
done()//只有调用done这个回调函数之后,才会执行after-enter这个钩子
},4000)
},
handleAfterEnter (el) {
el.style.color= "#000"
}
}
})
</script>
12.5 Velocity动画库的使用
<div id="root">
<transition
name="fade"
@before-enter="handleBeforeEnter"
@enter = "handleEnter"
@after-enter = "handleAfterEnter"
>
<div v-if='show'>hello world</div>
</transition>
<button @click='handleButton'>切换</button>
</div>
<script>
var vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleButton() {
this.show = !this.show
},
handleBeforeEnter (el) {
el.style.opacity= 0
},
handleEnter (el,done) {
Velocity(el,{opacity: 1},{duration: 1000})
//在1s之内把el的opacity从0变到1
},
handleAfterEnter (el) {
}
}
})
</script>
12.6 Vue中多个元素或组件的过渡
- 多个元素的过渡
注意:多个元素的过渡,每个元素要加一个key,如果不加vue中会进行dom复用,就没有动画效果啦
<style>
.v-enter,.v-leave-to{
opacity: 0
}
.v-enter-active,.v-leave-active {
transition: opacity 1s
}
</style>
<div id="root">
<transition mode="out-in">
<div v-if='show' key="hello">hello world</div>
<div v-else key="bye">bye world</div>
</transition>
<button @click='handleButton'>切换</button>
</div>
<script>
var vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleButton() {
this.show = !this.show
}
}
})
</script>
- 多个组件的过渡
<style>
.v-enter,.v-leave-to{
opacity: 0
}
.v-enter-active,.v-leave-active {
transition: opacity 1s
}
</style>
<div id="root">
<transition mode="out-in">
<child v-if='show'></child>
<child-one v-else></child-one>
</transition>
<button @click='handleButton'>切换</button>
</div>
<script>
Vue.component('child',{
template: '<div>child</div>'
})
Vue.component('child-one',{
template: '<div>child-one</div>'
})
var vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleButton() {
this.show = !this.show
}
}
})
</script>
多个组件可以转化为动态组件
<style>
.v-enter,.v-leave-to{
opacity: 0
}
.v-enter-active,.v-leave-active {
transition: opacity 1s
}
</style>
<div id="root">
<transition mode="out-in">
<component :is="type"></component>
</transition>
<button @click='handleButton'>切换</button>
</div>
<script>
Vue.component('child',{
template: '<div>child</div>'
})
Vue.component('child-one',{
template: '<div>child-one</div>'
})
var vm = new Vue({
el: '#root',
data: {
type: "child"
},
methods: {
handleButton() {
this.type = this.type === "child"?"child-one":"child"
}
}
})
</script>
12.7 vue中的列表过渡
原理:
<transition-group> <div v-for="item of list" :key="item.id">{{item.title}}</div> </transition-group>
把循环的每一项都变成了
<transition> <div>{{item.title}}</div> </transition>
在进行渲染
<style>
.v-enter,.v-leave-to {
opacity: 0
}
.v-enter-active,.v-leave-active {
transition: opacity 1s
}
</style>
<div id="root">
<transition-group>
<div v-for="item of list" :key="item.id">{{item.title}}</div>
</transition-group>
<button @click="handleAddOne">addOne</button>
</div>
<script>
var count = 0
var vm = new Vue({
el: '#root',
data: {
list: []
},
methods: {
handleAddOne () {
this.list.push({
title: "xxx我又想你" + count + "次",
id: count++
})
}
}
})
</script>
12.8 vue动画封装
<div id="root">
<fade :show="show">
<div>hello world</div>
</fade>
<fade :show="show">
<div>I love you</div>
</fade>
<button @click='handleButton'>切换</button>
</div>
<script>
Vue.component('fade',{
props: ['show'],
template:
`<transition @before-enter="handleBeforeEnter"
@enter="handleEnter"
>
<slot v-if="show"></slot>
</transition>
`,
methods: {
handleBeforeEnter (el) {
el.style.color = "red"
},
handleEnter (el,done) {
setTimeout(()=>{
el.style.color = "green"
done()
},2000)
}
}
})
13. Vue-router的学习
基本写法:vue本质上是一个单页面应用,页面间跳转就是组件之间的跳转
13.1 vue-router的使用步骤(vue-cli的情况下)
- 在src目录下建立router目录,目录下创建index.js
- 在index.js中引入
import Vue from 'vue'; import Router from 'vue-router'
- 使用Router
Vue.use(Router)
因为vue-router是一个插件,vue中使用插件就要Vue.use()
- 定义路由
import Home from '@/pages/home/Home'
import City from '@/pages/city/City'
export const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/city',
name: 'City',
component: City
},
]
- 创建Router实例,对路由进行配置
const router = new Router({
routes, //routes: routes
mode: 'history', //默认是hash模式(路径中有#,seo不好),改成history,可以把路径中#去掉,不过这样写刷新页面后会出现404,还需在后端配置
base: /base/, //会在routes设置的路径前面加上/base/(可以自定义),用于区分一
//些页面,注意/base/不是必须的,去掉它页面还会正常显示
})
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import City from '@/pages/city/City'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/city',
name: 'City',
component: City
},
]
})
四、vue-router学习
Ⅰ. vue-router在项目中的使用
当项目比较大时,可以将route拆分成两个文件
routes.js
import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'
export default [
{
path: '/',
redirect: '/app'
},
{
path: '/app',
component: Todo
},
{
path: '/login',
component: Login
}
]
router.js
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(Router)
const router = new Router({ // 不推荐这样做,这样做传出去的就一个router,做服务端渲染的时候会内存溢出
routes,
// mode: 'history', // 可以去掉hash路由的#
// base: '/base/', // 在routes配置的路前都加上/base/, 里面可以是任何内容,
linkActiveClass: 'active-class', // 可以自定义router-link a 标签里面的类名
linkExactActiveClass: 'exact-active-class' // 精确匹配路径
})
export default router
router.js另一种写法:(做服务端渲染的时候要使用这种方法,要不然会内存溢出)
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(Router)
export default () => { // 这样每次传出去的就是一个新router
return new Router({
routes: routes
})
}
// 在入口文件中引入
import creatRouter from './config/router'
const router = creatRouter()
然后在项目入口文件中引入router.js,在实例中注册好,将router-view放到app.vue中就好啦
Ⅱ、router对象(new Router)中的一些配置项
1. mode: history
作用:将URL中难看的#
去掉
分析:
- 为什么vue-router跳转要有hash和history两种模式呢?
vue是单页面应用,所以vue-router的核心就在于---改变路由的同时不会向后端发送请求
hash模式:hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 #是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中也不会不包括#;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置
history模式:HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面
注意:但当用户直接在用户栏输入地址并带有参数时:
Hash模式:xxx.com/#/id=5 请求地址为 xxx.com,没有问题;
History模式: xxx.com/id=5 请求地址为 xxx.com/id=5,如果后端没有对应的路由处理,就会返回404错误
所以,我们使用history模式的时候,一定要在后台进行配置
我们在开发环境时,使用devServer启动服务,所以可以在devServer中配置
historyApiFallback: {
index: '/index.html'
},
2. base: '/base/'
---配置base项,base项的值可以任意写
作用:在routes配置的路前都加上/base/, 里面可以是任何内容,可以用来标记一些特殊的页面。
注意:这个base配置的值在url中不是必须的,去掉url还是正常显示的
3. linkActiveClass 和 linkExactActiveClass
作用:可以改变router-link
形成的 a 标签中的两个类名
最初类名:
<a data-v-06ebb29e="" href="/base/app" class="router-link-exact-active router-link-active">app</a>
配置自定义类名:
linkActiveClass: 'active-class',
linkExactActiveClass: 'exact-active-class' // 精确匹配路径
自定义后的类名:
<a data-v-06ebb29e="" href="/base/app" class="exact-active-class active-class">app</a>
问题:这两个类有什么作用?他们两个有什么区别?
作用:用来给激活的链接加样式
区别:
-
linkActiveClass
: 全局配置 <router-link> 的默认“激活 class 类名” -
linkExactActiveClass
: 全局配置 <router-link> 精确激活的默认的 class
举例:页面上两个链接:login,login exact, login 路径是/login
, login exact的路径是login/exact
那么当我们激活 login exact 的时候,页面上的类显示:
<a data-v-06ebb29e="" href="/base/login" class="active-class">login</a>
<a data-v-06ebb29e="" href="/base/login/exact" class="exact-active-class active-class">login exact</a>
4. scrollBehavior
的配置
作用:配置路由页面的滚动行为
说明:
- to from 都是路由对象,to将要跳转的路由,from就是当前还未跳转的路由
- savedPosition就是要跳转的页面以前滚动保留下的位置,如存在,跳转后到保留的位置,不存在,就到页面顶端呗
- 当然不同页面的位置可以有to from定制
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
5. parseQuery 和 stringifyQuery
作用:url中的经常会有查询参数(?后面的一串),我们需要把它们转化为json对象才能使用,其实vue会帮我们转化它们,不过如果我们有一些特殊的需求的话,可以用他们配置
parseQuery (query) {
// 把查询参数(string)转化为json对象
},
stringifyQuery (obj) {
// 把对象转化为字符串
}
6. fallback: true
作用:当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式。默认值为 true。
如果你不想退回 hash 模式,那写成 false ,改成false之后,那么vue单页应用变成多页应用,每次router-link跳转都会去后端拿数据,比较耗时
所以一般不要改成false
Ⅲ、routes(路由)配置的的要点
1. name
的配置与作用
{
path: '/app',
component: Todo,
name: 'app' // 与path无关,可以定义任何名字r
}
作用:路由跳转router-link
中可以使用
比如:下面它们是等效的
<router-link to="/app">app</router-link>
<router-link :to="{name: 'app'}">app</router-link>
2. meta
的配置与作用
作用:保存一下当前路由的一些信息的,我们再写html页面时,head标签里面的meta里面保存的信息,我们称他们为页面的源信息,这些信息有利于我们处理seo,根据description里面的内容来排列他们的搜索结果,而在vue组件里面,我们没有办法在组件中写这些东西,所以可以在路由里面的meta里面写我们需要的东西
meta: {
title: 'this is an app',
description: 'asddffg'
}
3. children
的配置和使用
作用:当前路由的子路由
注意:当前路由可能不止一个子路由,所以children是一个数组,在routes配置好之后,在当前路由需要的位置引入router-view即可
children: [
{
path: 'test',
component: Login
}
]
4. 路由参数的配置
作用:通过向像跳转的组件传参数,来改变组件的行为(类似于props)
分为id和query
// 在routes中配置参数
{
path: '/app/:id', // 配置参数
component: Todo,
name: 'app',
meta: {
title: 'this is an app',
description: 'asddffg'
}
// 向要跳转的路由传参数(这个参数一般都是一个变量)
<router-link to="/app/123">app</router-link>
//接收到参数的路由怎么使用参数?
mounted () {
console.log(this.$route) // this.$route包含着路由匹配成功后路由中的所有信息
console.log(this.$route.params.id) // 拿到了id值
console.log(this.$route.query) // 拿到了query值(query不用在routes设置,传参的时候有query, 就能拿到)
}
注意:在同一个路由下面this.$route
在哪一个组件中都是一样的
this.$route对象:
使用场景:当我们定义路径的时候,有一个列表请求(比如商品列表),我们可以通过传参的方式拿到单个商品的id,进而去请求这个商品的详细内容(类似于props,都是通过传来的数据,来改变当前组件的某些行为)
5. props
的配置
注意:尽量使用props方法,少使用$route
,使组件与路由解耦,提高组件的复用率
-
props: true
布尔模式
作用:可以把要传递给要跳转路由的参数转化成props传递
好处:在组件中使用$route
会使之与对应路由高度耦合,该组件只能在特定url(路由)下使用,组件单独使用时this.$route
会不匹配,拿不到id
的值。使用props: true
则可以解决这个问题,通过props: ['id']
可以接收到外部传来的id
的值,这其实就是一种解耦。
上面的代码就可以改成下面这样:
// 在routes中配置参数
{
path: '/app/:id', // 配置参数
props: true,
component: Todo,
name: 'app',
meta: {
title: 'this is an app',
description: 'asddffg'
}
// 向要跳转的路由传参数(这个参数一般都是一个变量)
<router-link to="/app/123">app</router-link>
//接收到参数的路由怎么使用参数?
props: ['id']
mounted () {
console.log(this.id)
}
// 注意:如果是命名视图,props要包一层
components:{
default: Todo,
a: login
},
props: {
default: true,
a: true
}
-
props
对象模式
作用:如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用
props: {
id: '456' // 不会变化
}
-
props
函数模式
props: (route) => ({ query: route.query.a, id: route.query.b })
接收route参数,返回一个对象
Ⅳ、vue-router的一些高级用法
1. 命名视图
作用:在router-view
上设置好名字,这样可以根据我们的需求在同一个路由下显示不同的组件
使用场景:可以同时展示多个视图,而不用嵌套。例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
。
配置和使用:
// 在routes中配置
{
path: '/login',
components: {
default: Login,
a: Todo
}
}
// 在组件中使用
<transition name="fade">
<router-view></router-view>
</transition>
<Footer></Footer>
<router-view name='a'></router-view>
2. vue-router
之导航守卫
路由守卫
全局守卫
---对所有的路由有效果,只要路由跳转,就会触发
- 全局前置守卫
作用:对一些页面进行校验,比如:验证一些页面是需要用户登录才可以显示的
router.beforeEach((to, from, next) => {
console.log('before each invoked')
if (to.fullPath === '/app') {
next('/login')
} else {
next()
}
})
- 全局解析守卫
router.beforeResolve((to, from, next) => {
console.log('before resolved invoked')
next()
})
- 全局后置钩子
router.afterEach((to, from) => {
console.log('after each invoked')
})
路由内守卫
---只对该路由有效果
- 路由独享的守卫
// 在routes中配置
beforeEnter: (to, from, next) => {
console.log('before enter route')
next()
}
组件内守卫
---对本组件进入,离开,以及组件的复用有效果
-
beforeRouteEnter
无法拿到this
,可以在next方法里面执行一个回调拿到当前组件
beforeRouteEnter (to, from, next) {
console.log('befor route enter')
next(vm => {
console.log(vm.id)
})
}
-
beforeRouteUpdate
执行:同一个组件在不同的路由下面显示的时候。比如:/app/123和/app/456
可以拿到this
beforeRouteUpdate (to, from, next) {
console.log('before route update')
next()
}
注意:当我们在两个相似路径(/app/123和/app/456
)跳转时,第二次还会不会触发mounted
钩子呢?
不会,当我们进入/app/123
时,mounted
钩子执行,这时跳到/app/456
钩子,mounted
钩子就不再执行。所以我们不要把根据id
变化而变化的数据写在mounted
里面,可以写在beforeRouteUpdate
里面,也可以写在watch
里面
-
beforeRouteLeave
使用场景:控制页面离开行为。比如你修改了一个表单,还没有保存,现在你要跳到其他页面,可以在这里给你设置一个提醒
beforeRouteLeave (to, from, next) {
console.log('before route leave')
if (global.confirm('are you sure?')) {
next()
}
}
3. 异步组件实现按需加载
作用:每次加载页面,就要把所有的业务逻辑代码加载一下,若是项目比较大,势必会大大影响页面的初始加载速度,若是app.js小于1mb以下,就没有必要使用异步组件啦
异步组件不仅可以在路由中使用,在页面组件中中只要引入组件的地方都可以使用
// 在路由routes 引入
component: () => import('../views/todo/todo.vue')
component: () => import('../views/login/login.vue')
// 安装 `babel-plugin-syntax-dynamic-import` 插件
npm i babel-plugin-syntax-dynamic-import
// 在 `babelrc`中配置
"plugins": [
"transform-vue-jsx", // 解析 jsx 语法
"syntax-dynamic-import" // 异步加载组件
]
五、vuex的学习
Ⅰ、vuex在项目中的使用
// 安装
npm install vuex --save
// 在 src 目录下建立store目录,建立store.js 文件
// store.js 文件配置
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
updateCount (state, num) {
state.count = num
}
}
})
export default store
// 在入口文件中引入 store ,注册
// 因为vue是一个树形结构,只有把 store 注册到入口文件根节点上,下面那些子节点才都可以拿到 store对象
在组件中使用store
拿到state中的值
computed: {
count () {
return this.$store.state.count
}
}
// 通过mutations修改state中的值
mounted () {
let i = 0
console.log(this.$store)
setInterval(() => {
this.$store.commit('updateCount', i++)
}, 1000)
}
在大型项目或者可扩展项目中可采用一下目录结构,把每个模块都拆分出来形成单独的目录
Ⅱ、getters的使用
作用:getters的作用就是对state中的原始数据做一些处理和封装,可以让我们在组件中更好的使用,相当于组件中的computed
很多时候,后端提供我们的数据我们不能直接使用,当然我们可以拿到数据在组件中通过computed进行处理,但是很多页面都需要处理数据,每个页面都写computed就会造成代码冗余,维护也困难,getters可以帮我们解决这个问题
export default {
fullName (state) {
return `${state.firstName} ${state.lastName}`
}
}
Ⅲ、map 语法在state和getters中的使用
作用:使组件中属性方法和store中模块的属性方法形成一个映射,可以直接把state和getters中的值拿过来
注意:mapState和mapGetters
是访问,拿到数据,所以在组件的计算属性 computed
里面混入
// 首先安装一个转义器包,是Babel用来翻译最新的语法的
npm i babel-present-stage-1 -D
// 在 .babelrc 文件中配置
{
"presets": [
"env", // babel-present-env 主要对javascript最新的语法糖进行编译,并不负责转译javascript新增的api和全局对象
"stage-1" // babel-preset-stage-1,转义器包,要配合env使用,包含一些插件,可以识别最新语法
],
"plugins": [
"transform-vue-jsx", // 解析 jsx 语法
"syntax-dynamic-import" // 异步加载组件
]
}
// 现在就可以在组件中使用 对象展开运算符 啦
import { mapState, mapGetters } from 'vuex'
...mapState(['count']) // 映射 this.count 为 store.state.count
...maoState({
counter: 'count'
})
...mapState({
counter: (state) => state.counte // 通过函数可以对拿到的数据做一些处理
})
...mapGetters(['fullName'])
Ⅳ、Vuex之mutations和actions
1. mutations
代码演示:
// mutations.js
export default { // 只有两个参数
updateCount (state, num) {
state.count = num
},
firstName (state, num) {
state.firstName = num
}
}
// 多个数值的话
export default {
updateCount (state, {num, num2}) {
console.log(num2)
state.count = num
},
firstName (state, num) {
state.firstName = num
}
}
// 组件中
this.$store.commit('updateCount', { // payload,载荷
num: i++,
num2: 2
})
注意:state
中的值只能通过mutations
中修改,其实在组件中可以通过
this.$store.state.count = 2
也是可以修改的,但是这样修改的话,多人协作的话就会很困难,数据不利于维护。
所以我们一般会限制组件中直接修改 state
的值,做法是:
在开发环境下,给 store
实例对象加上严格模式,这样的话,我们如果在组件中修改 state
,会有警告
const isDev = process.env.NODE_ENV === 'development'
export default () => {
return new Vuex.Store({
strict: isDev, // 在开发环境的时候使用
state: defaultState,
mutations: mutations,
getters
})
}
2. actions
作用:处理异步操作,或者一个动作要多次修改mutations,往往也用actions封装一下
export default {
updateCountAsync (ctx, data) { // context 上下文, store 实例具有相同方法和属性的 context 对象
setTimeout(() => {
ctx.commit('updateCount', {
num: data.num
})
}, data.time)
}
}
// 在组件中调用 actions,用 dispatch 方法
this.$store.dispatch('updateCountAsync', {
num: 5,
time: 2000
})
3. mapMutatios 和 mapActions
的用法
注意:这两个map是操作,所以要写在 methods
里面
代码如下:
methods: {
...mapActions(['updateCountAsync']), // 将 `this.updateCountAsync()` 映射为 `this.$store.dispatch('updateCountAsync')`
...mapMutations(['updateCount'])
}
// 现在代码就可以改了
this.$store.dispatch('updateCountAsync', { // store 对象的方法
num: 5,
time: 2000
})
this.updateCountAsync({ // 组件自身的方法
num: 5,
time: 2000
})
Ⅴ、Vuex之模块(module)
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
1. 模块中的 state
// 在 store.js 中的store对象中配置如下:
modules: {
a: {
state: {
text: 'a'
}
},
b: {
state: {
text: 'b'
}
}
}
// 在组件中调用
computed: {
textA () {
return this.$store.state.a.text
}
}
2. 模块中的 mutations
注意: vuex默认的会把全部的mutation都放在全局的命名空间中,所以在不同的模块也可以直接调用。
如果我们想把各个模块的mutation就在自己的模块,因为太多的mutation都放在全局,容易引发命名上的冲突,怎么办?
我们可以给模块加一个命名空间 namespaced: true,
modules: {
// namespaced: true,
a: {
state: {
text: 'a'
},
mutations: {
updateText (state, text) {
state.text = text
}
}
}
}
// 组件中使用
methods: {
...mapMutations([ 'updateText'])
}
mounted () {
this.updateText('123')
}
为模块加上命名空间,如何调用?
methods: {
...mapMutations([ 'a/updateText'])
}
mounted () {
this['a/updateText']('123')
}
3. 模块中的 getters
modules: {
a: {
namespaced: true,
state: {
text: 'a'
},
mutations: {
updateText (state, text) {
state.text = text
}
},
getters: {
textPlus (state) {
console.log(state.text)
return state.text + 1
}
}
}
}
// 在项目中使用
computed: {
// ...mapGetters(['a/textPlus']), // this['a/textPlus']调用
...mapGetters({
fullName: 'fullName',
textPlus: 'a/textPlus' // 可以直接在模板中使用 {{textPlus}}
})
}
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,当我们加上命名空间后,变成局部的,此时我们如何拿到全局的 state 和 getter 呢?
getters: {
textPlus (state, getters, rootState, rootGetters) {
// state 当前模块的state对象
// getters 当前模块的 getters 对象
// rootState rootGetters 全局的,不仅可以取到根模块的值,其他模块也可以
// return state.text + rootState.count
return state.text + rootState.b.count
}
}
4. 模块中的 actions
modules: {
a: {
namespaced: true,
state: {
text: 'a'
},
mutations: {
updateText (state, text) {
state.text = text
}
},
getters: {
textPlus (state, getters, rootState, rootGetters) {
console.log(state.text)
return state.text + rootState.b.text
}
},
actions: {
add (ctx) { // 上下文
ctx.commit('updateText', 8)
}
}
}
// 使用
methods: {
...mapActions(['updateCountAsync', 'a/add']),
...mapMutations(['updateCount', 'a/updateText'])
}
mounted () {
this['a/add']()
}
但是我们一般都用 ES6 的结构赋值简化
// 此时调用的 updateText 在 a 命名空间下,如果我们想要调取全局的mutation怎么办?
actions: {
add ({ state, commit, rootState }) {
commit('updateText', rootState.count)
}
}
// 首先调用的mutation全局里面存在,加上参数 {root:true}
actions: {
add ({ state, commit, rootState }) {
commit('updateCount', { num: '56789' }, { root: true })
}
}
// 模块的actions调用其他模块的mutation
// b 模块没有加命名空间
b: {
state: {
text: 'b'
},
actions: {
testAction ({ commit }) {
commit('a/updateText', 'test action')
}
}
}
methods: {
...mapActions(['updateCountAsync', 'a/add', 'testAction']),
...mapMutations(['updateCount', 'a/updateText'])
}
mounted () {
this.testAction()
}
// b 模块加上命名空间呢
b: {
namespaced: true,
state: {
text: 'b'
},
actions: {
testAction ({ commit }) {
commit('a/updateText', 'test action', { root: true })
}
}
}
methods: {
...mapActions(['updateCountAsync', 'a/add', 'b/testAction']),
...mapMutations(['updateCount', 'a/updateText'])
}
mounted () {
this['b/testAction']()
}
5. 动态注册模块
// 在有store对象的位置,一般在入口文件里,注册
const store = createStore()
store.registerModule('c', {
state: {
text: 'ccc'
}
})
store.unregisterModule('c') // 解绑一个model
// 使用方法与其他一样
Ⅵ、 热重载
当我们修改store里面的代码时,我们发现都是刷新整个页面进行更新的, 在我们做webapp的时候,状态经常是变化的,如果因为修改了一下 store 中的数据,刷新整个页面,使得之前的操作记录也会消失,浪费时间
热重载
export default () => {
const store = new Vuex.Store({
strict: isDev, // 在开发环境的时候使用
state: defaultState,
mutations: mutations,
getters,
actions
})
if (module.hot) {
module.hot.accept([
'./state/state',
'./mutations/mutation',
'./getters/getter',
'./actions/actions'
], () => {
const newState = require('./state/state').default
const newMutations = require('./mutations/mutation').default
const newGetters = require('./getters/getter').default
const newActions = require('./actions/actions').default
store.hotUpdate({
state: newState,
mutations: newMutations,
getters: newGetters,
actions: newActions
})
})
}
return store
}
Ⅶ、vuex之其他一些API和配置
1. store.watch
第一个函数的返回值发生变化时,第二个函数就会执行
store.watch((state) => state.count + 1, (newCount) => {
console.log(`newCount: ${newCount}`)
})
2. store.subscribe
(常用于插件)
监听mutations,调用的话,可以得到哪一个mutation变化,以及变化的数据
store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
3. store.subscribeAction
(常用于插件)
store.subscribeAction((action, state) => {
console.log(action.type)
console.log(action.payload)
})
4. vuex插件的制作
plugins 在vuex初始化的时候就已经执行,所以可以在里面使用上面subscribe 和 subscribeAction
订阅一些内容,来进行一些操作
export default () => {
const store = new Vuex.Store({
strict: isDev, // 在开发环境的时候使用
state: defaultState,
mutations: mutations,
getters,
actions,
plugins: [
(store) => {
console.log('my plugin invoked')
}
]
})