如何用TypeScript类型限制对象重复赋值并关联枚举键与属性
TypeScript 类型约束与重复赋值拦截方案
问题场景
现有一段基于Mobx的TypeScript代码,存在以下问题:
- 可重复给
systemColumns[SystemColumnsName.STATUS]赋值相同枚举键的SystemColumn实例,编译器无报错 systemColumns的枚举键与对应SystemColumn实例的name参数无强关联,可能出现不匹配的情况
原代码如下:
import {makeAutoObservable, makeObservable, runInAction} from "mobx"; import {MainStore} from "./MainStore"; export const enum SystemColumnsName { STATUS = 'Статус', } type SystemColumnsType = { [key: string]: SystemColumn; }; class SystemColumn { readonly id: string; readonly name: string; readonly referencePath: string; public isActive: boolean; constructor(item_key: string, name: string, referencePath: string, isActive: boolean) { makeAutoObservable(this) this.id = item_key; this.name = name; this.referencePath = referencePath; this.isActive = isActive; } } export class ViewStore { public systemColumns: SystemColumnsType = {}; private _mainStore: MainStore; constructor(mainStore: MainStore) { makeAutoObservable(this) this._mainStore = mainStore; this.systemColumns[SystemColumnsName.STATUS] = new SystemColumn('systemsStatusItem', SystemColumnsName.STATUS, 'statusColumn', true); this.systemColumns[SystemColumnsName.STATUS] = new SystemColumn('systemsStatusItem', SystemColumnsName.STATUS, 'statusColumn', true); this.systemColumns[SystemColumnsName.STATUS] = new SystemColumn('systemsStatusItem', SystemColumnsName.STATUS, 'statusColumn', true); this.systemColumns[SystemColumnsName.STATUS] = new SystemColumn('systemsStatusItem', SystemColumnsName.STATUS, 'statusColumn', true); this.systemColumns[SystemColumnsName.STATUS] = new SystemColumn('systemsStatusItem', SystemColumnsName.STATUS, 'statusColumn', true); } public setSystemColumnStatus(systemColumnKey: string, isActive: boolean) { this.systemColumns[systemColumnKey].isActive = isActive; } }
需要实现三个目标:
- 禁止重复给同一枚举键赋值
SystemColumn实例 - 建立枚举键与对应
SystemColumn实例name参数的强关联 - 重复赋值时强制编译器抛出错误
解决方案
1. 泛型约束SystemColumn的name参数
通过泛型让SystemColumn的name属性严格对应SystemColumnsName的枚举成员,确保两者一致:
// 为每个枚举成员定义专属的SystemColumn类型映射 type SystemColumnMap = { [K in SystemColumnsName]: SystemColumn<{ name: K }>; }; // 用泛型约束name参数必须是指定的枚举成员 class SystemColumn<T extends { name: SystemColumnsName }> { readonly id: string; readonly name: T['name']; readonly referencePath: string; public isActive: boolean; constructor(item_key: string, name: T['name'], referencePath: string, isActive: boolean) { makeAutoObservable(this); this.id = item_key; this.name = name; this.referencePath = referencePath; this.isActive = isActive; } }
2. 拦截重复赋值的泛型添加方法
使用Partial<SystemColumnMap>初始化systemColumns,并通过泛型方法addSystemColumn确保每次添加的枚举键都是未定义过的:
export class ViewStore { // 初始为Partial类型,逐步填充为完整的SystemColumnMap public systemColumns: Partial<SystemColumnMap> = {}; private _mainStore: MainStore; constructor(mainStore: MainStore) { makeAutoObservable(this); this._mainStore = mainStore; // 合法添加列 this.addSystemColumn(SystemColumnsName.STATUS, 'systemsStatusItem', 'statusColumn', true); // 以下代码会触发编译错误:无法将类型"SystemColumnsName.STATUS"分配给类型"Exclude<SystemColumnsName, SystemColumnsName.STATUS>" // this.addSystemColumn(SystemColumnsName.STATUS, 'systemsStatusItem', 'statusColumn', true); } // 泛型方法:仅允许添加systemColumns中尚未存在的枚举键 private addSystemColumn<K extends Exclude<SystemColumnsName, keyof typeof this.systemColumns>>( columnName: K, itemKey: string, referencePath: string, isActive: boolean ) { this.systemColumns[columnName] = new SystemColumn<{ name: K }>(itemKey, columnName, referencePath, isActive) as SystemColumnMap[K]; } // 约束set方法的参数为合法枚举成员 public setSystemColumnStatus<K extends SystemColumnsName>(systemColumnKey: K, isActive: boolean) { const column = this.systemColumns[systemColumnKey]; if (column) { column.isActive = isActive; } } }
3. 完整代码验证
整合后的完整代码如下,此时重复添加同一枚举键会直接触发编译错误,且SystemColumn的name参数必须与枚举键严格匹配:
import { makeAutoObservable } from "mobx"; import { MainStore } from "./MainStore"; export const enum SystemColumnsName { STATUS = 'Статус', } type SystemColumnMap = { [K in SystemColumnsName]: SystemColumn<{ name: K }>; }; class SystemColumn<T extends { name: SystemColumnsName }> { readonly id: string; readonly name: T['name']; readonly referencePath: string; public isActive: boolean; constructor(item_key: string, name: T['name'], referencePath: string, isActive: boolean) { makeAutoObservable(this); this.id = item_key; this.name = name; this.referencePath = referencePath; this.isActive = isActive; } } export class ViewStore { public systemColumns: Partial<SystemColumnMap> = {}; private _mainStore: MainStore; constructor(mainStore: MainStore) { makeAutoObservable(this); this._mainStore = mainStore; this.addSystemColumn(SystemColumnsName.STATUS, 'systemsStatusItem', 'statusColumn', true); // 重复添加会报错 // this.addSystemColumn(SystemColumnsName.STATUS, 'systemsStatusItem', 'statusColumn', true); } private addSystemColumn<K extends Exclude<SystemColumnsName, keyof typeof this.systemColumns>>( columnName: K, itemKey: string, referencePath: string, isActive: boolean ) { this.systemColumns[columnName] = new SystemColumn<{ name: K }>(itemKey, columnName, referencePath, isActive) as SystemColumnMap[K]; } public setSystemColumnStatus<K extends SystemColumnsName>(systemColumnKey: K, isActive: boolean) { const column = this.systemColumns[systemColumnKey]; if (column) { column.isActive = isActive; } } }
内容的提问来源于stack exchange,提问作者Георгий Барсегян
相关产品推荐
相关产品推荐

