Vue3中Provide/Inject与this.$slots.default的响应性差异及解决方案
问题解答
一、this.$slots.default访问vnode与存入数组的核心差异
$slots是实时响应的动态集合
Vue的$slots属于组件渲染上下文的一部分,每次组件触发重新渲染时,$slots.default都会返回当前最新的vnode列表——它内部绑定了组件的依赖追踪机制,当slot内容(比如子组件的props)变化时,$slots会自动更新,确保拿到的是最新状态的vnode。存入数组的vnode是静态快照
当你把this.$.vnode或从slot中拿到的vnode存入数组,本质是保存了某一时刻的vnode引用:
- vnode本身不是响应式对象,即使数组用
reactive包裹,也只能追踪数组的增删改操作,无法感知vnode内部props的变化; this.$.vnode是组件内部的私有实例,它的更新由Vue渲染引擎管控,手动存储后脱离了原有的响应链路,后续props变更不会同步到数组中的vnode副本。
二、通过Provide/Inject确保vnode变化被正确响应的解决方案
方案1:传递响应式属性而非直接传递vnode
放弃传递整个vnode,转而把Column的核心配置(如字段名、表头、可见性等)用响应式对象包装后传递给DataTable,让DataTable直接监听这些属性的变化:
<!-- Column组件 --> <script setup> import { defineProps, inject, toRefs } from 'vue'; const props = defineProps(['field', 'header', 'visible']); const registerColumn = inject('registerColumn'); // 传递响应式的props引用 registerColumn(toRefs(props)); </script>
<!-- DataTable组件 --> <script setup> import { reactive, provide } from 'vue'; const columns = reactive([]); const registerColumn = (columnProps) => { columns.push(columnProps); }; provide('registerColumn', registerColumn); // 后续基于columns中的响应式props渲染表格,属性变更会自动触发更新 </script>
方案2:Column属性变更时主动通知DataTable
在Column组件中监听自身props的变化,当属性更新时调用DataTable提供的方法主动同步:
<!-- Column组件 --> <script setup> import { defineProps, inject, watch, onMounted } from 'vue'; const props = defineProps(['field', 'header']); const registerColumn = inject('registerColumn'); const updateColumn = inject('updateColumn'); const columnKey = Symbol(); onMounted(() => { // 注册时传递唯一标识和初始props registerColumn(columnKey, { ...props }); }); // 深度监听props变化,主动通知更新 watch(props, (newProps) => { updateColumn(columnKey, { ...newProps }); }, { deep: true }); </script>
<!-- DataTable组件 --> <script setup> import { reactive, provide } from 'vue'; const columnsMap = reactive(new Map()); const registerColumn = (key, props) => { columnsMap.set(key, props); }; const updateColumn = (key, newProps) => { columnsMap.set(key, newProps); }; provide('registerColumn', registerColumn); provide('updateColumn', updateColumn); // 渲染时从columnsMap中获取最新配置 </script>
方案3:结合slot的响应性与Provide/Inject标记
如果必须基于vnode处理,可以保留$slots的响应性,同时用Provide/Inject给Column打标识,让DataTable在每次渲染时从slot中筛选出目标vnode:
<!-- Column组件 --> <script setup> import { provide } from 'vue'; // 提供标识,标记这是一个DataTable Column provide('isDataTableColumn', true); </script>
<!-- DataTable组件 --> <script setup> import { computed } from 'vue'; // 每次渲染都从slot中筛选出标记过的Column vnode const tableColumns = computed(() => { return (this.$slots.default?.() || []).filter(vnode => { // 从vnode的组件实例中获取注入的标识 return vnode.component?.provides.isDataTableColumn; }); }); </script>
这种方式既保留了slot的自动响应性,又支持Column被自定义组件包裹的场景。
内容的提问来源于stack exchange,提问作者Antonio Dell
相关产品推荐
相关产品推荐

