You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中从DeepReadonly对象传递HTML属性遇阻求方案

解决DeepReadonly与ButtonHTMLAttributes的数组类型兼容问题

让我来帮你搞定这个类型不兼容的问题——我仔细看了你的代码和类型实现,找到了两个关键的问题点,咱们一步步来解决:

问题回顾

你原本的组件代码运行正常,但当把otherbuttonProps定义为DeepReadonly类型后,因为ButtonHTMLAttributes期望普通数组而非ReadonlyArray,出现了类型错误。你尝试用DeepMutable转换,但没成功,以下是你的代码和类型实现:

原组件代码

type Props = DR<IBtnWrapperPropsBasic> & {otherbuttonProps?: ButtonHTMLAttributes<HTMLButtonElement>}
class Btn extends Component<Props, {}> {
 ...
 public render() {
  const {handleClick, stl, otherbuttonProps, ...otherProps} = this.props
  return (
   <button type="button"
    // @ts-ignore
    className={css(getNewDeepMutable([style, stl]))}
    onMouseDown={this.handleClickW}
    onKeyDown={this.handleKeyDown}
    {...otherbuttonProps}
    {...otherProps}
   />
  )
 }
}

自定义的DeepReadonly/DeepMutable实现

type DeepReadonly<T> = 
 // tslint:disable-next-line: ban-types
 T extends AnyFunction | Primitive ? T :
 T extends ReadonlyArray<infer R> ? IDRArray<R> :
 T extends ReadonlyMap<infer K, infer V> ? IDRMap<K, V> :
 T extends ReadonlySet<infer ItemType>? ReadonlySetDeep<ItemType>:
 T extends object ? DRObject<T> : T
interface IDRArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
type DRObject<T> = {
 readonly [P in keyof T]: DeepReadonly<T[P]>;
}
interface IDRMap<K, V> extends ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> {}
interface ReadonlySetDeep<ItemType> extends ReadonlySet<DeepReadonly<ItemType>> {}

export type DeepMutable<T> = 
 // tslint:disable-next-line: ban-types
 T extends AnyFunction | Primitive ? T :
 T extends ReadonlyArray<infer R> ? IDeepMuttableArray<R> :
 T extends ReadonlyMap<infer K, infer V> ? IDeepMutableMap<K, V> :
 T extends ReadonlySet<infer ItemType>? DeepMutableSetDeep<ItemType>:
 T extends object ? DeepMutableObject<T> : T
interface IDeepMuttableArray<T> extends Array<DeepMutable<T>> {}
type DeepMutableObject<T> = {
 readonly [P in keyof T]: DeepMutable<T[P]>;
}
interface IDeepMutableMap<K, V> extends Map<DeepMutable<K>, DeepMutable<V>> {}
interface DeepMutableSetDeep<ItemType> extends Set<DeepMutable<ItemType>> {}

问题分析

你的DeepMutable实现有个致命的小错误:DeepMutableObject里保留了readonly修饰符!这意味着即使你把DeepReadonly的对象转成DeepMutable,对象的属性还是只读的,自然无法和ButtonHTMLAttributes里的普通属性(包括数组类型)兼容。

另外,你的类型判断只匹配了ReadonlyArray,没覆盖普通Array的情况,虽然DeepReadonly会把普通数组转成只读,但转换回去的时候需要更全面的匹配,确保所有数组类型都能正确转为可变数组。

解决方案

1. 修复DeepMutable的对象类型定义

首先,把DeepMutableObject中的readonly去掉,用TypeScript的-readonly修饰符来移除属性的只读限制:

type DeepMutableObject<T> = {
  -readonly [P in keyof T]: DeepMutable<T[P]>;
}

2. 优化类型实现的兼容性

我们可以把数组、Map、Set的判断逻辑调整为同时匹配普通和只读版本,并且用类型别名替代接口,避免接口带来的类型兼容问题:

优化后的DeepReadonly

type Primitive = string | number | boolean | null | undefined | symbol | bigint;
type AnyFunction = (...args: any[]) => any;

type DeepReadonly<T> = 
 T extends AnyFunction | Primitive ? T :
 // 同时匹配普通数组和只读数组
 T extends Array<infer R> | ReadonlyArray<infer R> ? readonly DeepReadonly<R>[] :
 // 同时匹配普通Map和只读Map
 T extends Map<infer K, infer V> | ReadonlyMap<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> :
 // 同时匹配普通Set和只读Set
 T extends Set<infer ItemType> | ReadonlySet<infer ItemType>? ReadonlySet<DeepReadonly<ItemType>>:
 T extends object ? {
   readonly [P in keyof T]: DeepReadonly<T[P]>;
 } : T;

优化后的DeepMutable

export type DeepMutable<T> = 
 T extends AnyFunction | Primitive ? T :
 // 同时匹配普通数组和只读数组
 T extends Array<infer R> | ReadonlyArray<infer R> ? DeepMutable<R>[] :
 // 同时匹配普通Map和只读Map
 T extends Map<infer K, infer V> | ReadonlyMap<infer K, infer V> ? Map<DeepMutable<K>, DeepMutable<V>> :
 // 同时匹配普通Set和只读Set
 T extends Set<infer ItemType> | ReadonlySet<infer ItemType>? Set<DeepMutable<ItemType>>:
 T extends object ? {
   -readonly [P in keyof T]: DeepMutable<T[P]>;
 } : T;

3. 在组件中正确使用转换后的类型

现在你可以移除@ts-ignore,并正确转换otherbuttonProps的类型:

public render() {
  const {handleClick, stl, otherbuttonProps, ...otherProps} = this.props;
  // 将只读的button props转为可变类型
  const mutableButtonProps = otherbuttonProps as DeepMutable<typeof otherbuttonProps>;
  
  return (
   <button type="button"
    className={css(getNewDeepMutable([style, stl]))}
    onMouseDown={this.handleClickW}
    onKeyDown={this.handleKeyDown}
    {...mutableButtonProps}
    {...otherProps}
   />
  )
 }

额外小贴士

如果你用的是TypeScript 4.5及以上版本,可以考虑使用内置的ReadonlyDeep类型(需要启用strictNullChecks和strictFunctionTypes编译选项),不过自定义的类型在复杂场景下会更灵活,适合你的组件需求。

内容的提问来源于stack exchange,提问作者Ben Carp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:40:39