1. 问题背景
在使用 Arco Design Vue 的穿梭框组件 a-transfer 时,为了实现更复杂的左侧面板布局(例如,使用表格展示数据),我们通常会利用其 source 作用域插槽。在插槽内,我们放置一个 a-table 组件来渲染数据。
场景复现: 将 a-table 放入 a-transfer 的 source 插槽中,并尝试通过 row-selection 属性来管理表格的行选择,将 a-transfer 提供的 selectedKeys 和 onSelect 方法绑定到 a-table。
遇到的问题: 点击 a-table 中的复选框(Checkbox)时,行没有被选中,并且 row-selection 的 onChange 事件没有被触发。
2. 根本原因分析:Prop 的不稳定性
这个问题的核心在于 Vue 组件的 Prop 稳定性。
在 a-transfer 的 source 插槽中,每次 a-transfer 内部状态发生变化(例如,用户输入搜索词),该插槽内容都会被重新渲染。
在最初的错误实现中,代码如下:
vue
<!-- 错误示例 -->
<template #source="{ data, selectedKeys, onSelect }">
<a-table
:data="data"
:row-selection="{
type: 'checkbox',
showCheckedAll: true,
selectedRowKeys: selectedKeys, // 动态数据
onChange: (keys) => { onSelect(keys) } // 动态方法
}"
...
/>
</template>
这里的 :row-selection 绑定的是一个在每次渲染时都重新创建的匿名对象。对于子组件 a-table 来说,它在每次父级(插槽)更新时都会接收到一个全新的 row-selection prop 对象。
当 Vue 检测到一个对象类型的 prop 引用发生变化时,它可能会认为这是一个全新的配置,从而导致子组件内部依赖此 prop 的逻辑(如事件监听器)被重置或销毁再重建。这种不稳定的 prop 传递破坏了 a-table 内部事件系统的连续性,导致 onChange 事件监听器失效。
3. 解决方案:分离配置与状态
正确的做法是遵循 Vue 的单向数据流和组件通信最佳实践,将静态配置与动态状态分离。a-table 组件为此提供了 v-model:selectedKeys 的语法糖,其本质是 :selected-keys 和 @update:selected-keys 的组合。
推荐的解决方案代码:
vue
<!-- 正确示例 -->
<template #source="{ data, selectedKeys, onSelect }">
<a-table
row-key="value"
:columns="columns"
:data="data"
:pagination="pagination"
// 1. 静态配置:提供一个在渲染中保持不变的静态对象
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
// 2. 状态下发:使用 a-transfer 提供的 selectedKeys 来控制表格的选中项
:selected-keys="selectedKeys"
// 3. 状态更新:监听表格的更新事件,并调用 a-transfer 提供的 onSelect 方法
@update:selected-keys="onSelect"
...
/>
</template>
4. 方案解析
这种方法之所以有效,是因为它将职责清晰地分开了:
-
:row-selection="{ type: 'checkbox', ... }"
- 这个 prop 现在接收的是一个静态对象。它的引用在组件的多次渲染之间保持不变。
a-table因此可以稳定地初始化其选择功能,不会因为父组件的重渲染而丢失内部状态或事件监听器。
-
:selected-keys="selectedKeys"
- 这是“状态向下”的传递。
a-transfer将其当前的选中项selectedKeys传递给a-table,a-table根据这个数组来决定哪些行应该被勾选。
- 这是“状态向下”的传递。
-
@update:selected-keys="onSelect"
- 这是“事件向上”的通知。当用户在
a-table中点击复选框,a-table内部会计算出新的选中项数组,并通过update:selected-keys事件将其派发出去。 - 我们监听到这个事件,并直接调用
a-transfer提供的onSelect方法,将新的选中项数组传递给它,从而完成了状态同步的闭环。
- 这是“事件向上”的通知。当用户在
5. 结论
在 Vue 开发中,尤其是在使用复杂组件的插槽时,务必注意传递给子组件 prop 的稳定性。对于期望接收对象或数组的 prop,应避免在模板中直接创建匿名对象或数组,因为这会在每次渲染时生成新的引用,可能导致子组件行为异常。
通过利用组件(如 a-table)提供的 v-model 或其展开形式(:prop + @update:prop),我们可以建立一个清晰、稳定且高效的父子组件通信模式,从而避免此类问题的发生。