目录
1.1 Vue的特点
-
组件化模式,提高代码复用率,更易于维护
-
声明式编码,让编码人员无需直接操作DOM,提高开发效率
-
使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点
1.2 MVVM模型
-
M: Model,模型,对应data中的数据
-
V: View,视图,对应模板
-
VM: ViewModel,视图模型,Vue的实例对象
1.3 Vue学习
1.3.1 Object.defineProperty
let number=20
let person = {
name:"张三",
sex:"男",
// age:18
}
//此方法添加的属性一些基本属性都是false,可以进行属性修改
Object.defineProperty(person,'age',{
// value:19,
// enumerable:true,//控制属性是否可枚举,默认为false
// writable:true,//控制属性是否可修改,默认为false
// configurable:true,//控制属性是否可以被删除,默认值为false
//当有人读取person的age属性时,get(getter)会被调用,返回值为age的值,
//此方法可以绑定age的值为number的值,当number的值改变时,age会自动改变,不需要再次声明age=number
get(){
console.log("读取age属性");
return number
},
//当有人修改person的age属性时,set(setter)会被调用,并且能收到修改的值
set(n){
console.log("修改age属性");
number=n;
return n
}
})
// console.log(Object.keys(person))
console.log(person);
1.3.2 数据代理
使用Object.defineProperty方法可以进行数据代理
//数据代理:通过一个对象代理对另一个对象中的属性进行一系列操作(读/写)
let obj={x:100};
let obj2={y:200}
Object.defineProperty(obj2,'x',{
get(){
return obj.x
},
set(n){
obj.x=n
}
})
· Vue中的数据代理: 通过vm(Vue实例)对象来代理vm的data对象中的属性;
·Vue中的数据代理好处:可以更方便地操作data中的数据(不用通过{{data.name}}来获取name的值,
而是可以直接通 过{{name}}来获取)
·Vue中的数据代理的基本原理:通过Object.defineProperty()把data对象中的所有属性都添加到vm上,再为每一 个添加到vm上的属性都指定一个getter/setter,在getter/setter内部操作(读/写)对应的 属性
1.3.3 修饰符
事件修饰符:
-
prevent:阻止默认事件
-
stop:阻止事件冒泡
-
once:事件只触发一次
-
capture:使用事件的捕获方式
-
self:只有event.target是当前操作的元素时才触发事件
-
passive:事件的默认行为立即执行,无需等待事件回调执行完毕
键盘别名:
·Vue给一些常用的键提供了别名,对未提供别名的按键,可以使用按键原始的key值绑定,但要转为kebab-case(短横线命名),例如切换大小写按键的key值是CapsLock,使用时要改为caps-lock。
·系统的修饰键用法较为特殊:ctrl、alt、shift、meta(win键)
(1)配合keyup使用时,按下修饰键的同时再按下其他键,随后释放其他键,才会触发事件(若想只通过系统修饰键+指定按键触发事件,可在系统修饰键后加上.再加上指定按键,例如,只想通过ctrl+y来触发事件,可以在ctrl后加上.y,即@keyup.ctrl.y)
(2)配合keydown使用时,可以正常触发事件
·也可以使用keyCode来指定具体的按键(不推荐,不同键盘 键码可能不统一)
·可以通过Vue.config.keyCodes.自定义键名 = 键码,来定制按键别名
-
enter => 回车
-
delete =>删除/退格
-
esc => 退出
-
space => 空格
-
tab => 换行(比较特殊,必须配合keydown使用
-
up => 上
-
down => 下
-
left => 左
-
right => 右
1.3.4 数据监测
-
Vue会监测data里所有层次的数据
-
如何监测对象中的数据:
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,vue默认不做响应式处理
(2).若需要给后添加的属性做响应式,可使用如下API:
Vue.set(target,propertyName/index,value)或 vm.$set(target,propertyName/index,value)
-
如何监测数组中的数据:
通过包裹数组更新元素的方法实现,本质是做了两件事:
(1).调用原生对应的方法对数组进行更新
(2).重新解析模板,更新页面
Vue.set(target,propertyName/index,value)或 vm.$set(target,propertyName/index,value)
-
在vue修改数组中的某个元素一定要使用如下几个方法:
(1). push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2).vue.set() 或 vm.$set()
注意:Vue.set()和vm.$stet() ,不能给vm或vm的根数据对象(如data)添加属性!
1.3.5 全局事件总线
-
可用于任意组件间的通信
-
安装全局事件总线:
/* 全局事件总线 */
//1.使用VueComponent当全局总线,
// const vc = Vue.extend({})
// Vue.prototype.x = new vc
//2.使用Vue当全局总线,在生命周期beforeCreate()创建
new Vue({
el: '#app',
router,
render: h => h(App),
//安装全局事件总线,在beforeCreate()创建
beforeCreate() {
// Vue.prototype.x=this //将x改为$bus(标准命名)
Vue.prototype.$bus=this //$bus就是当前应用的vm
},
})
-
使用事件总线:
1.接收数据:A组件想接收数据,则在组件A中给$bus绑定自定义事件,事件的回调留着A组件自身
methods(){
demo(data){...}
}
......
mounted(){
this.$bus.$on('xxx',this.demo)
}
也可以直接将回调函数写成箭头函数:
export default {
name:'School',
data() {
return {
name:'bupt',
address:'beijing',
studentName:''
}
},
mounted(){
this.$bus.$on('hello',data=>{
this.studentName=data
})
},
//避免资源占用,页面销毁前应解绑自定义事件
beforeDestroy(){
this.$bus.$off('hello')
}
}
2.提供数据 :
this.$bus.$emit('xxx',数据)
//或
methods:{
sentname(){
this.$bus.$emit('hello',this.name)
}
}
-
最好能在生命周期钩子beforeDestroy中,用$off解绑当前组件用到的事件。
1.3.6 消息订阅与发布
Vue中用的不多,因为和全局事件总线差不多
第三方库:pubsub npm i pubsub-js
·一种组件间通信的方式,适用于任意组件间的通信。
·先import
引入
·接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
mounted(){
//订阅消息使用pubsub.subscribe()函数
//注意pubsub.subscribe()有两个参数,第一个是消息名,第二个才是消息内容的回调函数
this.pubId = pubsub.subscribe('hi',(MsgName,data)=>{
console.log('@@@@',data)
this.submeg=data;
console.log('有人发布了hi消息,hi消息的回调执行了')
})
},
beforeDestroy(){
//取消订阅消息,需要订阅消息的id
pubsub.unsubscribe(this.pubId)
}
·提供数据:
methods:{
//发布消息用pubsub.publish()函数
pubmeg(){
pubsub.publish('hi',this.meg)
}
}
·最好在生命周期钩子beforeDestroy中,用pubsub.unsubscribe(this.pubId)
取消订阅。
1.3.7 配置代理
方法一:在vue.config.js中添加配置(没有此文件需手动创建):
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}
版本不同,可在config/index.js中找到proxyTable,添加配置(此时好像不能用方法一?):
proxyTable: {
'/stus': {
target: 'http://localhost:5000',
pathRewrite:{'^/stus':''} ,
ws: true,//用于支持websocket
changeOrigin: true, //用于控制请求头中的host值(即是否更改请求源信息),一般默认为true
},
'/cars': {
target: 'http://localhost:5001',
pathRewrite:{'^/cars':''} ,
ws: true,//用于支持websocket
changeOrigin: true, //用于控制请求头中的host值(即是否更改请求源信息),一般默认为true
},
},
优点:配置简单,请求资源时直接发给前端(8080)即可。
缺点:不能配置多个代理,且不能灵活的控制请求是否走代理。
工作方式:若按照上述配置代理,当请求了前端不存在的资源时,该请求会转发给服务器(优先匹配前端资源)
方法二:配置具体的代理规则:
proxyTable: {
'/stus': {//匹配所有以'/stus'开头的请求路径
target: 'http://localhost:5000',//代理目标的基础路径
pathRewrite:{'^/stus':''} ,
ws: true,//用于支持websocket
changeOrigin: true, //用于控制请求头中的host值(即是否更改请求源信息),一般默认为true
},
'/cars': {
target: 'http://localhost:5001',
pathRewrite:{'^/cars':''} ,
ws: true,//用于支持websocket
changeOrigin: true, //用于控制请求头中的host值(即是否更改请求源信息),一般默认为true
},
},
优点:可以配置多个代理,且可以灵活控制请求是否走代理。
缺点:配置较为繁琐,请求资源时必须加前缀
1.4 vue理解使用
1.4 .1生命周期
初始化:生命周期、事件,但数据代理还未开始。
beforeCreate():此时,无法通过vm访问到data中存放的数据、methods中的方法。
初始化:数据监测、数据代理
created():此时,可以通过vm访问到data中存放的数据、methods中的方法。
此阶段Vue开始解析模板,生成虚拟DOM(内存中),页面还不能显示解析好的内容。
beforeMount():此时,1.页面呈现的是未经Vue编译的DOM结构 。2.所有对DOM的操作,最终的时候都不奏效
将内存中的虚拟DOM转化为真实DOM插入页面
mounted():此时,1.页面中呈现的是经过Vue编译的DOM。2.对DOM的操作均有效(但应尽可能避免)。
至此初始化过程结束,一般在此进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件 等初始化操作。
beforeUpdate():此时,数据是新的,但页面是旧的,即:页面尚未和数据保持同步。
根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面解析,即:完成Mode==>View的更新。
updated():此时,数据是新的,页面也是新的,即:页面和数据保持同步。
beforeDestroy():此时,vm中所有的data、methods、指令等都处于可用状态,马上要执行销毁过程,一般在此 阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作。
destroyed():此时,页面摧毁
// 生命周期函数:
beforeCreate() {
console.log("beforeCreate")
console.log(this)
// debugger
},
created() {
console.log("create")
console.log(this)
// debugger
},
beforeMount() {
console.log("beforeMount")
console.log(this)
// debugger
},
mounted() {
console.log("mounted")
console.log(this)
// debugger
},
beforeUpdate() {
console.log("beforeUpdate")
console.log(this)
// debugger
},
updated() {
console.log("updated")
console.log(this)
// debugger
},
//此时,数据和方法都能访问和调用,但不会更新
//销毁后自定义事件会失效,但原生DOM事件依然有效
beforeDestroy() {
console.log("beforeDestroy")
console.log(this)
// debugger
},
destroyed() {
console.log("destroyed")
console.log(this)
// debugger
},
此外,还有几个特殊的生命周期函数:activated()
和deactivated()
(vue路由独有的两个钩子);
nextTick()
(DOM渲染后执行里面的回调函数)
1.4.2 计算属性
一般用法:函数形式,有返回值
fullName:function(){
return this.firstname+" "+this.lastname
}
实际上,计算属性是一个对象,里面有两个属性:get()
和set()
,上面的用法是get()
的简写形式,set()
一般不用,即一般没有set方法(只读属性),当属性发生改变时会调用set()
,可以在里面写修改属性的函数语句
fullname:{
get:function(){
return this.firstname+" "+this.lastname
},
set:function(newvalue){
const newname=newvalue.split(' ')
this.firstname=newname[0]
this.lastname=newname[1]
}
}
计算属性与方法methods
不同之处在于vue内部会对计算属性computed
进行缓存,例如同样的属性在进行多次使用时,由于methods
是调用函数,所有会在每一次使用时都调用一次函数,而computed
只会调用一次。
<div id="test">
<p>computed:</p>
{{fullname}}
{{fullname}}
{{fullname}}
{{fullname}}
<br>
<p>methods:</p>
{{getFullname()}}
{{getFullname()}}
{{getFullname()}}
{{getFullname()}}
</div>
<script src="./vue.js"></script>
<script>
var vm = new Vue({
el: "#test", //css选择器
data: {
firstname: "aa",
lastname: "bb"
},
computed: {
fullname: function () {
console.log('computed---') //打印一次
return this.firstname + " " + this.lastname
}
},
methods: {
getFullname() {
console.log("methods---") //打印4次
return this.firstname + " " + this.lastname
}
},
})
</script>
1.5 vuex
当我们的应用遇到多个组件共享状态时:
-
多个视图依赖于同一状态。
-
来自不同视图的行为需要变更同一状态。
1.5.1 环境搭建与使用:
和路由一样,新建vuex的文件,然后在main.js中引入import store from './store'
在store/index.js中开始配置使用:
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex)
//创建并暴露store
export default new vuex.Store({
// 用于响应组件中的动作
actions:{ },
// 用于操作数据(state)
mutations:{},
// 用于存储数据
state:{},
//用于将state中的数据进行加工,(相当于全局计算属性,会缓存属性)
getters:{}
})
-
state
:Vuex 使用单一状态树,每个应用将仅仅包含一个 store 实例,state
保存项目全局变量,通过this.$store.state
访问 -
getters
:从 store 中的 state 中派生出一些状态并进行缓存,多个组件都会用到时会从缓存中取而不是频繁调用函数,相当于一个store的计算属性。-
用法:函数参数有两个,
state
和getters
,只用到state
可以不写getters
getters: { // ... doneTodos: state => { return state.todos.filter(todo => todo.done) }, // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length }, // ... //返回值为函数,可传递参数 getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) } }
-
访问:通过属性访问或通过方法访问(一般需要传递参数时),也可以通过
mapGetters
辅助函数将 store 中的 getter 映射到局部的计算属性中进行使用//通过属性访问 store.getters.doneTodos store.getters.doneTodosCount //通过方法访问,可传递参数 store.getters.getTodoById(2)
-
-
mutation
:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,里面的函数参数有两个参数state
和需要传递的参数。mutation 必须是同步函数,否则开发者工具devtools 状态不会改变,因为它 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。-
访问时使用
store.commit('increment', 10)
提交(一般提交) -
使用对象风格提交时,整个对象都作为载荷传给 mutation 函数,此时
mutations
的函数中第二个参数是此对象
//一般提交 store.commit('increment', 10) // store中: mutations: { increment (state, n) { state.count += n } } //对象提交 store.commit({ type: 'increment', amount: 10 }) // store中: mutations: { increment (state, payload) { state.count += payload.amount } }
-
-
action
:执行异步操作,可与Promise
一起使用。类似于 mutation,不同在于:-
Action 提交的是 mutation,而不是直接变更状态。
-
Action 可以包含任意异步操作。
mutations: { increment (state) { state.count++ } }, actions: { incrementAsync (context,payload) { setTimeout(()=>{ context.commit('increment') },1000) } } //使用action: store.dispatch('incrementAsync',payload)
-
1.5.2 map方法:
首先在vuex中引入import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
1. mapState方法:用于帮助我们映射state
中的数据为计算属性
2. mapGetters方法:用于帮助我们映射getters
中的数据为计算属性
3. mapActions方法:用于帮助我们生成与actions
对话的方法,即:$store.dispatch(xxx)
的函数
-
mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即:$store.commit(xxx)
的函数注意:如果 mapActions和mapMutations使用时,若要传递参数,需要在模板中绑定事件时传递好参数,否则参数就是事件对象了。
methods:{
//借助mapMutations生成对应的方法,方法中会被调用commit去联系mutations(对象写法)
...mapMutations({increment:'increment',decrement:'decrement',incrementodd:'incrementodd',incrementwait:'incrementwait'})
//借助mapMutations生成对应的方法,方法中会被调用commit去联系mutations(数组写法)
...mapMutations(['increment','decrement','incrementodd','incrementwait'])
},
computed:{
//借助mapState生成计算属性,从state中提取数据(对象写法)
...mapState({sum:'sum',place:'province',name:'name'}),
//借助mapState生成计算属性,从state中提取数据(数组写法),此时生成的计算属性名字和数据名要一样。
// 数组中的元素(例如sum)两种用途:一种是指向生成的计算属性的 名字(sum),一种是state里的数据($store.state.sum)
...mapState(['sum','province','name']),
...mapGetters(['bigsum'])
}
1.5.3 vuex模块化:
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:(模块也可以单独写到各自文件中,在index.js中引入即可。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
1.5.4 命名空间:
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
1.5.5 带命名空间的绑定函数:
当使用 mapState
, mapGetters
, mapActions
和 mapMutations
这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
1.6 路由
1.6.1 概念与基本使用
-
路由:一组key-value的对应关系
-
多个路由,需要经过路由器管理
-
路由组件通常存放在pages文件夹,一般组件存放在components文件夹
-
通过切换,"隐藏"了的路由组件默认是被销毁的,需要时再挂载
-
每个组件都有自己的$route属性,里面存放自己的路由信息
-
整个应用只有一个router,可以通过组件的$router属性获取
使用:下载插件-->安装插件vue.use(插件)
-->创建router
-->注册router
(在new Vue
时记得注册)
-
设置路由组件映射时记得导入组件
const Home = () => import('../views/home/Home')
-
路由组件映射关系设置默认路径时可以使用重定向(
redirect
) -
router-link
默认a标签,通过tag
属性可以更改 -
router-link
默认使用history.pushState
即可以返回前进,如果想改成history.replaceState
,不让他后退前进,可以添加replace
属性 -
修改当前状态路由的按钮样式可以使用
active-class="active"
属性修改样式,若所有激活状态都是一个样式,可以在router
中设置属性linkActiveClass
-
使用函数代码实现路由跳转:可以使用
this.$router.push('/home')
、this.$router.replace('/home')
-
this.$route
表示当前活跃(激活)的路由,通过this.$route.params
取出参数,例如this.$route.params.userid
// 1.安装插件
Vue.use(VueRouter)
const routes = [
{
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
// 2.创建router
const router = new VueRouter({
// 创建路由组件映射关系
routes,
mode: 'history' //路由模式
linkActiveClass:'active' //设置激活的样式(所有激活状态都是这个样式)
})
<router-link to="/home" tag='button' replace>a</router-link>
<router-link to="/about" tag='li'>b</router-link>
<router-view></router-view>
1.6.2 嵌套路由(多级路由)
使用children配置项:注意:children是一个数组,并且子路由不用写“/”
routes: [
{
path:'/home',
component:home,
children:[
{
path:'aaa',
component:aaa
},
{
path:'bbb',
component:bbb
}
]
},
跳转时要写完整路由:
<router-link to="/home/aaa">a</router-link>
<router-link to="/home/bbb">b</router-link>
<router-view></router-view>
1.6.3 路由传参
-
query参数(路径是
http://localhost:8080/#/home/aaa?id=003&name=消息3
样式的)
传递参数:to的字符串写法和对象写法(对象写法可以使用name或者path)
<ul>
<li v-for="m in meg" :key="m.id">
<!-- 跳转路由并携带query参数,to的字符串写法 -->
<!-- <router-link :to="`/home/aaa?id=${m.id}&name=${m.name}`">{{m.name}}</router-link> -->
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
path:'/home/aaa',
query:{id:m.id,name:m.name}
}">{{m.name}}</router-link>
</li>
</ul>
接收参数:使用$route.query.id
和$route.query.name
接收参数
-
params参数(路径是
http://localhost:8080/#/home/bbb/01/标题1
样式的)
注意:配置路由时必须声明接收params参数(占位)path:'bbb/:num/:title'
{
path:'bbb/:num/:title',//使用占位符声明接收params参数
name:'detail',
component:bbb
}
传递参数:to的字符串写法和对象写法(对象写法只能使用name写法)
<ul>
<li v-for="m in megb" :key="m.num">
<!-- 跳转路由并携带params参数,to的字符串写法 -->
<!-- <router-link :to="`/home/bbb/${m.num}/${m.title}`">{{m.title}}</router-link> -->
<!-- 跳转路由并携带params参数,to的对象写法,注意只能用name写法,不能使用path -->
<router-link :to="{
name:'detail',
params:{num:m.num,title:m.title}
}">{{m.title}}</router-link>
</li>
</ul>
接收参数:使用$route.params.num
和$route.params.title
接收参数
-
props配置:让路由组件更加方便地收到参数,接收时通过
props:['num','title']
接收参数
注意:布尔写法只能传递收到的所有params参数,而query参数不能传递;函数写法都能传递
{
path:'bbb/:num/:title',
name:'detail',
component:bbb,
//props对象写法,此对象中所有的key-value都会以props的形式传给detail组件(当 props 是静态的时候有用)
// props:{a:1,b:'text'}
//props布尔写法,若为真,会把该路由组件收到的所有params参数以props的形式传给detail组件(route.params 将会被设置为组件属性)
// props:true
//props函数写法,函数返回的对象中每一组key-value都会通过props传给detail组件
props($route){
return{num:$route.params.num,title:$route.params.title}
}
}
1.6.4 路由模式与导航
-
路由模式:
默认默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。(即路径中有#这种),兼容性好
路由的 history 模式充分利用 了history.pushState
API 来完成 URL 跳转而无须重新加载页面。可在router中配置:mode:'history'
,兼容性略差,应用部署上线时需要后端解决刷新页面服务端404的问题
router-link
的replace属性:可以控制路由跳转时操作浏览器历史记录的模式。 浏览器历史记录有两种写入方式:push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转默认为push
。开启replace
模式:<router-link replace ......>xxx</router-link>
-
编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。
使用$router.xxx
实现,可用于按钮的点击事件中。
$router.push(...)
:这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到 之前的 URL。当点击 <router-link>
时,这个方法会在内部调用,所以说,点击 <router-link :to="...">
等同于调用 router.push(...)
。
$router.replace(...)
:这个方法和push方法一样,不过它不会向 history 添加新记录,而是 替换掉当前的 history 记录,即:浏览器返回不到原来的url。
上述两个方法中的参数(即...
)可以是字符串路径也可以是描述地址的对象(同to的字符串写法和对象写法)。
$router.back()
:相当于浏览器的后退按钮,返回上一个存在的(未被replace的)url。
$router.forward()
:相当于浏览器的前进按钮,进入到下一个存在的url。
$router.go(n)
:这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步。例如:
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
-
缓存路由组件:让不展示的路由组件保持挂载,不被销毁,使用
keep-alive
标签名,属性:
include
:字符串或正则表达式,匹配的组件会被缓存exclude
:字符串或正则表达式,匹配的组件不会被缓存注意:1. include里添加的是组件名,如果不写include,会将所有的router-view的组件都缓存。
-
keep-alive包在router-view外面。
-
若include包含多个组件,可写成数组形式(
:include="['News','Message']"
)
-
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
-
两个生命周期钩子:vue-router独有的两个生命周期函数,
activated()
和deactivated()
表示组件的激活状态(是否活跃/隐藏),可用于希望在切换路由,同时组件不被销毁的状态下决定部分数据是否缓存,搭配<keep-alive/>
使用
1.6.5 导航守卫
-
路由元信息:定义路由的时候可以配置
meta
字段来给路由添加属性信息。 -
全局前置守卫
router.beforeEach((to, from, next) => {})
其中:to是即将要进入的目标的路由对象,from是当前导航正要离开的路由。
next: Function
: 一定要调用该方法来 resolve这个钩子。执行效果依赖next
方法的调用参数。-
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed(确认的)。 -
next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。 -
next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在router-link
的to
、prop
或router.push
中的选项。 -
next(error)
: 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
-
-
全局解析守卫
router.beforeResolve
,和router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。 -
全局后置钩子
router.afterEach((to, from) => {})
,和守卫不同的是,这些钩子不会接受next
函数也不会改变导航本身。 -
路由独享守卫
beforeEnter:(to, from, next) => {}
(在路由配置上定义),与全局前置守卫的方法参数是一样的。 -
组件内守卫
beforeRouteEnter(to, from, next) {}
(路由组件内定义)beforeRouteUpdate(to, from, next) {}
(路由组件内定义)beforeRouteLeave(to, from, next) {}
(路由组件内定义)const Foo = { template: `...`, beforeRouteEnter(to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this`, 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate(to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave(to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` } }
-
导航解析流程:
-
导航被触发。
-
在失活的组件里调用
beforeRouteLeave
守卫。 -
调用全局的
beforeEach
守卫。 -
在重用的组件里调用
beforeRouteUpdate
守卫 。 -
在路由配置里调用
beforeEnter
。 -
解析异步路由组件。
-
在被激活的组件里调用
beforeRouteEnter
。 -
调用全局的
beforeResolve
守卫 。 -
导航被确认。
-
调用全局的
afterEach
钩子。 -
触发 DOM 更新。
-
调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
小知识点
-
自定义事件,使用$emit定义,解绑使用 $off(),也可以使用$destroy()(或使用生命周期函数)销毁所有自定义事件,例如:
//自定义事件demo,传入参数this.name
this.$emit('demo',this.name);
this.$emit('demo2',this.age);
//解绑事件
this.$off('demo');//解绑一个自定义事件
this.$off(['demo','demo2']);//解绑多个自定义事件(用数组表示)
this.$off();//解绑所有自定义事件
//组件销毁
this.$destroy()//销毁组件实例,同时销毁所有的自定义事件
-
组件内使用原生事件如@click等,需要在后面加.native,即:@click.native = "xxx"
-
重要的内置关系:VueComponent.prototype.__proto__===Vue.prototype(让组件的实例对象可以访问到Vue原型上的属性、方法)
-
对象给对象赋值,会完全变成另一个对象,可能会改变原对象的数据结构,例如:
obj1 = {name:'小白粥',age:22,sex:"男"} obj2 = {name:'深深',age:18} obj1 = obj2 console.log(obj1)//输出{name:'深深',age:18},只有name属性和age属性,丢失sex属性
若想保留原对象中未改变的值,可使用...运算符,例如:
obj1 = {name:'小白粥',age:22,sex:"男"} obj2 = {name:'深深',age:18} obj1 = {...obj1,...obj2}//obj1和obj2共有的属性,以后面的obj2为准,obj1存在而obj2没有的属性会被保留,obj2存在而obj1没有的属性会被添加到obj1 console.log(obj1)//输出{name:'深深',age:18,sex:"男"}
-
$nextTick:方法会先执行完再对DOM进行操作,若想在执行了部分代码后更新DOM再执行后面的代码(回调函数),或者后半部分的代码(回调函数)是基于前半部分更新后的DOM进行操作,使用$nextTick方法。
语法:
this.$nextTick(回调函数)
-
折叠注释代码块:
#region
和#endregion
通过b站coderwhy老师视频和尚硅谷的视频整理,也参考了其他老师的视频笔记以及博客等 ,很多地方官网讲的也比较清楚就直接引用了,日后学习的时候再看看。有错误欢迎指出哈,大家一起交流~~