Vue组件之间通信方式有哪些
vue是组件化开发框架,所以对于vue应用来说组件间的数据通信非常重要。
1. 组件通信常用方式有以下8种:
-
props
-
$emit/
$on -
$children/$parent -
$attrs/
$listeners -
ref
-
$root
-
eventbus
-
vuex
注意vue3中废弃的几个API
在vue3中废除$children
移除$listeners
$on、$off和$once实例方法被移除
2.根据组件之间关系讨论组件通信最为清晰有效
-
父子组件
props / $emit / $parent / ref / $attrs
-
兄弟组件
$parent / $root / eventbus / vuex
-
跨层级关系
eventbus / vuex / provide + inject
v-if和v-for优先级问题
文档中曾有详细说明v2|v3;
v2中文文档地址https://cn.vuejs.org/v2/style-guide/#%E9%81%BF%E5%85%8D-v-if-%E5%92%8C-v-for-%E7%94%A8%E5%9C%A8%E4%B8%80%E8%B5%B7%E5%BF%85%E8%A6%81
v3中文文档地址https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for
-
实践中不应该把v-for和v-if放一起
-
在vue2中,v-for的优先级是高于v-if,把它们放在一起,输出的渲染函数中可以看出会先执行循环再判断条件,哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比较浪费;另外需要注意的是在vue3中则完全相反,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,就会导致异常
-
通常有两种情况下导致我们这样做:
-
为了过滤列表中的项目 (比如 v-for="user in users" v-if="user.isActive" )。此时定义一个计算属性 (比如 activeUsers ),让其返回过滤后的列表即可(比如users.filter(u=>u.isActive) )。
-
为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers" )。此时把 v-if 移动至容器元素上 (比如 ul 、 ol )或者外面包一层 template 即可。
-
文档中明确指出永远不要把 v-if 和 v-for 同时用在同一个元素上,显然这是一个重要的注意事项。
-
源码里面关于代码生成的部分,能够清晰的看到是先处理v-if还是v-for,顺序上vue2和vue3正好相反,因此产生了一些症状的不同,但是不管怎样都是不能把它们写在一起的。
知其所以然:
v2源码
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
}
JavaScript
v3源码
switch(node.type){
case NodeTypes.ELEMENT:
case NodeTypes.IF:
case NodeTypes.FOR;
__DEV__&&
assert(
node.codegenNode != null,
`Codegen node is missing for element/if/for node. `+
`Apply appropriate transforms first.`
)
genNode(node.codegenNode!, context)
break
}
JavaScript
Vue的生命周期以及每个阶段做的事情
1.每个Vue组件实例被创建后都会经过一系列初始化步骤,比如,它需要数据观测,模板编译,挂载实例到dom上,以及数据变化时更新dom。这个过程中会运行叫做生命周期钩子的函数,以便用户在特定阶段有机会添加他们自己的代码。
2.Vue生命周期总共可以分为8个阶段:创建前后, 载入前后, 更新前后, 销毁前后,以及一些特殊场景的生命周期。vue3中新增了三个用于调试和服务端渲染场景。
生命周期V2 |
生命周期V3 |
描述 |
beforeCreate |
beforeCreate |
组件实例被创建之初 |
created |
created |
组件实例已经完全创建 |
beforeMount |
beforeMount |
组件挂载之前 |
mounted |
mounted |
组件挂载到实例上去之后 |
beforeUpdate |
beforeUpdate |
组件数据发生变化,更新之前 |
updated |
updated |
数据数据更新之后 |
beforeDestroy |
beforeUnmounted |
组件实例销毁之前 |
destroyed |
unmounted |
组件实例销毁之后 |
生命周期V2 |
生命周期V3 |
描述 |
activated |
activated |
keep-alive 缓存的组件激活时 |
deactivated |
deactivated |
keep-alive 缓存的组件停用时调用 |
errorCaptured |
errorCaptured |
捕获一个来自子孙组件的错误时被调用 |
- |
renderTracked |
调试钩子,响应式依赖被收集时调用 |
- |
renderTriggered |
调试钩子,响应式依赖被触发时调用 |
- |
serverPrefetch |
ssr only,组件实例在服务器上被渲染前调用 |
-
生命周期流程图:
-
结合实践:
beforeCreate:通常用于插件开发中执行一些初始化任务。
created:组件初始化完毕,可以访问各种数据,获取接口数据等。
mounted:dom已创建,可用于获取访问数据和dom元素;访问子组件等。
beforeUpdate:此时 view 层还未更新,可用于获取更新前各种状态。
updated:完成 view 层的更新,更新后,所有状态已是最新。
beforeunmounted:实例被销毁前调用,可用于一些定时器或订阅的取消。
unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器。
注意 :
setup执行时间比created更早 为了兼容vue2、 beforeCreate 和 created 在vue3中可以正常使用,但是不能在setup 中 写 onbeforeCreate 以及 created
Vue双向绑定+响应式原理
数据的变化影响了试图 试图的变化影响了数据 这就是mvvm
-
原理
mvvm是通过数据劫持实现的,通过Object.defineProperty(),通过对各个属性进行一个getter,setter进行一个监听,当数据发生变化的时候通知订阅者,触发回调函数,更新试图。
-
数据劫持
mvvm 是一个类 通过Observer (劫持监听所有属性、setter)在通过Watcher(观察者模式)通知变化,在通过添加订阅者,再更新试图。 最后通过Compile解析指令。
指令解析器---------Compile
数据监听器--------Observer
更新试图-----------Watcher
vue是采用数据劫持配合发布者-订阅者模式的方式,通过Object.definerProperty()来劫持各个属性的setter 和 getter ,在数据变动时,发布消息给依赖收集器,去通知观察者更新,做出对应的回调函数,去更新试图。
MVVM 作为绑定的入口,整合了Observer,Compile 和Watcher 三者,通过Observer来监听model 数据变化表,通过Compile 来解析编译模版指令,最终利用Watcher搭起Observer 和Compile 之间的通信桥梁。达到数据变化去影响试图的更新,试图交互变化影响数据model变更的双向绑定效果
-
MVVM框架中要解决的一个核心问题是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,要做到这点的就需要对数据做响应式处理,这样一旦数据发生变化就可以立即做出更新处理。
-
以vue为例说明,通过数据响应式加上虚拟DOM和patch算法,开发人员只需要操作数据,关心业务,完全不用接触繁琐的DOM操作,从而大大提升开发效率,降低开发难度。
-
vue2中的数据响应式会根据数据类型来做不同处理,如果是对象则采用Object.defineProperty()的方式定义数据拦截,当数据被访问或发生变化时,我们感知并作出响应;如果是数组则通过覆盖数组对象原型的7个变更方法,使这些方法可以额外的做更新通知,从而作出响应。这种机制很好的解决了数据响应化的问题,但在实际使用中也存在一些缺点:比如初始化时的递归遍历会造成性能损失;新增或删除属性时需要用户使用Vue.set/delete这样特殊的api才能生效;对于es6中新产生的Map、Set这些数据结构不支持等问题。
-
为了解决这些问题,vue3重新编写了这一部分的实现:利用ES6的Proxy代理要响应化的数据,它有很多好处,编程体验是一致的,不需要使用特殊api,初始化性能和内存消耗都得到了大幅改善;另外由于响应化的实现代码抽取为独立的reactivity包,使得我们可以更灵活的使用它,第三方的扩展开发起来更加灵活了。
nextTick的用法和原理
nextTick是用于获取下次DOM更新刷新的使用函数。
-
Vue有个异步更新策略,意思是如果数据变化,Vue不会立刻更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在DOM上,此时如果想要获取更新后的DOM状态,就需要使用nextTick。
-
开发时,比如我希望获取列表更新后的高度就可以通过nextTick实现。
-
nextTick签名如下: function nextTick(callback?: () => void): Promise<void> 所以我们只需要在传入的回调函数中访问最新DOM状态即可,或者我们可以await nextTick方法返回的Promise之后做这件事。
-
在Vue内部,nextTick之所以能够让我们看到DOM更新后的结果,是因为我们传入的callback会被添加到队列刷新函数(flushSchedulerQueue)的后面,这样等队列内部的更新函数都执行之后,所有DOM操作也就结束了,callback自然能够获取到最新的DOM值。
剑飞csdn博客附加$set
watch和computed的区别
-
计算属性可以从组件数据派生出新数据,最常⻅的使用方式是设置一个函数,返回计算之后的结果,computed和methods的差异是它具备缓存性,如果依赖项不变时不会重新计算。侦听器可以侦测某个响应式数据的变化并执行副作用,常⻅用法是传递一个函数,执行副作用,watch没有返回值,但可以执行异步操作等复杂逻辑。
-
计算属性常用场景是简化行内模板中的复杂表达式,模板中出现太多逻辑会是模板变得臃肿不易维护。侦听器常用场景是状态变化之后做一些额外的DOM操作或者异步操作。选择采用何用方案时首先看是否需要派生出新值,基本能用计算属性实现的方式首选计算属性。
-
使用过程中有一些细节,比如计算属性也是可以传递对象,成为既可读又可写的计算属性。watch可以传递对象,设置deep、immediate等选项。
-
vue3中watch选项发生了一些变化,例如不再能侦测一个点操作符之外的字符串形式的表达式; reactivityAPI中新出现了watch、watchEffect可以完全替代目前的watch选项,且功能更加强大。