Vue2简单学习

Vue2快速学习

详细学习Vue2前端框架,可以详细阅读Vue中文官网

学习Vue2框架的前提,要具备html+css+JavaScript+ES6的基础知识,不过遗憾的是,vue2已经停止维护了,现在是vue3的时代了,但是本篇讲解的还是vue2的内容

Vue2简介

vue框架的作者是尤雨溪,用户体验好,API齐全,对初学者非常友好。

使用框架,其框架在帮我们运行我们编写好的代码。

框架的特点:初始化自身的一些行为,执行开发者所编写的代码,释放一些资源。

Vue2的使用模板

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app"></div>
		<script>
			new Vue({
				el:'#app',
				data:{
					
				},
				methods:{
					
				}
			})
		</script>
	</body>
</html>

Vue2安装和初使用

vue安装

打开官网:https://v2.cn.vuejs.org/v2/guide/installation.html

找到开发版本的下载按钮,点击下载vue.js文件到本地。

引入vue

引入vue.js, Vue 会被注册为一个全局变量。

<!-- 引入vue.js,Vue 会被注册为一个全局变量。 -->
<script src="./vue.js"></script>

初始化vue

实例化Vue对象,然后在里面,定义选择器,数据,方法,这些都可以通过插值被渲染到dom中

// 2.初始化
new Vue({
    // el:是元素选择器,也是element的缩写
    el:'选择器',
    // data中写的是数据属性
    data:{
        变量:},
    // methods中写的是方法
    methods:{
        函数名(){/*代码部分*/}
    }
})

插值

vue中通过{{}} 方式在dom中插入数据,渲染数据,{{}} 中可以插入任意内容,比如vue对象中的data变量,methods方法,对象,表达式,等等

{{msg}} //假设vue中的data有个msg变量
{{2}} // 直接插数值
{{'hello'}} //直接插字符串
{{ {id:1} }} //直接插对象,但是一定要插入空格 {{空格{键:值}空格}}
{{ 1>2 ? '真的' : '假的'}} // 直接插入表达式
{{txt.split('').reverse().join('') }} //插入算法
{{getContent()}} // 插入函数

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>vue的起步和插值</title>
		<!-- 引入vue.js,Vue 会被注册为一个全局变量。 -->
		<script src="./vue.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- {{data中的变量}} 就是vue中的插值 -->
			<h2>{{msg}}</h2>
			<!-- 还能直接插数值 -->
			<h2>{{2}}</h2>
			<!-- 还能直接插字符串 -->
			<h2>{{'hello'}}</h2>
			<!-- 还能直接插对象,但是一定要插入空格 {{空格{键:值}空格}} -->
			<h2>{{ {id:1} }}</h2>
			<!-- 还能直接插入表达式 -->
			<h2>{{ 1>2 ? '真的' : '假的'}}</h2>
			<!-- 还能插入算法 -->
			<h2>{{txt.split('').reverse().join('') }}</h2>
			<!-- 还能插入函数 -->
			<h2>{{getContent()}}</h2>
			
			
		</div>
	<script>
		// 2.初始化
		const vm = new Vue({
		    // el:是元素选择器,也是element的缩写
		    el:'#app',
			// 数据属性
			data:{
				msg:'hello vue',
				txt:'hello',
				msg2:'content'
			},
			// 方法
			methods:{
				getContent(){
					// return 'content';
					// 这里的this指向的就是 vm 对象
					return this.msg + ' ' + this.msg2;
				}
			}
		})
	</script>	
	</body>
</html>

Vue2中的指令

v-text和v-html指令

v-test指令

v-test 插入文本 它会寻找data中的变量 类似于js中的innerText

<!-- v-test 插入文本 它会寻找data中的变量 类似于js中的innerText -->
<h2 v-test='msg'></h2>

v-html指令

<!-- v-html 插入标签,它会寻找data中的变量,类似于js中的innerHTML -->
<div v-html='htmlMsg'></div>

{{}} 和v-test的作用是一样的,都是插入值,直接渲染
v-html 既能插入值,又能插入标签

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>v-text和v-html指令</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<h1>{{msg}}</h1>
			<!-- v-test 插入文本 它会寻找data中的变量 类似于js中的innerText -->
			<h2 v-test='msg'></h2>
			
			<!-- v-html 插入标签,它会寻找data中的变量,类似于js中的innerHTML -->
			<div v-html='htmlMsg'></div>
			
			<!-- {{}} 和v-test的作用是一样的,都是插入值,直接渲染 -->
			<!-- v-html 既能插入值,又能插入标签 -->
			
		</div>
		<script>
			new Vue({
				el:'#app',
				data:{
					msg:'插入标签',
					htmlMsg:'<h3>小马哥</h3>'
				}
			})
		</script>
	</body>
</html>

v-if和v-show指令

v-if指令

v-if,与之配合的还有v-if 和v-else-if和v-else

v-if的形式,当值为true的时候,元素存在,当值为false的时候,元素不存在

<div id="app">
    <div v-if='true'>
        显示
    </div>
    <div v-else>
        隐藏
    </div>
</div>
<script>
    // v-if v-else-if v-else
    // 一定要初始化vue
    new Vue({
        el:'#app',
        data:{   
        }
    })
</script>

v-show指令

v-show的值为true时,元素显示,为false时,css样式的display为none

优点:不管显示还是隐藏,它的元素一直存在

<div id="app">
    <h3 v-show="true">小马哥</h3>
</div>
<script>
    // v-show
    new Vue({
        el:'#app',
        data:{
        }
    })
</script>

使用情况:显示隐藏比较频繁的时候,建议用v-show,不频繁的时候,建议用v-if

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>v-if和v-show指令</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<div v-if='isShow'>
				显示
			</div>
			<div v-else>
				隐藏
			</div>
			
			<!-- v-show的值为true时,元素显示,为false时,css样式的display为none 优点:不管显示还是隐藏,它的元素一直存在 -->
			<!-- v-if的形式,当值为true的时候,元素存在,当值为false的时候,元素不存在 -->
			<!-- 显示隐藏比较频繁的时候,建议用v-show,不频繁的时候,建议用v-if -->
			<h3 v-show="show">小马哥</h3>
			
		</div>
		
		<script>
			// v-if v-else-if v-else
			new Vue({
				el:'#app',
				data:{
					isShow:Math.random() > 0.5,
					show:true
				}
			})
			
			// v-show
		</script>
	</body>
</html>

v-bind指令

v-bind指令用来绑定元素中属性,让元素的属性值可以动态的切换

一、v-bind:属性 给标签中的属性绑定值;

二、:属性 是v-bind:属性的简写;

三、v-bind:class属性=“{class的名:布尔值}”

​ 如果布尔值为true,那么就会给该元素添加class属性,值为class的名,如果为false则不添加或者删除 这样就可以动态的切换class;

四、:自定义属性 可以绑定自定义属性;

五、:style 也可以绑定元素的样式;

v-bind指令用法

用法1:
v-bind:属性 = "vue中的data中的变量"

用法2:
:属性 = "vue中的data中的变量"

例如,用v-bind绑定a标签中的href的值

// v-bind:属性 = "vue中的data中的res对象中url的值"
<a v-bind:href="res.url" v-bind:title="res.title">{{res.name}}</a>

例如:用v-bind指令动态修改class属性值

// 如果isActive的值为true,那就给该标签添加class = 'active'属性,否则就不添加,当然一个标签可以同时拥有多个class属性值
<h3 v-bind:class="{active:isActive}">v-bind的用法</h3>

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>v-bind绑定指令</title>
		<script src="vue.js"></script>
		<style>
			.active{
				color: red;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<!-- 1.v-bind:属性  给标签中的属性绑定值 -->
			<a v-bind:href="res.url" v-bind:title="res.title">{{res.name}}</a>
			<!-- 2. :属性 是v-bind:属性的简写 -->
			<img :src="imgSrc" alt="">
			<!-- 3.v-bind:class属性="{class的名:布尔值}",如果布尔值为true,那么就会给该元素添加class属性,值为class的名,如果为false则不添加或者删除 这样就可以动态的切换class -->
			<h3 v-bind:class="{active:isActive}">v-bind的用法</h3>
			<!-- 4. :自定义属性	可以绑定自定义属性 -->
			<h4 :aaa='res.name'></h4>
			<!-- 5. :style 也可以绑定元素的样式 -->
			<h4 :style='{color:isColor,fontSize:fontSize+"px"}'>hello bind</h4>
		</div>
		<script>
			new Vue({
				el:'#app',
				data:{
					res:{
						name:'百度',
						url:'https://www.baidu.com',
						title:'百度一下'
					},
					imgSrc:'https://img0.baidu.com/it/u=3481750343,2130961727&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1723395600&t=7abcd6c0547fce007d71b24c3b44405e',
					isActive:true,
					isColor:'green',
					fontSize:30
				}
			})
			
		</script>
	</body>
</html>

v-on事件绑定指令

v-on事件可以给元素绑定js中的各种事件类型

使用方式

1.v-on:事件=“事件方法”

2.也可以简写: @事件=“事件方法”

例如

// 使用方式1
<button v-on:click="handleClick"></button>
// 使用方式2
<button @click="changeClick">切换</button>

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>v-on事件绑定指令</title>
		<script src="vue.js"></script>
		<style>
			.box{
				width: 200px;
				height: 200px;
				background-color: red;
			}
			.active{
				background-color: green;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<h3>{{num}}</h3>
			<!-- v-on:事件="事件方法" -->
			<button v-on:click="handleClick">+1</button>
			
			<div class="box" :class="{active:isActive}"></div>
			<!-- @事件="事件方法" 是 v-on:事件="事件方法" 的简写 -->
			<button @click="changeClick">切换</button>
		</div>
		<script>
			new Vue({
				el:'#app',
				data:{
					num:0,
					isActive:false
				},
				methods:{
					// 要处理的事件方法
					handleClick(){
						this.num+=1;
					},
					changeClick(){
						this.isActive = !this.isActive;
					}
				},
			})
		</script>
	</body>
</html>
v-on事件修饰符

详细可查看 官方的事件修饰介绍

使用方式

v-on:事件.事件修饰符 = “事件方法”

事件修饰符

修饰符可以串联

.stop:阻止事件冒泡

.prevent:阻止事件默认行为

.capture:使用事件捕获模式触发事件,父元素的事件处理函数会先于子元素的事件处理函数被调用

.self:只有当事件在该元素本身(而不是子元素)触发时,才会触发事件处理函数

.once : 只触发一次事件

.passive:告诉浏览器,你永远不会调用preventDefault()函数来阻止事件的默认行为。

once事件修饰符的案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>事件修饰符</title>
		<script src="vue.js"></script>
	</head>
	<div id="app">
		<div>{{num}}</div>
		<!-- 案例:给button按钮绑定点击事件,并且用once事件修饰符修饰,使得该事件只会触发一次 -->
		<button v-on:click.once = "change">+1</button>
	</div>
	<body>
		<!-- v-on:事件.事件修饰符 -->
		<script>
			new Vue({
				el:'#app',
				data:{
					num:0
				},
				methods:{
					change(){
						this.num+=1;
					}
				}
			})
		</script>
	</body>
</html>

v-for指令

用来循环遍历元素

v-for的使用模板

// 一个变量的情况
v-for='变量 in data中的变量'

// 多个变量
v-for='(变量1,变量2,...) in data中的变量'

// 获取遍历中的元素的索引
v-for='index in data中的变量'

// 获取遍历中的元素对象的键
v-for='key in data中的变量'

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,建议使用v-for的时候,带上:key=‘变量.id’

使用案例

menus:[
    {id:1,name:'大腰子'},
    {id:2,name:'烤鸡翅'},
    {id:3,name:'烤韭菜'},
    {id:4,name:'烤大蒜'},
]
// 对应上述的数据,建议用 :key="item.id" 获取id
<li v-for="item in data" :key="item.id"></li>
obj:{
    title:'hello 循环',
    author:'小马哥'
}
// 对于上述的数据,建议用 :key="key" 获取对象的键
<li v-for="val in obj" :key="key"></li>

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>v-for指令</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<div>
				<ul>
					<!-- v-for='变量 in data中的变量' -->
					<!-- v-for='index in data中的变量' index是索引 -->
					<!-- 建议使用v-for的时候,带上:key='变量.id',那么它就会只更新变化的部分,否则它就成了全部更新 -->
					<li v-for='(item,index) in menus' :key='item.id'>
						<h3>{{index}}-id:{{item.id}} 菜名:{{item.name}}</h3>
					</li>
				</ul>
				<ol>
					<!-- v-for='key in data中的变量' key是键 -->
					<li v-for='(val,key) in obj' :key='key'>
						{{key}}--{{val}}
					</li>
				</ol>
			</div>
		</div>
		<script>
			new Vue({
				el:'#app',
				data:{
					menus:[
						{id:1,name:'大腰子'},
						{id:2,name:'烤鸡翅'},
						{id:3,name:'烤韭菜'},
						{id:4,name:'烤大蒜'},
					],
					obj:{
						title:'hello 循环',
						author:'小马哥'
					}
				},
				methods:{
					
				},
			})
		</script>
	</body>
</html>

v-model指令

v-model指令用来实现数据双向绑定,主要针对的是与用户能够进行交互的标签,比如输入框,多选框,单选框,下拉框等

使用方式

v-model = '变量'

首先在vue对象中的data中定义一个变量,然后在标签中写 v-model = ‘data中的变量’,然后再在另外一个标签中引入data中的变量,这样实现的效果就是,当被双向数据绑定的标签中的内容发生改变时,另外一个标签的内容也会同步发生改变。

使用案例

在这段代码中,在data中定义了msg变量,在input标签中定义了 v-model =‘msg’ ,然后再p标签中引入了msg变量,这样input标签与p标签就产生了数据双向绑定

当input输入框中的内容发生改变了,p标签中的内容也会同步发生改变

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>v-model数据双向绑定指令</title>
		<script src="vue.js"></script>
		<style>
			
		</style>
	</head>
	<body>
		<div id="app">
			<p>{{msg}}</p>
			<!-- p标签中的内容会随着输入框的内容改变而改变,这便是双向数据绑定 -->
			<input type="text" v-model='msg' />
		</div>
		<script>
			new Vue({
				el:'#app',
				data:{
					msg:'小马哥',
				},
				methods:{
					
				}
			})
		</script>
	</body>
</html>
表单输入绑定
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>表单输入绑定</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- 复选框单选
            <!-- 所有的复选框都使用相同的 name 属性值  -->
			<label for="checkbox">{{checked}}</label>
			<input type="checkbox" id="checkbox" v-model="checked"/>
			
			
			<!-- 当点击选项框的时候,就会自动将value的值push到v-model中的变量 -->
			<div class="box">
				<label for="a">黄瓜</label>
				<!-- 当选中黄瓜的时候,将黄瓜添加到checkedNames数组中 -->
				<input type="checkbox" id="a" value="黄瓜" v-model="checkedNames"/>
				
				<label for="b">西红柿</label>
				<!-- 当选中西红柿的时候,将西红柿添加到checkedNames数组中 -->
				<input type="checkbox" id="b" value="西红柿" v-model="checkedNames"/>
				
				
				<label for="c">芸豆</label>
				<!-- 当选中芸豆的时候,将芸豆添加到checkedNames数组中 -->
				<input type="checkbox" id="c" value="芸豆" v-model="checkedNames"/>
				
				<br>
				<span>{{checkedNames}}</span>
			</div>
			
			<!-- 使用v-model.lazy进行数据双向绑定,会等输入框中的内容输入完毕后,鼠标点击空白部分,数据才会更新,而使用默认的v-model就会实时同步更新 -->
			<label>{{txt}}</label>
			<input v-model.lazy='txt'>
			
			
			<!-- 使用 v-model.trim 进行数据双向绑定,会自动去除输入的前后空格 -->
			<label>去除空格:{{msg2}}</label>
			<input v-model.trim='msg2'>
			
		</div>
		
		<script>
			new Vue({
				el:'#app',
				data:{
					txt:'',
					msg2:'',
					checked:false,
					checkedNames:[],
				},
				methods:{
					
				}
			})
		</script>
	</body>
</html>

监听器watch

watch监听器是用来监听页面中的数据的变化

基本的数据类型可以使用watch直接监听,复杂数据类型Object和Array要深度监听

使用方式

普通监听

// 1.普通数据类型的监听
// '要监听的变量':function(newV,old){} newV是新值,old是旧值
new Vue({
    el:'',
    data:{},
    watch:{
        // newV是新值,old是旧值
        '要监听的变量':function(newV,oldV){
            // 要做的事情
            console.log(newV,oldV);
        }
    }
})

深度监听

// 2.对于复杂的数据类型(Object,Array),要深度监视
// '要监听的数据':{deep:true,handler:function(newV,oldV){/*要做的事情*/}}
'要监听的数据':{
    deep:'true', // 表示开启深度监听
        // newV是新值,old是旧值
        handler:function(newV,oldV){
            // 要做的事情
            console.log(newV,oldV);
        }
}

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>监听器watch的用法</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<input type="text" v-model="msg">
			<h3>{{msg}}</h3>
			<h3>{{stus[0].name}}</h3>
			<button @click="stus[0].name='Tom'">改变</button>
		</div>
		
		<script>
			// 基本的数据类型可以使用watch直接监听,复杂数据类型Object和Array要深度监听
			
			
			new Vue({
				el:'#app',
				data:{
					msg:'',
					stus:[{name:'jack'}]
				},
				methods:{
					
				},
				// 添加watach监听器
				watch:{
					// '要监听的变量':function(newV,old){} newV是新值,old是旧值
					'msg':function(newV,oldV){
						console.log(newV,oldV);
						// 例如:当输入框内的值为100的时候,要做些事情
						if(newV === '100'){
							console.log('hello');
						}
					},
					// 对于复杂的数据类型(Object,Array),要深度监视
					// '要监听的数据':{deep:true,handler:function(newV,oldV){/*要做的事情*/}}
					'stus':{
						deep:'true', // 表示开启深度
						handler:function(newV,oldV){
							console.log(newV[0].name);
						}
					}
				}
			})
		</script>
		
	</body>
</html>

计算属性computed

computed会监听页面中的指定数据,当数据一旦发生变化,就立即触发computed中的函数方法,与methods不同的是,computed自动触发内部的函数,methods要手动调用

最大的优点能产生缓存 如果数据没有发生变化,直接从缓存中取

computed中存放自定义方法,默认只有getter方法

使用方法

new Vue({
    el:'#app',
    data:{},
    // 计算属性
    // 特点:computed会监听页面中的指定数据,当数据一旦发生变化,就立即触发computed中的函数方法,优点能产生缓存 如果数据没有发生变化,直接从缓存中取
    computed:{
        // computed内用于存放方法,默认只有getter方法
        // reverseMsg 自定义方法
        方法名:function(){
            // 要做的事情
        }
    }
})

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>computed计算属性</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- 这里会去找computed中的自定义方法reverseMsg -->
			{{reverseMsg}}
			<h3>{{fullName}}</h3>
			<button @click='handlerClick'>改变</button>
		</div>
		<script>
			new Vue({
				el:'#app',
				data:{
					msg:'hello world',
					firstName:'小马',
					lastName:'哥'
				},
				methods:{
					handlerClick(){
						this.msg = '计算属性computed';
						this.lastName = '妹';
					}
				},
				// 计算属性
				// 特点:computed会监听页面中的指定数据,当数据一旦发生变化,就立即触发computed中的函数方法,优点能产生缓存 如果数据没有发生变化,直接从缓存中取
				computed:{
					// computed内用于存放方法,默认只有getter方法
					// reverseMsg 自定义方法
					reverseMsg:function(){
						return this.msg.split('').reverse().join();
					},
					fullName:function(){
						return this.firstName + this.lastName;
					}
				}
			})
		</script>
	</body>
</html>

set方法

当给computed中的函数赋值时,会自动调用函数内的set方法,当取computed中的函数值时,会自动调用get方法

使用方式

new Vue({
    el:'#app',
    data:{},
    computed:{
        方法名:{
            // computed中默认没有set方法,这里人为的添加了
            // 当外部代码 给 方法名 设置值的时候,就会执行该方法中的set函数
            set:function(newV){
                this.msg = newV;
            },
            // 当外部代码 取 方法名 值的时候,就会执行该方法中的get函数
            get:function(){
                return this.msg;
            }
        }
    }
})

完整代码

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>计算属性的setter方法</title>
        <script src="vue.js"></script>
    </head>
    <body>
        <div id="app">
            {{content}}
            <input type="text" v-model="content" @input='handlerInput'>
            <button @click='handleClick'>获取</button>
        </div>
        <script>
            new Vue({
                el:'#app',
                data:{
                    msg:'',
                },
                methods:{
                    handlerInput:function(event){
                        // 解构数据
                        const {value} = event.target;
                        // this.msg = value; // 没有设置set方法的时候

                        // 当给computed中的函数赋值时,会自动调用函数内(centent)的set方法,当取computed中的函数值时,会自动调用get方法
                        this.content = value; // 赋值操作
                    },
                    handleClick(){
                        console.log(this.content);
                    }

                },
                computed:{
                    content:{
                        // computed默认没有set方法,这里手动给添加
                        set:function(newV){
                            this.msg = newV;
                        },
                        get:function(){
                            return this.msg;
                        }
                    }
                }
            })
        </script>
    </body>
</html>

过滤器fliters

过滤器fliters 就是为数据添油加醋

在vue中有两种过滤器,一种是局部过滤器,一种是全局过滤器

局部过滤器

使用方式

<!-- 使用过滤器:数据 | 自定义过滤器名 -->
<h3>{{要过滤的数据 | 自定义局部过滤器名('参数2')}}</h3>

// 过滤器fliters 就是为数据添油加醋
// 在vue中有两种过滤器,一种是局部过滤器,一种是全局过滤器
new Vue({
    el:'',
    data:{},
    // 局部过滤器
    filters:{
        // 自定义过滤器名:function(要过滤的数据){}
        自定义过滤器名:function(参数1,参数2){
            // 参数2可以是 标签内传入的参数
            return 参数2+ 参数1;
        }
    }
})

全局过滤器

使用方式

<!-- 2.使用全局过滤器 -->
<h3>{{要过滤的数据 | 自定义全局过滤器名}}</h3>

// 1.创建全局过滤器
// Vue.filter('自定义过滤器名',()=>{回调函数})
Vue.filter('自定义过滤器名',(接收标签内传入的要过滤的参数)=>{
    // 要做的事情
    return ...;
})

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>过滤器fliters</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- 使用过滤器:数据 | 自定义过滤器名 -->
			<h3>{{price | myPrice('¥')}}</h3>
			<!-- 使用全局过滤器 -->
			<h3>{{msg | myReverse}}</h3>
		</div>
		<script>
			// 创建全局过滤器
			// Vue.filter('自定义过滤器名',()=>{回调函数})
			Vue.filter('myReverse',(val)=>{
				// 反转字符串
				return val.split('').reverse().join('');
			})
			
			
			
			// 过滤器fliters 就是为数据添油加醋
			// 在vue中有两种过滤器,一种是局部过滤器,一种是全局过滤器
			new Vue({
				el:'#app',
				data:{
					price:10,
					msg:'hello 过滤器'
				},
				// 局部过滤器
				filters:{
					// 自定义过滤器名:function(要过滤的数据){}
					myPrice:function(price,a){
						// a 是标签内传入的参数
						return a + price;
					}
				}
			})
		</script>
	</body>
</html>

案例:音乐播放器

未使用计算属性

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>案例:音乐播放器</title>
		<script src="../vue.js"></script>
		<style>
			*{
				margin: 0;
				padding: 0;
			}
			ul{
				list-style: none;
			}
			ul li{
				margin: 20px 20px;
				padding: 10px 5px;
				border-radius: 3px;
			}
			ul li.active{
				background-color: #d2e2f3;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<audio :src="currentSrc" controls autoplay @ended='handleEnded'></audio>
			<ul>
				<li :class="{active:index===currentIndex}" v-for='(item,index) in musicData' :key="item.id" @click='handleClick(item.songSrc,index)'>
					<h2>{{item.id}}-歌名:{{item.name}}</h2>
					<p>{{item.author}}</p>
				</li>
			</ul>
			<button @click='handleNext'>下一首</button>
		</div>
		<script>
			
			const musicData = [{
				id: 1,
				name: '于荣光 - 少林英雄',
				author: '于荣光',
				songSrc: 'static/于荣光 - 少林英雄.mp3'
			},
			{
				id: 2,
				name: 'Joel Adams - Please Dont Go',
				author: 'Joel Adams',
				songSrc: 'static/Joel Adams - Please Dont Go.mp3'
			},
			{
				id: 3,
				name: 'MKJ - Time',
				author: 'MKJ',
				songSrc: 'static/MKJ - Time.mp3'
			},
			{
				id: 4,
				name: 'Russ - Psycho (Pt. 2)',
				author: 'Russ',
				songSrc: 'static/Russ - Psycho (Pt. 2).mp3'
			}
		];
			
		new Vue({
			el:'#app',
			data:{
				musicData:musicData,
				currentSrc:'static/于荣光 - 少林英雄.mp3',
				currentIndex:0 // 0为fasle 1为true
			},
			methods:{
				handleClick(src,index){
					this.currentSrc = src;
					this.currentIndex = index;
				},
				// 当 当前音乐播放完毕
				handleEnded(){
					// // 自动进入下一组播放
					// this.currentIndex++;
					// this.currentSrc = this.musicData[this.currentIndex].songSrc
					this.handleNext();
				},
				// 点击下一首按钮
				handleNext(){
					this.currentIndex++;
					if(this.currentIndex == this.musicData.length){
						this.currentIndex = 0;
					}
					this.currentSrc = this.musicData[this.currentIndex].songSrc
				}
			}
		})
			
		</script>
	</body>
</html>

使用计算属性

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>案例:音乐播放器---使用计算属性</title>
		<script src="../vue.js"></script>
		<style>
			*{
				margin: 0;
				padding: 0;
			}
			ul{
				list-style: none;
			}
			ul li{
				margin: 20px 20px;
				padding: 10px 5px;
				border-radius: 3px;
			}
			ul li.active{
				background-color: #d2e2f3;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<!-- ended内置属性,表示音乐播放完毕 -->
			<audio :src="getCurrentSongSrc" controls autoplay @ended='handleEnded'></audio>
			<ul>
				<li :class="{active:index===currentIndex}" v-for='(item,index) in musicData' :key="item.id" @click='handleClick(index)'>
					<h2>{{item.id}}-歌名:{{item.name}}</h2>
					<p>{{item.author}}</p>
				</li>
			</ul>
			<button @click='handleNext'>下一首</button>
		</div>
		<script>
			
			const musicData = [{
				id: 1,
				name: '于荣光 - 少林英雄',
				author: '于荣光',
				songSrc: 'static/于荣光 - 少林英雄.mp3'
			},
			{
				id: 2,
				name: 'Joel Adams - Please Dont Go',
				author: 'Joel Adams',
				songSrc: 'static/Joel Adams - Please Dont Go.mp3'
			},
			{
				id: 3,
				name: 'MKJ - Time',
				author: 'MKJ',
				songSrc: 'static/MKJ - Time.mp3'
			},
			{
				id: 4,
				name: 'Russ - Psycho (Pt. 2)',
				author: 'Russ',
				songSrc: 'static/Russ - Psycho (Pt. 2).mp3'
			}
		];
			
		new Vue({
			el:'#app',
			data:{
				musicData:musicData,
				currentIndex:0 // 0为fasle 1为true
			},
			// 计算属性
			computed:{
				// 当 audio的src数据改变
				getCurrentSongSrc(){
					// 自动修改音乐的路径
					return this.musicData[this.currentIndex].songSrc;
				}
			},
			methods:{
				handleClick(index){
					this.currentIndex = index;
				},
				// 当 当前音乐播放完毕
				handleEnded(){
					// // 自动进入下一组播放
					// this.currentIndex++;
					// this.currentSrc = this.musicData[this.currentIndex].songSrc
					this.handleNext();
				},
				// 点击下一首按钮
				handleNext(){
					this.currentIndex++;
					if(this.currentIndex == this.musicData.length){
						this.currentIndex = 0;
					}
					
				}
			}
		})
			
		</script>
	</body>
</html>

Vue2组件

组件的概念

可以理解为,一个组件是由html+css+js的整合,一个精美的搜索框就可以看作是一个组件,按钮,侧边栏,导航栏等页面上的元素均可以看作是一个组件,组件的作用就是复用性。

组件也分为全局组件局部组件全局组件可以在任意地方使用,局部组件只有在加载到当前内容组件的时候,局部组件才会加载出来。

一系列的组件,组合在一起,组件又分为父组件,子组件,同级组件,组成了一个DOM树。

组件的创建和使用

局部组件

局部组件的创建

const 变量 = {template:`html代码`}

// 创建局部组件
const App = {
    // 1.创建组件
    // const 变量 = {template:`html代码`} 这里是用双反引号将html代码包裹起来的
    // 在template中如果由多个组件,一定要在最外层包含组件
    template:`
					<h3>我是App组件</h3>
				`
}
局部组件的挂载
// 假设创建了一个App组件,将该组件挂载到vue对象中的components中
new Vue({
    el:'',
    data:{},
    // 2.components 用于挂载组件
    components:{
        // 将上面创建的App子组件,挂载
        App
    },

})
局部组件的使用
<div id="app">
    <!-- 3.使用子组件 <App></App> 其中App是创建出来的局部组件的变量名字-->
    <App></App>
</div>

在创建的局部组件的对象中,可以使用data(){}函数,template模板,methods,computed等

局部组件使用的基本代码案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>局部组件的创建和使用</title>
		<script src="../vue.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- 3.使用子组件 <App></App> 其中App是创建出来的局部组件的变量名字-->
			<App></App>
		</div>
		<script>
			// 一个组件可以看作是一个对象
			// App组件 可以包含html+css+js
			const App = {
				// 1.创建组件
				// const 变量 = {template:`html代码`}
                // 在template中如果由多个组件,一定要在最外层包含组件
				template:`
					<h3>我是App组件</h3>
				`
			}
			new Vue({
				el:'#app',
				data:{
					return {}
				},
				// 2.components 用于挂载组件
				components:{
					// 将上面创建的App子组件,挂载
					App
				},
				
			})
		</script>
	</body>	
</html>
组件使用的注意事项

上面创建的局部组件的案例,是创建一组标签为例子,但是如果想要同时创建多组标签的话,就必须要用一个最外层的标签,将多组标签包裹起来,否则会报错

const App = {
				// ....... 省略上面代码
				template:`
					<!-- 多组标签 一定要被外层父类标签包裹,否则报错 -->
					<div>
						<h3>我是app组件</h3>
						<button>按钮</button>
					</div>
				`,
				// .....省略下面代码
			}

如果想要将

我是h3标签

中文本内容变成动态值,就必须要用到data(){}中的变量,但是这个 data必须是一个函数,这个data函数还必须要返回一个对象

const App = {
				// 注意:在组件中,这个data(){}必须是一个函数,返回一个对象
				data(){
					return {
						msg:'我是app组件'
					}
				},
				
				template:`
					<div>
						 <!-- 注意:在组件中,这个data必须是一个函数,返回一个对象 -->
						 <!-- 这里的msg就是上述data(){}中定义的,而不是vue({})对象中的data -->
						<h3>{{msg}}</h3>
						<button>按钮</button>
					</div>
				`,
    			// .....省略下面的代码
			}

在创建组件的同时,也可以给组件绑定事件,而在组件对象中可以直接定义methods,注意,下面的methods是创建出来组件对象中的methods,而不是vue({})对象中的methods

const App = {
    // 1.创建组件
    // 注意:在组件中,这个data必须是一个函数,返回一个对象
    data(){
        return {
            msg:'我是app组件'
        }
    },

    template:`
					<div>
						 <!-- 注意:在组件中,这个data必须是一个函数,返回一个对象 -->
						<h3>{{msg}}</h3>
						<!-- 给button按钮绑定click事件,事件函数在App对象中的methods,而不是vue({})中 -->
						<button @click = 'handleClick'>按钮</button>
					</div>
				`,
    methods:{
        // button按钮中的绑定事件
        handleClick(){
            this.msg = '学习局部组件';
        }
    },
}

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>局部组件的创建和使用</title>
		<script src="../vue.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- 3.使用子组件 <App></App> 其中App是创建出来的局部组件的变量名字-->
			<App></App>
		</div>
		<script>
			// 一个组件可以看作是一个对象
			// App组件 可以包含html+css+js
			const App = {
				// 1.创建组件
				// const 变量 = {template:`html代码`}
				// 在template中如果由多个组件,一定要在最外层包含组件
				// 注意:在组件中,这个data必须是一个函数,返回一个对象
				data(){
					return {
						msg:'我是app组件'
					}
				},
				
				template:`
					<div>
						 <!-- 注意:在组件中,这个data必须是一个函数,返回一个对象 -->
						<h3>{{msg}}</h3>
						<button @click = 'handleClick'>按钮</button>
					</div>
				`,
				methods:{
					handleClick(){
						this.msg = '学习局部组件';
					}
				},
				computed:{},
			}
			new Vue({
				el:'#app',
				data:{
					
				},
				// 2.components 用于挂载组件
				components:{
					// 将上面创建的App子组件,挂载
					App
				},
				
				
			})
		</script>
	</body>
</html>

全局组件

创建全局组件 可以在任意地方(template)使用

全局组件的创建

Vue.component(‘全局组件名称’,{template:`html代码`})

// 创建全局组件
Vue.component('全局组件名称',{
    template:`
    	html代码
    `
})

例如

Vue.component('Vheader',{
    template:`
					<div>
						我是导航组件
					</div>
				`
})
<!-- 记得下面还要创建Vue({})对象,然后再el选中元素-->
全局组件不需要挂载

全局组件,不需要挂载,就可以直接使用

全局组件的使用
<div id="">
    <全局组件名称></全局组件名称>
</div>

<!-- 记得下面还要创建Vue({})对象,然后再el选中上述元素-->

例如

<div id="app">
    <!-- 使用全局组件Vheader -->
    <Vheader></Vheader>
</div>

<!-- 记得下面还要创建Vue({})对象,然后再el选中#app-->

整体初步使用代码模板

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- 2.使用全局组件 -->
			<Vheader></Vheader>
		</div>
		
		<script>
			// 1.创建全局组件
			Vue.component('Vheader',{
				template:`
					<div>我是全局组件</div>
				`
			})
			
            // 3.创建Vue对象,然后选中标签元素
			new Vue({
				el:'#app',
				data:{
					
				},
				
			})
			
		</script>
	</body>
</html>

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>全局组件的创建和使用</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- 使用子组件 -->
			<App></App>
		</div>
		<script>
			// 创建全局组件 可以在任意地方(template)使用
			// Vue.component('全局组件名称',{template:`html代码`})
			// 全局组件,不需要挂载,就可以直接使用
			Vue.component('Vheader',{
				template:`
					<div>
						我是导航组件
					</div>
				`
			})
			
			// 再创建一个全局组件
			Vue.component('Vaside',{
				template:`
					<div>
						我是侧边栏组件
					</div>
				`
			})
			
			// 再创建一个局部组件
			const Vcontent = {
				data(){
					return {}
				},
				template:`
					<div>
						我是内容组件
					</div>
				`
			}
			
			// 创建局部组件
			const App = {
				data(){
					return {
						
					}
				},
				components:{
					// 挂载自定义组件 Vcontent
					Vcontent
				},
				template:`
					<div>
						<!-- 使用自定义全局组件 Vheader -->
						<Vheader></Vheader>
						<div>
							<Vaside />
							<Vcontent />
						</div>
					</div>
				`,
			}
			
			new Vue({
				el:'#app',
				components:{
					App
				},
			})
			
		</script>
	</body>
</html>

组件通信

父类传值给子类

父组件将值传递给子组件

使用方式

1.在子组件中定义props属性: props:["变量"]

2.在子组件中的template中用{{props中的变量}}的方式将值传给父组件

3.在父组件中用 :props中的变量 = 'data(){}中的值'

1.在子组件中声明props接收在父组件挂载的属性

// 这是子组件
Vue.component('子组件名',{
    template:``,
    // ....省略以上代码
    
    // 定义props属性
    props:['变量']
})

2.在子组件中的template中使用

// 这是子组件
Vue.component('子组件名',{
    template:`
    <div>
    	<!- 使用props属性 -->
    	<h3>{{props[]中的变量}}</h3>
    </div>`,
    
    props:['变量']
})

3.在父组件中绑定props属性

// 这是父组件
const App = {
    data(){
        return {
            msg:'我是父组件传进来的值'
        }
    },
    template:
    `
					<div>
						<子组件名 :props中的属性='父组件中的data(){}中的变量'></子组件名>
					</div>
				`
}

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>组件通信-父传子</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		
		<script>
			// 父传子: 通过prop来进行通信
			// 1.在子组件中声明props接收在父组件挂载的属性
			// 2.可以在子组件的template中任意使用
			// 3.在父组件绑定自定义的属性
			
			
			
			// 创建全局组件
			// 子组件
			Vue.component('Child',{
				template:`
					<div>
						<h3>我是一个子组件</h3>
						<!-- 2.使用props中的变量名的值 -->
						<h3>{{childData}}</h3>
					</div>
				`,
				// 1. 组件通信 props:['变量名']
				// 定义 props:['变量名']
				props:['childData']
			})
			
			
			// 这个App就可以看作是父组件
			const App = {
				data(){
					return {
						msg:'我是父组件传进来的值'
					}
				},
				template:
				`
					<div>
						<!-- 3.绑定props中的变量名 :props中的变量名 -->
						<!-- :props中的变量名='值' 这里的msg来自上面的data()-->
						<Child :childData='msg'></Child>
					</div>
				`
				
			}
			
			new Vue({
				el:'#app',
				data:{
					
				},
				components:{
					App
				}
			})
			
		</script>
		
	</body>
</html>

子类传值给父类

子组件数据传给父组件。

在父组件中,子组件上绑定自定义事件

在子组件中 触发原生的事件 在事件函数通过this.$emit触发自定义事件

使用方式

1.在子组件中的template中绑定事件 @事件='自定义事件处理函数'

2.在子组件中的methods属性定义 自定义事件处理函数方法

3.在父组件中的template属性中定义 <子组件 @自定义事件='事件处理函数'></子组件>

4.在父组件中的methods中定义 事件处理函数的方法

5.在子组件中的methods属性中的自定义事件处理函数方法使用this.$emit('父组件中的子组件的@自定义事件',接收父组件中的methods中的函数传入的值)

6.在父组件中的template属性中使用{{父组件中的data(){}中的属性}}

1.子组件中的template绑定事件

// 这是子组件
// 其中 Child 是子组件名
Vue.component('Child',{
    template:`
					<div>
						<!-- 绑定事件 @内置事件='自定义事件处理函数' -->
						<input type='text' @input='handleInput' />
					</div>
				`,
    // .... 省略下面代码
})

2.在子组件中的methods中定义 自定义事件处理函数的代码

// 这是子组件
// 其中 Child 是子组件名
Vue.component('Child',{
    // .... 省略上面代码
    methods:{
        //handleInput对应子组件中的template中的绑定的事件处理函数
        handleInput(e){
            // 要做的事情,这步可以先不管
        }
    }
})

3.在父组件中的template中的子组件中挂载自定义事件

// 父组件
const App = {
    // ..... 省略上面部分代码
    template:
    `
					<div>
						<!-- 挂载自定义事件 其中,inputHandler是自定义的事件,不是内置的事件-->
						<Child @inputHandler='input'></Child>
					</div>

				`
    // .... 省略下面部分代码

}

4.在父组件中methods中定义 挂载的自定义事件 的具体方法

// 父组件
const App = {
    data(){
        return {
            newVal:''
        }
    },
    methods:{
        // 定义父组件中的template中的自定义事件方法,其中newVal是接收子类传来的值,然后赋值给了data中的newVal变量
        // input函数是 父类中的template中的挂载自定义事件的
        input(newVal){
            // console.log(newVal);
            // 将从子组件中传来的值,保存起来,放在data(){}中
            this.newVal = newVal;
        }
    },
    // 省略下面代码

}

5.在子组件中的methods属性中的自定义处理函数方法中用 this.$emit()来触发在父组件中的template中的子组件中的自定义事件

// 子组件
Vue.component('Child',{
    template:`
					<div>
						<!-- 绑定事件 @事件='自定义事件处理函数' -->
						<input type='text' @input='handleInput' />
					</div>
				`,
    methods:{
        // handleInput 就是子组件中的template中的 <input type='text' @input='handleInput' />
        handleInput(e){
            const val = e.target.value;
            // .$emit() 方法用来触发自定义事件
            // this.$emit('父组件中的template中的子组件中的自定义事件',要传给父类的数据)
            // inputHandler就是 父组件中的template中的<Child @inputHandler='input'></Child>
            // val是接收了前端页面子组件的触发事件得到的数据,用于传递给父类
            this.$emit('inputHandler',val); // ******这行是核心代码****
            console.log(val);
        }
    }
})

6.在父组件中使用从子组件中获取的值

// 父组件
const App = {
    data(){
        return {
            newVal:''
        }
    },
    methods:{
        input(newVal){
            // console.log(newVal);
            this.newVal = newVal;
        }
    },
    template:
    `
					<div>
						<!-- 展示子组件传给父组件的值 -->
						<div class='father'>
							<!-- 从子组件中传进来的值 -->
							数据:{{newVal}}
						</div>
						<!-- 挂载自定义事件 -->
						<Child @inputHandler='input'></Child>
					</div>
				`

}

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>组件通信-子传父</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		
		<script>
			// 子往父传值
			// 在父组件中,子组件上绑定自定义事件
			// 在子组件中 触发原生的事件 在事件函数通过this.$emit触发自定义事件
			
			// 创建全局组件
			// 子组件
			Vue.component('Child',{
				template:`
					<div>
						<h3>我是一个子组件</h3>
						<!-- 2.使用props中的变量名的值 -->
						<h3>{{childData}}</h3>
						<input type='text' @input='handleInput' />
					</div>
				`,
				// 1. 组件通信 props:['变量名']
				// 定义 props:['变量名']
				props:['childData'],
				methods:{
					handleInput(e){
						const val = e.target.value;
						// .$emit() 方法用来触发自定义事件
						this.$emit('inputHandler',val);
						console.log(val);
					}
				}
			})
			
			
			// 父组件
			const App = {
				data(){
					return {
						msg:'我是父组件传进来的值',
						newVal:''
					}
				},
				methods:{
					input(newVal){
						// console.log(newVal);
						this.newVal = newVal;
					}
				},
				template:
				`
					<div>
						<div class='father'>
							数据:{{newVal}}
						</div>
						<!-- 挂载自定义事件 -->
						<Child :childData='msg' @inputHandler='input'></Child>
					</div>
				`
				
			}
			
			new Vue({
				el:'#app',
				data:{
					
				},
				components:{
					App
				}
			})
			
		</script>
		
	</body>
</html>

平行组件通信

在一个父类中,有两个同级子组件,这两个子组件通过vue中提供的中央事件总线 (其实是一个对象 bus)来传递数据。

使用方式

1.创建一个中央事件总线bus的Vue对象

2.给要传值的组件在created属性中用中央事件总线对象.$on绑定事件

3.给要接收值的组件在methods属性中用中央事件总线对象.$emit触发绑定事件

案例代码

​ 场景描述:有A 和B两个组件,现在想要将B组件中的数据传递给A组件

1.创建一个新的Vue对象,用于中央事件总线bus

// 创建一个新的vue对象,让两个平行组件的事件都绑定到该对象上
const bus = new Vue();

2.在B组件中,在data(){} 中设置要传的变量值,在create(){}属性中用$on来绑定事件,对data中的值进行处理,然后再template中以{{}}的方式展现

// B组件 要将数据传递给A组件
Vue.component('B',{
    data(){
        return {
            // 设置要传的变量值 count
            count:0
        }
    },
    template:`
					<div>{{count}}</div>
				`,
    // 当组件一旦被创建出来,created属性就会立即调用
    created(){
        // $on 绑定事件 其中,bus是中央事件总线对象
        // add是自定义事件名,n是接收A组件的参数
        bus.$on('add',(n)=>{
            // 对data中的count变量进行处理
            this.count += n;
        })
    }
})

3.在A组件中,在data(){}设置要从B组件中接收的变量,在template中绑定事件,在methods中,定义绑定事件的函数,用中央事件总线对象.$emit('B组件中的created中的自定义事件名',传入的参数) 来触发事件

// A组件 从B组件中接收数据
Vue.component('A',{
    data(){
        return {
            // 注意:这里的变量要与传入的变量保持一直
            count:0
        }
    },
    template:`
					<div>
						<button @click='handleClick'>加入购物车</button>
					</div>
				`,
    methods:{
        handleClick(){
            // 触发事件 其中add是A组件中所绑定的事件
            bus.$emit('add',1);
        }
    }
})

4.最后将A和B组件放在主组件中的template来显示效果

// html代码部分
<div id="app">
	<App></App>
</div>

//====================================
const App = {
    data(){
        // 在 Vue 组件中,data 必须是一个函数,这个函数返回一个对象
        return {}
    },
    template:`
					<div>
						<A></A>
						<B></B>
					</div>

				`,
}

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>平行组件通信</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		
		<script>
			// B组件与A组件 通过中央事件总线bus
			// 创建一个新的vue对象,让两个平行组件的事件都绑定到该对象上
			const bus = new Vue();
			
			// B组件
			Vue.component('B',{
				data(){
					return {
						count:0
					}
				},
				template:`
					<div>{{count}}</div>
				`,
				// 当组件一旦被创建出来,created属性就会立即调用
				created(){
					// $on 绑定事件
					bus.$on('add',(n)=>{
						this.count += n;
					})
					// $emit 触发事件
				}
			})
			
			// A组件
			Vue.component('A',{
				data(){
					return {
						count:0
					}
				},
				template:`
					<div>
						<button @click='handleClick'>加入购物车</button>
					</div>
				`,
				methods:{
					handleClick(){
						// 触发事件
						bus.$emit('add',1);
					}
				}
			})
			
			
			const App = {
				data(){
					// 在 Vue 组件中,data 必须是一个函数,这个函数返回一个对象
					return {}
				},
				template:`
					<div>
						<A></A>
						<B></B>
					</div>
				
				`,
			}
			
			
			new Vue({
				el:'#app',
				data:{
					
				},
				components:{
					App
				}
			})
			
			
		</script>
		
	</body>
</html>

provide和inject传值

当组件中嵌套多个子组件的时候,再用props属性传值,就比较麻烦了,所以vue中提供了provide属性传值,inject属性来接收值,这种方式更加方便,无论组件嵌套有多深,父组件的属性都能一步到位传递给指定的子组件

父组件,通过provide来提供变量,然后在子组件通过inject注入变量,无论组件嵌套多深,都能拿到父组件中提供的变量

使用方式

1.在父组件中,定义provide(){}属性,在里面定义要传递的值,然后用return返回出去

2.在子组件中,定义inject:[]属性,在里面接收父组件传递的值

3.在子组件中,在created(){}属性中,可以处理父类传来的值

使用案例

场景描述: A是父组件,B是子组件,现在要A组件的值传递给B组件

1.在父组件中,定义provide(){}属性

// A组件 父组件
Vue.component('A',{
    // .......省略上面代码
    
    // provide属性中定于 要传给子组件的数据
    provide(){
        return {
            msg:"父类中的数据"
        }
    },
    // ....省略下面代码
})

2.在子组件中,定义inject:[]属性

// B组件 子组件
Vue.component('B',{
    //....省略上面代码
    
    // inject中接收父类传来的值
    inject:['msg'],
    
    // ......省略下面代码
})

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>provide和inject传值</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		
		<script>
			// provide
			// inject
			// 父组件,通过provide来提供变量,然后在子组件通过inject注入变量,无论组件嵌套多深,都能拿到父组件中提供的变量
			
			
			// B组件 子组件
			Vue.component('B',{
				data(){
					// 这里必须要返回一个对象,即使是空对象
					return{
						
					}
				},
				// inject中接收父类传来的值
				inject:['msg'],
				// created属性在组件创建的时候就会被自动调用
				created(){
					// 在created中获取传入的值,这里要用this.传入的值
					console.log(this.msg);
				},
				template:`
					<div>
						<!-- 展示以下父类组件传来的值 -->
						{{msg}}
					</div>
				
				`,
			})
			
			
			
			// A组件 父组件
			Vue.component('A',{
				data(){
					// 这里必须要返回一个对象,即使是空对象
					return{
						
					}
				},
				created(){
					// 获取 App组件中的data中的属性
					// $parent获取父类组件对象(App组件)
					console.log(this.$parent.title);
					console.log(this.$parent.$parent);
					// $children获取子类组件对象(B组件)
					console.log(this.$children);
				},
				// provide属性中定于 要传给子组件的数据
				provide(){
					return {
						msg:"父类中的数据"
					}
				},
				template:`
					<div>
						<!-- 引入B子组件 -->
						<B></B>
					</div>
				`,
			})
			
			
			
			const App = {
				data(){
					// 注意:这里必须返回一个对象,空对象也行
					return {
						title:'老爹'
					}
				},
				template:`
					<div>
						<A></A>
					</div>
				`,
			}
			
			new Vue({
				el:'#app',
				data:{
					
				},
				// 局部组件挂载
				components:{
					App
				}
			})
			
			
			
		</script>
	</body>
</html>

插槽

在vue2中插槽大致分为:匿名插槽,具名插槽,作用域插槽。

插槽标签本身无意义,可以理解为就是一个代码中的占位符

使用插槽,可以让一个组件拥有多种形态,样式属性可以变换

匿名插槽

是匿名插槽,匿名插槽一个组件中只存在一个

使用方式

1.在子组件中使用了匿名插槽

2.在主组件中的template中引入子组件

效果就是:主组件中的子组件中的组件都会被插入到子组件中的插槽函数中取,此时该插槽函数就相当于占位符

使用案例

1.创建一个全局组件,并且使用匿名插槽

Vue.component('MBtn',{
				template:`
					<button>
						<!-- 使用匿名插槽 -->
						<!-- 匿名插槽一个组件中只存在一个 -->
						<slot></slot>
					</button>
				`
			})

2.在主组件中的template中引入上面创建的全局组件(其中包含匿名插槽函数)

// 创建局部组件
const App = {
    data(){
        return{

        }
    },
    template:`
					<div>
						<!-- 引入带匿名插槽的子组件,其中m-btn 等同于 MBtn vue中遵循驼峰命名规则,但不区分大小写-->
						<!-- m-btn就是上面创建的MBtn组件,<m-btn>标签中的元素都会被插入到MBtn组件中的<solt></solt> 中去,此时<slot></slot>就相当于占位符-->
						<m-btn>登录</m-btn>
						<m-btn>注册</m-btn>
					</div>
				`,
}

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>匿名插槽</title>
		<script src="vue.js"></script>
	</head>
	<body>		
		<div id="app">
			<App></App>
		</div>
		
		<script>
			// vue中遵循驼峰命名规则,但不区分大小写
			// MBtn 等同于 m-btn
			
			// 插槽标签:<slot></slot>
			
			// 在子组件中使用使用了匿名插槽,在主组件中的template中引入了子组件中,然后主组件中的子组件中的组件都会被插入到子组件中的<slot></solt>插槽中取,此时该插槽就相当于占位符
			
			
			Vue.component('MBtn',{
				template:`
					<button>
						<!-- 使用匿名插槽 -->
						<slot></slot>
					</button>
				`
			})
			
			
			// 创建局部组件
			const App = {
				data(){
					return{
						
					}
				},
				template:`
					<div>
						<!-- m-btn 等同于 MBtn-->
						<m-btn>登录</m-btn>
						<m-btn>注册</m-btn>
					</div>
				`,
			}
			
			new Vue({
				el:'#app',
				data:{
					
				},
				components:{
					// 挂载子组件
					App
				}
			})
			
		</script>
		
	</body>
</html>

具名插槽

匿名插值只能有一个,但是具名插槽可以有多个,具名插槽就是在匿名插槽的基础上,给插槽命名

<!-- 具名插槽定义方式 -->
<slot name='name1'></slot>
<slot name='name2'></slot>
<slot name='name3'></slot>

使用方式

1.在子组件中,给匿名插槽函数添加name属性,就成了具名插槽,这种插槽可以有多个,不同name的属性值

2.在主组件中,引入带插槽的子组件,在子组件中使用,在该标签中,添加属性slot=‘具名插槽的名字’

具体案例

1.在组件中定义具名插槽

// 只要匹配到slot标签的name值,tempalte中的内容就会被插入到这个槽中
Vue.component('MBtn',{
    template:`
					<button>
						<!-- 使用具名插槽函数 -->
						<slot name='login'></slot>
					</button>
				`
})

2.在主组件中使用具名插槽

// 创建局部组件
const App = {
    data(){
        return{

        }
    },
    template:`
					<div>
						<m-btn>
							<!-- 使用具名插槽 -->
							<!-- 在引入的子组件(带具名插槽)中的使用tempalte标签,并且给该标签设置属性:
                            	slot='具名插槽的名字',那么template标签中的内容都会被插入到子组件中的对应名字的具名插槽中
                            -->
							<template slot='login'>
								<a href='#'>登录</a>
							</template>
						</m-btn>
					</div>
				`,
}

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>具名插槽</title>
		<script src="vue.js"></script>
	</head>
	<body>		
		<div id="app">
			<App></App>
		</div>
		
		<script>
			// 只要匹配到slot标签的name值,tempalte中的内容就会被插入到这个槽中
			Vue.component('MBtn',{
				template:`
					<button>
						<!-- 使用具名插槽函数 -->
						<slot name='login'></slot>
						<slot name='submit'></slot>
						<slot name='register'></slot>
					</button>
				`
			})
			
			
			// 创建局部组件
			const App = {
				data(){
					return{
						
					}
				},
				template:`
					<div>
						<m-btn>
							<!-- 使用具名插槽 -->
							<template slot='login'>
								<a href='#'>登录</a>
							</template>
						</m-btn>
						<m-btn>
							<template slot='submit'>
								提交
							</template>
						</m-btn>
						<m-btn>
							<template slot='register'>
								注册
							</template>
						</m-btn>
					</div>
				`,
			}
			
			new Vue({
				el:'#app',
				data:{
					
				},
				components:{
					// 挂载子组件
					App
				}
			})
			
		</script>
		
	</body>
</html>

作用域插槽

作用域插槽是针对于使用场景

已经开发了一个待办事项列表的组件,很多模块都在使用
假设有A和B两个模块都使用了同一个组件,但是有个需求就是只想在A模块中对组件添加新功能,B模块保持不变,这时候就需要用到作用域组件了
1.之前的数据格式和引用接口不变,正常显示
2.新功能模块 增加打勾模式

实现方式

1.在子组件中添加 <slot :属性='值'><slot>

2.在主组件中使用 <tempalte v-solt='data'></tempalte>,其中data就是子组件中的插槽中的属性值

作用域插槽,针对于不同模块,使用同一个组件,但又想在其中一个模块上对该组件进行添加新的功能,又不影响到其他使用该组件的模块的,就可以在这个模块中使用插槽函数,仅在该模块中,对同一个组件添加新的内容

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        // 已经开发了一个待办事项列表的组件,很多模块都在
        // A B
        // 1.之前数据格式和引用接口不变,正常显示
        // 2.新功能模块增加对勾
        const todoList = {
            data() {
                return {

                }
            },
            props: {
                todos: Array,
                defaultValue: []
            },
            template: `
        <ul>
            <li v-for='item in todos' :key='item.id'>
            	<!-- 定义插槽 -->
                <slot :itemValue = 'item'>
                   
                </slot>
                 {{item.title}}
               
            </li>
        </ul>
        `
        }

       

        const App = {
            data() {
                return {
                    todoList: [{
                            title: '大哥你好么',
                            isComplate: true,
                            id: 1
                        },
                        {
                            title: '小弟我还行',
                            isComplate: false,
                            id: 2
                        },
                        {
                            title: '你在干什么',
                            isComplate: false,
                            id: 3
                        },
                        {
                            title: '抽烟喝酒烫头',
                            isComplate: true,
                            id: 4
                        }
                    ]
                }
            },
            components: {
                todoList
            },
            template: `
            	  <todoList :todos='todoList'>
            	  	<!-- 使用插槽 -->
                     <template v-slot='data'>
                        <input type="checkbox" v-model='data.itemValue.isComplate' />
                    </template>
            	  </todoList>
        `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                App
            }

        })
    </script>
</body>

</html>

生命周期

生命周期中的常用方法
beforeCreate,created,beforeMount,mounted,
beforeUpdate,updated,activated,deactivated,
beforeDestory,destoryed

其中activated是激活,deactivated是停用,要配合keep-alive(保持活跃状态)使用
keep-alice的应用,当用户切换其他页面,然后又切换回来,保持组件的状态不变

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>生命周期</title>
		<script src="vue.js"></script>
		<style>
			.active{
				color: red;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<App></App>			
		</div>
		<script>
			/*
				生命周期中的常用方法
				beforeCreate,created,beforeMount,mounted,
				beforeUpdate,updated,activated,deactivated,
				beforeDestory,destoryed
				
				其中activated是激活,deactivated是停用,要配合keep-alive(保持活跃状态)使用
				keep-alice的应用,当用户切换其他页面,然后又切换回来,保持组件的状态不变
				
			
			*/
			
			
			Vue.component('Test',{
				data(){
					return {
						msg:"小马哥",
						isRed:false,
					}
				},
				methods:{
					handlerClick(){
						this.msg = 'aaa';
						this.isRed = true;
					}
				},
				template:`
					<div>
						<button @click='handlerClick'>按钮</button>
						<h3 :class='{active:isRed}'>{{msg}}</h3>
					</div>
				`,
				// beforeCreate方法意义不大,在组件创建前调用
				beforeCreate(){
					console.log('组件创建之前',this.$data);
				},
				// created比较重要,在组件创建完成之后调用
				created(){
					// 一般情况下,在这里发送ajax向后端请求数据
					// $data是内置属性,用来获取data(){}中的数据
					console.log('组件创建完成',this.$data);
				},
				// beforeMount在DOM挂载之前调用
				beforeMount(){
					// 即将挂载
					console.log('DOM挂载之前',document.getElementById('app'));
				},
				//mounted 在DOM挂载完成之后调用
				mounted(){
					// 这里也可以发送ajax请求后端数据
					console.log('DOM挂载完成',document.getElementById('app'));
				},
				// beforeUpdate 获取更新之前的DOM调用
				beforeUpdate(){
					console.log('更新之前的DOM',document.getElementById('app').innerHTML);
				},
				// updated 获取更新后的DOM调用
				updated(){
					console.log('更新之后的DOM',document.getElementById('app').innerHTML);
				},
				// beforeDestroy 在销毁当前组件之前调用 一般不用
				beforeDestroy(){
					console.log('销毁之前');
				},
				// destroyed 销毁当前组件完成调用 
				destroyed(){
					console.log('销毁完成');
				},
				// activated 激活组件 要配合<keep-alice></keep-alice>使用 就是将组件包裹在keep-alice标签中
                // 被包裹的组件,即使是用户切换页面,也能保持该组件的状态
				activated(){
					console.log('组件被激活了');
				},
				// deactivated 停用组件 要配合<keep-alice></keep-alice>使用 就是将组件包裹在keep-alice标签中
				// 被包裹的组件,即使是用户切换页面,也能保持该组件的状态
                deactivated(){
					console.log('组件被停用了');
				}
			})
			
			
			const App = {
				data(){
					// 注意:这里必须要返回一个对象,空对象也行
					return {
						isShow:true
					}
				},
				components:{/* 挂载子组件 全局组件不需要挂载*/},
				methods:{
					clickHandler(){
						this.isShow = !this.isShow;
					}
				},
				template:`
					<div>
						<!-- 内置组件keep-alive 保持活跃 -->
						<!-- 用户切换页面,保持组件的状态不变 -->
						<keep-alive>
							<!-- 使用子组件 -->
							<Test v-if='isShow'></Test>
						</keep-alive>
						<button @click='clickHandler'>改变生死</button>
					</div>
				`,
			}
			
			
			
			new Vue({
				el:'#app',
				data:{
					
				},
				methods:{
					
				},
				// 挂载App组件
				components:{
					App
				}
				
			})
		</script>
	</body>
</html>

异步组件加载

在业务规模较大时,组件不会定义到html文件中,会定义到外部的js文件中,然后在html文件中调用,这种调用方式属于异步,也不需要挂载,直接在主组件中的components属性中直接导入即可使用:组件名称:()=>import('组件所在文件的路径')

注意,当引入外部文件的组件的时候,script标签的type要改成module

完整代码

Test.js文件

export default const Test = {
	data(){
		return{
			msg:'小马哥'
		}
	},
	template:`
		<h3>{{msg}}</h3>
	`
}

html文件

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>异步组件</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		<script type="module">
			const App = {
				data(){
					// 注意:这里必须要有返回值,空值也行
					return{
						isShow:false
					}
				},
				methods:{
					asyncLoad(){
						
					}
				},
				components:{
					// 组件名称:()=>import('组件所在文件的路径')
					// 也就是说当触发某条件的时候,才会加载该组件
					Test:()=>import('./Test.js')
				},
				template:`
					<div>
						<button @click='asyncLoad'>异步加载</button>
						<Test v-if='isShow'></Test>
					</div>
				`,
				
			}
			
			new Vue({
				el:'#app',
				data:{
					
				},
				components:{
					App
				}
				
			})
		</script>
		
	</body>
</html>

ref属性的使用

ref属性配合$refs内置方法,可以获取一个标签对象或者真实的DOM节点,但是不建议经常使用,在特殊情况下再使用

1.如果给标签添加ref,获取的就是真实的DOM节点
2.如果给子组件添加ref,获取的是当前子组件对象

$refs 获取标签中的ref属性,是一个对象

通过$refs返回的对象.ref属性的值可以获取对应的标签

使用方式

1.在组件中的template中的标签添加ref属性

2.在组件中的mounted属性或者methods属性或者其他能够操作修改数据的属性中使用 this.$refs.对应标签中的ref属性的值 就能获取对应的标签

使用案例

1.添加ref属性

const App = {
    // 省略上面代码............
    template:`
					<!-- 当用个组件的时候,必须用一个标签包裹起来 -->
					<div>
						<!-- 使用ref属性 -->
						<button ref='btn'>改变生死</button>
					</div>

				`,
   	// 省略下面代码............

}

2.获取ref属性对应的标签

const App = {
    // 当DOM加载完毕后执行
    mounted(){

        //$refs 获取标签中的ref属性,是一个对象
        // 通过$refs返回的对象.ref属性的值可以获取对应的标签
        console.log(this.$refs.btn);

    },

}

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>ref的使用</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<App></App>
		</div>
		<script type="module">
			Vue.component('Test',{
				data(){
					return{
						msg:'小马哥'
					}
				},
				template:`
					<div>
						<h3>{{msg}}</h3>
					</div>
				`,
			})
			
			
			const App = {
				data(){
					// 注意:这里必须要有返回值,空值也行
					return{
						
					}
				},
				// 当DOM加载完毕后执行
				mounted(){
					// 1.如果给标签添加ref,获取的就是真实的DOM节点
					// 2.如果给子组件添加ref,获取的是当前子组件对象
					// ref不要滥用,在特殊情况下使用
					
					//$refs 获取标签中的ref属性,是一个对象
					// 通过$refs返回的对象.ref属性的值可以获取对应的标签
					// console.log(this.$refs);
					// <button ref='btn'>改变生死</button>
					console.log(this.$refs.btn);
					
					// 获取到页面带ref属性的输入框,并且加载页面自动获取焦点
					this.$refs.input.focus();
					
					// 获取带ref的组件 是一个对象
					console.log(this.$refs.test);
					
					
				},
				methods:{
					asyncLoad(){
						
					}
				},
				components:{
					
				},
				template:`
					<!-- 当用个组件的时候,必须用一个标签包裹起来 -->
					<div>
						<Test ref='test'></Test>
						<!-- 使用ref属性 -->
						<input type='text' ref='input' />
						<button ref='btn'>改变生死</button>
					</div>
					
				`,
				
			}
			
			new Vue({
				el:'#app',
				data:{
					
				},
				components:{
					App
				}
				
			})
		</script>
		
	</body>
</html>

nextTick内置函数

为了数据变化之后等待vue完成更新DOM,可以在数据变化后立即调用Vue.nextTick 在当前回调函数中获取最新的DOM

nextTick的使用

使用方式

1.创建Vue对象

2.创建的Vue对象.nextTick(()={}) 获取标签中更新后的数据

案例代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>nextTick的使用</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<h3>{{message}}</h3>
		</div>
		<script>
            // 1.创建Vue对象
			const vm = new Vue({
				el:'#app',
				data:{
					message:'小马哥'
				}
			})
			vm.message = 'new Message';// 修改数据
			// vm是创建出来的Vue对象,$el是内置对象,获取vue对象中的el属性,textContent用来获取标签中文本
			console.log(vm.$el.textContent);//打印小马哥,发现没有获取新数据
			// Vue.nextTick(()={}) 获取标签中更新后的数据
			// 为了数据变化之后等待vue完成更新DOM,可以在数据变化后立即调用Vue.nextTick 在当前回调函数中获取最新的DOM
			// 2.使用nextTick函数
            Vue.nextTick(()=>{
				console.log(vm.$el.textContent); // new Message
			})
		</script>
	</body>
</html>

nextTick的应用

Vue对象.nextTick(()={}) 也可以另外一种写法$nextTick(()=>{})

n e x t T i c k 的应用,从后端获取数据以及改变页面的数据同时进行的话,就要用到 nextTick的应用,从后端获取数据以及改变页面的数据 同时进行的话,就要用到 nextTick的应用,从后端获取数据以及改变页面的数据同时进行的话,就要用到nextTick,否则数据是获取到了,但是渲染不到页面,当然这种情况较少,所以nextTick的应用也少

完整代码案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>nextTick的应用</title>
	<script src="./vue.js"></script>
</head>

<body>
    <div id='app'>
        <App></App>
    </div>
    
    <script>
        /* 
        需求:
            在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的,
            然后我在接口一返回数据就展示了这个浮层组件,展示的同时,
            上报一些数据给后台(这些数据是父组件从接口拿的),
            这个时候,神奇的事情发生了,虽然我拿到数据了,但是浮层展现的时候,
            这些数据还未更新到组件上去,上报失败
        */
        const Pop = {
            data() {
                return {
					isShow:false
                }
            },
            props: {
				name:{
					type:String,
					default:''
				},
            },
            template: `
               <div v-if='isShow'>
					{{name}}
			   </div>
            `,
            methods:{
				show(){
					this.isShow = true;//弹窗组件展示
					console.log(this.name);
					
					
				}
            },
        }
        const App = {
            data() {
                return {
					name:''
                }
            },
            created() {
				// 模拟异步请求 获取后端传入的数据
				setTimeout(()=>{
					// 数据更新
					this.name = '小马哥';
					// $nextTick的应用,从后端获取数据以及改变页面的数据 同时进行的话,要用到$nextTick
					this.$nextTick(()=>{
						this.$refs.pop.show();
					})
					
				},1000);
            },
            components: {
                Pop
            },
            template: `<pop ref='pop' :name='name'></pop>`
        }
        const vm = new Vue({
            el: '#app',
            components: {
                App
            }
        })
    </script>
</body>

</html>

添加响应式属性

由于Vue不能检测对象属性的添加和删除,所以想给已经存在的对象,动态的添加新值,就不能直接用this.对象.属性= 属性值的方式,对于以及创建的对象,vue不允许动态添加属性,如果想要添加响应式属性,必须要用$.set(要添加属性的对象,添加的属性,属性值)

$set(object,key,value) 添加响应式属性

例如

// 原本组件中的data(){}属性值只有
user:{
    name:'张三'
}
//然后想往user对象中添加年龄
this.user.age = 20;//这种方式是不行的 要用到内置方法$set()来添加值
this.$set(user,age,20);//必须使用这种方式来给原有的对象动态的添加值

// 也可以通过es6中的Object.assign()方法给对象添加多个响应式属性
this.user = Object.assign({},this.user,{age:20,phone:1111111111})

代码案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>对象变更检测注意事项</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			<h3>
				{{user.name}},{{user.age}},{{user.phone}}
				<button @click='handlerAdd'>添加属性</button>
			</h3>
		</div>
		
		<script>
			// Vue不能检测对象属性的添加和删除
			new Vue({
				el:'#app',
				data:{
					user:{}
				},
				methods:{
					handlerAdd(){
						// 这里会发现, 想要给user对象中添加age属性,发现添加不进去
						// 对于以及创建的对象,vue不允许动态添加属性,如果想要添加响应式属性,必须要用Vue.set(要添加属性的对象,添加的属性,属性值)
						// $set(object,key,value) 添加响应式属性
						// this.user.age = 20; // 这种添加方式不行,要用下面的方式
						// 1.this.$set(this.user,'age',20);
						
						// 2.通过es6中的Object.assign()方法给对象添加多个响应式属性
						this.user = Object.assign({},this.user,{
							age:20,
							phone:11111111111
						})
						
					}
				},
				created(){
					// 模拟从后端传进的数据
					setTimeout(()=>{
						this.user = {
							name:'张三'
						}
					},1250)
				}
			})
		</script>
		
	</body>
</html>

Mixin混入技术

mixin来分发Vue组件中可复用的功能,写了许多代码后,会发现组件中总有相同的属性,例如data(){},create(){},methods等等,而mixin技术就是将这些重复性的属性的内容抽离出来,以更简洁的方式让组件拥有这些相同的方法,减少代码的冗余性

mixin混入技术的使用

1.自定义一个变量,变量中定义好相同的属性,例如data(){},create(){},methods等等

2.在组件中使用 mixins:[自定义变量] 来引入mixin技术,让组件拥有多余的data(){},create(){},methods等

具体代码

1.定义属性

// 1.自定义minxins技术属性
const myMinxin = {
    data(){
        return{
            msg:'123'
        }
    },
    created(){
        this.sayHello();
    },
    methods:{
        sayHello(){
            console.log('hello mixin');
        }
    }
}

2.在组件中使用mixins技术

new Vue({
    el:'#app',
    data:{
        title:'小马哥'
    },
    // 2.使用minxins技术
    mixins:[myMinxin],

})

完整代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>mixin混入技术的使用</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app">
			{{msg}}
		</div>
		<script>
			// mixin来分发Vue组件中可复用的功能
			
			// 1.自定义minxins技术属性
			const myMinxin = {
				data(){
					return{
						msg:'123'
					}
				},
				created(){
					this.sayHello();
				},
				methods:{
					sayHello(){
						console.log('hello mixin');
					}
				}
			}
			
			new Vue({
				el:'#app',
				data:{
					title:'小马哥'
				},
				// 2.使用minxins技术
				mixins:[myMinxin],
				
			})
		</script>
		
	</body>
</html>

mixin混入技术的应用

假设一个项目中有两个组件:
一个是模态框,一个是提示框,让这两个组件都能切换一个属性的布尔值
这两个看起来不一样,用法不一样,但是逻辑一样,都是切换布尔值

那么这个时候就可以使用mixin混入技术了,将这两个一样的逻辑都封装成mixin,然后这两个组件就可以引入mixin了

完整代码案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>mixin混入技术的应用</title>
		<script src="vue.js"></script>
	</head>
	<body>
		<div id="app"></div>
		
		<script>
			// 假设一个项目中有两个组件
			// 一个是模态框,一个是提示框,让这两个组件都能切换一个属性的布尔值
			// 这两个看起来不一样,用法不一样,但是逻辑一样,都是切换布尔值
			
			/* 全局mixixn,使用的时候,要格外的小心,因为每个组件实例创建时,它都会被调用
				Vue.mixin({})
			*/
			
			
			// 创建 mixins
			const toggleShow = {
				data(){
					return {
						isShow:false
					}
				},
				methods:{
					toggleShow(){
						this.isShow = !this.isShow;
					}
				},
			}
			
			// 模态框组件
			const Modal = {
				template:`
					<div v-if='isShow'><h3>模态框组件</h3></div>
				`,
				// data(){
				// 	return {
				// 		isShow:false
				// 	}
				// },
				// methods:{
				// 	toggleShow(){
				// 		this.isShow = !this.isShow;
				// 	}
				// },
				// 该组件中的data(){} 和 methods{} 与提示框组件的内容是一样的 所以这里就把者两个一样的属性抽离出来,使用mixins技术
				mixins:[toggleShow],//混入局部的mixxins
				
			}
			
			
			// 提示框组件
			const ToolTip = {
				template:`
					<div v-if='isShow'>
						<h2>提示框组件</h2>
					</div>
				`,
				// data(){
				// 	return {
				// 		isShow:false
				// 	}
				// },
				// methods:{
				// 	toggleShow(){
				// 		this.isShow = !this.isShow;
				// 	}
				// },
				// 该组件中的data(){} 和 methods{} 与模态框组件的内容是一样的 所以这里就把者两个一样的属性抽离出来,使用mixins技术
				mixins:[toggleShow],//混入局部的mixxins
				
			}
			
			
			// 主组件
			new Vue({
				el:'#app',
				data:{
					
				},
				components:{
					// 引入模态框和提示框组件
					Modal,
					ToolTip
				},
				template:`
					<div>
						<button @click='handleModel'>模态框</button>
						<button @click='handleToolTip'>提示框</button>
						<Modal ref='modal'></Modal>
						<ToolTip ref='toolTip'></ToolTip>
					</div>
				`,
				methods:{
					handleModel(){
						// 通过$refs.ref值获取对应的模态框组件对象,然后调用该组件中的mixins中的toggleShow,也就是外部创建的toggleShow属性
						this.$refs.modal.toggleShow();
					},
					handleToolTip(){
						// 通过$refs.ref值获取对应的提示框组件对象,然后调用该组件中的mixins中的toggleShow,也就是外部创建的toggleShow属性
						this.$refs.toolTip.toggleShow();
					},
					
				}
			})
		</script>
	</body>
</html>

Vue-cli3

vue-cli3开发单文件组件,官方文档

Vue-cli3简介

上面之前的在html文件或者js文件中创建组件,然后使用组件,在项目扩展起来的时候,这种写法非常不方便,也不易于后期的扩展

之前的代码结构的缺点:

1.全局定义组件的名字时不能重复的,但是当工程量大起来的时候,往往会忘记自己曾经定义的组件名,这么一来就会冲突;

2.es6提供了字符串模板,也非常繁琐,当遇到特殊符号还要用反斜杠转义,麻烦;

3.vue组件不支持css,也就是css不支持模块化;

4.没有构建步骤,目前浏览器支持html+css+es5,支持部分es6,这个时候就要用到第三方工具将浏览器不支持的代码转成es5代码,在Vue中,把.vue文件称为单文件组件,然而,浏览器是不会识别.vue文件的,但是可以使用webpack等构建工具将.vue文件构建成html+css,使得浏览器能够识别。一个.vue文件包含了template模板和js逻辑和css样式。

所以,往后的开发建议用.vue文件形式

Vue-cli3脚手架安装

vue-cli3脚手架,可以快速帮我们搭建项目的开发框架和项目的基本配置。vue-cli3依赖于node.js。

node.js官网下载

1.下载安装node.js

打开node的官网下面页面,点击下载Node.js(LTS),也就是稳定版,下载的是一个.msi的文件,然后点击开始安装,设定好自己想安装的路径,然后一路下一步即可安装

2.测试node.js是否安装成功

打开命令提示框,windows系统下按住键盘 win + r打开运行窗口,输入cmd回车即可打开命令提示框

node -v

输入node-v的命令,如果返回一个版本号,说明安装成功

3.安装淘宝镜像源

使用淘宝镜像源,以后下载node的相关插件或者脚本,速度会快

还是先打开命令提示,输入以下指令

npm install -g cnpm --registry=http://registry.npmmirror.com

执行以上命令之后,以后下载就不需要用npm了,直接用cnpm命令即可

输入cnpm -v 命令,如果返回信息中带版本号,说明淘宝镜像成功安装

4.安装vue-cli3脚手架

还是先打开命令提示,输入以下指令

cnpm install -g @vue/cli

输入 vue -V命令,如果返回版本号,则说明脚手架安装成功

快速原型开发(不推荐)

使⽤ vue servevue build 命令对单个.vue ⽂件进⾏快速原型 开发。

vue serve 的缺点就是它需要安装全局依赖,这使得它在不同机器上 的⼀致性不能得到保证。因此这只适⽤于快速原型开发。

首先在项目目录下打开一个终端,依次进行以下操作

# 初始化
npm init

# 然后会提示你起个名字,这里就起个single把
single

# 然后下面的 version和description和entry和point: (index.js)和test command 和git repository 和keywords和license: (ISC)这些就直接空格就行。其中author(作者)的话可以写自己的昵称,然后回车,此时就会在当前项目文件下生成package.json文件

# 然后Is this OK? (yes)直接回车就行
// package.json文件中的内容
{
  "name": "single",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "jiuli",
  "license": "ISC",
  "description": ""
}

其次安装一个全局的扩展

其中:-g 表示的是全局

继续在当前项目目录的终端中输入以下命令

cnpm install -g @vue/cli-service-global

然后在当前目录下创建一个App.vue文件,vue文件的基本格式如下

<template>
	<!-- 编写对应的html -->
	<div>
		<!-- 引入data对象中的数据 -->
		<h3>{{msg}}</h3>
	</div>
</template>

<script>
	export default{
		// 这里面可以写data(){}对象
		data(){
			return{
				msg:"vue-cli3开发单文件组件"
			}
		},
	}
</script>

<style scoped>
	/* scope 是注入的意思,表示当前样式仅对当前组件有效 */
	h3{
		color: red;
	}
</style>

然后就可以用 vue serve 命令来运行App.vue文件

# 在当前项目目录的终端下执行
vue serve
# 执行该命令,vue-cli3就会在浏览器中运行App.vue的文件,并且终端会返回一个浏览器地址,在浏览访问终端上的地址即可

但这种⽅式仅限于快速原型开发,终归还是使⽤vue cli3来启 动项⽬

Vue-cli3生成项目

在项目目录下打开终端,根据下面步骤完成:

创建项目

vue create 项目名

执行上面的命令会显示以下选项

Vue CLI v5.0.8
? Please pick a preset: (Use arrow keys)
> Default ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
  Manually select features

选择 Manually select features

然后会出现以下选项

>(*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 ( ) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

保持默认选择,直接回车就好了,然后选择vue2.x,会出现以下选项

> ESLint with error prevention only
  ESLint + Airbnb config 
  ESLint + Standard config 
  ESLint + Prettier

默认选中第一个即可,然后出现以下选项

>(*) Lint on save
 ( ) Lint and fix on commit
 ( ) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

直接默认回车即可,然后出现以下选项

> In dedicated config files
  In package.json

直接默认回车即可,然后出现以下选项

 # 是否保存当前预设
 Save this as a preset for future projects? (y/N) 

输入y,然后会提示给预设设置个名字,下一次生成项目,就可以直接用这个名字来生成,也就是说可以免去上面的步骤

? Save this as a preset for future projects? Yes
? Save preset as:  singlePage #这里是自己定义的预设名字

然后回车,就会自动下载,当终端出现 Successfully的时候,就说明安装成功

然后依次输入以下命令

# 进入到自己项目目录 注意mysite是命令提示框中的,不是自定义的
cd mysite
# 运行项目
npm run serve 

然后回车,就会启动项目,然后开始编译,等终端出现以下

100 packages are looking for funding
  run `npm fund` for details
🚀  Invoking generators...
📦  Installing additional dependencies...
  - Local:   http://localhost:8080/
  - Network: http://192.168.0.102:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

的字样的时候,说明运行成功,启动了vue项目,然后点击Local: http://localhost:8080/在浏览器上显示打开vue项目,当页面中出现了Welcome to Your Vue.js App 字样说明成功打开。

此时就查看项目目录,就会发生多了个名为mysite的文件夹,里面包含了一些配置文件。

其中src文件夹需要关注,在开发阶段使用,asssets存放着当前项目的依赖,components存放着组件,main.js是整个项目的入口文件,App.vue是主组件。、

删除预设

删除本地磁盘中的.vuerc文件 即可,一般再c盘中

axios的使用

安装axios

cnpm i axios -S

在main.js文件中,导入axios

import axios from 'axios'

挂载在Vue的原型对象上,这样其他地方就可以用$http来发起请求了

Vue.prototype.$http = axios;
// 如果说不挂载的话,下面使用axios就要用Axios,而不是$http

使用案例

首先在vue.config.js文件中,定义接口,没有这个文件,就自己创建一个,注意名字必须是vue.config.js

// 在vue中要抛出数据得用module.exports,而不是exports
module.exports = {
	devServer:{
		// 每次修改完这个文件,一定要重新启动才能生效
		//mock数据模拟
		before(app,server){
			// app 是node.js提供的
			// 设置接口
			app.get('/api/cartList',(req,res)=>{
				res.json({
					result:[
						{id:1,title:"Vue实战开发",price:188,active:true,count:1},
						{id:2,title:'VueReact实战开发',price:188,active:true,count:1},
					]
				})
			})
		}
	}
}

在任意组件中使用axios,发起请求,数据,案例如下

export default {
  // 省略上面代码
  created(){
    // 使用axios。这里之所以使用$http,是因为在main.js文件中,定义了Vue.prototype.$http = axios;
	this.$http.get('/api/cartlist')
	  .then(res=>{
		  console.log(res);
	  }).catch(err=>{
		  console.log(err);
	  })
  },
  // 省略下面代码
}

上述代码改成 异步代码同步化

 // 省略上面代码   
async created(){
	// this.$http.get('/api/cartlist')
	//   .then(res=>{
	// 	  // 将获取到的接口请求数据保存到data()对象中的cartList变量中
	// 	  this.cartList = res.data.result;
	//   }).catch(err=>{
	// 	  console.log(err);
	//   })
	try{
		const res = await this.$http.get('/api/cartList');
		console.log(res);
	}catch(error){
		console.log(error);
	}
  },
 // 省略下面代码

购物车案例

App.vue文件

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
	<ul>
		<!-- cartList 来自于data()对象中的属性 -->
		<li v-for="(item,index) in cartList" :key="item.id">
			<h1>{{title}}</h1>
			<!-- 展示购物车的列表 -->
			<h2>{{item.title}}</h2>
			<p>¥{{item.price}}</p>
			<button @click="addCart(index)">添加购物车</button>
		</li>
	</ul>
	<!-- 使用子组件CartComponent 往该组件传值-->
	<!-- :cart=data()中的cartList属性,这样在该子组件,在props中使用cart就可以引入,:title也是data()中的title属性,也是在子组件中以props['title']的方式引入 -->
	<my-cart :title='title'></my-cart>
  </div>
</template>

<script>
// 引入CartComponent组件
import MyCart from './components/Cart';

// 使用Mock工具 模拟数据

export default {
  name: 'App',
  data(){
	return {
		// cartList:[s
		// 	{id:1,title:'Vue实战开发',price:188,active:true,count:1},
		// 	{id:2,title:'React实战开发',price:288,active:true,count:1},
		// ],
		cartList:[],
		title:'购物车',
	}
  },
  async created(){
	// this.$http.get('/api/cartlist')
	//   .then(res=>{
	// 	  // 将获取到的接口请求数据保存到data()对象中的cartList变量中
	// 	  this.cartList = res.data.result;
	//   }).catch(err=>{
	// 	  console.log(err);
	//   })
	try{
		const res = await this.$http.get('/api/cartList');
		this.cartList = res.data.result;
	}catch(error){
		console.log(error);
	}
  },
  methods: {
    addCart(i) {
      const good = this.cartList[i];
      this.$bus.$emit('addCart',good);
    }
  },
  components: {
	//注册子组件 Cart.vue中的CartComponent组件
	// MyCart 是上面的 import MyCart from './components/Cart'; 中的自定义别名
	MyCart
  }
}
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Cart.vue文件

<template>
	<div>
		<!-- title来源于props中的title属性 也就是父组件App传入的值 -->
		<h2>{{title}}</h2>
		<table border="1">
			<tr>
				<th>#</th>
				<th>课程</th>
				<th>单价</th>
				<th>数量</th>
				<th>总价</th>
			</tr>
			<!-- cart来源于props中cart的属性 也就是父组件App传入的值 -->
			<tr v-for="(c,index) in cart" :key='c.id'>
				<td>
					<input type="checkbox" v-model="c.active" />
				</td>
				<td>{{c.title}}</td>
				<td>{{c.price}}</td>
				<td>
					<button @click="substract(index)">-</button>
					{{c.count}}
					<button @click="add(index)">+</button>
				</td>
				<td>¥{{c.price*c.count}}</td>
			</tr>
			<tr>
				<td></td>
				<td colspan="2">{{activeCount}}/{{count}}</td>
				<td colspan="2">¥{{total}}</td>
			</tr>
		</table>
	</div>
</template>

<script>
	export default{
		//在 Vue.js 中,使用 ESLint 进行代码检查时,vue/multi-word-component-names 规则要求组件的名称应该是多单词的,以防止与现有的以及未来的 HTML 元素和属性冲突。这个规则有助于保持代码的清晰性和可维护性。
		// 所以name本来想要只写cart,但是会报错,必须要多加几个单词
		name:'CartComponent',
		// 接收父组件(App组件)传来的数据
		// <my-cart :cart='cartList' :title='title'></my-cart>
		// props:['title','cart'],// 在没有使用本地缓存的时候,要接收父类传来的参数
		props:['title'],
		data(){
			return{
				// 获取本地浏览器的缓存数据
				cart:JSON.parse(localStorage.getItem('cart')) || [],
			}
		},
		watch:{
			cart:{
				handler(n,o){
					// n 表示新值 o表示旧值
					this.setLocalData(n);
					console.log(o);
				},
				deep:true
			}
		},
		created(){
			this.$bus.$on('addCart',good => {
                // 查找要添加的数据的id 与 已添加的数据的id是否重复
				const ret = this.cart.find(v => v.id === good.id);
				if(!ret){
					// 购物车没有数据
					this.cart.push(good);
				}else{
					ret.count += 1;
				}
			})
		},
		methods:{
			// 定义方法,数据持久化
			setLocalData(n){
				// 计算总课数量
				
				// 将新添加到购物车的数据保存到本地浏览器缓存
				localStorage.setItem('cart',JSON.stringify(n));
			},
			remove(i){
				if(window.confirm("确定是否要删除?")){
					this.cart.splice(i,1);
				}
			},
			// 减数量
			substract(i){
				let count = this.cart[i].count;
				count > 1 ? this.cart[i].count -= 1 : this.remove(i);
			},
			// 加数量
			add(i){
				this.cart[i].count++;
			},
		},
		// 计算属性
		computed:{
			count(){
				return this.cart.length;
			},
			activeCount(){
				// 过滤出active为true的数据
				return this.cart.filter(v=>v.active).length;
			},
			total(){
				// let sum = 0;
				// this.cart.forEach(c=>{
				// 	if(c.active){
				// 		sum += c.price * c.count;
				// 	}
				// });
				// return sum;
				
				// 另外一种写法
				return this.cart.reduce((sum,c)=>{
					if(c.active){
						sum += c.price * c.count;
					}
					return sum;
				},0) // 这里0表示起始值
			}
		}
	}
</script>

<style>
</style>

main.js文件

import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'

Vue.config.productionTip = false

Vue.prototype.$http = axios;
new Vue({
  render: h => h(App),
}).$mount('#app')

vue.config.js文件

// const { defineConfig } = require('@vue/cli-service')
// module.exports = defineConfig({
//   transpileDependencies: true
// })

// 在vue中要抛出数据得用module.exports,而不是exports
module.exports = {
	devServer:{
		// 每次修改完这个文件,一定要重新启动才能生效
		//mock数据模拟
		before(app,server){
			// app 是node.js提供的
			// 设置接口
			app.get('/api/cartList',(req,res)=>{
				res.json({
					result:[
						{id:1,title:"Vue实战开发",price:188,active:true,count:1},
						{id:2,title:'VueReact实战开发',price:188,active:true,count:1},
					]
				})
			})
		}
	}
}

如果上述代码报错,则试试下面的代码

// 引入 express
const express = require('express');

module.exports = {
  lintOnSave: false, // 关闭eslint
  devServer: {
    setupMiddlewares(middlewares, devServer) {
      const app = express();

      // 自定义中间件
      app.get('/api/cartList', (req, res) => {
        res.json({
          result: [
            { id: 1, title: "Vue实战开发", price: 188, active: true, count: 1 },
            { id: 2, title: "React实战开发", price: 288, active: true, count: 1 },
          ]
        });
      });

      // 将自定义中间件添加到中间件数组
      middlewares.unshift(app);

      return middlewares;
    }
  }
};

组件深入

组件的分类

通用组件:基础组件,比如表单,布局,弹窗等

业务组件:与需求挂钩,会被复用,比如抽奖,摇一摇等

页面组件:每个页面都是一个组件,不会复用

第三方组件Element的安装

vue中流行的组件element,就是典型的通用组件,官方地址

可以按照官方给定的方式使用,这里想要介绍的是用vue-cli3的方式使用。

注意:在安装element组件前,一定一定一定一定一定一定一定一定一定一定一定一定一定要提前备份好App.vue文件中的内容,因为安装element组件后,该组件会把App.vue中的内容更改**

使用vue-cli3安装element组件

vue add element

执行上面命令之后会显示以下选项

? How do you want to import Element? (Use arrow keys)
> Fully import #全部导入
  Import on demand #按需导入

这里选择按需导入。然后选择zh-CN选项,等待安装完成。

安装完成之后,就会生成一个App.vue文件,把项目中原先的App.vue文件的替换掉,查看该文件,就会发现该文件被修改了,还会发生项目中多了plugins文件夹和该文件里面的element.js文件

在main.js文件中导入了 import ‘./plugins/element.js’,查看element.js文件中会发现,里面使用了局部样式导入

import Vue from 'vue'
import { Button } from 'element-ui'

Vue.use(Button)

这个element插件帮我们修改上述的一些配置,我们就可以在任意地方直接使用element的组件了,例如,想要引入一个button组件,就可以使用<el-button></el-button> 该标签有个属性type,修改该属性可以改变样式,比如type=success

element组件的使用

1.首先先官网上,找到自己想要的组件的代码,复制下来,到自己的项目中

比如,我这里复制了Table,TableColumn,InputNumber的组件,并且自己进行了一些修改,包括数据,样式

<el-table ref="multipleTable" :data="cart" border tooltip-effect="dark" style="width: 100%" @selection-change="handleSelectionChange">
				<el-table-column type="selection" width="55"></el-table-column>
				<el-table-column prop="title" label="课程" width="120"></el-table-column>
				<el-table-column prop="price" label="价格" width="120"></el-table-column>
				<el-table-column label="数量" width="120">
					<template slot-scope="scope">
						<el-input-number v-model="scope.row.count" @change="handleChange" :min="1" :max="200"></el-input-number>
					</template>
				</el-table-column>
				<el-table-column label="总价" width="120">
					<template slot-scope="scope">{{ scope.row.count * scope.row.price }}</template>
				</el-table-column>
			</el-table>
			
			<div style="margin-top: 20px">
				<el-button @click="toggleSelection([tableData[1], tableData[2]])">切换第二、第三行的选中状态</el-button>
				<el-button @click="toggleSelection()">取消选择</el-button>
			</div>

2.根据官方的说明,在methods中定义方法,来配合组件使用

// 省略下面代码
methods:{
			toggleSelection(rows) {
				if (rows) {
					rows.forEach(row => {
					this.$refs.multipleTable.toggleRowSelection(row);
					});
				} else {
					this.$refs.multipleTable.clearSelection();
				}
			},
			handleSelectionChange(val) {
				this.multipleSelection = val;
			}
		},
        // 省略下面代码
		

3.在element.js中导入Table,TableColumn,InputNumber组件,并且use这些组件

import Vue from 'vue'
import { Button,Table,TableColumn,InputNumber} from 'element-ui'

Vue.use(Button)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(InputNumber)

Vue全家桶

Vue全家桶包含Vue-router和Vuex

vue-router插件

vue-router简介

vue-router 用于路由的管理,官方文档

多页应用MPA,每一个页面都是.html文件

单页应用SPA,相当于一个a标签,切换不同的试图,适用用户群体少

vue-element-admin用于后台管理系统的组件

vue-router的安装

原有项目安装vue-router
npm i vue-router -S

通过添加插件的方式安装(推荐),记得一定要提前备份代码

vue add router

以上两种方式,是基于原有的项目进行安装

创建新项目的方式安装

创建一个新项目,名为test_router

vue create test_router

回车之后,会显示以下内容

? Please pick a preset: (Use arrow keys)
> Default ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
  Manually select features

选择Manually select features,按下回车,会显示以下内容

(*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 (*) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

选择Router选择,然后按下空格,直至出现*,然后回车,会显示以下内容

? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
> 3.x
  2.x

选择2.x,然后回车,会显示以下内容

? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)

选择y,然后回车,会显示以下内容

? Pick a linter / formatter config: (Use arrow keys)
> ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier

直接默认回车,会显示以下内容

>(*) Lint on save
 ( ) Lint and fix on commit

直接默认回车,会显示以下内容

> In dedicated config files
  In package.json

直接默认回车,会显示以下内容

 Save this as a preset for future projects? (y/N)

选择y,然后回车,会显示以下内容

? Save preset as:test_router

输入项目名称,其中test_router是项目名称,然后按下回车,等待安装完成。

当出现以下,则说明安装成功

112 packages are looking for funding
  run `npm fund` for details
⚓  Running completion hooks...

📄  Generating README.md...

🎉  Successfully created project test_router.
👉  Get started with the following commands:

 $ cd test_router
 $ npm run serve

然后查看项目文件下,的src目录下,会有router和views的文件,当然还有其他的文件和变化,主要看router和views的文件有没有存在。

然后根据提示,进入到项目的目录下,打开cmd命令窗口,输入以下命令,启动项目

npm run serve

打开浏览器,输入Local: http://localhost:8080/ 访问网页,看就vue的logo图片上有Home和About字样。

自此。安装完毕。

vue-router的使用

在src目录下的router目录下的index.js文件中

1.在index.js文件中导入vue-router

import VueRouter from "vue-router"; // 引入vue-router

2.在index.js文件中使用vue-router

// 使用vue-route插件
Vue.use(VueRouter)

3.在index.js文件中,创建vueRouter对象,并且抛出,然后再routes中,配置好path(路径),以及component(组件)

// 创建VueRouter对象
export default new VueRouter({
	// 路由配置 routes:[{}]
	routes:[
		{
			path:"/", // 路径
			component:Home // 加载Home组件
		},
        {	
			path:'/about',
			component:About
		},
	]
});

4.在index.js文件中,导入vueRouter对象中所需要的组件

import Home from '@/views/Home';//引入Home组件,其中@符号是相互路径
import Home from '@/views/About';//引入About组件,其中@符号是相互路径

5.在main.js文件中,引入router文件

// 引入router
import router from './router'

6.在main.js文件中,将router挂载到vue实例中

new Vue({
	// 将router挂载到vue实例中
  router,
  render: h => h(App)
}).$mount('#app')

7.在app.vue中使用router

app.vue中提供组件 router-link 和route-view,router-link默认会被渲染成a标签,to属性会被渲染中herf属性,router-view 相当于路径组件的出口,其中 router-link组件中的to=“/” 和 to="/about"均来自于index.js文件中的routes定义

<template>
  <div id="app">
	<!-- app.vue中提供组件 router-link 和route-view -->
	<!-- router-link默认会被渲染成a标签,to属性会被渲染中herf属性 -->
	<!-- 其中 to="/" 和 to="/about"均来自于index.js文件中的routes定义 -->
	<router-link to="/">首页</router-link>
	<router-link to="/about">关于</router-link>
	<!-- router-view 相当于路径组件的出口 -->
	<router-view></router-view>
  </div>
</template>

路由的流程:访问浏览器,浏览器会渲染App.vue文件,其中router-link相当于a标签,to相当于href标签,当页面点击对应的标签,浏览器就会根据to中的路径去寻找main.js中挂载的router文件夹,然后再去访问router文件夹中的index.js文件中VueRouter对象中的routes属性中对应路径的组件,而组件来自于views中的对应的文件。

完整代码

index.js文件中的内容

import Vue from 'vue';
import VueRouter from "vue-router"; // 引入vue-router

// 使用vue-route插件
Vue.use(VueRouter)

import Home from '@/views/HomeView';//引入Home组件,其中@符号是相互路径
import About from '@/views/AboutView';//引入About组件,其中@符号是相互路径




// 创建VueRouter对象
export default new VueRouter({
	// 路由配置 routes:[{}]
	routes:[
		{
			path:"/", // 路径
			component:Home // 加载Home组件
		},
		{
			path:'/about',
			component:About
		}
	]
});

main.js文件中的内容

import Vue from 'vue'
import App from './App.vue'
// 1.引入router
import router from './router'

Vue.config.productionTip = false

new Vue({
	// 2.将router挂载到vue实例中
  router,
  render: h => h(App)
}).$mount('#app')

app.vue中的内容

<template>
  <div id="app">
	<!-- app.vue中提供组件 router-link 和route-view -->
	<!-- router-link默认会被渲染成a标签,to属性会被渲染中herf属性 -->
	<!-- 其中 to="/" 和 to="/about"均来自于index.js文件中的routes定义 -->
	<router-link to="/">首页</router-link>
	<router-link to="/about">关于</router-link>
	<!-- router-view 相当于路径组件的出口 -->
	<router-view></router-view>
  </div>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;
}

nav a {
  font-weight: bold;
  color: #2c3e50;
}

nav a.router-link-exact-active {
  color: #42b983;
}
</style>

命名路由

命名路由就是给路由起个别名

使用方式

1.在index.js文件中的VueRouter对象中的routes属性对应的路由添加name属性,这样就是给路由起个名字

export default new VueRouter({
	// 路由配置 routes:[{}]
	routes:[
		{
			path:"/", // 路径
			name:'home',//命名路由
			component:Home // 加载Home组件
		},
		{
			path:'/about',
			name:'about',//命名路由
			component:About
		}
	]
});

2.在App.vue文件中使用命名的路由

​ 在router-link组件中,给to属性添加:,然后值改为{name:‘index.js文件中的路由配置中对应路由的name属性值’}

<!-- 使用命名路由 -->
<!-- 给to属性添加:,然后值改为{name:'index.js文件中的路由配置中对应路由的name属性值'} -->
<!-- index.js文件中 {path:"/", name:'home',/*name属性,命名路由*/component:Home} -->
<router-link :to="{name:'home'}">首页</router-link>
<router-link :to="{name:'about'}">关于</router-link>

动态路由匹配

像 http://localhost:8080/user/1 http://localhost:8080/user/2 这样的路由,同属于user下的路由,只是后面的参数不同,这样就是动态的路由。也就是说,同一个页面,展示对应的不同的信息。

动态路由匹配的使用方式

1.在index.js文件中,在path属性中添加:属性,这里的属性是id

// 创建VueRouter对象
export default new VueRouter({
	// 路由配置 routes:[{}]
	routes:[
		{
			// :id 就可以让id动态的匹配,(:属性,让属性的值动态匹配)
			path:'/user/:id',
			name:'user',
			component:User
		}
	]
});

2.在App.vue文件中,给组件路由添加动态路由 params:{id:1}

<!-- params:{} 就是用接收动态的参数,这里是动态的接收id,来匹配index.js中的path属性中带:的属性 path:'/user/:id', -->
<router-link :to="{name:'user',params:{id:1}}">User1</router-link>|
<router-link :to="{name:'user',params:{id:2}}">User2</router-link>|

3.在路由名为user的组件中,获取动态路由(params),获取对应组件的动态路由 也就是获取App.vue中的组件的params的id值

例如,获取id

// 获取查询参数方式一
export default{
    name: 'UserComponent',
    // 当路由的参数变化时,从/user1切换到user/2 原来的组件实例会被复用
    // 因为两个路由渲染了同个组件 复用高效
    created(){
        // 当组件加载完毕后,监听路由的变化

        // 获取App.vue中的对应的组件中路由的变化
        console.log(this.$route.params.id); // 获取动态路由
    },
}
// 获取查询参数方式二
export default{
		name: 'UserComponent',
		// 监听属性 监听路由的变化
		watch:{
			// $route:(to,from)=>{
			// 	// to:到哪里去;from:从哪里来
			// 	console.log(to.params.id);
			// 	// 发起ajax,请求后端接口数据
			// }
			
            // vue中不建议这里使用箭头函数,所以用来下面的方式
			$route:function(to,from){
				// to:到哪里去;from:从哪里来
				console.log(to.params.id); // 获取动态路由
				// 发起ajax,请求后端接口数据
			}	
		},
}
// 获取查询参数方式三
export default{
		name: 'UserComponent',
		// vue中的导航守卫 也可以监听路由的变化
		beforeRouteUpdate(to, from, next) {
			console.log(to.params.id);// 获取动态路由
			// next 相当于中间件
			// 一定要调用next方法,不然会堵塞
			next();
		}
}

现在,动态路由就配置好了,当用于点击User1的时候,就会请求到/user/id=1的路由,然后就可以展示user组件中id为1的界面,当用户点击User2的时候,就会请求到/user/id=2的路由,然后就可以展示user组件中id为2的界面。

404路由

当用户输入的路径,匹配不到我们定义的路由的时候,我们就会返回404页面给用户

1.首先,在views文件夹中,创建一个404.vue页面,代码如下

<template>
	<div>
		<h3>404页面</h3>
	</div>
</template>

<script>
	export default{
		name:'404Component'
	}
</script>

<style>
</style>

2.在index.js文件中添加路由,注意,一定要在最后添加404的页面路由

// 创建VueRouter对象
export default new VueRouter({
	// 路由配置 routes:[{}]
	routes:[
        // .........省略上面代码........
        
		// 404路由一定要放在最后
		{
			// 当匹配不到路由的时候
			path:'*',
			// 使用异步加载方式,导入组件
			component:()=>import('@/views/404')
		}
        // ******下面不要写路由了*****
	]
});

此时当匹配不到用户在浏览器地址栏中输入的路由,就会显示404.vue的页面

匹配任意路由

给定一个路径:http://xxx.com/user-admin,先更要匹配user-后面的内容,也就是admin的时候,当然也可以是其他字符,也可以用*的方式来定义路由。

1.首先在views目录下创建User-admin.vue文件,代码如下

<template>
	<div>
		<h3>User-admin页面</h3>
		<!-- $route.params.pathMatch 能匹配到路由user-*中的*的内容 -->
		<h4>{{$route.params.pathMatch}}</h4>
	</div>
</template>

<script>
	export default{
		name:"User-admin-Component"
	}
</script>

<style>
</style>

2.在main.js文件中定义路由

export default new VueRouter({
    // ........省略上面代码........
		{
			// 匹配 /user-任意字符的路由
			path:'/user-*',
			// 使用异步加载方式,导入组件
			component:()=>import('@/views/User-admin')
		},
     // ........省略下面代码........
	]
});

此时,在浏览器中输入http://xxx.com/user-admin ,或者输入 http://xxx.com/user-aaa 再或者 输入 http://xxx.com/user-bbb等其他任意以/user-xxx为结尾的路由,都会展示User-admin.vue的页面。

路由匹配的优先级

同一个路径可以匹配多个路由,匹配的优先级按照路由的定义顺序,谁先定义的,谁的优先级最高

// 创建VueRouter对象
export default new VueRouter({
	// 路由配置 routes:[{}]
	routes:[
        // ............省略上面代码.............
		{	
			path:'/about',
			name:'about',//命名路由
			component:User
		},
		{
			// 同一个路径可以匹配多个路由,匹配的优先级按照路由的定义顺序
			// 谁先定义的,谁的优先级最高
			path:'/about',
			name:'about',//命名路由
			component:About
		},
		// ............省略下面代码.............
	]
});

此时,按照上面的路由配置,如果在页面中点击了带/about路由的组件,那么返回的页面则是User.vue文件的页面,而不是About.vue的页面。

路由查询参数

像http://www.xxx.com/user?id=1 中的id=1,也就是问号后面的,就是查询参数。

使用查询参数

首先,先创建一个Page.vue,用来做路由查询参数的例子。

Page.vue中template内容如下

<template>
	<div>
		<h3>Page页面</h3>
	</div>
</template>

在index.js文件中,路由匹配就按之前的一样,正常写就行

export default new VueRouter({
    routes:[
        {
            path:'/page',
            name:'page',
            component:()=>import('@/views/Page')
        },
    ]
});

在App.vue文件中,在router-link组件的对应Page组件中,给:to属性中添加query:{键:值}

<router-link :to="{name:'page',query:{id:1,title:'foo'}}">Page</router-link>|

在Page.vue中,就可以通过$route.query方式获取到查询参数了

export default{
    name:'pageComponent',
    created(){
        // console.log(this.$route);
        const {id,title} = this.$route.query;//这是es6中的解构赋值的写法
        console.log(id,title); // 获取到查询参数

        // 与后端交互
    }
}

路由的重定向

在main.js文件中,使用redirect属性来重定向,也就是页面跳转

export default new VueRouter({
	mode:'history',//纯净模式,取消路径中的#
	// 路由配置 routes:[{}]
	routes:[
		{
			path:'/',
			// redirect:'/home',//重定向路由,到path为/home路由下
			redirect:{name:'home'},//重定向路由,到name为home路由下
		},
		{
			path:"/home", // 路径
			name:'home',//命名路由
			component:Home // 加载Home组件
		},
	]
});

路由的别名

给路由起个别名,使用alias属性,但没有实际作用

{
			path:'/page',
			name:'page',
			alias:'/aaa',// 给路由别名,但没有实际作用
			component:()=>import('@/views/Page')
		},

组件的传值

之前想要将路径参数传递给组件,需要用到$route对象,现在可以使用props属性来直接,将url的路径参数和查询参数直接传递给组件

1.在index.js文件中,给对应组件的路由添加props属性,这里给的是User组件

{
			// :id 就可以让id动态的匹配,(:属性,让属性的值动态匹配)
			path:'/user/:id',
			name:'user',
			component:User,
			// props:true,//将:id传递给组件
                
            
			// 组件的传值 将url中的参数传递给User组件
			props:(route)=>({
				id:route.params.id, // 路径参数
				title:route.query.title, //查询参数
			})
		},

2.在User.vue文件中,也是使用props属性接收由路径传来的参数或者查询参数

export default{
		name: 'UserComponent',
    	// .........省略以上代码.........
		// 接收路由传来的url参数
		props:['id','title'],
    	// .........省略以下代码.........

	}

此时,在浏览器上访问 http://xxx.com/user/1/?title=foo ,就会将1作为id传递给组件,将foo值作为title传递给组件

编程式导航

<router-link :to="{name:'page',query:{id:1,title:'foo'}}">Page</router-link>|

像以上这种方式,称为声明式导航

编程式导航,就是对在对应组件的vue文件中,对某个元素绑定方法,然后使用this.$router.push的方式进行页面跳转,原理就是将路径添加到router对象中。

//跳转方式1
this.$router.push('path的参数'); // path来自于main.js文件中的路径参数
//跳转方式2
this.$router.push({
    name:'user', // name来自于main.js文件中的路径参数
    params:{id:2},// url的路径参数
})

具体代码

<template>
	<div>
		<!-- 获取App.vue中对应组件的router的查询参数 -->
		<!-- {{$route.params.属性}} -->
		用户页面{{$route.params.id}}
		<br>
		{{id}} ------ {{title}}
		<button @click="goHome">跳转到首页</button>
		<button @click="goBack">上一页</button>
	</div>
</template>

<script>
	export default{
		name: 'UserComponent',
		methods:{
			goBack(){
				// -1表示返回上一页;0表示刷新当前页;1表示前进到下一页
				this.$router.go(-1);
			},
			goHome(){
				// this.$router.push('/'); // 跳转到首页
				
				// 跳转到id为2的user页面
				this.$router.push({
					name:'user', // name对应的是main.js文件中的路径中的name属性
					params:{id:2},//查询参数
				})
			}
		},

	}
</script>

<style>
</style>

嵌套路由

像 http://www.xxx.com/user/1/profile 和 http://www.xxx.com/user/1/posts 这样的路由,一个页面包含多个子页面的现象,就是嵌套路由。这里是user/1/ 下的页面中由两个子路由,对应两个不同的页面。

嵌套路由使用案例

案例实现的效果:将两个子组件页面分别添加到User组件中的页面

其中App.vue作为主组件,User.vue作为父组件,Profile.vue和Posts.vue作为User.vue的子组件

1.首先,创建两个子路由所需要的组件

Profile.vue文件中的内容

<template>
	<div>
		<h3>Profile</h3>
	</div>
</template>

<script>
	export default{
		name:'profileComponent'
	}
</script>

<style>
</style>

Posts.vue文件中的内容

<template>
	<div>
		<h2>Posts</h2>
	</div>
</template>

<script>
	export default{
		name:'postsComponent'
	}
</script>

<style>
</style>

2.在对应路由中定义子路由

children:[{path:‘’,component:}]

export default new VueRouter({
	mode:'history',//纯净模式,取消路径中的#
	// 路由配置 routes:[{}]
	routes:[
		{
			// :id 就可以让id动态的匹配,(:属性,让属性的值动态匹配)
			path:'/user/:id',
			name:'user',
			component:User,
			// 嵌套路由
			children:[
				{
					path:'profile',//注意:这里一定不能加/
					component:()=>import('@/views/Profile')
				},
				{
					path:'posts',
					component:()=>import('@/views/Posts')
				}
			]
		},
	
	]
});

3.在User.vue(父组件)文件中的template中添加路由出口,作为子路由的出口

<template>
	<div>
		<!-- 嵌套路由的出口 -->
		<router-view></router-view>
    </div>
</template>

4.在App.vue中展示两个子路由的选项卡

<!-- 嵌套路由 -->
<router-link to="/user/1/profile">user/1/profile</router-link>|
<router-link to="/user/1/posts">/user/1/posts</router-link>

嵌套路由就是这样使用。

当加载到id为1的User组件的页面的时候,该组件页面中由两个子组件的的选项卡,分别点击对应的选项卡,就会在id为1的User组件的页面下展示对应的子组件(子路由)的页面。

命名视图

就是给视图起个别名,让子视图在主视图中展示出来

使用方式

需求概述:由两个组件文件,分别是Main.vue和SideBar.vue,现在想要让这个两个组件在首页(HomeView.vue)中展示出来。

也就是说,要将这两个组件的路由定义到对应的home路由中,作为home的子路由

具体代码实现

1.首先,准备好两个文件。

Main.vue文件中的内容

<template>
	<div>
		<h2>Main视图</h2>
	</div>
</template>

<script>
	export default{
		name:'mainComponent'
	}
</script>

<style>
</style>

SideBar.vue文件中的内容

<template>
	<div>
		<h2>SideBar视图</h2>
	</div>
</template>

<script>
	export default{
		name:'silde-bar-Component'
	}
</script>

<style>
</style>

2.在index.js文件中增加命名视图的属性,这里要加在home路由下

{
    path:"/home", // 路径
        name:'home',//命名路由
            // component:Home ,// 加载Home组件
            // 如果说使用了components,就不要使用component了

            // 命名视图
            components:{
                default:Home,//默认的名字
                main:()=>import('@/views/Main'),
                sideBar:()=>import('@/views/SideBar')
                // 此时,当用户访问/home路由的时候,就会访问到HomeView组件,而main组件和sideBar组件则会在HomeView组件对应的页面中显示
            }

},

3.在App.vue文件中使用命名路由

<template>
  <div id="app">
    <!-- 省略以上代码 -->
      
  	<!-- 命名视图的使用 -->
	<!-- name的值来自于index.js文件中的components属性中 -->
	<router-view name="main" class="main"></router-view>
	<router-view name="sideBar" class="sideBar"></router-view>
      
      <!-- 省略以下代码 -->
  </div>
</template>

这样在访问/home的路由的时候,main组件和sideBar组件就会展示在该路由对应的页面中。

全局守卫

全局守卫的作用是监听整个项目的所有组件

// main.js文件

// 引入router
import router from './router'

router.beforeEach((to,from,next)=>{
    // 需要全局守卫干的事情
    
    next();// 一定要调用next()方法,否则程序就会卡在这里
}

使用全局守卫来做登录操作

需求:现在有两个页面,一个是Notes.vue页面,一个是Login.vue页面,当用户想要访问Notes.vue页面,要先登录才能访问,所以只有登录过的用户才能访问到该页面,没有登录的用户访问这个页面,会自动跳转到登录页面,等登录成功后,再返回到Notes.vue。那么这种场景下,就可以用到全局守卫

具体代码

Notes.vue文件

<template>
	<div>
		<h2>我的笔记</h2>
	</div>
</template>

<script>
	export default{
		name:'notesComponent',
	}
</script>

<style>
</style>

Login.vue文件

<template>
	<div>
		<h2>登录页面</h2>
		<input type="text" v-model="user">
		<input type="password" v-model="pwd">
		<button @click="handleLogin">登录</button>
	</div>
</template>

<script>
	export default{
		name:'loginComponent',
		data(){
			return{
				user:'',
				pwd:''
			}
		},
		methods:{
			handleLogin(){
				// 获取用户名和密码,这步骤先省略
				
				// 模拟后端传来用户登录后的数据
				setTimeout(()=>{
					let data = {
						user:this.user
					}
					
					
					// 将获取到的用户信息保存到本地
					localStorage.setItem('user',JSON.stringify(data));
					
					// 跳转到我的笔记页面
					this.$router.push({
						name:'notes',
					})
				},1000)
			}
		}
	}
</script>

<style>
</style>

index.js文件

export default new VueRouter({
    routes:[
        {
			path:'/notes',
			name:'notes',
			component:()=>import('@/views/Notes')
		},
		{
			path:'/login',
			name:'login',
			component:()=>import('@/views/Login')
		},
    ]
});

App.vue中的文件

<template>
  <div id="app">
      <router-link :to="{name:'notes'}">我的笔记</router-link>|
  </div>
</template>

main.js文件

该文件中就使用了全局守卫 来监控整个项目

import Vue from 'vue'
import App from './App.vue'
// 引入router
import router from './router'

Vue.config.productionTip = false

// 全局守卫
// 作用就是监听整个项目的所有组件
router.beforeEach((to,from,next)=>{
	// 判断当用户访问了/notes的时候,是否登录
	// to.path能够获取当前访问的路径
	console.log(to.path);
	if(to.path === '/notes'){
		// 从本地缓存中,获取用户的登录信息
		const user = JSON.parse(localStorage.getItem('user'));
		if(user){
			// 用户以登录
			next();//则放行
		}else{
			// 用户没有登录
			next('/login');// 则跳转登录页面
		}
	}
	
	next();// 一定要调用next()方法,否则程序就会卡在这里
})

new Vue({
	// 将router挂载到vue实例中
  router,
  render: h => h(App)
}).$mount('#app')

组件内的守卫

局部守卫有多种,比如beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave等,本文要介绍的是beforeRouteLeave,当离开页面的操作。

组件内的守卫的应用

需求:给定一个文本编辑的网页,当用户在进行编辑的时候,不小心点到了其他的页面,为了防止这种现象导致用户未编辑完的文本丢失,这里就可以使用局部守卫的beforeRouteLeave,当离开页面的时候,可以做一些事情,来提示或者阻止用户离开该页面。

具体代码

创建一个Eaditor.vue文件,用来编辑文本的组件,代码如下

<template>
	<div>
		<h2>文本编辑</h2>
		<textarea v-model="content" cols="30" rows="10"></textarea>
		<button @click="saveContent">保存</button>
		<ul>
			<li v-for="(item,index) in list" :key="index">
				<h3>{{item.title}}</h3>
			</li>
		</ul>
	</div>
</template>

<script>
	export default{
		name:'eaditorComponent',
		data(){
			return{
				content:'',
				list:[],
			}
			
		},
		methods:{
			saveContent(){
				console.log(this.content);
				// 将文本域中输入的信息保存到list列表中
				this.list.push({
					title:this.content
				})
				// 保存成功之后,清空文本域
				this.content = '';
			}
		},
		
		// 使用组件内的守卫
		beforeRouteLeave(to,from,next){
			// 如果文本域中还有文本,则说明用户正在编辑
			if(this.content){
				alert('请确保保存信息之后,再离开');
				next(false);// 阻止用户离开当前页
			}else{
				// 如果文本域没有文本,则说明用户没有在编辑
				next();// 放行当前页
			}
		}
		
	}
</script>

<style>
</style>

index.js文件

export default new VueRouter({
	mode:'history',//纯净模式,取消路径中的#
	// 路由配置 routes:[{}]
	routes:[
        {
			path:'/eaditor',
			name:'eaditor',
			component:()=>import('@/views/Eaditor')
		},
    ]
}

App.vue文件

<template>
  <div id="app">
      <router-link :to="{name:'eaditor'}">编辑</router-link>|
   </div>
</template>

元信息

vue中的元信息,可以用来实现权限控制,就是main.js中的路由配置里,要对需要权限的路由添加meta:{requireAuth: true}属性即可,然后再配合全局守卫来实现权限控制

案例演示

需求:现在有两个路由,一个是/note,另一个是/blog路由,这两个路由分别对应Note.vue和Blog.vue文件,当然还有一个登录的组件,名为Login.vue,对应的路由是/login,现在要实现的功能是,当用户访问/note和/blog路由的时候,要求先登录才能访问,如果没有登录,就自动跳转到登录页面,登录成功之后,再返回上一页。

具体代码

1.首先,准备好三个文件,分别是Note.vue Blog.vue

Note.vue文件

<template>
	<div>
		<h2>我的笔记</h2>
	</div>
</template>

<script>
	export default{
		name:'notesComponent',
	}
</script>

<style>
</style>

Blog.vue文件

<template>
	<div>
		<h2>博客页面</h2>
	</div>
</template>

<script>
	export default{
		name:'blogComponent'
	}
</script>

<style>
</style>

2.再main.js文件中配置好对应的路由,并且给/note和/blog组件添加 元信息 来实现权限控制

{
			path:'/notes',
			name:'notes',
			component:()=>import('@/views/Notes'),
			// 当需要给路由,设置要身份验证后才可以访问,就可以用meta属性
			meta:{
				// 添加黑名单
				requireAuth: true
			}
		},
		{
			path:'/blog',
			name:'blog',
			component:()=>import('@/views/Blog'),
			// 当需要给路由,设置要身份验证后才可以访问,就可以用meta属性
			meta:{
				// 添加黑名单
				requireAuth: true
			}
		},
		{
			path:'/login',
			name:'login',
			component:()=>import('@/views/Login')
		},

3.在main.js文件中定义全局守卫

// 全局守卫
router.beforeEach((to,from,next)=>{
	// 判断用户的权限
	if(to.matched.some(record=>record.meta.requireAuth)){
		// 如果用没有登录,需要权限,在黑名单
		if(!localStorage.getItem('user')){
			// 则跳转登录页面
			next({
				path:'/login',// 跳转到/login路由
				query:{
					redirect:to.fullPath,// 获取到当前的完整路由,并且放在url中做查询参数,方便/login路由视图跳转
				}
				
			})
		}else{
			// 身份验证通过,用户已经通过。给予放行
			next();
		}
	}
	next();// 一定要调用该方法,否则程序会卡在这里
})

4.在Login.vue文件中,设置登录成功后,返回上一页

<template>
	<div>
		<h2>登录页面</h2>
		<input type="text" v-model="user">
		<input type="password" v-model="pwd">
		<button @click="handleLogin">登录</button>
	</div>
</template>

<script>
	export default{
		name:'loginComponent',
		data(){
			return{
				user:'',
				pwd:''
			}
		},
		methods:{
			handleLogin(){
				// 获取用户名和密码,这步骤先省略
				
				// 模拟后端传来用户登录后的数据
				setTimeout(()=>{
					let data = {
						user:this.user
					}
					
					
					// 将获取到的用户信息保存到本地
					localStorage.setItem('user',JSON.stringify(data));
					
					// // 跳转到我的笔记页面,未在路由中添加meta属性的时候
					// this.$router.push({
					// 	name:'notes',
					// })
					
					
					console.log(this.$route.query.redirect);
					// 跳转会上一个页面,在路由添加meta属性的时候
                    
                    
                    // ************************这部分是核心代码*****************
					this.$router.push({
						// 由于在全局守卫中,定义跳转到此页面的同时,把上一页的完整url放在了浏览器的地址栏中
						// 所以这里,直接从浏览器的地址栏中获取查询参数,即可获取到上一页的路由
						// 来源于 mian.js文件中的 query:{redirect:to.fullPath,}
						path:this.$route.query.redirect,
					})
					
					
					
				},1000)
			}
		}
	}
</script>

<style>
</style>

数据获取

有时候,进入某个路由后,需要从服务器获取数据,比如说,在渲染用户信息的时候,需要从服务器获取用户数据,可以通过两种方式实现。一种是导航完成之前获取,另一种便是导航完成之后获取,这样要介绍的是导航完成之后获取数据。

导航完成之后获取数据

为了模拟后端获取数据,可以先造一个假数据,或者说是造一个假的服务器,用于模拟给前端发送数据。

在项目的根目录下,创建一个vue.config.js文件,记住,必须叫vue.config.js。(如果已经有了,那就不用创建)

vue.config.js文件

// 引入 express
const express = require('express');

module.exports = {
  lintOnSave: false, // 关闭eslint
  devServer: {
    setupMiddlewares(middlewares, devServer) {
      const app = express();

      // 自定义中间件
      app.get('/api/post', (req, res) => {
        res.json({
			title:'vue-router的获取数据',
			body:'1.在导航完成之后获取数据'

        });
      });

      // 将自定义中间件添加到中间件数组
      middlewares.unshift(app);

      return middlewares;
    }
  }
};

此时,可以在浏览器访问http://loaclhost:8080/api/post 查看是否请求到了上面我自定义的数据。

然后在该项目下安装axios

cnpm i axios -S

在main.js文件中导入axios,并且挂载到Vue的原型对象上

import axios from 'axios'

// 挂载到原型对象上
Vue.prototype.$https = axios;

创建一个Post.vue,用来接收后端请求的数据(自己造的假服务器),

Post.vue文件中template的内容如下

<template>
	<div>
    	
    </div>
</template>

<script>
	export default{
		name:'postComponent',
	}
</script>

<style>
</style>

然后在index.js中给post.vue配置好路由

{
			path:'/post',
			name:'post',
			component:()=>import('@/views/Post')
		},

然后再App.vue文件中渲染Post.vue组件

<router-link :to="{name:'post'}">Post</router-link>|

前面的案例,配置完成了,现在再Post.vue中就可以模拟获取到后端传来的数据,并且将获取的数据,渲染到组件页面中。

Post.vue中的完整代码

<template>
	<div>
		<!-- 如果数据正在加载的时候,才会显示Loading... 但是由于现在的网速过快,一般是看不到的 -->
		<div v-if="loading" class="loading">Loading....</div>
		<div v-if="error" class="error">{{error}}</div>
		<div v-if="post">
			<h3>标题:{{post.title}}</h3>
			<p>内容:{{post.body}}</p>
		</div>
	</div>
</template>

<script>
	export default{
		name:'postComponent',
		data(){
			return{
				post:null,
				error:null,
				loading:false
			}
		},
		created(){
			// 导航完成之后获取数据
			console.log(this.$https);
			this.getPostData();
		},
		methods:{
            // 请求数据的方法
			async getPostData(){
				try{
					this.loading = true;
					// 获取服务器传来的数据
					const {data} = await this.$https.get('/api/post');//采用了解构赋值的方法
					this.loading = false;
					this.post = data
				}catch(error){
					this.error = error.toString();
				}
			}
		},
	}
</script>

<style>
</style>

Vuex插件

官方文档说明

Vuex的简介

vuex是一个专门为vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

vuex主要用来解决组件之间的数据传递,原先vue中的数据传递时靠父子组件通信,这方式,在组件嵌套关系比较复杂的时候,就不适用了。如果说用中央事件总线的话,效果当然可以实现,但是当项目规模大的话,组件多的情况下,就要用到许多中央事件总线,这就显得代码非常的臃肿,不易维护。这时候就可以使用vuex来实现组件间的数据传递了。

vuex可以看作是一个独立的实例对象,它可以用来管理共享的状态(state{})。通俗的讲,就是将组件内部中想要共享的属性,定义在vuex中,这样其他组件就可以对vuex中的数据进行修改。

vuex的使用场景:购物车,用户权限,登录注册等。vuex一般应用在中大型的项目中。

Vuex的基本使用

vuex的安装

在原有的项目基础上安装

vue add vuex

创建带vuex的新项目

在任意地方,想要存放vuex项目的目录下,打开cmd

1.创建新项目

vue create test_vuex

按下回车之后,会出现以下选项

? Please pick a preset: (Use arrow keys)
> test_router ([Vue 2] babel, router, eslint)
  Default ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
  Manually select features

选择Manually select features,按下回车之后会出现以下选项

(*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 (* ) Router
 ( *) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

上下左右键 分别选中Vuex和Router,然后按下空格,vuex和Router对应的括号就会出现*,然后按下回车,会出现以下界面

? Choose a version of Vue.js that you want to start the project with
  3.x
> 2.x

选择2.x,然后回车,会显示以下内容

? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)

选择y,然后回车,会显示以下内容

? Pick a linter / formatter config: (Use arrow keys)
> ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier

直接默认回车,会显示以下内容

>(*) Lint on save
 ( ) Lint and fix on commit

直接默认回车,会显示以下内容

> In dedicated config files
  In package.json

直接默认回车,会显示以下内容

 Save this as a preset for future projects? (y/N)

选择y,然后回车,会显示以下内容

? Save preset as:test_vux

输入项目名称,其中test_vux是项目名称,然后按下回车,等待安装完成。

当出现以下,则说明安装成功

100 packages are looking for funding
  run `npm fund` for details
🚀  Invoking generators...
📦  Installing additional dependencies...


added 89 packages in 7s

112 packages are looking for funding
  run `npm fund` for details
⚓  Running completion hooks...

📄  Generating README.md...

🎉  Successfully created project test_vuex.
👉  Get started with the following commands:

 $ cd test_vuex
 $ npm run serve

然后查看项目文件下,的src目录下,会多出stroe的文件。以及再main.js文件中会有导入stroe文件,以及挂载store。在index.js文件中导入了vuex模块,然后使用了Vuex的插件。

// 带vuex项目的index.js 文件中的内容
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: { // 当前状态
  },
  getters: { 
  },
  mutations: { // 同步的方法
  },
  actions: {// 异步的方法
  },
  modules: {
  }
})

然后进入到text_vuex目录下,输入 npm run serve 启动项目

#进入到text_vuex目录下
cd test_vuex

#启动项目
npm run serve

然后再浏览器上输入 http://localhost:8080/ ,打开网页,发现Welcome to Your Vue.js App文字,则说明启动成功。

vuex的使用简单介绍

获取共享数据

在index.js文件中的state属性中,定义数据,然后再组件中使用this.$store.state.定义的数据 来获取共享数据。假设再state属性中定义了count:0 然后再组件中定义的方法取出该数据 this.$store.state.count 即可或者到count数据。

可以在组件中的computed属性中监听count值:count(){return this.$store.state.count}

修改共享数据

首先再组件中的created属性中定义一个方法来操作数据,就叫increment吧,然后再index.js文件中的actions属性中写入increment({commit}){commit('increment')},接着再index.js文件中的mutations属性中写入increment(state){state.共享的数据}。然后再回到组件中,在methos中写入increment(){this.$store.dispatch('increment');}

购物车数量+1的案例

首先,要定义个变量,来控制购物车的数量,为了让整个count变量能够让所有组件都能访问,就可以把count属性定义在index.js文件中的state属性

state: { // 当前状态
	count:0
  },

然后在购物车视图里定义 购物车数量增加(increment) 和 减少(decrement) 的方法

<template>
  <div class="about">
    <h1>This is an about page</h1>
	<!-- 此时,这里的count来自于index.js中state中的count -->
	{{count}}
	<button @click="increment">+1</button>
	<button @click="decrement">-1</button>
  </div>
</template>

然后再index.js文件中的actions属性中使用commit来调用mutations属性中的incrementdecrement方法

  // 修改状态的唯一方法是提交mutations
actions: {// 异步的方法
	increment({commit}){
		// commit mutations中声明的方法
		// increment来自于mutations中的方法
		commit("increment")
	},
	decrement({commit}){
		// decrement来自于decrement中的方法
		commit('decrement')
	}
  },

然后再index.js文件中的mutations属性中来定义 购物车视图里 的incrementdecrement方法的业务逻辑

  mutations: { // 同步的方法
	increment(state){
        // 业务逻辑
		// 修改状态
		state.count++;
	},
	decrement(state){
        // 业务逻辑
		// 修改状态
		state.count--;
	}
  },

接着回到购物车视图中,在methods属性中分别调用index.js中的actions属性中的incrementdecrement方法

methods:{
    // count+1操作
    increment(){
        // dispatch actions中声明的方法
        // this.$store.dispath('index.js中的actions中的方法')
        // increment就是index.js中的actions中的方法
        this.$store.dispatch('increment')

        // 如果说index.js文件中的actions没有定义,只定义了mutations,那就用以下方式
        // this.$store.commit('increment')


    },
        //count-1操作
        decrement(){
            // decrement就是index.js中的actions中的方法
            this.$store.dispatch('decrement')

            // 如果说index.js文件中的actions没有定义,只定义了mutations,那就用以下方式
            // this.$store.commit('decrement')
        }

}

自此。就可以在购物车视图里进行加减操作了。

vuex中的辅助函数

辅助函数有:mapState,mapGetters,mapMutations,mapActions

使用辅助函数,要先在视图组件中导入

import {mapState,mapGetters,mapMutations,mapActions} from "vuex";
mapState辅助函数

原先想要在组件中操作store中的数据,要用this.$store.state.数据来获取,现在有了辅助函数就更方便了,直接使用...mapState(['数据1','数据2',...]) 就可以获取到数据了.

// 假设store中有count 和username变量
...mapState(['count','useranme'])
// 这样方式获取,要保证组件视图中和store中的数据名称相同

另外一种方式,就可以不相同了

...mapState({
    //自定义数据名:'store中的数据名'
    myCount:'count',
    user:'username'
})

//然后再视图组件中就可以通过myCount和user来获取store中的count和username的值
<template>
  <div class="about">
	<!-- {{count}}----{{username}} -->
    {{count}}-----{{user}}
  </div>
</template>
mapGetters辅助函数

mapGetters辅助函数操作的是index.js文件中的getters属性中的方法

index.js文件中的getters属性的内容

export default new Vuex.Store({
// .......省略以上代码.......
getters: { 
	evenOrOadd(state){
		return state.count % 2 === 0 ? '偶数' : '奇数';
	}
    // .......省略以下代码.......
})

再组件视图中 调用index.js文件中的getters属性的方法

原先的调用形式

<template>
  <div class="about">
	{{evenOrOadd}}
  </div>
</template>
<script>
    // 导入辅助函数
	import {mapGetters} from 'vuex';
    computed:{
        //evenOrOadd 来自于index.js文件中getters属性
        evenOrOadd(){
				return this.$store.getters.evenOrOadd;
			}
    }
</script>

使用辅助函数

<template>
  <div class="about">
	{{evenOrOadd}}
  </div>
</template>
<script>
    // 导入辅助函数
	import {mapGetters} from 'vuex';
    computed:{
        //evenOrOadd 来自于index.js文件中getters属性
        // 使用辅助函数mapGetters
		...mapGetters(['evenOrOadd']),
    }
</script>
mapMutations辅助函数

在组件视图中的methods属性中

// 使用辅助函数mapMutations来调用index.js文件中的mutations的方法
...mapMutations(['increment','decrement']),
mapActions辅助函数

在组件视图中的methods属性中

// 使用辅助函数mapActions来调用index.js文件中的actions的方法
...mapActions(['increment']),

组件内部提交数据

将组件内部的数据提交给vuex。

组件视图中的内容

methods:{
    // ......省略以上代码......
    
    // 异步+1
    incrementAsync(){

        // 在组件内部提交数据 载荷形式分发
        // 想要将组件中定义的amount变量传递给store;
        // 使用this.$store.dispatch('要传递给的方法',{变量:值})
        this.$store.dispatch('incrementAsync',{
            amount:10
        });

        // // 另外一种写法 组件内部提交数据
        // this.$store.dispatch({
        // 	type:"incrementAsync",
        // 	amount:10
        // })


    },
        // ......省略以下代码......
}

在index.js文件中,actions属性中接受组件传来的值

 actions: {// 异步的方法
	// 异步+1
	incrementAsync({ commit },{ amount }){ //注意是:{空格amount空格}
		console.log(amount); // 接受组件传来的值
		// amount 是组件中incrementAsync方法传来的值
		setTimeout(()=>{
			commit('incrementAsync',amount)
		},1000)
	}
  },

然后,在index.js文件中,再mutations属性中,调用actions属性中的方法,再定义业务逻辑

mutations: { // 同步的方法
	// 异步+1
	incrementAsync(state,amount){
		// state.count++
		//{amount} 来自于actions属性中的incrementAsync中的括号里
		// incrementAsync中的括号里的amount 来自于组件中methods中的incrementAsync方法中的 this.$store.dispatch('incrementAsync',{amount:10}) 中的amount
		
		state.count+=amount;
	}
	
  },

vuex的模块化

原本是所有组件都共用一个vuex的store,但是引入模块化后,就是一个组件对应一个store,一般情况下,会在store目录下创建modules目录来存放各个组件的模块。每个模块都各自维护各自的state,getters,action,mutations属性。

代码案例

假设有两个组件,一个是购物车组件(ShoppingCart.vue),一个是商品列表组件(ProductList.vue),分别对应cart.js 和 products.js模块。

1.首先先在store目录下创建modules文件夹,然后再创建cart.js和products.js模块。

cart.js中的内容

export default{
	namespaced:true,
	state:{
		// 购物车列表
		cartList:[],
		// 购物车内的商品数量
		count:0
	},
	getters:{
		
	},
	mutations:{
		
	},
	actions:{
		
	}
}

products.js中的内容

export default{
	namespaced:true,
	state:{
		// 商品列表
		products:[]
	},
	getters:{
		
	},
	mutations:{
		
	},
	actions:{
		
	}
}

2.然后再index.js文件中,将这两个模块进行导入

import cart from './modules/cart
import products from './modules/products'

3.然后在index.js文件中的modules属性中,将这两个导入的模块挂载

// ....省略以上代码
modules:{
    cart,
    products
}
// 省略以下代码

此时,如果说在其中某个模块,就比如说cart.js模块把,在该模块中的getters属性中定义了一个方法,想要在组件中的computed属性中使用getters属性中的方法,用辅助函数的方式调取:...mapGetters('模块名称',['getters属性中的方法'])。…mapGetters(‘cart’,‘方法名’)。 当然了,使用其他辅助函数也是通用的方式 …辅助函数(‘’,[])

4.在component文件中新建两个组件文件,分别是购物车组件(ShoppingCart.vue) 和商品列表组件(ProductList.vue)。

ShoppingCart.vue文件中的内容

<template>
	<div>
		<h2>我的购物车</h2>
	</div>
</template>

<script>
	export default{
		name:'ShoppingCartComponents'
	}
</script>

<style>
</style>

ProductList.vue文件中的内容

<template>
	<div>
		<h2>我的商铺</h2>
	</div>
</template>

<script>
	export default{
		name:'ProductListComponents'
	}
</script>

<style>
</style>

5.然后在主组件中,比如说AboutView.vue中,将ShoppingCart.vue和ProductList.vue组件文件导入和注册

import ProductList from '@/components/ProductList'
import ShoppingCart from '@/components/ShoppingCart'

export default{
    components:{
        // 注册组件
        ProductList,
        ShoppingCart
    }
}

6.然后再在主组件中的templates中使用这两个组件

<ProductList></ProductList>
<hr>
<ShoppingCart></ShoppingCart>

自此,就实现了vuex的模块化,即一个组件对应一个store模块,ShoppingCart.vue对应cart.js,ProductList.vue对应products.js,各自操作各自的state,getters,actions,mutations等属性。

vuex中全局组件的使用

比如说在component文件夹中有个ShoppingCart.vue组件,想要挂载到全局

在main.js中

// 1.导入ShoppingCart组件
import ShoppingCart from '@/components/ShoppingCart'
// 2.挂载到Vue对象
// 其中ShoppingCart.name是指获取ShoppingCart.vue中 export default{name:'ShoppingCartComponents'} name的值
Vue.conponent(ShoppingCart.name,ShoppingCart)

vuex实现购物车案例

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

// 全局组件
import ShoppingCart from '@/components/ShoppingCart'
// 其中ShoppingCart.name是指获取ShoppingCart.vue中 export default{name:'ShoppingCartComponents'} name的值
Vue.component(ShoppingCart.name,ShoppingCart)
// 在主组件中想要引入该全局组件,就要用到ShoppingCart.vue中 export default{name:'ShoppingCartComponents'} name的值


// 全局过滤器
Vue.filter('currency',(value)=>{
	// currency过滤器名称,value组件传来的值
	return '$' + value;
})


new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 导入组件对应的store模块文件
import cart from './modules/cart.js'
import products from './modules/products.js'
import createLogger from 'vuex/dist/logger'

export default new Vuex.Store({
	plugins:[createLogger()],// 生成快照
  state: { // 当前状态
	count:0,
	username:'aaa',
  },
  getters: { 
  },
  mutations: { // 同步的方法
	
  },
  actions: {// 异步的方法
      
  },
  modules: {
	// 挂载组件对应的state模块
	cart,
	products
  }
})

AboutView.vue(主组件)

<template>
  <div class="about">
    <h1>This is an about page</h1>
	
	<!-- 3.使用组件模块 -->
	<ProductList></ProductList>
	<hr/>
	<!-- <ShoppingCart></ShoppingCart> -->
	
	<!-- 使用全局组件 -->
	<ShoppingCartComponents></ShoppingCartComponents>
  </div>
</template>


<script>
	// 1.导入组件模块
	import ProductList from '@/components/ProductList';
	import ShoppingCart from '@/components/ShoppingCart'
	
	import {mapState,mapGetters} from 'vuex';
	export default{
		name:'AboutView',
		components:{
			// 2.注册组件模块
			ProductList,
			ShoppingCart
		},
		data(){
			return{
					
			}
		},
		computed:{
				
		},
		methods:{	
		
		},
		
	}
</script>

ShoppingCart.vue

<template>
	<div>
		<h2>我的购物车</h2>
		<ul>
			<li v-for="(item,index) in getCartList" :key="index">
				{{item.title}} - {{item.price}} x {{item.quantity}}
			</li>
		</ul>
		总价格:{{cartTotalPrice | currency}}
	</div>
</template>

<script>
	import { mapGetters } from 'vuex'
	export default{
		name:'ShoppingCartComponents',
		computed:{
			// 调用cart.js中的getters中的getCartList方法中的数据
			...mapGetters('cart',['getCartList','cartTotalPrice'])
		},
	}
</script>

<style>
</style>

cart.js

import products from "./products";

export default{
	namespaced:true,
	state:{
		// 购物车列表
		cartList:[],
		// 商品数量
		count:0
	},
	getters:{
		// 方法名(state,getters,rootState){} 固定写法
		// rootState就是获取根节点(index.js)的state属性
		getCartList(state,getters,rootState){
			// rootState.模块名.状态名	状态名就是index.js中的modules属性中的products
			// console.log(rootState.products.products);
			return state.cartList.map(({ id,quantity })=>{
				// 判断购物车中的id 是否与 商品的id一样
				const product = rootState.products.products.find(item=>item.id === id);
				// 根据id返回对应的一整条数据,然后选择一整条数据中的个别数据返回出去
				return {
					title:product.title,
					price:product.price,
					quantity
					
				}
			})
		},
		// 计算总价格
		cartTotalPrice(state,getters,rootState){
			return getters.getCartList.reduce((total,product)=>{
				return total + product.price * product.quantity;
			},0);// 表示从0开始
		}
	},
	mutations:{
		// 将商品添加到购物车中
		pushProductToCart(state,{ id,quantity }){
			// quantity表示商品数量
			state.cartList.push({
				id,
				quantity
			})
		},
		// 商品数量+1
		incrementCartItemQuantity(state,{ id }){
			// 根据组件传来的id 在cartList中匹配,然后将匹配出来的商品数量+1
			// quantity表示商品数量
			const product = state.cartList.find(item=>item.id===id);
			product.quantity++;
		}
		
	},
	actions:{
		// 将商品添加至购物车 
		// ...mapActions('cart',['addProductToCart'])
		addProductToCart({commit,state},product){
			// product是组件中传来的值
			// <button @click="addProductToCart(product)">添加购物车</button>
			
			// 如果库存大于0
			if(product.inventory >0){
				// 根据组件穿过来的id 在cartList中寻找,如果找到,则返回对应id的一整条数据
				const cartItem = state.cartList.find(item=>item.id === product.id);
				// console.log(cartItem);
				if(!cartItem){
					// 如果没有找到,说明商品未添加到购物车
					// 添加商品到cartList中	quantity表示数量
					commit('pushProductToCart',{id:product.id,quantity:1})
				}else{
					// 购物车中已有数据
					commit('incrementCartItemQuantity',{id:product.id})
				}
				// 如果想提交另一个模块的方法,需要第三个参数{root:true}
				commit('products/decrementProductInventory',{id:product.id},{root:true})
			}
		}
	}
}

ProductList.vue

<template>
	<div>
		<h2>我的商铺</h2>
		<ul>
			<!-- 5.products 来自于 ...mapState('products',['products']) -->
			<li v-for="product in products" :key="product.id">
				<!-- 使用过滤器 -->
				{{product.title}} --- {{product.price | currency}} -- {{product.inventory}}
				<!-- 当库存为0的的时候,禁言该按钮 -->
				<button :disabled='!product.inventory' @click="addProductToCart(product)">加入购物车</button>
			</li>
		</ul>
	</div>
</template>

<script>
	import { mapState,mapActions } from 'vuex'
	export default{
		name:'ProductListComponents',
		computed:{
			//4. 	...mapState('模块的名称',['模块中的state中的数据'])
			// 获取 products.js中的state属性中的products值
			...mapState('products',['products']),
		},
		methods:{
			// 调用cart.js中的actions属性中的 addProductToCart
			...mapActions('cart',['addProductToCart'])
		},
		created(){
			// 1.调用products.js文件中的actions属性中的getAllProducts方法
			this.$store.dispatch('products/getAllProducts')
		}
	}
</script>

<style>
</style>

products.js

import axios from 'axios';

export default{
	namespaced:true,
	state:{
		// 商品列表
		products:[]
	},
	getters:{
		
	},
	mutations:{
		getAllProducts(state,products){
			// 3.products 是接受actions属性中的getAllProducts方法中的commit('getAllProducts',results)中的results
			state.products = products;
		},
		decrementProductInventory(state,{ id }){
			const product = state.products.find(item=>item.id === id);
			product.inventory--; // 库存-1
		}
		
	},
	actions:{
		async getAllProducts({commit}){
			// 2.从后端获取数据
			try{
				// 业务逻辑
				const res = await axios.get('/api/products');
				// console.log(res);
				const results = res.data.results;
			
				// 提交到mutations
				commit('getAllProducts',results)
				
			}catch(error){
				console.log(error);
			}
		}
	}
}
  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值