【问题标题】:Enforced properties on a React component with TypeScript使用 TypeScript 在 React 组件上强制执行属性
【发布时间】:2021-10-19 12:47:05
【问题描述】:

我想使用 TypeScript 在 React 组件上强制执行属性,但我遇到了奇怪的行为。下面我只粘贴简单的例子:

function FunctionalComponent(props: { color: string }) {
  return <></>;
}

type ComponentWithName<I extends React.FunctionComponent<{ name: string } & React.ComponentProps<I>>> = I;
const component: ComponentWithName<typeof FunctionalComponent> = FunctionalComponent;

即使我声明组件必须具有属性名称,上面的代码也会通过。使用此代码我需要得到一个错误,因为 FunctionalComponent 不包含 name 属性。

另一方面,这是可行的:

function FunctionalComponent(props: { color: string }) {
  return <></>;
}

type ComponentWithName<I extends React.FunctionComponent<{ name: string }>> = I;
const component: ComponentWithName<typeof FunctionalComponent> = FunctionalComponent

这段代码会抛出一个 TypeScript 错误,这正是我所需要的。但问题是,FunctionalComponent 不能有其他属性,除非我手动将它们添加到React.FunctionComponent

目标是强制组件具有“名称”属性,但允许具有更多附加(未指定)属性。

我正在使用 TypeScript 版本 4.4.4 和 React 版本 17.0.2

编辑:

真正的用例是这样的:

function Component<
  I extends
    | React.ComponentClass<
        {
          onChange: (event: React.ChangeEvent) => void;
        } & React.ComponentProps<I>
      >
    | React.ComponentType<
        {
          onChange: (event: React.ChangeEvent) => void;
        } & React.ComponentProps<I>
      >
>(
  props: {
    component?: I;
  } & Omit<React.ComponentProps<I>, "onChange">
) {
  const { component: Component, ...rest } = props;

  const handleChange = () => {
    //
  };

  return (
    <div>
      {Component ? (
        <Component
          {...(rest as React.ComponentProps<I>)}
          onChange={handleChange}
        />
      ) : (
        <input onChange={handleChange} />
      )}
    </div>
  );
}

class ComponentClass extends React.Component<{
  color: "blue" | "yellow";
}> {
  render() {
    return (
        <input style={{color: this.props.color}} />
    );
  }
}

function ComponentFunction(props: { color: "blue" | "yellow" }) {
  return <input style={{color: props.color}} />;
}

function App() {
  return (
    <>
      <Component component={ComponentClass} color="blue" />
      <Component component={ComponentFunction} color="blue" />
    </>
  );
}

&lt;Component component={ComponentClass} color="blue" /&gt; 将引发类型错误,但 &lt;Component component={ComponentFunction} color="blue" /&gt; 不会。我需要强制传递的组件具有指定类型的 onChange 属性。

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    我可能遗漏了一些东西,但您是否只需要强制执行 Props 的类型而不是创建类型化组件?

    interface NameProps {
       name: string;
    }
    
    type NamedComponent<T extends NameProps> = (props: T) => JSX.Element;
    
    const notANamedComponent: NamedComponent<{ int: number }> // ...this will give you an error
    const aNamedComponent: NamedComponent<{ int: number; name: string}> //OK
    

    【讨论】:

    • 谢谢,但这没有帮助。请查看我刚刚在上面发布的完整用例。
    【解决方案2】:

    问题是向组件传递额外的字段总是有效的,如果你想要求它做一些更难输入的事情。

    例如这是有效的代码:

    // from this context the argument will be called with an argument with both a and b properties
    function takeF(f: (data: {a:string, b:number})=>any){}
    
    // this function takes an object with an a property, but passing other properties would still be valid
    function f(data: {a:string}){}
    
    // this is allowed because passing an object with extra fields is still valid.
    takeF(f)
    

    {name: string}{color:string} 出现错误的原因是因为它们有 no 重叠,所以打字稿确实会给你错误,所以解决方案是将你的泛型限制为你实际的需要。

    declare function Component<
      ComponentProps extends { onChange: (event: React.ChangeEvent) => void; }
    >(
      props: {
        component?: React.ComponentClass<ComponentProps> | React.ComponentType<ComponentProps>;
      } & Omit<ComponentProps, "onChange">
    ): any
    
    

    这样,如果组件没有 onChange 则没有重叠,并且您会收到预期的错误,并且如果有额外的属性很好,因为这些属性已经被通用行为捕获。另请注意,这与@Marcus 所说的基本相同,只需将泛型限制为您实际需要的即可。

    【讨论】:

    • 太好了,谢谢它有效!
    猜你喜欢
    • 2022-01-08
    • 2018-08-18
    • 2018-08-08
    • 2019-10-16
    • 1970-01-01
    • 1970-01-01
    • 2011-10-08
    • 2017-11-05
    • 2021-01-09
    相关资源
    最近更新 更多