provide和使用computed跨组件传值 && EventBus【订阅模式】传值 && nextTick原理 && keep-alive原理
provide和inject
provide用于跨组件的传值。在祖先组件的data中提供一个对象,该对象可被注入到子孙组件中,不论组件的层级有多深。但是必须要是嵌套关系,才能实现注入
provide和inject需要一起使用,provide进行传递inject进行注入,实现祖先组件向后代组件进行传值。
传值操作
data中创建一个对象数据,然后provide进行传递,inject进行接收
<template> <hello-world></hello-world> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', data () { return { obj : { name : 'jack', age : 18 } } }, methods : { chenge() { console.log(1) this.obj.name = 'Welcome to Your Vue.js App111' } }, components: { HelloWorld }, provide () { return { obj: this.obj.name, // 这里的值,是基本值,和data中的数据没有关系
obj: this.obj // 需要传递一个对象,而且这个对象还要再data中进行了声明
chenge: this.chenge // 如果是基本值,跟data的数据没有关系,修改的话是修改不了的 } } } </script> <style lang="less"> </style>
子组件进行数据的接收和修改
<template> <div> <h1>{{obj}}</h1> <div> <button @click="chenge">点击修改</button> </div> </div> </template> <script> export default { name: 'HelloWorld', inject : ['obj','chenge'], } </script> <style scoped lang="less"> </style>
注意点
provide属性不是响应式数据,provide里面的数据和data中的数据不是一个数据,provide里面的数据是对data中的数据进行解析获取的。
如果我们想让provide里面的数据是响应式数据,inject可以对其修改。我们首先需要再data中声明一个对象,然后给provide赋值一个对象,在methods中声明一个方法,修改data中的数据。最后给provide赋值一个修改数据的方法,进行传递。inject进行数据和方法的接收,就可以完成数据的修改。
为什么一个要赋值一个对象才可以被修改?因为如果赋值一个基本值,会被解析传递,解析传递和data数据都不是同一个数据。如果是对象数据,不会被解析传递,和data数据是同一个数据。
这样做起来比较麻烦,子组件的数据,需要在父组件中进行声明还必须要声明一个对象。使用Vue.observable()进行响应式的实现。
Vue.observable()响应式的实现
创建一个observable.js文件使用Vue.observable方法把一个普通数据处理成响应式数据
observable.js文件作为公共的存放数据的地方
import Vue from 'vue' // 可以把一个普通数据处理成响应式数据 export const info = Vue.observable({ name: '王路飞', }) //进行方法的声明,修改普通数据 export const close = () => { info.name = '黑胡子' }
App.vue页面直接进行页面的传值
<template> <hello-world></hello-world> </template> <script> import HelloWorld from './components/HelloWorld.vue' import { info , close } from "@/oberver"; export default { name: 'App', components: { HelloWorld }, provide () { return { info, close } } } </script> <style lang="less"> </style>
子组件进行数据的接收
<template> <div> <h1>{{info.name}}</h1> <div> <button @click="close">点击修改</button> </div> </div> </template> <script> export default { name: 'HelloWorld', inject : ['info','close'], } </script> <style scoped lang="less"></style>
这样,就解决了,传递的数据必须是对象,而且必须要在父组件中进行数值的声明
EventBus【订阅模式】组件传值
EventBus,官方定义解决兄弟组件之间的传值,但是它还可以解决了所有组件之间的传值
用法:创建一个单独的js文件,导出一个new Vue()实例
然后在两个组件之间进行数据的传递
创建一个js文件,eventbus的js文件
// import Vue from 'vue' // export default new Vue() /** * 订阅模式 * 共有的事件池子,放入方法,然后去寻找方法 * EventBus,解决兄弟组件之间的传值,可以解决所有组件之间的传值 * 用法:单独的js文件,导出一个new Vue()实例 * new Vue()可以提供一个$on方法,用于监听事件,传递一个事件名,传递一个回调函数,触发事件时,回调函数会被调用 * new Vue()可以提供一个$emit方法,用于触发事件,传递一个事件名,传递一个参数,会触发事件池子中对应的事件 * 非常典型的订阅模式,跨组件通信还是要用Vuex */ // class EventBus { // constructor() { // this.callbacks = {} // } // $on(name, fn) { // this.callbacks[name] = this.callbacks[name] || [] // this.callbacks[name].push(fn) // } // $emit(name, args) { // if (this.callbacks[name]) { // this.callbacks[name].forEach(cb => cb(args)) // } // } // } class EventBus { callbacks = {} // 用于储存使用$on注册的事件 // fn为什么是回调函数? $on()监听事件,传递一个事件名,传递一个回调函数,触发事件时,回调函数会被调用 $on(name, fn) { // name为事件名称,fn为事件回调函数 if (!this.callbacks[name]) { this.callbacks[name].push(fn) } else { this.callbacks[name] = [fn] } } $emit(name, ...args) { if (this.callbacks[name]) { this.callbacks[name].forEach(cb => cb(...args)) } } } const bus = new EventBus() export default bus
声明一个事件进行事件的传递
<template> <div> <div>测试</div> <hello-world></hello-world> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' import eventbus from "@/eventbus"; export default { components: { HelloWorld }, mounted() {// $on()监听事件,传递一个事件名,传递一个回调函数,触发事件时,回调函数会被调用 eventbus.$on('changeMsg', (msg) => { console.log(msg) }) } } </script> <style lang="less"> </style>
进行事件的接收,调用事件,传递参数
<template> <div> <button @click="changeMsg">我是helloWorld组件</button> </div> </template> <script> import eventbus from "@/eventbus"; export default { name: 'HelloWorld', methods: { changeMsg() { eventbus.$emit('changeMsg', '我是helloWorld组件') } } } </script> <style scoped lang="less"></style>
new Vue()可以提供一个$on方法,用于监听事件,传递一个事件名,传递一个回调函数,触发事件时,回调函数会被调用
new Vue()可以提供一个$emit方法,用于触发事件,传递一个事件名,传递一个参数,会触发事件池子中对应的事件
放长典型的订阅模式,但是跨组件通信还是要用Vuex
nextTick异步更新原理
nextTick的作用是获取页面中最新的dom结构,使用到nextTick
当我们进行数据修改之后,会触发到数据的set方法,通知当前数据在get阶段收集到所有的【watcher】依赖,进行更新。
更新不是同步进行更新的,而是通过nextTick注册异步任务进行更新,所以修改完数据以后想要立即获取最新的页面结构是获取不到的。
这个时候我们可以使用nextTick继续注册一个异步任务,同步代码走完的时候,开始清空异步任务,这样我们就可以获取到最新的dom结构
nextTick的异步任务类型有四种,promise,mutationObserver,setImmediate,setTimeout,根据宿主环境进行判断的。
触发数据的get和set方法是哪里来的
当用户传入的数据挂载到vm上之后,vue内部进行判断,是不是一个函数,如果是一个函数通过call方法修改this指向,指向vm实例。如果不是一个函数直接进行赋值给data(1)。
使用observe观测data这个数据,判断是不是一个对象,如果不是一个对象就return出去,不用进行监控,判断是否被劫持过,也就是有没有__ob__属性,劫持过就return。如果不是一个对象并且没有被劫持过,new一个Observer函数观测这个数据。
观测这个数据的时候,先给这个数据添加一个__ob__的属性,然后先判断是不是一个数组,再判断是不是一个对象(2)。如果是一个数组,进行数组的七个方法的重构(3),这样可以让vue检测到数组的这七种方法,然后使用这七种方法的时候,可以动态的进行页面的修改(4),进行递归遍历一直递归遍历到是普通数据类型。然后判断是不是对象,如果是对象给对象进行遍历,一直遍历到是普通数据类型。然后给他们添加一个get方法和set方法。new 一个Dep(5)赋值给数据,数据进行【watcher】的依赖收集。get进行依赖的收集,set进行依赖的触发。当页面渲染的时候触发get方法,当页面数据进行变更的时候触发set方法。
(1)这里判断是不是一个函数之后,是修改this指向,不是直接进行赋值。
也就是为什么data需要是一个函数而不能是一个对象。
多个组件,会被多次调用,被多次调用之后会被多次实例化的。如果是一个对象的话,每次实例化组件的时候,用的都是同一个对象中的数据,在其他组件中继续使用data数据,会造成数据的污染。但是如果使用的是一个函数的话,函数每次实例化的时候会return出来一个对象,然后每个组件中data函数return出来的对象也都不是一个对象,这样就完成了数据的隔离。为什么new Vue中就可以是一个对象呢?new Vue只会被初始化一次,并不会被初始化多次,所以不用数据隔离。
function initData(vm) { // 数据响应式原理 data中的数据需要做一个数据劫持,当我改变数据时,应该更新视图 let data = vm.$options.data; // 用户传入的数据挂载到了vm.$options上 // this.data = vm.$options.data // vm._data 就是检测后的数据了 // vm._data是做什么的? 为了方便后续的操作,将用户传入的data数据,放到vm._data上 // 为data做一个代理,方便用户直接通过vm.key 获取到data里面的属性,用this.key也可以获取到data里面的属性 // 为什么可以使用this进行访问到? 因为在initState中,已经将data代理到了vm实例上了 // 修改this指向,指向vm实例 data = vm._data = typeof data === 'function' ? data.call(vm) : data; // 将数据全部代理到vm的实例上 for (let key in data) { // 将data上的所有属性都代理到vm上 proxy(vm, '_data', key); } // 效果:已经可以使用this.key 获取到data里面的属性了,也可以设置属性了,经过了数据的代理 observe(data); // 观测这个数据 }
(2)为什么先判断是不是一个数组,然后判断是不是一个对象。
按照个人理解来看,我们再type数组的时候,数组也是一个对象,如果我们先判断是不是一个对象,这样就会造成,数组和对象都是对象了,接下来再判断数组的话,就判断不到了。
(3)vue2如何对数组的七种方法进行重构的。
创建一个全新的对象。传入了原生数组的原型对象,作为全新空对象的原型对象。这样我们可以再全新的对象上边找到数组原型上的方法,而且修改对象的时候不会影响原数组的原型方法。
把数组的原型指向这个新的对象,当调用数组的方法时,先从这个新对象上找,如果找不到再去数组原型上找。
使用AOP面向切片编程,在不改变原有逻辑的基础上,对原有逻辑进行扩展,比如在原有逻辑之前或者之后执行一些逻辑。
(4)七种方法才能修改原数组,涉及到一个问题,当页面添加删除对象属性的时候,vue检测不到,也就不会进行更新。当使用数组索引进行数组的修改的时候,vue页检测不到不会进行更新
对象的添加和删除属性:为什么vue会检测不到对象的添加和删除,vue2响应式,在数据劫持的时候,每次会进行递归,然后给数据添加一个__ob__的属性,添加之后会添加一个get和set方法也会添加dep去收集【watcher】依赖。所以这个时候我们添加对象的属性的时候,没有被vue劫持到,vue根本就不知道,vue不知道也就无法检测到对象属性的新增或者删除。
解决办法:vue给我们提供了办法,使用this.$set方法进行数据的新增和删除。实现原理,set方法通过Vue.prototype.$set挂载在vue的原型上边。外部通过索引修改数组,内部通过splice进行数据的修改
使用数组的索引对数组进行修改的时候:编译的时候触发了get依赖,进行更新的时候,触发set方法,但是没有触发到set方法,新添加属性没有进行数据劫持,只能检测到数据的修改,但是不能触发set方法,修改数据
(5)dep和【watcher】观察者模式
当我们对数据进行拦截以后,会添加__ob__,之后我们会添加get和set方法。还有会添加一个dep进行watcher的收集,为什么要使用dep收集watcher?
watcher是一个典型的观察者模式,dep相当于一个目标(有一个subs数组),dep进行watcher的收集,收集之后放在subs数组中,watcher对数据进行观测,当目标数据发生变化的时候,通知所有的观察者,观察者执行对应的方法,更新视图。一次执行watcher的set方法,set方法会触发dep.notify(),dep.notify()会触发watcher的updata方法,将watcher储存到i队列中,使用nextTick中的定时器进行异步更新,同步代码走完,清理异步代码。进行页面的修改。执行watcher的run方法,调用get方法,重新进行页面渲染。
注意点:vue的页面更新时组件级别的更新,不同组件的watcher id是不相同的,但是同一个组件的watcher id是相同的,当页面的多个数据进行改变更新的时候,同一个页面的watcher id是相同的,就不会把相同的id储存到队列中,所以多个数据的更新,只有一个组件的watcher,只更新一次,更新数据的时候,使用的是nextTick异步更新,所以多个而数据的更新,只有一个watcher,只更新了一次。
keep-alive
keep-alive是vue的内置组件,他的作用是让组件不会被销毁。
场景:项目中的表单提交,但是这个表单有多个页面组件,每个页面组件下边都有表单数据,如果不加上keep-alive,用户在切换页面的时候,上一个页面的数据就会清空,用户的体验感非常的差,每次切换之后就要重新输入数据才可以,使用keep-alive包裹了表单组件,切换的时候表单内部的数据是不会被销毁的。
其中有一个max-的参数,可以定义缓存多少个组件实例。用到LRU算法,当超过我们定义的max的时候,会把长时间不经常使用的数据给清楚掉,然后添加上我们的数据。原理应该是,我们每一次进行调用的时候,会清除对应的组件实例,然后重新添加进去,添加到最后一个,然后时间久了前边的组件实例也就是长时间未使用的,当我们添加一个新的组件实例的时候,就会挤掉第一个。
缓存之后,就不会走普通的生命周期钩子了,所以组件不会进行更新了,想要组件进行更新。
需要用到keep-alive的钩子函数,添加keep-alive的时候,组件也会添加两个钩子函数,aactivated和deactivated
首次进入组件的时候:beforeRouteEnter (路由守卫的生命周期函数) > beforeCreate > created > mounted > activated > ... ... > beforeRouteLeave > deactivated
再次进入组件的时候: beforeRouteEnter > activated > ... ... >beforeRouteLeave > deactivated (也就是说先走路由守卫导航,然后走生命周期钩子走到deactivated不会走更新的生命周期钩子)
缓存的是什么东西?
缓存的是当前组件的vnode
keep-alive组件内部维护的cache对象(虚拟dom)
如果说后续二次渲染的时候,先到cache里面查找一下,如果找到了,直接使用缓存的组件(虚拟dom),没有找到的话,继续实例化。
使用场景:我们某些页面不需要让页面组件缓存
我们有些页面不需要缓存,去路由中设置keepAlice属性判断是否缓存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用