【问题标题】:How can I make TypeScript know which proptypes I am using?如何让 TypeScript 知道我正在使用哪些 proptypes?
【发布时间】:2021-09-15 11:26:50
【问题描述】:

如何让 TypeScript 知道组件正在接收哪些 prop 类型? 例如,如果 multiple 为真,我希望 TypeScript 期望 selectValue 是一个字符串数组,或者如果它不存在,那么 TypeScript 应该期望 selectValue 是一个字符串。

我真的不喜欢将我的 props 转换为合适的类型才能让 TypeScript 满意。

这是我的自定义 <Select> 组件的当前代码:

import React, { ReactNode } from 'react';
import MultipleSelect from './MultipleSelect';
import SingleSelect from './SingleSelect';

interface MultipleSelectProps {
  selectValue: string[];
  multiple: true;
  onSelectChange: (values: string[]) => void;
  renderValue?: (value: string[]) => React.ReactNode;
}

interface SingleSelectProps {
  selectValue: string;
  onSelectChange: (values: string) => void;
  multiple?: never;
  renderValue?: (value: string) => React.ReactNode;
}

type SelectProps = SingleSelectProps | MultipleSelectProps;

const Select: React.FC<SelectProps> = ({
  selectValue,
  multiple,
  onSelectChange,
  renderValue,
  children,
}) => {
  if (multiple) {
    const multipleSelectValue = selectValue as string[];
    const multipleSelectChange = onSelectChange as (value: string[]) => void;
    const multipleRenderValue = renderValue as (value: string[]) => ReactNode;

    return (
      <MultipleSelect
        selectValue={multipleSelectValue}
        onSelectChange={multipleSelectChange}
        renderValue={multipleRenderValue}
      >
        {children}
      </MultipleSelect>
    );
  }

  const singleSelectValue = selectValue as string;
  const singleSelectChange = onSelectChange as (value: string) => void;
  const singleRenderValue = renderValue as (value: string) => ReactNode;

  return (
    <SingleSelect
      selectValue={singleSelectValue}
      onSelectChange={singleSelectChange}
      renderValue={singleRenderValue}
    >
      {children}
    </SingleSelect>
  );
};
export default Select;

我想写成这样:

import React, { ReactNode } from 'react';
import MultipleSelect from './MultipleSelect';
import SingleSelect from './SingleSelect';

interface MultipleSelectProps {
  selectValue: string[];
  multiple: true;
  onSelectChange: (values: string[]) => void;
  renderValue?: (value: string[]) => React.ReactNode;
}

interface SingleSelectProps {
  selectValue: string;
  onSelectChange: (values: string) => void;
  multiple?: never;
  renderValue?: (value: string) => React.ReactNode;
}

type SelectProps = SingleSelectProps | MultipleSelectProps;

const Select: React.FC<SelectProps> = ({
  selectValue,
  multiple,
  onSelectChange,
  renderValue,
  children,
}) => {
  return (
{
  multiple ?
    <MultipleSelect
      selectValue={selectValue}
      onSelectChange={onSelectChange}
      renderValue={renderValue}
    >
      {children}
    </MultipleSelect>
    :
    <SingleSelect
      selectValue={selectValue}
      onSelectChange={onSelectChange}
      renderValue={renderValue}
    >
      {children}
    </SingleSelect>
  );
};
export default Select;

并且我希望能够像使用原生 html &lt;select&gt; 一样使用我的组件。像这样:

<Select selectValue={selectValue} onSelectChange={onSelectChange} multiple>
  {options}
</Select>

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    这就是我的做法。通过定义类型,TypeScript 可以知道 selectValue 的类型和其他可能会改变的类型。

    import React, { ReactNode } from 'react';
    import MultipleSelect from './MultipleSelect';
    import SingleSelect from './SingleSelect';
    
    
    type SelectProps = {
        kind: 'multiple',
        selectValue: string[];
        onSelectChange: (values: string[]) => void;
        renderValue?: (value: string[]) => React.ReactNode;
    } | {
        kind: 'single',
        selectValue: string;
        onSelectChange: (values: string) => void;
        renderValue?: (value: string) => React.ReactNode;
    
    }
    
    const Selector = (props: SelectProps, children): JSX.Element => {
      return (
        <>
            {
            props.kind === 'multiple' 
            ?
                <MultipleSelect
                selectValue={props.selectValue}
                onSelectChange={props.onSelectChange}
                renderValue={props.renderValue}
                >
                {children}
                </MultipleSelect>
            :
                <SingleSelect
                selectValue={props.selectValue}
                onSelectChange={props.onSelectChange}
                renderValue={props.renderValue}
                >
                {children}
                </SingleSelect>
            
            }
        </>
      )
    }
    
    export default Selector
    

    【讨论】:

    • 这与拥有多个 prop 并将其定义为 True 或 Never 有何不同?是因为它是可选的吗?我希望能够像这样编写我的组件:&lt;Select ...otherProps multiple&gt; 来模仿原生 html 的使用 &lt;select&gt;
    • 在我的示例代码中,如果您将鼠标悬停在 MultipleSelect 中的 props.selectValue 上,您会看到 TypeScript 知道该类型是字符串数组。在您的第二个代码块中,TypeScript 认为它是任意的。我没有尝试使用您提到的组件:
    • 嗯,我似乎无法弄清楚这一点。你使用的是哪个版本的 TypeScript?
    • 我使用的是 TypeScript 4.4.2
    【解决方案2】:

    您可以使用in检查是否设置了多个,如下所示:

    type MultipleSelectProps = {
      multiple: true;
      value: string[];
      onChange: (newValue: string[]) => void;
    };
    
    type SingleSelectProps = {
      value: string;
      onChange: (newValue: string) => void;
    };
    
    type SelectProps = MultipleSelectProps | SingleSelectProps;
    
    function SingleSelect(props: SingleSelectProps) {
      return null;
    }
    
    function MultiSelect(props: MultipleSelectProps) {
      return null;
    }
    
    function Select(props: SelectProps) {
      return "multiple" in props ? (
        <MultiSelect {...props} />
      ) : (
        <SingleSelect {...props} />
      );
    }
    

    【讨论】:

    • 我仍然收到错误Type 'string | string[]' is not assignable to type 'string[]'. Type 'string' is not assignable to type 'string[]'.ts(2322)。我不想使用 propspreading(lint 规则),没有可能吗?
    • 你在哪里得到这个错误?这个例子对我来说运行没有错误,但可能还有其他一些问题......你使用的是什么 TS 版本?
    • TypeScript 是 v4.2.4。您编写它的方式与给我错误的代码之间的唯一区别是您使用的是道具传播。例如{...props}。正常传递props不会报错吗?
    • 听起来你正在解构你的价值道具......如果你在检查multiple之前解构价值,那么ts无法缩小价值的类型。
    • 看来你是对的。在组件参数中解构时,是否可以使 ts 缩小类型?例如({selectValue, onSelectChange, renderValue, multiple}) =&gt; { // component code }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-14
    • 2021-01-04
    • 1970-01-01
    • 2011-11-12
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    相关资源
    最近更新 更多