جاوااسکریپت - تایپاسکریپت شرطی extends بر اساس مقدار پارامترها


من نسبتاً تازهکار در Typescript هستم و در تلاش هستم که یک توسعه شرطی بر اساس مقدار یک آرگومان تابع داشته باشم. زمینه این است که یک ویجت برای انتخاب شیء تعریف کنم که میتواند یک انتخابگر (select)، یک لیست رادیویی (یک گروه رادیویی) یا یک لیست چکباکس (یک گروه چکباکس) باشد. هر کدام از این ویجتهای مختلف پروپسهای متفاوتی را قبول میکنند: name: SelectProps، RadioGroupProps و CheckboxGroupProps. انتخاب ویجت بر اساس دو پارامتر است: expand (یعنی ما میخواهیم یک لیست چکباکس یا رادیویی داشته باشیم) و multiple (یعنی میخواهیم چند عنصر را انتخاب کنیم یا نه).

function SelectionWidget(props: SelectionWidgetProps) {}

اینجا من میخواهم که:
اگر props با {expand: true, multiple: false} مطابقت داشت، آنگاه SelectionWidgetProps باید از RadioGroupProps ارثبری کند.
اگر props با {expand: true, multiple: true} مطابقت داشت، آنگاه SelectionWidgetProps باید از CheckboxGroupProps ارثبری کند.
در غیر این صورت، SelectionWidgetProps باید از SelectProps ارثبری کند.

من موارد زیر را امتحان کردم:

type SelectTypeExtend<T> = 
    T extends { expand: false } ? SelectProps : 
    T extends { expand: true; multiple: false } ? RadioGroupProps :
    T extends { expand: true; multiple: true; } ? CheckboxGroupProps :
    never;

interface SelectTypeProps extends SelectTypeExtends {
    multiple?: boolean;
    expand?: boolean;
}

اما این کار نمیکند چون SelectTypeExtends یک آرگومان انتظار دارد. آیا ممکن است این کار را با تایپاسکریپت انجام داد؟ ممنون!

پاسخ شماره ۱

شما میتوانید interface را تنها با نوعهای شناخته شده به صورت ایستا گسترش دهید. این بدان معناست که اجازه نمیدهد SelectTypeProps را با SelectTypeExtends گسترش دهید، زیرا SelectTypeExtends یک جعبه سیاه است. هیچکس نوع T را نمیداند.

برای مدیریت این مورد، شما باید از type به جای interface استفاده کنید:

type SelectProps = {
  tag: 'SelectProps'
}

type RadioGroupProps = {
  tag: 'RadioGroupProps'
}

type CheckboxGroupProps = {
  tag: 'CheckboxGroupProps '
}

type SelectTypeExtend<T> =
  T extends { expand: false } ? SelectProps :
  T extends { expand: true; multiple: false } ? RadioGroupProps :
  T extends { expand: true; multiple: true; } ? CheckboxGroupProps :
  never;

type SelectTypeProps<T> = { // <------ type instead of interface
  multiple?: boolean;
  expand?: boolean;
} & SelectTypeExtend<T>

نمونه با کامپوننتهای ریاکت:

import React from "react";

import {
  Select,
  SelectProps,
  Checkbox,
  CheckboxGroupProps,
  Radio,
  RadioGroupProps
} from "formik-antd";


export type SelectionComponentProps =
  | { expand?: false } & { options: SelectProps }
  | { expand: true; multiple?: false } & { options: RadioGroupProps }
  | { expand: true; multiple: true } & { options: CheckboxGroupProps };

function SelectionComponent(props: SelectionComponentProps) {
  if (props.expand) {
    if (props.multiple) {
      return <Checkbox.Group {...props.options} />;
    } else {
      return <Radio.Group {...props.options} />;
    }
  }
  return <Select {...props.options} />;
};

const select: SelectProps = 'unimplemented' as any;
const radio: RadioGroupProps = 'unimplemented' as any;
const checkbox: CheckboxGroupProps = 'unimplemented' as any;

export const TestRadio = () => {
  // Accept properties from RadioGroupProps but not from SelectProps or CheckboxGroupProps
  return [
    <SelectionComponent expand options={radio} />, // ok
    <SelectionComponent expand options={select} />, // expected error
    <SelectionComponent expand options={checkbox} />, // expected error
  ]
};

export const TestCheckbox = () => {
  // Accept properties from CheckboxProps but not from SelectProps or RadioGroupProps
  return [
    <SelectionComponent expand multiple options={checkbox} />, // ok
    <SelectionComponent expand multiple options={select} />, // expected error
    <SelectionComponent expand multiple options={radio} /> // expected error
  ]
};

export const TestSelect = () => {
  // Accept properties from SelectProps but not from RadioGroupProps or CheckboxGroupProps
  return [
    <SelectionComponent options={select} />, // ok
    <SelectionComponent options={radio} />, // expected error
    <SelectionComponent options={checkbox} />, // expected error
  ]
};

زمین بازی

من نوع SelectionComponentProps را کمی تغییر دادهام. لطفاً به این نکته توجه داشته باشید که اگر برخی ویژگیها را به کامپوننت ریاکت ارائه ندهید، TS آن را به عنوان undefined در نظر میگیرد و نه به عنوان false، بنابراین شما نیاز دارید برخی از ویژگیهای false خود را به صورت partial تعریف کنید.