问题描述:
在将 Vue 代码从选项式 API 改造为组合式 API 的过程中,我遇到了一个关于参数传递的困惑:
原本的handleConfirmDelivery(order)方法,在组合式 API 中被封装到了useConfirmDelivery组合式函数中,变成了这样的结构:
export function useConfirmDelivery(orders, loading, nonce) {
const handleConfirmDelivery = async (order) => {
// 处理逻辑...
};
return { handleConfirmDelivery };
}
这里的疑问是:useConfirmDelivery函数的参数列表中包含了orders、loading、nonce这些 “全局” 数据,但实际处理逻辑中用到的order(当前操作的单个订单)却没有在useConfirmDelivery初始化时传递,而是在调用handleConfirmDelivery时才传入呢?
order 为什么不在组合式函数的参数里?
useConfirmDelivery 函数的参数 (orders, loading, nonce) 是组合式函数初始化时需要的 “全局” 数据,而 handleConfirmDelivery(order) 中的 order 是每次调用该方法时传入的 “局部” 数据(即用户当前操作的单个订单)。
具体来说:
orders:所有订单的响应式列表(从初始化数据来,整个组件中共享)。
loading:全局加载状态对象(用于管理所有订单的加载状态)。
nonce:安全验证标识(整个组件中通用,只需传入一次)。
而 order 是用户点击某个具体订单的 “确认收货” 按钮时才确定的单个订单数据,每次点击的订单可能不同,因此不需要在组合式函数初始化时传递,而是在调用 handleConfirmDelivery 时动态传入。
完整逻辑流程(以用户点击为例)
(1)组合式函数初始化(组件加载时)
// 在主组件的 setup 中
const { orders, loading } = useOrderData(initialOrders);
// 初始化 useConfirmDelivery 时,传入全局数据(orders/loading/nonce)
const { handleConfirmDelivery } = useConfirmDelivery(orders, loading, nonce);
此时,useConfirmDelivery 内部已经 “记住” 了 orders(所有订单)、loading(全局加载状态)、nonce(安全标识),为后续调用 handleConfirmDelivery 做好了准备。
(2)用户点击某个订单的 “确认收货” 按钮(触发时)
模板中:
<button @click="handleConfirmDelivery(order)">确认收货</button>
当用户点击时,当前订单对象 order 会作为参数传入 handleConfirmDelivery:
// useConfirmDelivery 内部的方法
const handleConfirmDelivery = async (order) => {
// 这里的 order 就是用户当前点击的单个订单
const result = await AlertService.confirm(`确认收到订单 #${order.number} 吗?`);
if (!result.isConfirmed) return;
// 使用初始化时传入的 loading 对象,标记当前订单为加载中
loading.value[order.id] = true;
try {
// 调用 API 时,使用初始化时传入的 nonce
const data = await confirmDelivery(order.id, nonce);
if (data.success) {
// 找到全局 orders 中对应的订单并更新状态
const targetOrder = orders.value.find(o => o.id === order.id);
if (targetOrder) {
targetOrder.status = 'completed';
}
}
} finally {
loading.value[order.id] = false;
}
};
所以,我们就知道:useConfirmDelivery(orders, loading, nonce) 传入的是组合式函数 “出生” 时就需要的全局数据,而 handleConfirmDelivery(order) 中的 order 是每次调用时才确定的局部数据(当前操作的订单)。这种设计既保证了组合式函数的通用性,又能灵活处理动态传入的具体数据,是组合式 API 中常见的逻辑组织方式。
原生JavaScript原理
核心是 JavaScript 的闭包特性
当 useConfirmDelivery 函数返回 handleConfirmDelivery 方法时,handleConfirmDelivery 会保留对 useConfirmDelivery 作用域中变量的引用(即 orders、loading、nonce),即使 useConfirmDelivery 执行完毕,这些变量也不会被销毁。
// JavaScript 基础示例(与 Vue 无关)
function createHandler(globalData) {
// 这里的 globalData 是 "全局" 数据(创建时传入)
return function handleAction(localData) {
// 闭包:可以访问外部函数的 globalData
console.log('全局数据:', globalData);
console.log('当前操作的局部数据:', localData);
};
}
// 初始化:传入全局数据
const handler = createHandler({ appName: 'MyApp' });
// 调用时:传入当前操作的局部数据
handler({ id: 1, name: 'item1' });
// 输出:
// 全局数据: { appName: 'MyApp' }
// 当前操作的局部数据: { id: 1, name: 'item1' }
Vue 组合式 API 的应用场景
Vue 组合式 API 利用了这种 JavaScript 特性,将相关逻辑(如 “确认收货” 的全局状态和局部操作)封装在组合式函数中:
orders/loading/nonce 是组件级的全局状态(需要在整个交互过程中共享)。
order 是每次用户操作的动态数据(随点击事件变化)。
这种设计让组合式函数既保持了对全局状态的访问能力,又能灵活处理每次操作的局部数据,是逻辑封装的最佳实践,但本质依赖的是 JavaScript 的闭包机制。