TypeScript 中如何给结构相同的对象定义不同的类型?
TypeScript结构类型兼容场景下的标称类型实现优化方案
你遇到的问题根源是TypeScript默认使用结构类型系统,只要两个类型的成员结构完全一致,就会被判定为兼容类型。你需要的是标称类型的效果:即使结构一致,只要定义来源不同,就被判定为不同类型。下面提供两种可直接落地的简化实现方案:
方案1:轻量化编译期品牌类型(优先推荐,无运行时开销)
该方案仅通过编译期类型标记实现类型隔离,不需要额外的运行时代码,实现成本最低:
// 通用品牌类型工具 type Brand<T, B extends string> = T & { __brand: B } // 定义通用Shape结构 type Shape<Data, BrandTag extends string> = Brand<{ getArea: () => number data: Data }, BrandTag> interface CreateShapeCategoryOpts<Data, BrandTag extends string> { brand: BrandTag getArea: (data: Data) => number } function createShapeCategory<Data, BrandTag extends string>({ brand, getArea }: CreateShapeCategoryOpts<Data, BrandTag>) { const create = (data: Data): Shape<Data, BrandTag> => { // 不需要运行时判断可直接删除__brand赋值,用as强转即可 return { getArea: () => getArea(data), data, __brand: brand } as Shape<Data, BrandTag> } return { create } }
使用示例:
// 定义矩形 interface RectangleData { width: number, height: number } const rectangle = createShapeCategory({ brand: 'Rectangle', getArea: ({ width, height }: RectangleData) => width * height }) // 直接通过工具类型推导Rectangle类型,无需手动重复声明 type Rectangle = ReturnType<typeof rectangle.create> // 定义三角形 interface TriangleData { width: number, height: number } const triangle = createShapeCategory({ brand: 'Triangle', getArea: ({ width, height }: TriangleData) => width * height / 2 }) type Triangle = ReturnType<typeof triangle.create> // 类型校验测试 const myRect = rectangle.create({ width: 2, height: 3 }) const myTri = triangle.create({ width: 2, height: 3 }) const rectOnlyFn = (rect: Rectangle) => {} rectOnlyFn(myTri) // 编译报错,符合预期
方案2:内置Symbol的标称类型(适合需要运行时类型判断的场景)
如果需要运行时唯一标识、避免品牌名字符串冲突,可以将Symbol的创建封装到工厂内部,无需外部手动传入Symbol,大幅减少样板代码:
interface Shape<S extends symbol, D> { sentinel: S getArea: () => number data: D } function createShapeCategory<D>(name: string, getArea: (data: D) => number) { // 内部自动生成唯一Symbol,无需外部传入 const sentinel = Symbol(name) const create = (data: D): Shape<typeof sentinel, D> => ({ sentinel, getArea: () => getArea(data), data, }) return { create } }
使用示例:
// 定义矩形 interface RectangleData { width: number, height: number } const rectangle = createShapeCategory('Rectangle', ({ width, height }: RectangleData) => width * height) type Rectangle = ReturnType<typeof rectangle.create> // 定义三角形 interface TriangleData { width: number, height: number } const triangle = createShapeCategory('Triangle', ({ width, height }: TriangleData) => width * height / 2) type Triangle = ReturnType<typeof triangle.create> // 类型校验测试 const myRect = rectangle.create({ width: 2, height: 3 }) const myTri = triangle.create({ width: 2, height: 3 }) const rectOnlyFn = (rect: Rectangle) => {} rectOnlyFn(myTri) // 编译报错,符合预期
方案选择建议
- 仅需编译期类型安全,无运行时类型判断需求:选择方案1,代码量最少,无额外运行时开销。
- 需要运行时区分实例类型、担心品牌名字符串冲突:选择方案2,相比原有实现减少70%以上的样板代码,无需外部手动创建传入Symbol。
内容的提问来源于stack exchange,提问作者Scotty Jamison
相关产品推荐
相关产品推荐

