vue2理论学习(全套教程,包含vuex、路由等)
本文是学习vue2理论的记录笔记,适合于入门学习,包含vue全家桶中的vuex、路由等理论知识。
文章目录
- 一、 vue基础
- 二、 vue-cli
- 三、vue组件
- 四、vue生命周期
- 五、ref引用
- 六、动态组件
- 七、插槽 slot
- 八、自定义指令
- 九、vuex
- 十、 路由(vue-router)
- 十一、 Vue UI组件库
一、 vue基础
1. vue的介绍
1.1 vue的特性
-
数据驱动视图
优点:vue监听数据的变化,进而自动渲染刷新页面
缺点:数据驱动视图是单向的(数据变化—>刷新页面) -
双向数据绑定
在网页中,表单负责采集数据,ajax负责提交数据
- js数据的变化会被自动更新到页面上
- 页面上表单采集的数据发生变化时,会被vue获取并自动更新到js
1.2 MVVM
- vue是参考MVVM模型进行设计的
M:Model 页面渲染数据所依赖的数据源,对应vue中的data 数据
V:View 视图,当前页面所渲染的DOM结构,对应页面DOM结构
VM:ViewModel 是MVVM的核心,表示vue的实例对象
- data中的所有属性,都会出现在vm上
- vm上的所有属性及Vue原型上的所有属性,在Vue模板中都可以直接使用
1.3 vue数据代理
1.3.1 Object.defineproperty()
- 给对象添加属性
Object.defineProperty(obj, prop, descriptor)
参数1: obj 要定义属性的对象
参数2:prop 要定义或修改的属性的名称或 Symbol
参数3:descriptor 要定义或修改的属性值
返回值:被传递给函数的对象
<script>
let person = {
name:"dudu",
age:4,
}
Object.defineProperty(person,'gender',{
value:'女'
})
console.log(person) //{name: 'dudu', age: 4, gender: '女'}
</script>
- 通过Object.defineproperty设置的属性不可以枚举(不可以被遍历)
- value:设置属性值
- enumerable:是否可枚举,默认为false
- writable:是否可修改,默认为false
- configurable:是否可删除,默认为false
let person = {
name:"dudu",
age:4,
}
Object.defineProperty(person,'gender',{
value:'女',
enumerable:true, //是否可枚举
})
console.log(Object.keys(person)) //['name', 'age','gender'] console.log(Object.keys(person)) //['name', 'age']
- get() :当读取“参数2”时就会调用get方法(getter),其返回值为参数2的值(必须要有返回值),此时不可以在设置value
- set():当修改参数2时会调用的set方法(setter),接收一个参数
<script>
let number = 19
let person = {
name:'嘟嘟',
sex:'女'
}
Object.defineProperty(person,'age',{
// 当读取age属性时会调用
// 必须有返回值,返回值就是age的值
get() {
console.log("有人在读取age属性")
return number
},
set(val) {
console.log("age属性被修改了!")
number = val
}
})
</script>
1.3.2 数据代理
- 数据代理是什么
- 通过一个对象代理对另一个对象中的属性的操作(读/写)
let obj1 = {x:100}
let obj2 = {y:200}
// 通过obj2操作obj1中的x
Object.defineProperty(obj2,'x',{
get() {
return obj1.x
},
set(val) {
obj1.x = val
}
})
2. vue中的数据代理
- 通过vm对象来代理data对象中属性(读/写,即getter / setter)
- 这样更加方便操作data中的数据
- 基本原理
通过Object.defineProperty()把data对象中的所有属性添加到vm上。
为每一个添加到vm上的属性,指定一个getter、setter
在getter、setter内部去操作(读、写)data对应的属性
- _data内部是通过数据劫持实现的
2. vue基础用法
2.1 基本使用步骤
- 导入vue.js的script脚本
- 声明DOM区域
- 创建vm实例对象(vue实例对象)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue基本用法</title>
<!-- 1. 导入vue的库,此时在window全局就有了vue构造函数 -->
<script src="./lib/vue-2.6.14.js"></script>
</head>
<body>
<div id="app">{{username}}</div>
<!-- 2. 创建vue实例 -->
<script>
const vm = new Vue({
// el是固定用法,表示当前vm实例要控制的区域,值是一个选择器
el:'#app',
// data对象就是要渲染到页面的数据
data:{
username:'嘟嘟'
}
})
</script>
</body>
</html>
2.2 vue调试工具
- 安装vue-devtools调试工具
vue调试工具插件下载地址
- 将下载的文件解压,打开谷歌浏览器,右上角(…) ==》 更多工具 ==》扩展程序,将解压的Vue.jsxxxxx.crx文件拖入扩展程序中
注:安装好后需要重启谷歌才生效
3. vue指令
3.1 内容渲染指令
作用:用于辅助开发者渲染DOM元素的文本内容
3.1.1 v-text
- 用于更新元素的textContent
- v-text会覆盖元素内部原有的内容,一般不常用
3.1.2 插值语法 {{}}
- 插值表达式(Mustache),是最常用的用法
- 只能用在元素的内容节点中,不可用于属性节点
- {{}}中只能写简单的js表达式,不能写if等复杂的js语句
3.1.3 v-html
- 用于更新元素的innerHTML,渲染有标签的字符串为真正的DOM元素
- 会覆盖子组件
- 会有XXS风险
<body>
<div id="app">
<p>
姓名:
<span v-text="username"></span>
</p>
<p>
性别:{{gender}}
</p>
<div v-html="info"></div>
</div>
<!-- 2. 创建vue实例 -->
<script>
const vm = new Vue({
// el是固定用法,表示当前vm实例要控制的区域,值是一个选择器
el:'#app',
// data对象就是要渲染到页面的数据
data:{
username:'嘟嘟',
gender:"女",
info:`<h4 style="color:red">vue.js学习</h4>`
}
})
</script>
</body>
运行效果截图:
3.2 属性绑定指令 v-bind
3.2.1 简写成 :
动态绑定属性值
<input type="text" :placeholder="tips">
3.2.2 JS表达式
在vue的模板渲染语法中除了支持绑定数据值,还可以支持JavaScript表达式的运算,但一般不推荐在HTML中写js表达式
<div>{{age+1}}</div>
- 一旦使用了v-bind就会被认为js表达式
3.2.3 动态绑定class样式
:class=“xxx” xxx可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
3.2.4 动态绑定style样式
:style="{fontSize: xxx}“其中xxx是动态值。
:style=”[a,b]"其中a、b是样式对象。
3.3 事件绑定指令 v-on
3.3.1 简化写法 @
和事件处理函数methods搭配使用,@定义的方法要写在methods中
在绑定事件处理函数时可以进行传参
<body>
<div id="app">
<p>值:{{num}}</p>
<button @click="addNum(5)">加法</button>
<button @click="subNum">减法</button>
</div>
<!-- 2. 创建vue实例 -->
<script>
const vm = new Vue({
// el是固定用法,表示当前vm实例要控制的区域,值是一个选择器
el:'#app',
// data对象就是要渲染到页面的数据
data:{
num:0
},
methods: {
addNum(n) {
this.num += n
},
subNum() {
this.num--
}
}
})
</script>
</body>
注:vue2 中的methods方法中this指向的是vm实例
3.3.2 事件对象 $event
- 在绑定事件时如果不传参,就会默认接收一个事件对象 e
<body>
<div id="app">
<p>值:{{num}}</p>
<!-- 如果num为偶数背景色为蓝色 -->
<button @click="addNum">加法</button>
</div>
<!-- 2. 创建vue实例 -->
<script>
const vm = new Vue({
// el是固定用法,表示当前vm实例要控制的区域,值是一个选择器
el:'#app',
// data对象就是要渲染到页面的数据
data:{
num:0
},
methods: {
addNum(e) {
this.num += 1
// // 判断奇偶
if(this.num % 2 ===0){
// e.target 获取当前操作的DOM
e.target.style.backgroundColor = "skyBlue"
e.target.style.color = "#fff"
}else {
e.target.style.backgroundColor = ""
e.target.style.color = "#000"
}
}
}
})
</script>
</body>
- 如果传了参数要获取e,可以通过 $event(原生的DOM事件对象e)获取
<button @click="addNum(2,$event)">加法</button>
3.3.3 事件修饰符
- e.preventDefault() 或 e.stopPropagation() 阻止默认行为(原生js中)
- vue中通过事件修饰符 .prevent 阻止默认行为
<!-- 阻止a链接跳转 -->
<a href="https://www.baidu.com/" @click.prevent="showInfo">百度</a>
vue中的事件修饰符
- .prevent 阻止默认行为(如a链接跳转,表单的提交等)
- .stop 阻止事件冒泡
- .capture 以捕获模式触发当前的事件处理函数
- .once 绑定的事件值触发一次
- .self 只有在e.target是当前元素时触发
3.3.4 按键修饰符
用于监听详细的键盘事件
<body>
<div id="app">
<!-- 按下esc键清空输入框 -->
<!-- 按下enter键输出值 -->
<input type="text" @keyup.esc="cleanInput" @keyup.enter="enterFun">
</div>
<script>
const vm = new Vue({
el:'#app',
data: {
},
methods: {
cleanInput(e) {
// 清空输入
e.target.value = ''
},
enterFun(e) {
console.log(`输入的值为"${e.target.value}"`)
}
}
})
</script>
</body>
3.4 双向数据绑定指令 v-model
作用:在不操作DOM的前提下,快速捕获表单数据
- v-model是双向绑定,数据变化会引起页面变化,页面变化会引起数据变化,适用于表单元素
- v-bind 是单向绑定,只有数据变化会引起页面变化
<body>
<div id="app">
<p>用户名:{{username}}</p>
<!-- v-model是双向绑定,数据变化会引起页面变化,页面变化会引起数据变化 -->
<input type="text" v-model="username">
<!-- v-bind 是单向绑定,只有数据变化会引起页面变化 -->
<p><input type="text" :value="username"></p>
</div>
<script>
const vm = new Vue({
el:'#app',
data: {
username:"嘟嘟"
}
})
</script>
</body>
效果图:
常见的表单元素:
-
input输入框
type=“radio”
type=“checkbox”
type=“text”
… -
textarea
-
select
3.4.1 v-model的指令修饰符
- .number:将用户输入值转为数值(number)类型
如:输入电话号码时 - .trim:过滤输入的收尾空白字符
如:登录注册时 - .lazy:在"change"时更新而不是在“input”时更新
3.5 条件渲染指令:v-if 和 v-show
3.5.1 共同点
控制元素的显示和隐藏
3.5.2 区别
- v-if
每次都会创建或移除元素
适用于初始默认不展示,后面也可能不展示 - v-show
通过display来控制元素的隐藏或显示
适用于要频繁的切换元素的显示隐藏状态
3.5.3 v-else
v-if可以和v-else-if、v-else搭配使用
<div v-if="type === 'A'">优秀</div>
<div v-else-if="type === 'B'">良好</div>
<div v-else-if="type === 'C'">及格</div>
<div v-else>差</div>
3.6 列表渲染指令 v-for
3.6.1 基本列表
v-for=“被循环的每一项 in 待循环数组”
或者
v-for=“被循环的每一项 of 待循环数组”
<body>
<script src="./lib/vue-2.6.14.js"></script>
<div id="app">
<table class="table table-borderd table-hover">
<thead>
<th>索引</th>
<th>ID</th>
<th>姓名</th>
</thead>
<tbody>
<tr v-for="(item,index) of list" :key="item.id">
<td>{{index}}</td>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
</tr>
</tbody>
</table>
</div>
<script>
const vm = new Vue({
el:'#app',
data: {
list: [{
id:1,
name:"张三"
},{
id:2,
name:"李四"
},{
id:3,
name:"王二"
},{
id:4,
name:"麻子"
}]
}
})
</script>
</body>
- v-for支持 可选的 第二个参数,即当前的索引,语法为:(item,index) of list
- v-for 要搭配 :key属性;尽量将循环项的id作为key的值,不推荐用index作为key值,容易重复不具备唯一性;key值要求为字符串或者数值类型;key值不允许重复(唯一性)
3.6.2 key的原理与作用
- 作用:对节点进行标识,相当于身份证
-
虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下: -
对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2). 旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。 -
用index作为key可能会引发的问题
1) 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2) 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。 -
开发中如何选择key?:
1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2)如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
3.8 案例
效果图
<body>
<div id="app">
<!-- 卡片区域 -->
<div class="card">
<div class="card-header">
添加品牌
</div>
<div class="card-body">
<!-- 添加品牌的表单区域 -->
<form>
<div class="form-row align-items-center">
<div class="col-auto">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text">品牌名称</div>
</div>
<input type="text" class="form-control" placeholder="请输入品牌名称" v-model.trim="addName">
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-2" @click.prevent="addRow">添加</button>
</div>
</div>
</form>
</div>
</div>
<!-- 表格区域 -->
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">品牌名称</th>
<th scope="col">状态</th>
<th scope="col">创建时间</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item of list" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" :id="item.id" v-model="item.status">
<label class="custom-control-label" :for="item.id" v-if="item.status">已启用</label>
<label class="custom-control-label" :for="item.id" v-else>已禁用</label>
</div>
</td>
<td>{{item.time}}</td>
<td>
<a href="javascript:;" @click="delRow(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
addName:'', //要添加的品牌名
list:[
{
id:1,
name:'宝马',
status:true,
time: new Date()
},{
id:2,
name:'奔驰',
status:false,
time: new Date()
},{
id:3,
name:'奥迪',
status:false,
time: new Date()
},{
id:4,
name:'红旗',
status:true,
time: new Date()
}
]
},
methods: {
// 删除
delRow(id) {
// 过滤掉id不等于要删除的元素id,然后重新赋值给list
this.list = this.list.filter(item => item.id !== id)
},
// 添加
addRow() {
let keyword = this.addName
if(keyword === "") {
alert("您没有输入任何内容!!")
return
}else {
let len = this.list.length + 1
let item = {
id:len,
name:keyword,
status:true,
time:new Date()
}
// push()在数组最后添加元素
this.list.push(item)
this.addName = ''
}
}
},
})
</script>
</body>
3.9 过滤器 Filters
注: 过滤器只适用于vue2 ,vue3中已删除过滤器
- filters用于文本的格式化,可用在插值表达式和v-bind属性绑定
- 过滤器应该添加在JS表达式的尾部,由管道符( | )调用
- 过滤器通过filters声明,且一定要有返回值
- 过滤器函数的形参是管道符前面的值
<body>
<div id="app">
<p>{{message | capi}}</p>
</div>
<script>
const vm = new Vue({
el:'#app',
data: {
message:"hello!你好呀~"
},
methods: {
},
// 声明过滤器
// 过滤器本质为一个函数
filters:{
// val指向的是message的值
capi(val) {
// 实现首字母大写
// charAt()从字符串中取出对应索引的字符
const first = val.charAt(0).toUpperCase()
const other = val.slice(1)
// 过滤器一定要有返回值
return first + other
}
}
})
</script>
</body>
- 可以连续调用多个过滤器,最后得到的是最后一个过滤器的返回值
- 过滤器可以传参,在接收时要通过第二个开始
3.9.1 私有过滤器和全局过滤器
- 私有过滤器:定义在vue实例的filters中的过滤器
- 全局过滤器:可以实现多个vue实例之间共享过滤器,独立于每个vm实例
- 全局过滤器的定义:
Vue.filters(全局过滤器名字,全局过滤器的方法) - 全局过滤器要在vue实例之前声明
- 全局和私有过滤器重名,则按就近原则调用私有过滤器
3.9.2 全局过滤器使用Day.js完善案例中的时间格式
<script>
// 声明格式化时间的全局过滤器
Vue.filter('dateFormat',(time) => {
const timeformat = dayjs(time).format('YYYY-MM-DD HH:mm:ss')
return timeformat
})
</script>
// 使用全局过滤器
<p>{{item.time | dateFormat}}</p>
4. 侦听器 watch
监视data数据变化,接收两个参数,第一个参数是新值,第二个参数是旧值
4.1 方法格式
- 方法格式的监听器,只有在需要监听的数据发生变化时,才会触发,一般采用方法格式
<body>
<div id="app">
<p>
用户名:<input type="text" v-model="userName">
</p>
</div>
<script src="./lib/vue-2.6.14.js"></script>
<script>
var vm = new Vue({
el:"#app",
data:{
userName:""
},
// 侦听器
watch:{
// 方法格式的侦听器
userName(newVal,oldVal) {
console.log(`新值:${newVal}`)
console.log(`旧值:${oldVal}`)
}
}
})
</script>
</body>
4.2 对象格式
- 对象格式的侦听器可以通过 immediate 让侦听器立即触发,immediate的默认值为false
- 在对象格式中,通过handler定义侦听器的处理函数
// 对象格式的侦听器
userName: {
immediate:true,
handler(newVal,oldVal) {
console.log(`新值:${newVal}`)
}
}
- 深度侦听:deep
- 方法格式的侦听器在侦听对象时,如果对象中的属性发生了变化,不会触发侦听器。此时可以通过对象侦听的方式,设置deep属性,让侦听器监听对象中每个属性的变化
<body>
<div id="app">
<p>
用户名:<input type="text" v-model="info.userName">
</p>
</div>
<script src="./lib/vue-2.6.14.js"></script>
<script>
var vm = new Vue({
el:"#app",
data:{
info: {
userName:"嘟嘟"
}
},
// 侦听器
watch:{
// 深度侦听
info: {
immediate:true,
handler(newVal,oldVal) {
console.log(`${newVal}`)
},
deep:true, //开启深度监听
}
}
})
</script>
</body>
- 如果要监听对象的字书写变化,则必须包裹一层单引号
'info.userName'(newVal){
console.log(`新值:${newVal}`)
}
- watch支持异步,不支持缓存
- 应用场景
注册时,判断用户名是否被占用
5. 计算属性 computed
本质是一个属性,是通过一系列运算得到的属性
计算属性可用在 模板结构 或者 methods 方法中
- 计算属性要定义为方法格式,返回最终结果
- 使用时通过属性的形式。computed可以在template模板结构中可以使用;也可以在methods方法中使用,此时通过this.计算属性调用
- 优点1: 实现了代码复用
- 优点2:只有依赖的数据源变化才会自动重新计算
- computed支持缓存,不支持异步
<body>
<div id="app">
<p>r:<input type="text" v-model="r"></p>
<p>g:<input type="text" v-model="g"></p>
<p>b:<input type="text" v-model="b"></p>
<div class="box" :style="{backgroundColor:rgb}">
{{rgb}}
</div>
</div>
<script src="./lib/vue-2.6.14.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
r:0,
g:0,
b:0
},
// 就算属性
computed: {
rgb() {
// 动态生成rgb字符串
return `rgb(${this.r},${this.g},${this.b})`
}
}
})
</script>
</body>
6. watch和computed区别
- computed能完成的功能,watch都可以实现
- watch能完成的功能,computed不一定可以实现
比如:Watch可以进行异步操作,而计算属性不可以进行异步操作,它必须接收一个返回值 - 当watch和computed都可以实现时,优先选择computed
两个小原则
- 所有被vue管理的函数最好写成普通函数,这样this指向的才是vm或者组件实例对象
- 所有不被vue管理的函数(定时器、ajax、promise),最好写成箭头函数,这样this指向的才是vm或者组件实例对象
7. axios
基本语法
axios({
// 请求方式:get、post
method:'',
// 请求地址
url:'',
// get时的参数
params:{},
// post的参数
data:{}
}).then(()=>{})
可以用async、await 代替then
二、 vue-cli
1. 单页面应用程序(SPA)
- 含义:一个web网站中只有一个唯一的HTML页面
2. vue-cli
- 是vue,js开发的标准工具,简化了程序员基于webpack创建工程化vue项目的过程
2.1 全局安装vue-cli
cnpm install -g @vue/cli
2.2 vue-cli创建项目
winpty vue.cmd create 项目名
- 选择最后一项,然后回车
2.3 vue项目结构目录
-
node_modules:项目依赖
-
public:一般放置静态资源(如:图片),webpack打包时会原封不动的打包到dist文件夹
-
src:源代码文件夹
assets:放置静态资源(一般是多个组件公用的静态资源),webpack在打包时会把assets文件夹当做一个模块,打包到js文件中
components:放置非路由组件、全局组件
pages/views:放置路由组件(需创建)
api:存放axios相关文件(需自己创建)
store: 存放vuex相关文件(需自己创建)
router:存放路由的配置文件(需创建)
mock:存放模拟数据相关的(需创建)App.vue:唯一的根组件
main.js:入口文件,整个程序中最先执行的文件 -
gitignore git的忽略文件,一般不动
-
babel.config.js:babel相关的配置文件,一般不动
-
package.json:包管理配置文件,记录项目信息(项目怎么运行,有哪些依赖),类似于项目的“身份证”
-
package-lock.json:可以删除,缓存性的文件
-
readme.md:说明性文件
2.4 vue项目的运行流程
通过 main.js 把 App.vue 渲染到 index.html 指定区域中
其中:
- App.vue用于编写待渲染的模板结构
- index.html中需要预留一个el区域
- main.js把App.vue渲染到了index.html所预留的区域中
main.js代码
// 导入vue的包,得到Vue构造函数
import Vue from 'vue'
// 导入根组件,
import App from './App.vue'
Vue.config.productionTip = false
// 创建vue实例
new Vue({
// 把render指定的组件渲染到HTML页面中
render: h => h(App),
}).$mount('#app') //$mount()相当于el
三、vue组件
组件化开发:根据封装的思想,把页面上可复用的UI结构封装为组件,从而方便项目的开发和维护
vue中的组件后缀名为 .vue
1. vue组件的三个组成部分
- template:组件的模板结构,只能有一个根节点
- script:组件的JavaScript行为
script标签的内部必须写 默认导出(export default{})
组件中的data为一个函数,且必须返回一个对象 - style:组件的样式
在组件中,this指向当前组件的实例对象
组件的template中只能有一个根节点
2. 组件的使用
2.1 组件使用的三个步骤
- 通过import导入需要的组件
import Left from '@/components/left.vue'
import Right from '@/components/right.vue'
- 通过components节点注册组件
export default {
name: 'App',
components: {
Left,
Right
}
}
- 以标签的形式使用刚才注册的组件
<Left></Left>
<right></right>
2.2 vscode配置@路径提示的插件
-
安装插件 Path Autocomplete
-
修改setting.json配置
在setting.json开头输入以下代码
// 导入文件时是否携带文件的扩展名
"path-autocomplete.extensionOnImport": true,
// 配置@的路径提示
"path-autocomplete.pathMappings": {
"@":"${folder}/src"
},
注:如果不起作用,可能是因为同一个文件夹下有多个项目
vscode中目前已经安装的插件
2.3 全局注册
- 通过components注册的是私有子组件,只能在注册的组件中使用
- 如果某组件需要在多个地方使用,可以注册为全局组件,全局组件只需要注册一次就可以直接使用
在main.js入口文件中,通过 Vue.component(参数1,参数2) 方法注册
参数1:字符串格式,表示组件的“注册名”
参数2:需要被全局注册的组件
// 引入三级联动组件
import TypeNav from '@/components/TypeNav'
// 注册为全局组件
/**
* 第一个参数:全局组件的名字
* 第二个参数:哪一个组件
*/
Vue.component('nav',TypeNav)
3. 组件的props
props是组建的自定义属性,在封装通用组件时可以提高组件的复用性。
- 通过数组定义props
export default {
props:['自定义属性1','自定义属性2',...]
}
- 通过对象定义props
props: {
自定义属性1: {
// default设置默认值
default:0,
// type定义类型
type:所需的基本数据类型,
// 是否是必填项
required:false //默认是false
}
}
3.1 props是只读的
- props中的数据可以直接在模板中使用,这点和data一样
- 但props中的值是只读的,不可以直接修改props中的值,否则会报错如下
- 可以通过将props的值转存到data中(通过 this. 实现),来修改props的值
<!-- -->
<template>
<div>
和:{{sum}}
<button @click="add">+</button>
</div>
</template>
<script>
export default {
name:'',
props:['init'],
data () {
return {
sum:this.init
};
},
methods: {
add() {
this.sum++
}
},
}
</script>
<style lang='scss' scoped>
</style>
// 使用props的值
<count :init="9"></count>
3.2 props的default默认值
- 如果需要定义props的默认值,需要通过default实现,此时props应该写为对象形式
- 当在使用props时没有传递数据就会使用默认值
props: {
自定义属性1: {
// default设置默认值
default:0
}
}
3.3 props的type值类型
- 在声明自定义属性时,可以通过type来自定义属性的值类型
props: {
init: {
default:0,
type:Number
}
},
- 使用时如果传递的不是规定的类型,就会报错
<count init="9"></count>
- 如果不使用v-bind,init=‘9’中传递的是一个字符串’9’,通过v-bind就会解析为数值,正确写法
<count :init="9"></count>
3.4 props required必填项
- 设置了required和default,但是没有传值,也会报错,required只会校验有没有传值,不会去看默认值
4. 样式冲突问题
- vue组件中的样式会在全局生效,容易造成多个组件的样式冲突
- 可以通过给style标签添加scoped属性,限定样式只对当前组件生效
<style lang='scss' scoped>
</style>
- scss/sass/less中使用 /deep/ 样式穿透
5. 组件中的数据共享
- 组件之间常见的关系为父子关系、兄弟关系
5.1 父子组件传值
5.1.1 父向子传:props
- 父组件提供数据(v-bind),子组件通过props接收
- 子组件不能直接修改父组件传过来的值
5.1.2 子传父
子组件通过自定义事件向父组件传值
- 子组件通过 this.$emit(‘自定义事件’,要传递的数据)
- 父组件中通过 @自定义事件=“事件名” 的形式来调用事件
- 父组件通过methods定义“事件名(子组件传递的数据){}”来使用子组件传递的数据
<!-- 父组件 -->
<template>
<div id="app">
<test :message="msg" @changeParent="getSonData"></test>
<hr>
<div>
<p>父组件接收子组件传过来的值:{{countFromSon}}</p>
</div>
</div>
</template>
<script>
import Test from '@/components/test.vue'
export default {
name: 'App',
data() {
return {
msg:"hello,我是父组件中的数据!",
// 接收子组件的值
countFromSon:0
}
},
components: {
Test
},
methods: {
getSonData(val) {
this.countFromSon = val
}
},
}
</script>
<style lang="scss">
</style>
<!-- 子组件 -->
<template>
<div>
<div>来自父组件的值:{{message}}</div>
<hr>
<div>
<p>{{sonMsg}}:{{count}}</p>
<button @click="add">+</button>
</div>
</div>
</template>
<script>
export default {
name:'',
props:{
message: {
type:String
}
},
data () {
return {
sonMsg:"我是子组件的数据!",
count:0
};
},
methods: {
add() {
this.count++
this.$emit('changeParent',this.count)
}
},
}
</script>
<style lang='scss' scoped>
</style>
运行效果
5.2 兄弟组件传值——bus总线
vue2中兄弟组件通过EventBus
- 创建eventBus.js模块,并向外共享一个Vue的实例对象
- 在数据发送方,调用 bus.$emit(‘自定义事件’,要发送的数据) 方法触发自定义事件
- 在数据接收方,调用 bus.$on(‘自定义事件’,,事件处理函数) 方法注册一个自定义事件
6. 数组方法的回顾
6.1 some()循环
从数组中查找某个元素用some,可以通过return true来终止循环
const ARR = ['红红','蓝蓝','白白','灰灰','绿绿','紫紫']
// 查找'白白'对应的索引
ARR.some((item,index) => {
console.log("查找了1次")
if(item === '白白') {
console.log(index)
return true
}
})
6.2 every()
判断数组中的每一项是否都满足条件,如果全部都满足返回true,只要有一项不满足则返回false
const arr = [
{id:1,name:"西瓜",status:true},
{id:2,name:"草莓",status:true},
{id:3,name:"哈密瓜",status:true}
]
// 判断每一项的status是否为true
const result = arr.every(item => item.status)
console.log(result) //true
6.3 reduce()
将每一次循环的结果进行累加,返回最后累加的结果
语法:
reduce( (累加的结果,当前循环项) => {},初始值)
前一次循环累加的结果会作为下一次的初始值,直到循环结束,返回最终累加结果
示例:
const arr = [
{id:1,name:"西瓜",status:true,price:10,count:2},
{id:2,name:"草莓",status:false,price:30,count:3},
{id:3,name:"哈密瓜",status:true,price:20,count:5}
]
// 计算数组中status为true的水果的总价
const newArr = arr.filter( item => item.status)
const res = newArr.reduce((total,item) => {
return total += item.price * item.count
},0)
console.log(res) // 120
7. 组件的拆分
- 组件的拆分要根据“
单一原则
”判定范围,一个组件负责一个功能,如果需要更多功能,需要拆分为更小的组件 - 组件的特点:可复用、可维护、可组合
四、vue生命周期
包含创建 、 运行 、销毁三个阶段
- created 常用来发起ajax请求
- 如果要操作DOM元素,要在mounted之后(包含mounted)
- 如果要在数据发生变化后操作最新的DOM要在updated中实现
<script>
export default {
name:'',
props:['info'],
data () {
return {
msg:"hello!"
};
},
methods: {
show() {
console.log("methods方法")
}
},
// beforeCreate
// 此时props、data、methods还没有被创建,是不可使用的
beforeCreate() {
console.log("beforeCreate此时拿不到任何数据")
},
// created
// 此时props、data、methods已经被创建好,但还不可以操作DOM
// 该阶段常用来发起ajax请求
created() {
console.log(this.info)
console.log(this.msg)
this.show()
},
// beforeMount
beforeMount() {
console.log("beforeMount,此时DOM还没有渲染")
},
// mounted
mounted() {
console.log("mounted!此时DOM已经渲染完成")
console.log(this.$el)
},
// beforeUpdate
// 当组件的数据发生变化时才会触发
beforeUpdate() {
console.log("beforeUpdate!数据发生了变化")
},
// updated
// 根据最新的数据,重新渲染dom
// 如果要在数据发生变化后操作最新的DOM要在updated中实现
updated() {
},
// beforeDestroy
// 将要销毁组件
beforeDestroy() {
},
// destroyed
// 组件销毁完成
destroyed() {
},
}
</script>
五、ref引用
vue中几乎不需要操作DOM,只需要维护好数据。如果一定要操作DOM可以通过ref实现。
每个vue组件的实例上都包含一个 $refs对象,里面存储着对应的DOM元素或者组件的引用。默认情况下,组件的 $refs 指向一个空对象。
注:$开头的都是vue内置成员,都是程序员可用的
- ref的名字不可以重复,要是唯一的
1. ref引用DOM
<div class="root_box" ref="rootBox">
<h2>这是根组件</h2>
<button @click="changeBtn">切换背景颜色</button>
</div>
methods: {
changeBtn() {
this.$refs.rootBox.style.backgroundColor = "pink"
}
},
2. ref引用组件实例
3. this.$nextTick(callback)应用场景
- this.$nextTick 将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用它,然后等待DOM更新
- 即:等组件的DOM更新完毕,再执行callback回调函数,以保证cb回调函数可以操作到最新的DOM元素
methods: {
// ...
example() {
// 修改数据
this.message = 'changed'
// DOM 尚未更新
this.$nextTick(function() {
// DOM 现在更新了
// `this` 被绑定到当前实例
this.doSomethingElse()
})
}
}
六、动态组件
- 含义:动态切换组件的显示与隐藏
- 通过 < component :is=“组件名”> 组件实现动态组件的渲染,其中 is 用来指定要渲染的组件
1. keep-alive的使用
- 使用< component :is=“组件名”>时,当组件名发生改变时,原有的组件会被销毁
- 此时可以通过keep-alive保持状态
- keep-alive可以把内部的组件进行缓存
<keep-alive>
<component :is="componentId"></component>
</keep-alive>
2. keep-alive的生命周期函数
- 组件被缓存时,会触发组件的deactived生命周期函数
- 组件被激活时,会触发组件的actived的生命周期函数,组件第一次被创建就会激活一次
3. keep-alive的include和exclude
- include:指定要缓存的组件,只有名称匹配的组件被缓存,多个组件之间用逗号分隔
- exclude:指定不被缓存的组件,和include不可以同时使用
<keep-alive exclude="Right">
<component :is="componentId"></component>
</keep-alive>
4. 组件注册名称和组件声明时name的区别
- 在声明组件时,如果没有指定name值,则组件的名称就默认是注册时的注册名
- 当提供了name,组件名就是name名
- 建议在创建组件时设置name值
name:'LeftOwn',
- 组件的注册名,一般应用于:以标签的形式,将注册好的组件渲染和使用到页面结构
- 组件声明时的name名,一般应用于:结合< keep-alive>标签实现组件缓存功能,以及在调试工具中看到组件的name名
<keep-alive include="LeftOwn">
<component :is="componentId"></component>
</keep-alive>
七、插槽 slot
- 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件 ===> 子组件
- 分类:普通插槽、具名插槽、作用域插槽
1. 普通插槽
<!-- 父组件 -->
<子组件名>
需要的HTML结构
</子组件名>
<!-- 子组件 -->
<template>
<div>
<!-- 定义插槽(占个位,等组件的使用者填充内容) -->
<slot>插槽默认内容...当父组件没有传递内容时,就会显示默认值</slot>
</div>
</template>
2. 具名插槽
- 当有多个插槽时,可以通过name给每个插槽命名
- 在使用时,通过 slot=“name” 指定传内容到名为name的插槽
<!-- 子组件 -->
<slot name="slotName">默认内容</slot>
<!-- 父组件 -->
<div slot="slotName">插槽内容</div>
- 可以使用template包裹多个内容
- template标签是一个虚拟的标签,起包裹作用,实际不会被渲染
- template 可以和 v-slot:slotName 搭配使用,v-slot只能和template搭配使用
<!-- 子组件 -->
<slot name="slotName"></slot>
<!-- 父组件 -->
<left>
<template v-slot:slotName>
插槽内容
</template>
</left>
- v-slot 可以简写为 #,即 #slotName
3. 作用域插槽
- 数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
- slot标签传值,然后通过 < template slot-scope=“scopeData”> 使用所传的值
<slot name="slotName" :msg="要传的插槽内容!"></slot>
<template slot-scope="scopeData">
<p>
{{scopeData.msg}}
</p>
</template>
- 作用域插槽可以使用解构赋值
<category title="游戏">
<template slot-scope="{msg}">
<h4 >{{msg}}</h4>
</template>
</category>
- 作用域插槽也可以命名
八、自定义指令
1. 私有自定义指令
在每个组件中,可以通过directives节点声明私有自定义指令,如果通过v-自定义指令名进行使用
<h1 v-color>根组件</h1>
// 私有自定义指令
directives: {
color: {
// 一旦绑定到元素就会触发bind方法
// el代表所绑定的DOM
bind(el) {
el.style.color="red"
}
}
}
- 自定义指令的bind函数接收两个参数,第一个参数el为所绑定的DOM元素,第二个参数为binding对象,可以通过binding.value获取所传值
<h3 v-color="color">自定义指令1</h3>
<h3 v-color="'pink'">自定义指令2</h3>
// 私有自定义指令
directives: {
color: {
// 一旦绑定到元素就会触发bind方法
// el代表所绑定的DOM
bind(el,binding) {
el.style.color= binding.value
}
}
}
- bind 函数只调用一次,后续DOM更新时不会再触发bind,update函数会在每次DOM更新时被调用
- 如果bind和update函数的逻辑完全一致,则对象格式的自定义指令可以简写为函数格式
directives: {
color(el,binding) {
el.style.color= binding.value
}
}
2. 全局自定义指令
//main.js
// 参数1:字符串,name表示全局自定义指令的名字
// 参数2:对象,用来接收指令的参数值
Vue.directive('name',(el,binding) => {
})
九、vuex
1. vuex的理解
1.1 是什么
用于集中式状态管理,可以进行数据的读写操作和多组件数据共享,相当于一个大型仓库,可以进行任意组件的通信
1.2 适用场景(数据共享)
- 多组件依赖于同一状态
- 来自不同组件的行为要变更同一状态
2. vuex原理
理解:vuex是一个餐厅(store)
vue Component 为客人,客人点菜(store.dispatch)
actions 为服务员,获取到客人点的菜,然后提交给后厨(store.commit)
mutations 为后厨,根据服务员点的菜,进行处理
state 为点的菜
3. vuex环境搭建
- 安装
cnpm i vuex@3
- vue2 使用的是 vuex3
- vue3 使用的是 vuex4
- 创建store
src目录下创建store文件夹,store文件夹下新建index.js
index.js中的代码如下
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 使用vuex
Vue.use(Vuex)
// 创建actions,用于响应组件中的动作
const actions = {}
// 创建mutations,用于操作数据(state)
const mutations = {}
// 创建state,用于存储数据
const state = {}
// 创建并向外暴露(导出)store
export default new Vuex.Store({
actions,
mutations,
state
})
- 引入store(main.js)
// 引入store
import store from '@/store'
new Vue({
store, // 注册store
render: h => h(App),
}).$mount('#app')
如果是在main.js中使用vuex,此时会报如下错误:
错误原因:会先执行所有的import,在store/index.js中vuex还没有被创建
4. vuex案例(熟悉vuex的使用)
- state中存储需要共享的数据
const state = {
sum:0,
}
- 组件中通过dispatch触发需要的方法
this.$store.dispatch(‘方法名’,要传的数据)
addOdd() {
this.$store.dispatch('oddAdd',this.num)
},
- 当actions中没有任何业务逻辑,只是单纯的提交一个mutation时,可以不用通过dispatch触发action,而是直接在组件中commit需要的方法即可
increase() {
this.$store.commit('ADD',this.num)
},
- actions中定义方法,然后commit提交mutation
- actions中方法的名字要和dispatch提交的方法名一致
- actions中的方法接收两个参数
- 第一个参数:上下文参数,需要到使用其中的commit,用于提交mutation
- 可以通过解构赋值直接拿到commit,然后直接使用commit提交
add ( {commit} ,val)
-
第二个参数:接收到dispatch所传的值
-
actions中 可以处理业务逻辑
const actions = {
// 第一个参数:上下文
// 第二个参数:所传的值
oddAdd(context,val) {
if(context.state.sum % 2) {
context.commit('ADD',val)
}
}
}
- commit提交的方法,一般方法名要大写
- mutation 中定义actions传递的方法,然后进行相应的处理
// 创建mutations,用于操作数据(state)
const mutations = {
ADD(state,val) {
console.log(val)
state.sum += val
}
}
- mutations中一般不进行任何业务逻辑,只需要根据需求进行加工处理
5. getters的使用
- 用于加工处理state中的数据
- 创建
const getters = {
xxx(state) {
return xxxxxx
}
}
export default new Vuex.Store({
....
getters
})
- 接收state参数,返回需要的值
- 组件中读取
$store.getters.xxx
6. 优化:四个map方法的使用
- 当在组件中频繁的需要state或getters中数据,即书写 $store.xxx.xxx时,可以通过vuex的mapState和mapGetters方法从state或getters获取数据
import {mapState,mapGetters} from ‘vuex’
6.1 mapState()
把state中的数据映射为计算属性
- 对象写法:
…mapState({key1:‘value1’,key2:'value2…}),
value要和state中的key名保持一致
value和key都是字符串,key可以不加引号,value必须加引号
computed: {
...mapState({sum:'sum',school:'school',con:'con'}),
}
- 数组写法
…mapState([‘xxx’,‘xxx’,‘xxx’]),
多个内容用逗号分隔,且xxx要与state中的key名保持一致
computed: {
...mapState(['sum','school','con']),
}
6.2 mapGetters()
把getters中的数据映射为计算属性,语法和mapState一致
- 数组写法
computed: {
...mapGetters(['bigSum'])
}
- 对象写法
computed: {
...mapGetters({bigSum:'bigSum'})
}
6.3 mapActions
生成与actions对话的方法,即生成包含 $store.dispatch(xxx) 的函数
语法和mapState一致,但是需要在调用方法时传参
- 对象写法
...mapActions({addOdd:'oddAdd'})
<button @click="addOdd(num)">和为奇数再加</button>
- 数组写法
...mapActions(['oddAdd'])
6.4 mapMutations
生成与mutations对话的方法,即生成包含 $store.commit(xxx) 的函数
语法和mapState一致,但是需要在调用方法时传参
- 对象写法
...mapMutations({increase:'ADD',substraction:'SUB'}),
<button @click="increase(num)">+</button>
<button @click="substraction(num)">-</button>
- 数组写法
...mapMutations(['ADD','SUB'])
7. vuex的模块化(modules)
- 让代码更好维护,让多种数据分类更加明确
- 每个组件的state、actions、mutations、getters单独管理,最后引入
- 在store目录下建立需要使用vuex的组件文件夹,如:home/index.js、search/index.js,分别管理需要的vuex内容
const state = {
...
}
const mutations = {
...
}
// getters:计算属性,项目中主要用于简化仓库中的数据
const getters = {
...
}
const actions = {
...
}
export default {
state,
mutations,
getters,
actions
}
- store/index.js中引入
import Vue from 'vue'
import Vuex from 'vuex'
// 使用一次vuex
Vue.use(Vuex)
// 引入小仓库
import Home from './Home'
import Search from './Search'
// 对外暴露store的一个实例
export default new Vuex.Store({
// 实现vuex仓库模块式开发存储数据
modules: {
Home,
Search
}
})
注: 项目不大时,可以直接在store/index.js中分模块
const countAbout = {
namespaced:true,//开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
- 开启命名空间(namespace)后,组件中读取state数据:
// 直接读取
this.$store.state.模块名.要读取的state数据
// mapState读取
...mapState('模块名',['xxx','xxx','xxx']),
- 开启命名空间后,组件中读取getters数据:
// 直接读取
this.$store.getters['模块名/要读取的getters']
// mapState读取
...mapGetters('模块名',['xxx'])
- 开启命名空间后,组件中调用dispatch
// 直接dispatch
this.$store.dispatch('模块名/actions方法名',数据)
// 借助mapActions
...mapActions('模块名',{xxx:'xxxx',xxx:'xxxxx'})
- 开启命名空间后,组件中调用commit
// 直接commit
this.$store.commit('模块名/xxx',数据)
// mapMutations
...mapMutations('模块名',{xxx:'xxxxx'...})
十、 路由(vue-router)
1. 相关理解
1.1 vue-router的理解
vue的一个插件库,用来实现SPA应用
1.2 SPA的理解
- 单页web应用(single page web application,SPA)
- 整个应用只有一个index.html页面
- 点击页面中的导航不会刷新整个页面,知乎进行局部更新
- 数据需要通过ajax获取
1.3 路由的理解
- 是什么?
- 一个路由就是一组映射关系(key-value)
- key为路径,value为function或component
- 前端路由
- value是component,用于展示页面内容
- 当浏览器的路径改变时,会显示对应的组件
2. 基本路由
2.1 使用步骤
- 安装
cnpm i vue-router@3 --save
- 配置路由(router),新建 src / router / index.js
import Vue from 'vue'
// 引入路由
import VueRouter from 'vue-router'
// 使用
Vue.use(VueRouter)
// 引入组件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
// 向外导出
export default new VueRouter({
routes: [
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
},
]
})
- 注册路由(main.js)
// 引入路由
import router from '@/router'
new Vue({
router, //注册路由
render: h => h(App),
}).$mount('#app')
备注:
- src/views常用来存储路由组件
- src/components 用来存储非路由组件和全局组件
- 点击切换时,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
- 每个组件都有自己的 $route 属性,里面存储着自己的路由信息。
- 整个应用只有一个router,可以通过组件的 $router 属性获取到。
- 路由的跳转
- 声明式导航:router-link,必有to属性
<router-link to="/路径">xxxxx</router-link>
- 编程式导航:push、replace,编程式导航除了路由跳转还可以进行其他的业务逻辑
- 指定展示位置
<router-view></router-view>
3. 嵌套路由(多级路由)
- 路由里的路由(套娃)
- 通过给路由设置children属性来配置子路由
- 子路由的path不能加斜杠( / )
routes: [
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:New
},
{
path:'messages',
component:Message,
children:[
{
path:'detail',
component:Detail
}
]
},
]
},
]
- 跳转要写完整路径
<router-link to='/home/news'>News</router-link>
- 需要展示的区域设置
<router-view></router-view>
4. 路由传参
4.1 query参数
- 不属于路径中,不需要占位,通过问号(?)拼接,多个参数用 & 连接
<router-link :to="`/home/messages/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>
- 通过 $route.query.xxx 获取所传的参数
<p>标题:{{$route.query.title}}</p>
4.2 params参数
- 属于路径中的一部分,在配置路由时要进行占位
{
path:'detail/:id/:title',
component:Detail
}
- 使用时直接在路径后面拼接
<router-link :to="`/home/messages/detail/${item.id}/${item.title}`">{{item.title}}</router-link>
- 通过 $route.params.xxx 获取所传的参数
4.3 路由传参的几种形式
4.3.1 字符串形式
```javascript
// 方法一:通过字符串直接传参
// params形式
this.$router.push('/search/'+this.searchValue)
// query形式
this.$router.push('/search?key='+this.searchValue)
// params+query
this.$router.push('/search/'+this.searchValue+'?key='+this.searchValue.toUpperCase())
4.3.2 模板字符串
this.$router.push(`/search/${this.searchValue}?k=${this.searchValue.toUpperCase()}`)
4.3.3 对象写法(最常用的)
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
// 通过name命名路由
name:'search',
path:'/search/:keyword'
// 方法三:对象的写法(常用的),要对路由命名(name)
this.$router.push({
name:'search',
params:{
keyword:this.searchValue
},
query:{
key:this.searchValue.toUpperCase()
}
})
5. 命名路由
- 给路由添加name属性
routes: [
{
name:'about',
path:'/about',
component:About
},
]
- 作用:可以简化路由的跳转,简化后直接通过名字跳转
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
6. 路由的props配置
- 作用:让路由组件更方便的收到参数
- 在配置路由时进行配置
6.1 对象写法
- 值为对象,该对象中所有key-value都会以props的形式传递给路由组件
- 缺点:传递的是死数据,预先设置好的,不会改变
props: {
id:'0001',
title:'标题'
}
6.2 布尔值写法
- 值为布尔值,值为true,则会把该路由组件所有接收到的params参数,以props的形式传给detail组件
- 路由组件中通过props接收
props:['id','title'],
<p>标题:{{title}}</p>
6.3 函数写法
- 接收 $route 参数
props($route) {
return {
id:$route.query.id,
title:$route.query.title
}
}
- 接收参数时可以解构赋值
props({query}) {
return {
id:query.id,
title:query.title
}
}
- 接收参数时使用解构赋值的连续写法(连续解构赋值),不推荐改写法,语义化不够好
props({query:{id,title}}) {
return {
id:id,
title:title
}
}
- 该函数返回的对象中每一组key-value都会通过props传给Detail组件
7. 路由跳转的方式
7.1 声明式导航 router-link
- 通过router-link实现,必有to属性
<router-link to="路径">xxxxx</router-link>
7.1.1 router-link的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
- 如何开启replace模式
<router-link replace .......>xxx</router-link>
7.2 编程式导航 push、replace
- push、replace,编程式导航除了路由跳转还可以进行其他的业务逻辑
- 通过 this.$router.push() 或 this. $router.replace() 使用
this.$router.push({
name:'message',// 和路由配置中的name保持一致
query: {
id:item.id,
title:item.title
}
})
- 后退:this.$router.back()
- 前进:this.$router.forward()
- this.$router.go(num),num为正n表示向前走n步,为负数表示后退n步
8. 缓存路由组件
8.1 基本使用
- 作用:让不展示的路由组件保持挂载,不被销毁。
- 通过
keep-alive
实现
<keep-alive include="需要缓存的组件的名字(name)">
<router-view></router-view>
</keep-alive>
- 不加include表示缓存所有的组件
- 需要缓存多个时,动态绑定数组
<keep-alive :include="[name1,name2,....]">
<router-view></router-view>
</keep-alive>
8.2 两个新的生命周期
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态
activated
路由组件被激活时触发。deactivated
路由组件失活时触发
9. 路由守卫
-
作用:对路由进行权限控制
-
分类:全局守卫、独享守卫、组件内守卫
9.1 全局前置守卫
- 路由向外导出前进行配置
- 通过beforeEach(),在每次路由切换之前和初始化会调用里面的函数,该函数接受三个参数 to,from,next
- 必须加上 next() 才会进行跳转,继续接下来的操作
const router = new VueRouter({
routes: [.....]
})
// 全局前置路由守卫
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
// meta:路由元信息,需要在routes中配置
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})
export default router
9.2 全局后置路由守卫
- 初始化时执行、每次路由切换后执行
- 没有next参数
//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
9.3 独享路由守卫
- 每个路由单独的守卫
- beforeEnter,包含to、from、next三个参数
- 写在routes配置中
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
9.4 组件内路由守卫
- 进入守卫:beforeRouteEnter ,通过路由规则,进入该组件时被调用
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
- 离开守卫:beforeRouteLeave ,通过路由规则,离开该组件时被调用
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
- 和methods、data…平级,即写在组件 export default { } 中
10. hash和history模式
10.1 hash模式
- hash模式:浏览器地址栏中,
#
及其后面的内容就是hash值 - hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器
- 默认是hash模式
- 地址中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
10.2 history模式
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
- router中配置
mode: ‘history’
十一、 Vue UI组件库
1. 移动端UI
- Vant
- Cube UI
- Mint UI
2. PC端UI
- Element UI
- IView UI
更多推荐
所有评论(0)