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

如何实现Header组件的AllOrNone类型约束?TypeScript类型错误解决

解决Header组件Props的TypeScript双向约束问题

问题背景

项目中有一个全局复用的Header组件,页面使用时可传递控制按钮显示的options参数,以及对应的点击事件处理函数。需要实现双向约束:若options中指定某个按钮为显示状态,则必须传入对应的点击事件处理函数;反之,若传入了某个点击事件处理函数,则options中必须开启该按钮的显示。

原Prop接口

type Disabled = false;

interface HeaderProps {
  options?: {
    back?: boolean;
    close?: boolean;
    next?: boolean | [boolean, Disabled];
    complete?: boolean;
    notification?: boolean;
  };
  onClickBack?: () => void;
  onClickClose?: () => void;
  onClickNext?: () => void;
  onClickComplete?: () => void;
  onClickNotification?: () => void;
}

约束需求示例

// ❌ 错误:开启back按钮但未传对应点击函数
<Header options={{ back: true }} />

// 🟢 合法:开启back按钮且传入对应点击函数
<Header options={{ back: true }} onClickBack={() => console.log('dummy')}/>

// ❌ 错误:开启close和save按钮但只传了close的点击函数
<Header options={{ close: true, save: [true, false] }} onClickClose={() => console.log('dummy')}/>

// 🟢 合法:所有开启的按钮都传入了对应点击函数
<Header options={{ close: true, save: [true, false] }} onClickClose={() => console.log('dummy')} onClickSave={() => console.log('dummy')} />

// 🟢 合法:不传递任何按钮配置和点击函数
<Header />

尝试实现的错误代码

之前尝试通过泛型实现约束,但出现TypeScript类型错误:

type Disabled = false;

interface HeaderProps {
  options: {
    back: boolean;
    close: boolean;
    next: boolean | [boolean, Disabled];
    complete: boolean;
    notification: boolean;
  };
  onClickBack: () => void;
  onClickClose: () => void;
  onClickNext: () => void;
  onClickComplete: () => void;
  onClickNotification: () => void;
}

type AllOrNone<A extends HeaderProps, U extends A> = (
  | Required<U>
  | Partial<
      {
        options: Record<keyof U['options'], never>;
      } & {
        [M in keyof Omit<U, 'options'>]: never;
      }
    >
) &
  Omit<HeaderProps, 'options' | keyof Omit<U, 'options'>>;

type Props<A extends HeaderProps> = AllOrNone<
  A,
  { // 🙋‍♂️ 此处报错
    options: {
      back: boolean;
    };
    onClickBack: () => void;
  }
>;

错误信息

Type '{ options: { back: boolean; }; onClickBack: () => void }' does not satisfy the constraint 'A'.
'A' could be instantiated with an arbitrary type which could be unrelated to '{ options: { back: boolean; }; onClickBack: () => void; }'. ts(2344)

解决方案

错误根源在于原泛型约束逻辑过于复杂且方向错误,我们可以换一种思路:将每个按钮的配置项与对应的点击函数绑定为一组,每组要么同时存在,要么同时不存在,最后通过交叉类型合并所有按钮的约束。

实现代码如下:

type Disabled = false;

// 单个按钮的配置与点击函数绑定类型:同存同无
type ButtonConfig<K extends string> = 
  | {
      options?: Record<K, never>;
      [P in `onClick${Capitalize<K>}`]?: never;
    }
  | {
      options: Record<K, boolean | [boolean, Disabled]>;
      [P in `onClick${Capitalize<K>}`]: () => void;
    };

// 合并所有按钮的约束
type HeaderProps = ButtonConfig<'back'> & 
  ButtonConfig<'close'> & 
  ButtonConfig<'next'> & 
  ButtonConfig<'complete'> & 
  ButtonConfig<'notification'>;

// 测试验证
const test1: HeaderProps = { options: { back: true } }; // ❌ 报错:缺少onClickBack
const test2: HeaderProps = { options: { back: true }, onClickBack: () => {} }; // 🟢 合法
const test3: HeaderProps = { options: { close: true, next: [true, false] }, onClickClose: () => {} }; // ❌ 报错:缺少onClickNext
const test4: HeaderProps = { options: { close: true, next: [true, false] }, onClickClose: () => {}, onClickNext: () => {} }; // 🟢 合法
const test5: HeaderProps = {}; // 🟢 合法

逻辑说明

  1. ButtonConfig<K>:针对单个按钮(如back)定义两种状态:
    • 状态一:按钮配置和点击函数都不存在(用never约束禁止传入)
    • 状态二:按钮配置存在(符合指定类型)且点击函数必须存在
  2. HeaderProps:通过交叉类型合并所有按钮的约束,确保每个按钮都独立遵循"同存同无"的规则
  3. 该实现方式无需复杂泛型嵌套,直接通过联合+交叉类型实现精确约束,支持多按钮组合场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 03:35:27