vue组件传参-闭包与回调函数的巧妙运用

186 阅读2分钟

场景分析

在管理后台开发中,用户密码修改是一个典型的多组件协作场景。我们通常会遇到以下需求:

  1. 在用户列表页面展示操作按钮
  2. 点击按钮弹出密码修改对话框
  3. 对话框需要获取当前操作用户信息
  4. 提交时需将用户ID与新密码一并发送

组件结构设计

采用主从组件模式:

  • 主组件:用户列表页面(index.vue)
  • 子组件:密码修改对话框(update-password)

核心问题

在现有实现中,当用户点击修改密码按钮时,虽然可以通过openPasswordDialog方法获取当前行数据,但在提交表单时却无法将用户ID传递给提交处理函数,导致无法完成API请求。

主组件(index.vue)

<script setup lang="ts">
import { UpdatePassword } from '@smart-campus/components';
// 重置密码dialog
const passwordVisable = ref(false);
    
// 接受当前行的属性,并将处理后的属性绑定到组件上
let passwordBind;
const openPasswordDialog = (row: User) => {
  passwordBind = {
    title: `重置用户${row.username}的密码`,
    isConfirm: true,
    minLevel: 'medium',
    strengthLevel: 'strong',
    rules: { newPassword: passwordRules },
  };
  passwordVisable.value = true;
};
</script>
​
<template>
    <!-- 修改密码 -->
    <update-password v-model:visible="passwordVisable" v-bind="passwordBind" />
    ...
     <!-- 表格区域 -->
    <el-table>
         <el-table-column>
            <template #operation="{ row }"> 
                 <!-- 打开模态框,并传入当前行 -->
                <el-button @click="openPasswordDialog(row)"> 修改密码 </el-button>  
            </template>
          </el-table-column>
    </el-table>
</template>

解决方案:闭包与回调函数的巧妙运用

解决方案概述

通过JavaScript的闭包特性,我们可以在打开对话框时保留用户信息,确保在提交表单时能够同时访问用户ID和新密码。

子组件改造

首先,我们需要修改子组件的props定义,添加onSubmit回调函数属性:

// 在子组件的props类型定义中添加
onSubmit?: (newPassword: string) => Promise<void> | void;

关键实现点:

  1. 优先使用props传入的onSubmit回调:提供更灵活的控制方式
  2. 保持向后兼容:如果没有提供onSubmit则触发submit事件
  3. 确保表单验证通过后才执行提交:保证数据有效性
async function handleSubmit() {
  try {
    await formRef.value?.validate();
    if (isFormValid.value) {
      // 优先使用 props.onSubmit,如果不存在则使用 emits
      if (props.onSubmit) {
        await props.onSubmit(form.newPassword);
      } else {
        emits('submit', form.newPassword);
      }
      handleCancel();
    }
  } catch (error) {
    console.error('密码修改失败:', error);
  }
}

总的代码如下:

<template>
  <el-dialog v-model="dialogVisible">
    <el-form :model="form" :rules="props.rules" ref="formRef" >
      <el-form-item prop="newPassword">
        <el-input
          v-model="form.newPassword"
          type="password"
          show-password
          placeholder="请输入新密码"
        />
      </el-form-item>
    </el-form>
​
    <template #footer>
        <el-button @click="handleCancel">取消</el-button>
        <el-button type="primary" @click="handleSubmit" :disabled="!isFormValid" >
          确定
        </el-button>
    </template>
  </el-dialog>
</template>
​
<script setup lang="ts">
import { ref, reactive, computed } from 'vue';
import { ElDialog, ElForm, ElFormItem, ElInput, ElButton } from 'element-plus';
import type { FormItemRule, FormInstance } from 'element-plus';
​
const formRef = ref<FormInstance>();
type IProps = {
  // 其他属性...
  /** 提交回调函数 */
  onSubmit?: (newPassword: string) => Promise<void> | void;
};
const props = defineProps<IProps>();
const emits = defineEmits(['submit', 'update:visible']);
// 使用计算属性处理 v-model 双向绑定
const dialogVisible = computed({
  get: () => props.visible,
  set: (val) => emits('update:visible', val),
});
​
// 表单数据
const form = reactive({
  newPassword: '',
  confirmPassword: '',
});
    
// 表单验证
const isFormValid = computed(() => {
  ...
  return boolean
});
​
// 提交操作
async function handleSubmit() {
  try {
    await formRef.value?.validate();
    if (isFormValid.value) {
      // 优先使用 props.onSubmit,如果不存在则使用 emits
      if (props.onSubmit) {
        await props.onSubmit(form.newPassword);
      } else {
        emits('submit', form.newPassword);
      }
      handleCancel();
    }
  } catch (error) {
    console.error('密码修改失败:', error);
  }
}
</script>
​

主组件优化

利用JavaScript闭包特性,在打开对话框时保留用户信息:

let passwordBind;
const openPasswordDialog = (row: User) => {
  passwordBind = {
    title: `重置用户${row.username}的密码`,
    isConfirm: true,
    minLevel: 'weak',
    strengthLevel: 'strong',
    rules: { newPassword: passwordRules },
    onSubmit: (newValue: string) => editPassword(row, newValue), // 闭包保留row引用
  };
  passwordVisable.value = true;
};
​
const editPassword = async (row: User, newValue: string) => {
  // 这里可以同时访问row.id和newValue
  await updateUserPassword({
    id: row.id,
    newPassword: newValue
  });
};

方案优势

  1. 代码解耦:子组件不关心具体的业务逻辑,只负责密码输入和验证
  2. 数据完整性:确保提交时同时拥有用户ID和新密码
  3. 灵活性:既支持通过props传递回调函数,也支持通过emit事件
  4. 可维护性:业务逻辑集中在主组件,便于统一管理

总结

通过合理运用JavaScript闭包特性和Vue的组件通信机制,我们优雅地解决了密码修改功能中用户ID传递的问题。这种方案不仅解决了当前问题,还为类似场景提供了可复用的模式,值得在组件开发中推广应用。