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

使用TypeScript限制React组件的children属性为指定类型的单个组件

TypeScript: Restrict React Input Component to Accept Max One of Each Specific Child Component

Hey there! This is a super common scenario when building flexible yet constrained React components, and TypeScript has all the tools to enforce this at compile time alongside your existing runtime checks. Let's break down how to make this work.

First, Let's Anchor the Requirements

You want your <Input> component to:

  • Accept at most one of each allowed child component (e.g., Input.Label, Input.Button, Input.Icon, etc.)
  • Allow any combination of these allowed children (including zero or one of each)
  • Reject duplicate instances of the same child type
  • Reject any unlisted child components entirely

Valid Usage Examples

// Full valid combination matching your real-world use case
<Input placeholder="Username" name="username" type={InputType.TEXT} >
  <Input.Label htmlFor="username">Label</Input.Label>
  <Input.Max onClick={() => console.log("Max clicked!")} />
  <Input.Icon type={IconType.AVATAR} />
  <Input.Button onClick={onClickHandler} type={ButtonType.GRADIENT} loading={isLoading} >
    <Icon type={IconType.ARROW_RIGHT} />
  </Input.Button>
  <Input.Status>Status Message</Input.Status>
  <Input.Currency>BTC</Input.Currency>
</Input>

// Single valid child
<Input>
  <Input.Button />
</Input>

Invalid Usage Examples (Should Throw TS Errors)

// Duplicate child type (two Buttons)
<Input>
  <Input.Button />
  <Input.Button />
</Input>

// Unlisted child component
<Input>
  <div>Unauthorized content</div>
</Input>

The TypeScript Solution

The core idea is to define a strict type for your <Input>'s children prop that:

  1. Explicitly lists all allowed child component types
  2. Ensures no duplicate types are present in the children array
  3. Supports both single children and arrays of unique children

Let's walk through the code step by step:

1. Define Types for All Allowed Child Components

First, formalize the props type for each of your nested child components (if you haven't already):

// Example types for your nested components
type InputLabelProps = { htmlFor?: string } & React.HTMLAttributes<HTMLLabelElement>;
type InputButtonProps = { 
  onClick?: () => void; 
  type?: ButtonType; 
  loading?: boolean 
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
type InputMaxProps = { onClick?: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>;
type InputIconProps = { type?: IconType } & React.HTMLAttributes<HTMLSpanElement>;
type InputStatusProps = React.HTMLAttributes<HTMLDivElement>;
type InputCurrencyProps = React.HTMLAttributes<HTMLSpanElement>;

// Create a union type of all allowed child elements
type AllowedInputChild =
  | React.ReactElement<InputLabelProps>
  | React.ReactElement<InputButtonProps>
  | React.ReactElement<InputMaxProps>
  | React.ReactElement<InputIconProps>
  | React.ReactElement<InputStatusProps>
  | React.ReactElement<InputCurrencyProps>;

2. Create a Type Guard for Duplicate Children

Next, build a conditional type that checks if an array of children contains duplicate component types:

// Helper type to detect duplicate child types
type HasDuplicateChildren<T extends readonly AllowedInputChild[]> =
  T[number] extends infer U
    ? U extends AllowedInputChild
      ? // Check if more than one instance of U exists in T
        Extract<T[number], U> extends readonly [infer _, ...infer __]
          ? true
          : false
      : false
    : false;

3. Define the Input Component Props

Now, use the above types to constrain the children prop in your <Input>'s props type:

type InputProps = {
  placeholder?: string;
  name?: string;
  type?: InputType;
  // Constrain children: either a single allowed child, or an array with no duplicates
  children?:
    | AllowedInputChild
    | (readonly AllowedInputChild[] & {
        // If the array has duplicates, this type resolves to never (throws TS error)
        [K in keyof AllowedInputChild]: never extends HasDuplicateChildren<infer T> ? T : never;
      });
} & React.InputHTMLAttributes<HTMLInputElement>;

4. Implement the Input Component and Attach Child Components

Finally, build your <Input> component and attach the nested child components as static properties (so TypeScript recognizes them as valid children):

// Implement the Input component (with your existing runtime checks)
const Input = ({ children, ...rest }: InputProps) => {
  // Your existing runtime validation logic here (e.g., check for duplicates/unallowed children)
  const childArray = React.Children.toArray(children);
  
  return (
    <div className="input-wrapper">
      {childArray}
      <input {...rest} />
    </div>
  );
};

// Attach child components as static properties
Input.Label = (props: InputLabelProps) => <label {...props} />;
Input.Button = (props: InputButtonProps) => <button {...props} />;
Input.Max = (props: InputMaxProps) => <button {...props}>Max</button>;
Input.Icon = (props: InputIconProps) => <span className="icon" {...props} />;
Input.Status = (props: InputStatusProps) => <div className="status" {...props} />;
Input.Currency = (props: InputCurrencyProps) => <span className="currency" {...props} />;

How This Works

  • AllowedInputChild ensures only your specified nested components can be passed as children. Any other component (like a <div>) will throw a TypeScript error immediately.
  • HasDuplicateChildren uses TypeScript's Extract utility to check if any component type appears more than once in the children array. If duplicates exist, the type resolves to never, which triggers a TS error.
  • The children prop in InputProps supports both single child elements and arrays of unique children, giving users flexibility while maintaining strict constraints.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 01:14:05