vue组件内外数据保持同步 & 组件数据加载解析顺序

发布时间:2024年01月10日

背景与简介

一个公共的组件,比如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),将组件内部数据同步到组件外部,且不再触发dataSourcewatchthis.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,切不会触发listwatchthis.dataSource=[]set、push -> 同上

为什么始终执行某些字段的额外操作,不判断组件内外数据是否一致,或者不判断新老数据是否一致?
解释说明:

  1. 组件内部数据$set,push等修改原数据,新老数据一致
  2. 组件外部数据修改,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 缓存的组件失活时调用。

vue2.0官网图片

在这里插入图片描述

文章来源:https://blog.csdn.net/weixin_45549481/article/details/135501444
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。