Vue3祖先后代组件数据双向同步实现方法
在 Vue3 中实现祖先后代组件的双向数据同步,可以通过组合式 API 的 provide/inject
配合响应式数据实现。以下是两种常见实现方案:
方案一:共享响应式对象 + 方法
html
<!-- 祖先组件 --> <script setup> import { ref, provide } from 'vue'const value = ref('初始值')// 提供响应式数据与更新方法 const updateValue = (newVal) => { value.value = newVal } provide('data', { value, updateValue }) </script><!-- 后代组件 --> <script setup> import { inject } from 'vue'const { value, updateValue } = inject('data') </script>
方案二:计算属性双向绑定
html
<!-- 祖先组件 --> <script setup> import { ref, computed, provide } from 'vue'const innerValue = ref('初始值')// 通过计算属性实现双向绑定 provide('data', computed({get: () => innerValue.value,set: (val) => { innerValue.value = val } })) </script><!-- 后代组件 --> <script setup> import { inject } from 'vue'const data = inject('data') </script><!-- 使用时直接 v-model 绑定 --> <input v-model="data">
方案三:Vuex/Pinia 全局状态管理(适合复杂场景)
js
// store.js import { defineStore } from 'pinia'export useStore = defineStore('data', () => {const value = ref('初始值')return { value } })<!-- 任意组件 --> <script setup> import { useStore } from './store' const store = useStore() </script><!-- 直接绑定 --> <input v-model="store.value">
最佳实践建议:
-
简单场景使用
provide/inject + 响应式对象
-
中大型项目推荐使用 Pinia
-
需要严格数据流时,建议通过方法修改数据而非直接赋值
-
使用 TypeScript 时建议定义 InjectionKey 增强类型提示
ts
// 类型安全版本 import type { InjectionKey } from 'vue'interface DataType {value: Ref<string>update: (val: string) => void }const dataKey: InjectionKey<DataType> = Symbol()// 祖先组件 provide(dataKey, { value, updateValue })// 后代组件 const { value, update } = inject(dataKey)! // 非空断言
实例代码:
祖先组件:
<script setup lang="ts" name="SampleInput">
......
// 受理样品表格数据
const applySampleTableData = ref<ApplySample[]>([]);// 提供数据给后代
// 受理样品列表
provide("applySampleList", readonly(applySampleTableData)); // applySampleTableData是个Ref对象,不需要.value,传递ref对象本身(响应式数据)// 提供函数给后代,接收受理样品
provide("transmitApplySampleListData", receiveApplySampleListData);// 接收受理样品
const receiveApplySampleListData = (applySamples: ApplySample[]) => {applySampleTableData.value = applySamples;
};
......
</script>
后代组件:
<script setup lang="ts" name="CommonApplySampleTable">
......
// 接收祖先提供的数据
// 受理样品列表
const applySampleList = inject<Ref<ApplySample[]>>("applySampleList", ref([]));// 注入祖先提供的函数
// 将受理样品列表数据发送给祖先
const sendApplySampleListData = inject("transmitApplySampleListData", (data: ApplySample[]) => {});// 受理样品列表-本地副本
const localApplySampleList = ref<ApplySample[]>([]);
// 标识:是否来自祖先的更新。防循环处理:通过 isUpdatingFromAncestry 和 nextTick() 确保祖先组件更新不会触发后代组件同步
let isUpdatingFromAncestry = false;// 监听祖先组件的 applySampleList 变化,更新本地的 localApplySampleList
watch(() => applySampleList.value,async () => {// 标识是否来自祖先组件的更新为:是isUpdatingFromAncestry = true;// 拷贝祖先组件的 applySampleList 数据到本地的 localApplySampleListlocalApplySampleList.value = reactive(JSON.parse(JSON.stringify(toRaw(applySampleList.value))));// 等待 DOM 更新完成await nextTick();// 标识是否来自祖先组件的更新为:否isUpdatingFromAncestry = false;},{ deep: true }
);// 监听本地的 localApplySampleList 变化,同步数据给祖先组件
watch(() => localApplySampleList.value,() => {// 判断是否来自祖先组件的更新为:否// 也就是自身更新的,才同步数据给祖先组件,祖先组件更新过来,则不需要再更新回去,从而防止循环if (!isUpdatingFromAncestry) {// 同步数据给祖先组件sendApplySampleListData(localApplySampleList.value);}},{ deep: true }
);
......
</script>