TS和组件绑定过程(泛型表格)

 更新时间:2026年05月15日 09:13:43   作者:淑子啦  
文章介绍了使用React+TS和Vue3;TS实现泛型通用表格和标准公共组件的方法,重点在于类型规范、事件规范、插槽规范等,强调了全程无`null`、类型约束等,并提供了配套的全局类型规范文件,这些实践有助于提升代码质量和可复性性

一、React + TS 泛型通用表格

GenericTable.tsx

import React from 'react';

// 列配置类型
export type TableColumn<T> = {
  key: keyof T;
  title: string;
  width?: number;
  render?: (val: T[keyof T], record: T) => React.ReactNode;
};

// 组件入参
interface GenericTableProps<T> {
  columns: TableColumn<T>[];
  dataSource: T[];
  loading?: boolean;
}

// 泛型组件写法
function GenericTable<T>(props: GenericTableProps<T>) {
  const { columns, dataSource, loading = false } = props;

  if (loading) return <div>加载中...</div>;

  return (
    <table border={1} cellPadding={6} cellSpacing={0}>
      <thead>
        <tr>
          {columns.map((col) => (
            <th key={String(col.key)} style={{ width: col.width }}>
              {col.title}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {dataSource.map((record, idx) => (
          <tr key={idx}>
            {columns.map((col) => {
              const val = record[col.key];
              return (
                <td key={String(col.key)}>
                  {col.render ? col.render(val, record) : String(val)}
                </td>
              );
            })}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

export default GenericTable;

使用示例

import GenericTable from './GenericTable';

// 业务实体
interface UserItem {
  id: number;
  name: string;
  status: 0 | 1;
}

function Demo() {
  const columns: TableColumn<UserItem>[] = [
    { key: 'id', title: 'ID' },
    { key: 'name', title: '姓名' },
    {
      key: 'status',
      title: '状态',
      render: (val) => (val === 1 ? '启用' : '禁用')
    }
  ];

  const data: UserItem[] = [
    { id: 1, name: '张三', status: 1 },
    { id: 2, name: '李四', status: 0 }
  ];

  return <GenericTable columns={columns} dataSource={data} />;
}

二、Vue3 + TS 标准公共组件模板

BaseDialog.vue

<template>
  <!-- 遮罩层 -->
  <div 
    class="base-dialog-mask" 
    v-if="visible"
    @click.self="handleCloseMask"
  >
    <!-- 弹窗容器 -->
    <div class="base-dialog" :style="dialogStyle">
      <!-- 头部 -->
      <div class="dialog-header">
        <slot name="header">
          <span class="title">{{ title }}</span>
        </slot>
        <span class="close-btn" @click="handleClose">×</span>
      </div>

      <!-- 默认插槽:主体内容 -->
      <div class="dialog-body">
        <slot />
      </div>

      <!-- 底部 -->
      <div class="dialog-footer" v-if="showFooter">
        <slot name="footer">
          <button class="btn cancel-btn" @click="handleClose">取消</button>
          <button class="btn confirm-btn" @click="handleConfirm">确定</button>
        </slot>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
// 👉 1. 先定义类型:单独抽离,不写在行内
type DialogSize = 'small' | 'middle' | 'large';

interface BaseDialogProps {
  // 必传
  visible: boolean
  // 可选 + 默认值
  title?: string
  size?: DialogSize
  width?: string
  showFooter?: boolean
  closeOnMask?: boolean
}

// 👉 2. props 定义 + 精准默认值(TS 标准写法)
const props = withDefaults(defineProps<BaseDialogProps>(), {
  title: '提示',
  size: 'middle',
  showFooter: true,
  closeOnMask: true,
  width: ''
})

// 👉 3. 严格定义 emits 事件类型
interface DialogEmits {
  (e: 'update:visible', val: boolean): void
  (e: 'confirm'): void
  (e: 'close'): void
}
const emit = defineEmits<DialogEmits>()

// 👉 4. 计算弹窗宽度(根据 size 适配)
const dialogStyle = computed(() => {
  const sizeMap: Record<DialogSize, string> = {
    small: '400px',
    middle: '600px',
    large: '800px'
  }
  return {
    width: props.width || sizeMap[props.size]
  }
})

// 👉 5. 内部事件方法
const handleClose = () => {
  emit('update:visible', false)
  emit('close')
}

const handleConfirm = () => {
  emit('confirm')
}

const handleCloseMask = () => {
  if (props.closeOnMask) {
    handleClose()
  }
}

// 👉 6. 对外暴露组件实例方法(父组件可 ref 调用)
defineExpose({
  handleClose,
  handleConfirm
})
</script>

<style scoped>
.base-dialog-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
}
.base-dialog {
  background: #fff;
  border-radius: 8px;
  overflow: hidden;
}
.dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid #eee;
}
.title {
  font-size: 16px;
  font-weight: 600;
}
.close-btn {
  cursor: pointer;
  font-size: 20px;
  color: #999;
}
.dialog-body {
  padding: 20px;
}
.dialog-footer {
  padding: 12px 20px;
  border-top: 1px solid #eee;
  text-align: right;
}
.btn {
  padding: 6px 16px;
  margin-left: 8px;
  border-radius: 4px;
  border: none;
  cursor: pointer;
}
.cancel-btn {
  background: #f5f5f5;
}
.confirm-btn {
  background: #1677ff;
  color: #fff;
}
</style>

使用:

<template>
  <BaseDialog
    v-model:visible="dialogVisible"
    title="编辑内容"
    size="middle"
    :show-footer="true"
    @confirm="handleSubmit"
  >
    这里是弹窗主体内容
  </BaseDialog>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import BaseDialog from '@/components/BaseDialog.vue'

const dialogVisible = ref(false)
const handleSubmit = () => {
  console.log('点击确定')
}
</script>

重点拆解:为什么这是企业高级写法

1. 类型规范

  • 单独 interface 定义 Props、Emits
  • 枚举字面量 'small'|'middle'|'large',杜绝乱传字符串
  • 全程无 any,所有参数都有类型约束

2. props 默认值

withDefaults 给可选属性设默认值,TS 识别完美,不用自己逻辑判断。

3. 事件规范

  • update:visible 支持 v-model:visible 双向绑定
  • 事件参数类型严格约束,不会乱传参

4. 多插槽规范

  • 具名插槽 header / footer + 默认插槽
  • 外部可自定义头部、底部、内容,复用性拉满

5. defineExpose 暴露实例

父组件通过 ref 可以直接调用组件内部方法,适合复杂业务弹窗。

6. 自适应 + 配置化

通过 sizewidth 灵活控制弹窗大小,适配不同业务场景。

Vue3 + TS 公共组件,固定遵守这 6 条

  1. 所有 Props 先用 interface 定义,绝不写行内对象
  2. 可选属性统一用 withDefaults 给默认值
  3. 固定值选项用字面量联合类型,不用 string
  4. Emits 必须用接口约束事件名和参数
  5. 复杂配置抽 Recordcomputed 统一管理
  6. 需要父组件调用方法,必须 defineExpose

三、配套全局类型规范(必加)

在项目 src 新建 types/global.d.ts

// 通用枚举
export type StatusType = 0 | 1 | 2;

// 通用接口返回格式
export interface ResData<T> {
  code: number;
  data: T;
  message: string;
}

// 常用工具类型复用
export type PartialOptional<T> = Partial<T>;

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • JS获取字符对应的ASCII码实例

    JS获取字符对应的ASCII码实例

    下面小编就为大家带来一篇JS获取字符对应的ASCII码实例。小编觉得挺不错的,现在就想给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • 微信小程序事件流原理解析

    微信小程序事件流原理解析

    这篇文章主要介绍了微信小程序事件流原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • JavaScript 的继承

    JavaScript 的继承

    大家都知道,C#中使用的是传统的类继承是很简单,但在JS中,可就没这么简单了,因为它使用的是原型(prototype )继承,实现起来相对复杂了一点。
    2011-10-10
  • 微信小程序使用wx.navigateTo路由跳转层级限制问题小结

    微信小程序使用wx.navigateTo路由跳转层级限制问题小结

    在微信小程序开发中,wx.navigateTo和wx.redirectTo是两种页面跳转方式,wx.navigateTo允许跳转到新页面并保留当前页面,适合需要返回的场景,但受页面栈10层限制,wx.redirectTo则关闭当前页面后跳转,本文介绍微信小程序使用wx.navigateTo路由跳转层级限制问题
    2024-10-10
  • JavaScript布尔运算符原理使用解析

    JavaScript布尔运算符原理使用解析

    这篇文章主要介绍了JavaScript布尔运算符原理使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • javascript关于运动的各种问题经典总结

    javascript关于运动的各种问题经典总结

    这篇文章主要介绍了javascript关于运动的各种问题,实例总结了javascript关于滚动的常见错误、实现方法与相关注意事项,非常具有实用价值,需要的朋友可以参考下
    2015-04-04
  • JavaScript中的闭包(Closure)详细介绍

    JavaScript中的闭包(Closure)详细介绍

    这篇文章主要介绍了JavaScript中的闭包(Closure)详细介绍,函数调用对象与变量的作用域链、什么是闭包等内容,并给出了实例,需要的朋友可以参考下
    2014-12-12
  • iframe父页面获取子页面参数的方法

    iframe父页面获取子页面参数的方法

    这篇文章主要介绍了iframe父页面获取子页面参数的方法,需要的朋友可以参考下
    2014-02-02
  • Firefox/Chrome/Safari的中可直接使用$/$$函数进行调试

    Firefox/Chrome/Safari的中可直接使用$/$$函数进行调试

    偶然发现的,页面中没有引入Prototype和jQuery。控制台中敲$却发现是一个函数。又试着敲$$,也是个function
    2012-02-02
  • JavaScript实现移动端横竖屏检测

    JavaScript实现移动端横竖屏检测

    这篇文章主要为大家详细介绍了JavaScript实现移动端横竖屏检测,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07

最新评论