背景与简介
一个公共的组件,比如BaseTable,需要同步组件内外部数据的同步,组件内部数据修改,组件外数据同步修改,组件外部数据修改,组件内部数据同步修改;同时,当组件内外部数据修改,需要执行某些数据的计算或赋值,比如动态计算树形表格的索引treeIndex。还要避免数据同步来回更新造成的死循环。
组件数据加载解析顺序
实例初始化完成、props 解析 -> beforeCreate -> 响应式数据、计算属性、方法和侦听器设置完成 -> created -> beforeMount -> mounted
- 组件内外部使用push,$set,不修改原数组对象,会使得watch中的newValue等于oldValue,JSON.stringify(newValue) === JSON.stringify(oldValue)
- watch加了immediate: true,先执行响应式数据初始化,立即触发watch后,走到created生命周期。
- 没有immediate: true,的watch,触发时机是晚于created、mounted的,当数据再次发生变化后,beforeUpdate之前执行;
- watch加了immediate: true,可以监听到响应式数据初始化后的变化。
组件内外执行顺序
1、加载渲染过程
父组件 beforeCreate -> created -> beforeMounted
子组件 beforeCreate -> created -> beforeMounted -> mounted
父组件 mounted
2、更新过程
父组件beforeUpdate
子组件beforeUpdate
子组件updated
父组件updated
3、销毁过程
父组件beforeDestory
子组件beforeDestory
子组件destoryed
父组件destoryed
代码实现
通过watch对组件内外部数据进行监听,实现数据同步
组件外状态数据 dataSource
组件内状态数据 list
@Watch('dataSource', { deep: true, immediate: true })
onDataSourceChange(newValue, oldValue) {
console.log('%c ➡️ 张浩雨: onDataSourceChange -> newValue, oldValue ', 'font-size:16px;background-color:#ed9637;color:white;', newValue, oldValue, this.list)
if (JSON.stringify(newValue) !== JSON.stringify(this.list)) { //* 组件外数据修改,内外数据不相同,组件内数据修改,内外数据相同
//* 组件外数据不等于组件内的数据时,执行某些字段的额外操作
this.baseConfig.showTree && this.setTreeIndex(newValue);
}
this.list = newValue; //* 组件外部数据同步到组件内的数据
// this.listCopy = this.list;
}
@Watch('list', { deep: true })
onListChange(newValue, oldValue) {
console.log('%c 🕘 张浩雨: onListChange -> newValue, oldValue ', 'font-size:16px;background-color:#cfa574;color:white;', newValue, oldValue)
//todo 实时更新查询表单数据
//* 始终执行某些字段的额外操作
this.baseConfig.showTree && this.setTreeIndex(newValue);
if (!newValue.length) this.selectedRecords = []; //* 展示“暂无数据”图片时,销毁了table组件,需要手动置空selectedRecords
this.$emit('update:dataSource', newValue)
}
代码详解
1、组件内外数据,初始化时解析顺序
父组件 beforeCreate -> 响应式数据设置dataSource = [] -> created -> beforeMount
子组件 beforeCreate -> 响应式数据设置list = [],侦听器设置完成(dataSource的监听器immediate为true立即执行,监听到undefined 到 [] 的变化,运行代码this.list = this.dataSource,将组件外部数据同步到组件内的数据 ,没有immediate的不会立即执行)-> created -> beforeMount -> mounted
父组件 mounted
2、组件外部数据update
this.dataSource = [] -> dataSource的监听器监听到数据变更 -> 组件外数据不等于组件内的数据时,执行某些字段的额外操作,并且this.list = newValue,组件外部数据同步到组件内的数据 -> list的监听器监听到数据变更,this. e m i t ( ′ u p d a t e : d a t a S o u r c e ′ , n e w V a l u e ) ,将组件内部数据同步到组件外部,且不再触发 d a t a S o u r c e 的 w a t c h t h i s . d a t a S o u r c e = [ ] , emit('update:dataSource', newValue),将组件内部数据同步到组件外部,且不再触发dataSource的watch this.dataSource = [{}] , emit(′update:dataSource′,newValue),将组件内部数据同步到组件外部,且不再触发dataSource的watchthis.dataSource=[],set、push -> 同上
3、组件内部数据update
变更之前,this.list === this.datasource //* true
this.list = [{}, {}] -> list的监听器监听到数据变更 -> 始终执行某些字段的额外操作,并this.
e
m
i
t
(
′
u
p
d
a
t
e
:
d
a
t
a
S
o
u
r
c
e
′
,
n
e
w
V
a
l
u
e
)
,将组件内部数据同步到组件外部
−
>
d
a
t
a
S
o
u
r
c
e
的监听器监听到数据变更
−
>
组件内数据修改,组件内外数据相同,无需执行某些字段的额外操作,
t
h
i
s
.
l
i
s
t
=
n
e
w
V
a
l
u
e
,切不会触发
l
i
s
t
的
w
a
t
c
h
t
h
i
s
.
d
a
t
a
S
o
u
r
c
e
=
[
]
,
emit('update:dataSource', newValue),将组件内部数据同步到组件外部 -> dataSource的监听器监听到数据变更-> 组件内数据修改,组件内外数据相同,无需执行某些字段的额外操作,this.list = newValue,切不会触发 list的watch this.dataSource = [] ,
emit(′update:dataSource′,newValue),将组件内部数据同步到组件外部−>dataSource的监听器监听到数据变更−>组件内数据修改,组件内外数据相同,无需执行某些字段的额外操作,this.list=newValue,切不会触发list的watchthis.dataSource=[],set、push -> 同上
为什么始终执行某些字段的额外操作,不判断组件内外数据是否一致,或者不判断新老数据是否一致?
解释说明:
- 组件内部数据$set,push等修改原数据,新老数据一致
- 组件外部数据修改,this.list = this.dataSource,组件内外数据一致,所以始终都执行了某些字段的额外操作
参考vue3.0官网生命周期
vue生命周期 | 时机 | 详情 |
---|---|---|
beforeCreate | 在组件实例初始化完成之后立即调用。 | 会在实例初始化完成、props 解析之后、data() 和 computed 等选项处理之前立即调用。注意,组合式 API 中的 setup() 钩子会在所有选项式 API 钩子之前调用,beforeCreate() 也不例外。 |
created | 在组件实例处理完所有与状态相关的选项后调用。 | 当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。 |
beforeMount | 在组件被挂载之前调用。 | 当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。 |
mounted | 在组件被挂载之后调用。 | 组件在以下情况下被视为已挂载:所有同步子组件都已经被挂载。(不包含异步组件或 树内的组件)其自身的 DOM 树已经创建完成并插入了父容器中。注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用,或是在服务端渲染应用中用于确保 DOM 相关代码仅在客户端被调用。 |
beforeUpdate | 在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。 | 这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。 |
updated | 在组件因为一个响应式状态变更而更新其 DOM 树之后调用。 | 父组件的更新钩子将在其子组件的更新钩子之后调用。这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环! |
beforeUnmount | 在一个组件实例被卸载之前调用。 | 当这个钩子被调用时,组件实例依然还保有全部的功能。 |
unmounted | 在一个组件实例被卸载之后调用。 | 一个组件在以下情况下被视为已卸载:其所有子组件都已经被卸载。所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止。可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。 |
vue2.0的官网解释
vue生命周期 | 详情 |
---|---|
beforeCreate | 在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。 |
created | 在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。 |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。mounted: 实例被挂载后调用,这时 el 被新创建的 vm. e l 替换了。如果根实例挂载到了一个文档内的元素上,当 m o u n t e d 被调用时 v m . el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm. el替换了。如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.el 也在文档内。注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick:该钩子在服务器端渲染期间不被调用。 |
beforeUpdate | 在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行。 |
updated | 在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick:该钩子在服务器端渲染期间不被调用。 |
beforeDestroy | 实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。 |
destroyed | 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。该钩子在服务器端渲染期间不被调用。 |
activated | 被 keep-alive 缓存的组件激活时调用。 |
deactivated | 被 keep-alive 缓存的组件失活时调用。 |