如何缩短TypeScript+Sequelize场景下标志位虚拟字段泛型调用
解决方案
核心优化思路
- 利用TS的
this参数约束函数运行时的上下文类型,不需要把模型相关类型作为泛型传入,直接砍掉3个冗余泛型参数 - 提前约束使用该函数的模型必须包含
number类型的flags字段,让TS可以正确推导getDataValue的入参合法性和返回值类型 - 仅保留标志位配置的泛型参数,自动约束第二个入参为标志位类型的合法key
完整实现代码
基础类型与工具函数定义
import { Model, DataTypes } from 'sequelize-typescript'; // 约束所有使用标志位的模型必须包含number类型的flags字段 type FlagModel = Model & { flags: number }; // 仅需要传入标志位配置的泛型T function createFlagVirtual<T extends Record<string, number>>( flagPositions: T, flagKey: keyof T ) { return { type: DataTypes.BOOLEAN, get(this: FlagModel) { // 两种写法TS都可以正确识别类型 const flags = this.getDataValue('flags'); // 也可以直接写 const flags = this.flags; return !!(flags & (1 << flagPositions[flagKey])); }, set(this: FlagModel, value: boolean) { const flags = this.getDataValue('flags'); const mask = 1 << flagPositions[flagKey]; this.setDataValue('flags', value ? flags | mask : flags & ~mask); } } }
业务侧使用示例
// 定义你的标志位配置 interface IUserFlags { isDev: number; isAdmin: number; isMod: number; } const UserFlags: IUserFlags = { isDev: 0, isAdmin: 1, isMod: 2 } // 在User模型里使用,仅需要传IUserFlags一个泛型参数 @Table class User extends Model<IUserAttributes, IUserCreationAttributes> { @Column(DataTypes.INTEGER) flags: number; // 虚拟字段定义,TS会自动校验第二个参数是IUserFlags的合法key @Column(createFlagVirtual<IUserFlags>(UserFlags, 'isDev')) isDev: boolean; @Column(createFlagVirtual<IUserFlags>(UserFlags, 'isAdmin')) isAdmin: boolean; @Column(createFlagVirtual<IUserFlags>(UserFlags, 'isMod')) isMod: boolean; }
类型校验说明
- 调用时仅需要传入
IUserFlags一个泛型,第二个参数会自动触发TS类型提示,传入不在IUserFlags定义内的key会直接报错 this.getDataValue('flags')会被TS识别为合法调用,返回值自动推导为number类型,和直接访问this.flags效果完全一致
内容的提问来源于stack exchange,提问作者user16238654
相关产品推荐
相关产品推荐

