当前位置: 首页 > news >正文

jeecgboot TS + Vue 模板化 03

jeecgboot

TS对比JS

维度JavaScript (JS)TypeScript (TS)
语言类型动态类型,弱类型。变量类型在运行时才确定。静态类型,强类型。编译时可检查类型,支持类型推断、接口和泛型。
语法ECMAScript 标准,支持最新 JS 语法(ES6+)。基于 JS 超集,兼容 JS 所有语法,同时增加类型、接口、枚举、泛型、命名空间等。
类型系统无类型系统,类型检查依赖运行时。完整类型系统,编译器可提前发现类型错误,减少运行时 bug。
编译/执行直接被浏览器或 Node.js 执行,无需编译。需先编译成 JS(tsc 或 Babel),再由浏览器/Node.js 执行。
IDE 支持基础智能提示和自动补全,但类型推断有限。强大的智能提示、自动补全、重构、类型检查,提高开发效率和可维护性。
错误检测多数错误在运行时才发现。编译时捕获类型错误和潜在逻辑错误,提高代码安全性。
面向对象支持类、继承、ES6 模块,但类型不强制。完整的类、接口、抽象类、访问修饰符(private/protected/public)支持,更适合大规模项目。
函数特性支持高阶函数、回调、箭头函数、默认参数等。除 JS 所有特性外,可为函数参数、返回值、this 指定类型,支持函数重载。
模块化ES Module、CommonJS、AMD 等标准。完全兼容 JS 模块化,支持命名空间增强模块管理。
泛型支持无原生泛型。原生泛型支持,可实现类型安全的复用函数和类。
生态和库丰富的库和框架生态,直接使用。完全兼容 JS 生态,且可通过 DefinitelyTyped 获取类型声明文件(.d.ts)。
学习成本低,适合初学者快速上手。较高,需要理解类型系统和编译概念,但对大型项目更安全可维护。
调试直接在浏览器或 Node.js 调试。调试需要先编译为 JS,但 VS Code 等 IDE 可直接映射 TS 源码调试。
适用场景小型项目、快速开发、脚本任务。中大型项目、团队协作、需要严格类型和可维护性的系统。
社区和支持JS 社区庞大、资料丰富。TS 社区快速成长,Angular、NestJS、Vue3、React 等框架广泛采用 TS。
未来发展稳定成熟,随着 ES 发展持续更新。

越来越受大型项目和企业青睐,TypeScript 已成为 JS 的标准补充。

- 参数类型写在括号里 params: LoginParams - 返回值类型写在 defHttp.post<**T**> 的泛型参数里 - 不写返回值的显式类型 —— TS 会自动推断为 Promise<LoginResultModel> export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { return defHttp.post<LoginResultModel>( { url: Api.Login, params, }, { errorMessageMode: mode, } ); }
async login( params: LoginParams & { captcha: string; rememberMe: boolean }, //属性合并 mode: ErrorMessageMode = 'modal' ): Promise<GetUserInfoModel | null> {

- params: LoginParams & { captcha: string; rememberMe: boolean } —— 用 & 做"交叉类型",在现有接口基础上临时扩字段
- async 函数的返回值类型永远是 Promise<真实返回类型>
- const data = await loginApi(...) —— TS 会自动推断 data 为 LoginResultModel

export interface LoginParams { username: string; password: string; captcha?: string; checkKey?: string; remember_me?: boolean; }

- LoginParams —— 你"发出去"给后端的结构
- LoginResultModel —— 后端"返回给你"的结构
- 两者分开写,避免"一个 model 既要当入参又当出参"的模糊语义
- ? 表示"可选字段",调用方可以不传

const loading = ref<boolean>(false); const loginForm = reactive<LoginParams>({ username: '', password: '', captcha: '', }); async function handleLogin() { loading.value = true; try { // userStore.login 返回 Promise<GetUserInfoModel | null> const userInfo = await userStore.login({ ...loginForm, /// captcha: formModelRef.value?.captcha || '', rememberMe: formModelRef.value?.rememberMe || false, }); notification.success({ message: '登录成功', description: `欢迎回来,${userInfo?.realname}` }); router.push('/home'); } catch (e) { notification.error({ message: '登录失败', description: (e as Error).message }); } finally { loading.value = false; } }
特性refreactive
响应式对象类型基本类型 / 对象 / 数组对象 / 数组(深度)
访问方式.value直接访问属性
深度响应对象内部属性需访问.value,否则浅响应深度响应,内部属性也是响应式
使用场景单个值(bool、number、string)或希望手动解包对象表单对象、复杂数据结构、数组
loginForm.username = 'admin'; loginForm.password = '123456';

- ref<boolean>(false) —— 显式给泛型,防止 TS 推断出 never 或 undefined
- reactive<LoginParams>({...}) —— reactive 的类型建议写在尖括号里
- (e as Error) —— catch 里的变量默认是 unknown ,需要断言才能读 .message

captcha: formModelRef.value?.captcha || '',

formModelRef.value中取出captcha的值,如果取不到或者值为空,则使用空字符串''作为默认值。

|| '' 如果左边的值不存在(或为空值),就使用空字符串作为默认值。

这里的问号?.可选链运算符(Optional Chaining),作用就是安全地访问对象属性,避免对象为nullundefined时报错。

formModelRef.value == null ? undefined : formModelRef.value.captcha

  • 如果formModelRef.valuenullundefined,访问.captcha不会报错,而是返回undefined
  • 如果formModelRef.value有值,则返回它的captcha属性。
let captcha; if (formModelRef.value?.captcha) { captcha = formModelRef.value.captcha; } else { captcha = ''; }


rememberMe: formModelRef.value?.rememberMe || false,

  • 尝试取rememberMe的值
  • 如果取不到(value是 null/undefined 或rememberMe本身是 falsy),就使用默认值false

泛型 <T> :你最常打交道的东西

泛型 <T> 可以理解为"类型的参数"。就像函数的参数是"值的参数":

// 调用处 const result = await defHttp.post<LoginResultModel>({ url: '/login', params, }); post<T = any>( config: AxiosRequestConfig, options?: RequestOptions ): Promise<T> 实际 post( config, options ): Promise<LoginResultModel> return this.request<LoginResultModel>(...)

读这段代码的方式 :从调用方反着往回看 —— "你在调用方写了 <LoginResultModel> ,这个 T 就顺着 defHttp.post<T> → request<T> → Promise<T> 一路贯穿到底"。

例如:

interface LoginResultModel { token: string; userId: number; }

那么:

const result = await defHttp.post<LoginResultModel>(...)

IDE立刻知道:

result.token result.userId

有提示。

而:

result.username

会直接报错:

Property 'username' does not exist on type 'LoginResultModel'

这就是泛型最大的价值:

src/api/model/baseModel.ts 里有两个你会反复复制的泛型模板:

// 所有"分页查询"都可以用这个作为基础参数 export interface BasicPageParams { page: number; pageSize: number; } // 所有"分页返回"都应该是这种结构: // items 是数据项数组,total 是总数 export interface BasicFetchResult<T> { items: T[]; total: number; }
// src/api/sys/model/departModel.ts import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'; export interface DepartItem { id: string; departName: string; orgCode: string; createTime: string; } export type DepartListParams = BasicPageParams & { departName?: string }; // & 是 交叉类型(Intersection Type) 表示把 BasicPageParams 类型和 { departName?: string } 类型 合并 结果类型拥有两边的所有属性 //? 表示 可选属性 可以有,也可以没有 类型是 string export type DepartListResult = BasicFetchResult<DepartItem>; // ↑ 等价于:{ items: DepartItem[]; total: number }
使用场景推荐原因
描述对象结构(API返回、表单数据)interface结构语义明确,支持extends,适合对象建模
类型别名(简单替换)type更轻量,用于起别名更直观
联合类型 / 交叉类型type支持A | BA & Binterface不支持
函数类型定义type写法更简洁(a: number) => string
class 实现约束interfaceimplements只能接interface
复杂类型组合type可组合、可嵌套,表达能力更强
  • interface:更偏“对象结构设计”
  • type:更偏“类型运算和组合”
  • 对象结构 → interface
  • 组合/运算 → type
  • 不确定 → 默认 interface(更安全)
特性interfacetype
可重复声明✅ 支持合并❌ 不支持
扩展已有类型extends或合并✅ 交叉类型&
适合场景库设计、模块扩展、API对象建模联合类型、复杂类型组合、函数类型
type User = { name: string; }; type User = { age: number; }; // ❌ 会报错:Cannot redeclare block-scoped variable 'User'. type User = { name: string }; type UserWithAge = User & { age: number }; const u: UserWithAge = { name: 'Alice', age: 18 };

在 JeecgBoot 里,最典型的是 "API model 用 interface,组合/别名用 type":

// 描述对象 → interface export interface LoginParams { username: string; password: string; } // 描述"一个较长的类型的别名" → type export type ID = string | number; export type DepartListParams = BasicPageParams & { departName?: string }; // ↑ 交叉类型只能用 type

interface:专门描述“对象结构”

type:做“类型组合 / 运算”

  • 给一个复杂或常用类型起别名
  • 本质是“类型重命名 + 联合类型”
export type ID = string | number;

export type DepartListParams = BasicPageParams & { departName?: string };

👉 把两个类型“合并成一个新类型”

工具本质
interface“定义一个对象长什么样”
type“拼装 / 组合 / 变换类型”

### artial<T> —— 把 T 的所有字段变成可选
源码位置: src/utils/http/axios/index.ts (createAxios 的签名)

function createAxios(opt?: Partial<CreateAxiosOptions>) { return new VAxios(deepMerge({ transform, requestOptions, ... }, opt || {})); }

- CreateAxiosOptions 有一堆字段( timeout 、 headers 、 transform 、 requestOptions ...)
- 用 Partial<CreateAxiosOptions> 表示"传进来的 opt 可以只包含其中一部分字段"
- 里面再 deepMerge(默认, opt) 把缺的字段用默认值补全

Pick<T, K> —— 从 T 中挑出某些字段

场景:一个 User 有 10 个字段,但某个接口只需要 id 和 username 。

interface User { id: string; username: string; email: string; realname: string; avatar: string; } type LoginUser = Pick<User, 'id' | 'username'>; // 等价于:{ id: string; username: string }

### Omit<T, K> —— 从 T 中去掉某些字段
场景:"编辑接口"的参数和"新增接口"很像,只是多了一个 id 。

interface DepartAddParams { departName: string; orgCode: string; parentId?: string; } // 编辑比新增多了一个 id 必填 type DepartEditParams = DepartAddParams & { id: string }; // 或者反过来:从"完整版"里拿掉 id type DepartAddParams2 = Omit<DepartFullParams, 'id'>;

Record<string, T> —— 描述一个"键值对"对象

场景: useModal / useDrawer 的 attrs 传参,或动态表单配置。

// 一个字典:key 是字符串,value 是任意值 const dict: Record<string, any> = { name: 'Jeecg', version: 3, }; // 更具体的约束:某些固定 key type UserFieldMap = Record<'username' | 'password', FormFieldConfig>;

何时允许 any ?何时该封掉?

动态 JSON 对象 (后端返回的 userInfo 就是典型的"结构没定死的东西")

export interface LoginResultModel { userId: string | number; token: string; role: RoleInfo; userInfo?: any; // ← 后端 userInfo 的字段会变动,标 any 合理 }

- 封装层的顶层 ( defHttp.get<any>(...) —— 你自己调用的时候会把 any 替换成具体类型,所以底层留 any 没大碍)
- 第三方库不提供 d.ts —— 临时声明 declare module 'foo'; 或 const lib: any = require('bar')

### 应该避免 any 的场景(3 个)
1. 业务数据模型 ( LoginParams.username 写成 any ,那和没写 TS 没区别)
2. reactive / ref 状态 ( const loginForm = reactive<any>({}) —— 你会失去所有编辑器提示)
3. 函数返回值 ( async function login(): Promise<any> —— 调用方无法知道拿到的是什么)
最佳实践 : any 只在"确实无法确定结构"的地方用,其他地方都换成具体类型或泛型。你项目里的 LoginParams 、 LoginResultModel 就是好例子。

一个完整的"从零到页面"的 TS 套路

Step 1. 写 model(数据契约)

// src/api/sys/model/departModel.ts export interface DepartItem { id: string; departName: string; orgCode: string; createTime: string; } export type DepartListParams = BasicPageParams & { departName?: string }; export type DepartListResult = BasicFetchResult<DepartItem>; export interface DepartSaveParams { id?: string; departName: string; orgCode?: string; parentId?: string; }

Step 2. 写 API(带泛型返回值)

// src/api/sys/depart.ts import { defHttp } from '/@/utils/http/axios'; import { DepartListParams, DepartListResult, DepartItem, DepartSaveParams } from './model/departModel'; enum Api { LIST = '/sys/sysDepart/list', ADD = '/sys/sysDepart/add', EDIT = '/sys/sysDepart/edit', DELETE = '/sys/sysDepart/delete', } export const departListApi = (params: DepartListParams) => defHttp.get<DepartListResult>({ url: Api.LIST, params }); export const departAddApi = (params: DepartSaveParams) => defHttp.post({ url: Api.ADD, params }); export const departEditApi = (params: DepartSaveParams) => defHttp.put({ url: Api.EDIT, params }); export const departDeleteApi = (id: string) => defHttp.delete({ url: Api.DELETE, params: { id } });

Step 3. 写 store(把 TS 类型推到状态层)

// src/store/modules/depart.ts // 定义并导出一个 Pinia store 模块 // useDepartStore 是一个函数,调用它可以在组件中拿到 store 实例 export const useDepartStore = defineStore({ // store 的唯一 ID,用于区分不同模块 id: 'depart', // state 是一个函数,返回当前模块的响应式数据对象 state: () => ({ // 当前选中的部门,初始值为 null // TS 类型断言:可能是 DepartItem 类型或者 null currentDepart: null as DepartItem | null, // 部门列表,初始值为空数组 // TS 类型断言:数组中每一项都是 DepartItem 类型 departList: [] as DepartItem[], }), // actions 用来定义方法,可以同步或异步操作 state actions: { // 异步加载部门列表 // params: DepartListParams 类型,通常包含分页或搜索条件 async loadList(params: DepartListParams) { // 调用 API 获取部门列表 // departListApi(params) 返回一个 Promise<DepartItem[]> // await 等待异步结果 const list = await departListApi(params); // 把获取到的部门列表保存到 store 的 state // this.departList 直接访问 store 中的 departList this.departList = list; }, }, });

departList 的类型是:DepartItem[]

  • 空数组[]本身 TS 会推断类型为any[]
  • 如果不加类型断言,写法如下会报错:
this.departList = await departListApi(params); // TS 可能报类型不匹配

加上as DepartItem[]后:

  • departList明确类型,TS 能检查赋值正确性
  • 保证数组中元素都是DepartItem,避免后续访问报错

写法面向对象作用TS 类型检查
departList: [] as DepartItem[]变量/字段明确字段类型检查字段赋值类型
params: DepartListParams函数参数指定函数调用接口检查调用者传参类型

Step 4. 写页面(享受自动提示)

// src/views/system/depart/index.vue const { departList, total } = await departListApi({ page: 1, pageSize: 10, departName: formState.departName, // ← 这里敲 departName. 会出字段提示 });

写完 Step 1 → Step 4 之后, 只要后端的响应结构有变动 ,你改一下 departModel.ts ,所有用到它的地方 TS 都会帮你标出来。这就是 TS 的核心价值。

http://www.jsqmd.com/news/967075/

相关文章:

  • NVIDIA Profile Inspector性能优化指南:释放显卡隐藏性能的终极秘籍
  • RK平台千兆以太网稳如老狗的秘密:深度解析RTL8211F-CG外部时钟与RGMII-ID配置
  • 苹果开发者大会开幕:Siri 改版、健康套件升级,能否追上人工智能竞赛?
  • 华为OD转正上岸后,为什么我们成了‘人才堤坝’的第一批?聊聊一线交付与研发的认知差
  • SQL中CASE WHEN的实战心法:从数据分层到业务规则固化
  • 2026年最新白银市黄金回收白银回收铂金回收彩金回收权威TOP5口碑门店推荐+正规可靠机构联系方式 - 亦辰小黄鸭
  • STM32F407ZGT6标准库工程:VL53L5CX 4×4区域ToF测距完整实现(含I2C驱动、校准与bin固件)
  • keybench:可脚本化排序键值存储性能工具,多维度测试与分析全揭秘
  • 国内合规AI写作网站盘点:降AIGC查重,这六款工具谁更靠谱?
  • 动手复现Hinton经典:用PyTorch跑通1986年的反向传播论文代码
  • 贵阳市2026贵金属回收精选排名榜单 黄金铂金白银彩金回收靠谱正规门店推荐及联系电话汇总 - 前途无量YY
  • 深度理解 Python 装饰器:从原理到实战,彻底掌握高阶语法
  • 新手必看:哔哩下载姬downkyi如何让你轻松收藏B站高清视频
  • 用户停留时长×跳出率×跨端转化率×语义聚类得分×时效衰减因子,CSDN AI选题到底在算什么?
  • 纯C实现的xcorr互相关函数,兼容MATLAB接口,支持biased/unbiased/cross三种计算模式
  • 从振动传感器到预测性维护:智能故障诊断在风电行业的落地实战
  • 桂林市2026贵金属回收精选排名榜单 黄金铂金白银彩金回收靠谱正规门店推荐及联系电话汇总 - 前途无量YY
  • AVEVA PDMS二次开发避坑指南:从PML1到PML2迁移的5个常见错误
  • 纯C++控制台通讯录程序:离线增删改查+批量清空,含源码和可执行文件
  • 硕士论文写作刚需,5 个本土 AI 辅助写作平台实测,真实参考文献推荐、可选格式模版
  • 新手必看:用C++ switch和if-else两种方法搞定《信息学奥赛一本通》2058计算器题
  • 时序分析实战工具链:从数据清洗到生产部署的六层选型指南
  • GT20L16S1Y字库芯片的‘竖置横排’是啥?一篇讲透点阵数据与LCD屏幕的匹配原理
  • CSDN AI写稿模块技术领域覆盖真相(非官方但经逆向API+文档解析验证):Python✅、Java✅、TypeScript⚠️、Rust❌、Go⚠️——附4步手动启用隐藏前端支持技巧
  • 六盘水黄金白银回收正规资质TOP5盘点 - 余生黄金回收
  • 京东自动化抢购脚本:如何用Python实现毫秒级精准秒杀
  • 手把手教你排查RTL8211F-CG网口不通:从125MHz时钟到RGMII时序的保姆级调试指南
  • 多维聚合中的数据操作:Slice、Pivot、Roll-up实战指南
  • 2026年C型钢可靠供应商评测:开口楼承板、河北c型钢、河北z型钢、河北不锈钢天沟、河北彩钢板、河北铝镁锰板、燕尾式楼承板选择指南 - 优质品牌商家
  • 西电离散数学上机实操代码包:图连通性、关系判定与闭包计算全实现