提示:以下是本篇文章正文内容,下面案例可供参考
父子组件销毁时的执行顺序:
父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件 mounted --> 父组件mounted -->父组件beforeUpdate -->子组件beforeDestroy–> 子组件destroyed --> 父组件updated
「beforeCreate」、「created」、「beforeMount」、「mounted」、「beforeUpdate」、「updated」、「beforeDestroy」、「destroyed」
created mounted updated mounted
方式1
// Parent.vue
<Child @mounted='doSomething' />
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
this.$emit('mounted')
console.log('子组件触发 mounted 钩子函数 ...');
}
方式2
hook钩子函数
// Parent.vue
包裹在里组件,在切换时会保存其组件的状态,使其不被销毁,防止多次渲染
一般结合路由和动态组件一起使用,用于缓存组件
keep-alive拥有两个独立的生命周期(activated | deactivated),使keep-alive包裹的组件在切换时不被销毁,而是缓存到内存中并执行deactivated钩子,切换回组件时会获取内存,渲染后执行activated钩子
提供include和exclude属性,两者都支持字符串或正则表达式
include 表示只有名称匹配的组件才会被缓存
exclude 表示任何名称匹配的组件都不会被缓存
exclude优先级高于include
页面组件都有自己的name才会生效
1. hash 模式
利用URL中的hash(“#”);
利用History interface在HTML5中新增的方法;
后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。另外每次 hash 值的变化,还会触发hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听hashchange来实现更新页面部分内容的操作
hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件;window的#变化
切换历史状态
2. history 模式
history.pushState()
pushState设置的新url可以是与当前url同源的任意url,而hash只可修改#后面的部分,故只可设置与当前同文档的url
pushState设置的新url可以与当前url一模一样,这样也会把记录添加到栈中,而hash设置的新值必须与原来不一样才会触发记录添加到栈中
pushState通过stateObject可以添加任意类型的数据记录中,而hash只可添加短字符串
pushState可额外设置title属性供后续使用
history模式包括back,forward,go三个方法,对应浏览器的前进,后退,跳转操作
history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进
「全局前置钩子」:beforeEach,beforeResolve,afterEach
「路由独享守卫」:beforeEnter
「组件内部守卫」:beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave
导航解析流程:
导航被触发
在失活的组件里调用beforeRouteLeave离开守卫
调用全局的beforeEach守卫
在重用的组件里调用beforeRouteUpdate守卫
在路由配置里调用beforeEnter守卫
解析异步路由组件
在被激活的组件里调用beforeRouteEnter守卫
调用全局的beforeResolve守卫
导航被确认
调用全局的afterEach守卫
触发Dom更新
用创建好的实例调用beforeRouteEnter守卫中传给next的回调
1.全局守卫 main.js
beforeEach, beforeResolve,afterEach
router.beforeEach((to, from, next) => {
// 全局前置守卫
// if(to.fullPath === '/shoppingCart'){
// //如果没有登录?对不起先去登录一下
// next('/login')
// }
console.log('1 beforeEach', to, from)
next()
})
// 时间触发比 全局前置守卫慢些
router.beforeResolve((to, from, next) => {
// 全局解析守卫
console.log('3 beforeResolve', to, from)
next()
})
router.afterEach((to, from) => {
// 全局后置守卫、钩子
console.log('4 afterEach', to, from)
})
2.独立路由守卫 router.js
beforeEnter 参数 to from
{
path: '/a',
name: 'pageA',
components:{
default:pageA,
ppp:Test
},
beforeEnter:(to,from,next)=>{
console.log('2 beforeEnter',to,from)
next()
},
},
3.组件内的守卫 xxx.vue
export default {
beforeRouteEnter(to,from,next){
//这里 拿不到this
// 路由跳转,使用此组件时触发
console.log('beforeRouteEnter',to,from)
next()
},
beforeRouteUpdate(to,from,next){
//可以获取 this
// /a/123 /a/456 当 组件被复用时,触发此方法
console.log('beforeRouteUpdate',to,from)
next()
},
beforeRouteLeave(to,from,next){
//可以获取this
//路由跳转,不适用此组件时触发
console.log('beforeRouteLeave',to,from)
next()
}
}
首先在定义路由的时候就需要多添加一个自定义字段requireAuth,用于判断该路由的访问是否需要登录。如果用户已经登录,则顺利进入路由, 否则就进入登录页面。
const routes = [{
path: '/',
name: '/',
component: Index
},{
path: '/repository',
name: 'repository',
meta: {
requireAuth: true, // 添加该字段,表示进入这个路由是需要登录的
},
component: Repository
},{
path: '/login',
name: 'login',
component: Login
}];
定义完路由后,我们主要是利用vue-router提供的钩子函数beforeEach()对路由进行判断。
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) { // 判断该路由是否需要登录权限
if (store.state.token) { // 通过vuex state获取当前的token是否存在
next();
}
else {
next({
path: '/login',
query: {redirect: to.fullPath} // 将跳转的路由path作为参数,登录成功后跳转到该路由
})
}
}
else {
next();
}
})
每个钩子方法接收三个参数:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next(‘/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
确保要调用 next 方法,否则钩子就不会被 resolved。
1.使用import
const Home = () => import('@/components/home')
const Index = () => import('@/components/index')
const About = () => import('@/components/about')
2.异步加载
{ path: '/home', name: 'home', component: resolve => require(['@/components/home'],resolve) },
{ path: '/index', name: 'Index', component: resolve => require(['@/components/index'],resolve) },
{ path: '/about', name: 'about', component: resolve => require(['@/components/about'],resolve) }
1.路由模式 mode: hash
其实这跟配置资源的路径有关,打开项目根目录config文件夹下的index.js,定位到build下的assetsPublicPath(dev下也有一个assetsPublicPath,别搞错了),把assetsPublicPath: ‘/’ 修改为相对路径 assetsPublicPath: ‘./’
2.路由模式 mode: history
修改router.js
配置路由 router 的 base 属性, 设置为子目录路径即可
<router-link :to="{name:‘‘}">
查看
router-link>
<router-link :to="{path:‘/home‘}">
查看
router-link>
//name,path都行, 建议用name
// 注意:router-link中链接如果是‘/‘开始就是从根路由开始,如果开始不带‘/‘,则从当前路由开始。
// target=“_blank” 用来跳转到新页面
1.2携带参数跳转
<router-link :to="{name:‘home‘, params: {id:1}}">
// params传参数 (类似post)
// 路由配置 path: "/home/:id" 或者 path: "/home:id"
// 不配置path ,第一次可请求,刷新页面id会消失
// 配置path,刷新页面id会保留
// html 取参 $route.params.id
// script 取参 this.$route.params.id
//(二)
<router-link :to="{name:‘home‘, query: {id:1}}">
// query传参数 (类似get,url后面会显示参数)
// 路由可不配置
// html 取参 $route.query.id
// script 取参 this.$route.query.id
2.在方法里面跳转页面
2.1. 不带参数
this.$router.push(‘/home‘)
this.$router.push({name:‘home‘})
this.$router.push({path:‘/home‘})
想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
2.2. query传参
this.$router.push({name:‘home‘,query: {id:‘1‘}})
this.$router.push({path:‘/home‘,query: {id:‘1‘}})
// html 取参 $route.query.id
// script 取参 this.$route.query.id
2.3. params传参
this.$router.push({name:‘home‘,params: {id:‘1‘}}) // 只能用 name
// 路由配置 path: "/home/:id" 或者 path: "/home:id" ,
// 不配置path ,第一次可请求,刷新页面id会消失
// 配置path,刷新页面id会保留
// html 取参 $route.params.id
// script 取参 this.$route.params.id
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数
1.路由配置传参:/:/
这里的路由传参以编程式router.push(…)为例,声明式与之类似。此处模拟情景为从componentsA.vue页面跳转到componentsB.vue页面传参。首先,路由配置信息如下:
路由配置传参注意书写格式/:id,获取参数都是通过 r o u t e 而不是 route而不是 route而不是router
params传参和query传参区别类似于post和get方法。params传参地址栏不会显示参数,而query传参会将参数显示在地址栏中
params传参刷新页面参数会丢失,另外两种不会
params传参对应的路由属性是name,而query传参对应的路由属性既可以是name,也可以是path
2.query和params区别
query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用params刷新页面id还在
params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失
3. this.$router.replace() (用法同上,push)
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
1.props和#emit;
2.新建一个Vue事件bus对象,然后通过bus. e m i t 触发事件, b u s . emit触发事件,bus. emit触发事件,bus.on监听触发的事件。
bus.$emit('globalEvent',val)
bus.$on('globalEvent',(val)=>{
this.brothermessage=val;
})
var bus=new Vue();
provide:{ for:'test' },
inject:['for'],//得到父组件传递过来的数据
4.v-model
父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值
子组件
props:{
value:String, //v-model会自动传递一个字段为value的prop属性
},
changeValue(){
this.$emit('input',this.mymessage);//通过如此调用可以改变父组件上v-model绑定的值
}
父组件
<p>{{message}}</p>
<child v-model="message"></child>
changeValue(){
this.$parent.message = this.mymessage;//通过如此调用可以改变父组件的值
}
实现原理
v-model只不过是一个语法糖而已,真正的实现靠的还是
v-bind:绑定响应式数据,触发oninput 事件并传递数据
例子
<input v-model="sth" />
// 等同于
<input :value="sth" @input="sth = $event.target.value" />
//自html5开始,input每次输入都会触发oninput事件,所以输入时input的内容会绑定到
sth中,于是sth的值就被改变;
//$event 指代当前触发的事件对象;
//$event.target 指代当前触发的事件对象的dom;
//$event.target.value 就是当前dom的value值;
//在@input方法中,value => sth;
//在:value中,sth => value;
Vue是单项数据流,不是双向绑定;Vue所体现的双向绑定是一种语法糖;
v-bind:绑定响应式数据,触发oninput 事件并传递数据;Object.defineProperty是用来做响应式更新的;
<input :value="sth" @input="sth = $event.target.value" />
<input v-model="msg" />
computed:{
msg:{
get(){
return this.$store.state.obj.msg
},
set(value){
return this.$store.commit('updateMsg',value)
}
}
}
action v-model后不要使用严格模式
const store = new Vuex.Store({
// ...
strict: true
})
父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值
父组件
<p>{{message}}</p>
<child v-model="message"></child>
子组件
props:{
value:String, //v-model会自动传递一个字段为value的prop属性
},
changeValue(){
this.$emit('input',this.mymessage);//通过如此调用可以改变父组件上v-model绑定的值
}
-----------v-model 语法糖
<input v-model="searchText" />
等价于
<input :value="searchText" @input="searchText = $event.target.value" />
组件上
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>
子组件需要这样:
app.component('custom-input', {
props: ['modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
-----------------
app.component('custom-input', {
props: ['modelValue'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) { this.$emit('update:modelValue', value)
}
}
}
})
computed
(1)减少模板中的计算逻辑
(2)能够进行数据缓存
(3)响应式数据依赖固定的数据类型
computed是依赖于其他属性的一个计算值,并且具备缓存,只有当依赖的值发生变化才会更新(自动监听依赖值的变化,从而动态返回内容);
能使用watch属性的场景基本上都可以使用computed属性,而且computed属性开销小,性能高,因此能使用computed就尽量使用computed属性,computed当一个计算属性来执行。 实际上computed会拥有自己的watcher,它具有一个dirty属性来决定computed的值是需要重新计算还是直接复用之前的值,
例如这个例子:
computed: {
sum() {
return this.count + 1
}
}
在sum第一次进行求值的时候会读取响应式属性count,收集到这个响应式数据作为依赖。并且计算出一个值来保存在自身的value上,把dirty设为false,接下来在模板里再访问sum就直接返回这个求好的值value,并不进行重新求值。
而 count 发生变化了以后会通知 sum 所对应的 watcher 把自身的 dirty 属性设置成 true,这也就相当于把重新求值的开关打开来了。这个很好理解,只有 count 变化了, sum 才需要重新去求值。
那么下次模板中再访问到 this.sum 的时候,才会真正的去重新调用 sum 函数求值,并且再次把 dirty 设置为 false,等待下次的开启
watch
(1)比computed更加灵活
(2)watch中可以执行任何逻辑,比如函数节流、Ajax异步数据获取,甚至操作DOM,监听功能等
(3)响应式数据依赖固定的数据类型
watch 就是监听的意思,其专门用来观察和响应Vue实例上的数据的变动。watch是在监听的属性发生变化的时候,触发一个回调,在回调中执行一些逻辑。 但是当您想要执行异步或昂贵的操作以响应不断变化的数据时,这时watch就派上了大用场。其应用场景一般都是搜索框之类的,需要不断的响应数据的变化;
对数组监听的话需要加deep属性 才能监听 触发这个监听事件 Watch中的deep:true是如何实现的?
当用户指定了watch中的deep属性为true时,如果当前监控的值是数组类型,会对对象中的每一项进行求值,此时会将当前watcher存入到对应属性的依赖中,这样数组中的对象发生变化时也会通知数据更新。
不光是数组类型,对象类型也会对深层属性进行 依赖收集;
受现代 JavaScript 的限制 (以及废弃 Object.observe),Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
<div>
<p>obj.a: {{obj.a}}</p>
<p>obj.a: <input type="text" v-model="obj.a"></p>
</div>
new Vue({
el: '#root',
data: {
obj: {
a: 123
}
},
watch: {
obj: {
handler(newName, oldName) {
console.log('obj.a changed');
},
immediate: true,
deep: true
}
}
})
deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler。
优化,我们可以是使用字符串形式监听。
'obj.a': {
handler(newName, oldName) {
console.log('obj.a changed');
},
immediate: true,
对比
computed适用于一个数据被多个数据影响,而watch适用于一个数据影响多个数据。
使用watch监听时,属性必须存在,没有该属性是监听不了的。
watch只能通过监听某一个属性来影响另一个属性,而不会通过影响了某个属性之后再去影响原来的属性,只能是单向的。
So:computed和watch区别在于用法上的不同,computed适合在模板渲染中,如果是需要通过依赖来获取动态值,就可以使用计算属性。而如果是想在监听值变化时执行业务逻辑,就使用watch
返回一个带参数的函数即可给computed传参
computed:{
msg(){
return function(a,b){
return this.name + '的' + a + b;
}
}
},
{{msg('小米','手机') }}
3.可以解释下状态管理器吗?
Vue.directive('highlight', {
bind(el, binding, vnode) {
el.style.background = binding.expression
}
})
-------------------vue3
const app = Vue.createApp({})
app.directive('highlight', {
beforeMount(el, binding, vnode) {
el.style.background = binding.value
}
})
<p v-highlight="yellow">高亮显示此文本亮黄色</p>
------------------全局自定义
const app = Vue.createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
mounted(el) {
// Focus the element
el.focus()
}
})
// 点击按钮放大缩小
Vue.directive("touchScale", {
inserted: function (el) {
if (!el) {
return;
}
let transitionTime = 150;
let scale = 0.7;
el.style.transition = "transform " + transitionTime + "ms ease";
el.style.transform = "scale(1)";
let touchstart = function () {
el.style.transform = "scale(" + scale + ")";
el.lock = true;
setTimeout(() => {
if (!el.lock) {
el.style.transform = "scale(1)";
}
el.lock = false;
}, transitionTime);
};
let touchend = function () {
if (!el.lock) {
el.style.transform = "scale(1)";
} else {
el.lock = false;
}
};
el.addEventListener("touchstart", () => {
touchstart();
});
el.addEventListener("touchend", () => {
touchend();
});
el.addEventListener("mousedown", () => {
touchstart();
});
el.addEventListener("mouseup", () => {
touchend();
});
el.addEventListener("mouseleave", () => {
touchend();
});
},
});
---------局部
注册局部指令,组件中也接受一个 directives 的选项:
directives: {
focus: {
// 指令的定义
mounted(el) {
el.focus()
}
}
}
main.js里注册filter
import * as filters from '@/utils/filter'
Object.keys(filters).forEach(k => Vue.filter(k, filters[k]));
this.$options.filters['keepTwo'](amount)
{{amount|keepTwo}}
{{第一个参数|formatVisitTime(第二个参数)}}
formatVisitTime(beginTime, finishTime) {
return format.formatVisitTime(第一个参数, 第二个参数);
}
vue的内置组件
Vue2.0中一共有五个内置组件:动态渲染组件的component、用于过渡动画的transition-group与transition、缓存组件的keep-alive、内容分发插槽的slot。
路由传参
路由配置传参:/:/
这里的路由传参以编程式router.push(…)为例,声明式与之类似。此处模拟情景为从componentsA.vue页面跳转到componentsB.vue页面传参。首先,路由配置信息如下:
路由配置传参注意书写格式/:id,获取参数都是通过 r o u t e 而不是 route而不是 route而不是router
params传参和query传参区别类似于post和get方法。params传参地址栏不会显示参数,而query传参会将参数显示在地址栏中
params传参刷新页面参数会丢失,另外两种不会
params传参对应的路由属性是name,而query传参对应的路由属性既可以是name,也可以是path
vue双向数据绑定的原理
vue主要通过以下4个步骤实现响应式数据
实现一个监听器「Observer」:对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()在属性上都加上getter和setter,这样后,给对象的某个值赋值,就会触发setter,那么就能监听到数据变化
实现一个解析器「Compile」:解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新
实现一个订阅者「Watcher」:Watcher订阅者是Observer和Compile之间通信的桥梁,主要任务是订阅Observer中的属性值变化的消息,当收到属性值变化的消息时,触发解析器Compile中对应的更新函数
实现一个订阅器「Dep」:订阅器采用发布-订阅设计模式,用来收集订阅者Watcher,对监听器Observer和订阅者Watcher进行统一管理
vue双向绑定缺点
以上已经分析完了 Vue 的响应式原理,接下来说一点 Object.defineProperty 中的缺陷。
如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作,更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。