【问题标题】:Custom types for React component - props not matchingReact 组件的自定义类型 - 道具不匹配
【发布时间】:2021-02-01 15:18:22
【问题描述】:

我正在尝试创建一个接受 2 个参数的函数:一个 React Functional 组件和它的 props 对象,并且具有以下要求:

  • 当没有组件需要的道具时,该函数应该能够只接受一个参数
  • 当组件的 props 丢失或与相应类型不匹配时,Typescript 应该报错

`

import React, { PropsWithChildren, FC, FunctionComponent } from 'react';

function render<T> (
    Component: FC<PropsWithChildren<T>>,
    props = {} as PropsWithChildren<PropsWithChildren<T>>,
): JSX.Element {
    return <Component { ...props }>{ props?.children }</Component>;
}

interface FooProps { name: string; }

const Foo = ({ name }: FooProps): JSX.Element => <p>{name}</p>;
const Bar = (): JSX.Element => <p>bar</p>;


// Incorrect
render(Foo, {  name: 'asdd', a: '' }); // Passing. Expected to fail because prop 'a' is not expected
render(Foo); // Passing. Expected to fail because the component's prop 'name' is missing
render(Bar, { name: 'test' }); // Passing. Expected to fail, because Bar's props object is incorrect

// Correct
render(Foo, {}); // Failing. Expected to fail because prop 'name' is required
render(Foo, {  name: 'asdd' }); // Passing. Expected to pass with correct props
render(Bar); // Passing. Expected to pass because Bar does not require any props
render(Bar, {}); // Passing. Expected to pass because Bar does not require any props

`

使用当前版本的代码,我能够满足大部分要求,但以下要求除外:

  • Typescript 在需要时应该需要 props 对象
  • Typescript 不应允许传递组件不需要的道具

不知道我做错了什么。任何建议将不胜感激。

【问题讨论】:

    标签: javascript reactjs typescript


    【解决方案1】:

    您正在使用typescript generics,但是当您将Foo 作为第一个参数传入时,您如何期望TS 会发现应该使用FooProps。只有在调用时指定模板参数时,您的解决方案才有效。例如

    render&lt;FooProps&gt;(Foo, { name: 'asdd', a: '' });

    如果你把FC参数作为模板类可能会更好。例如

    import React, { PropsWithChildren, FC, FunctionComponent } from 'react';
    
    function render<T>(
        Component: (_props: T) => JSX.Element,
        props: PropsWithChildren<T> = {} as PropsWithChildren<T>,
    ): JSX.Element {
        return <Component {...props}>{props?.children}</Component>;
    }
    
    interface FooProps {
        name: string;
    }
    
    interface BarProps {
    }
    
    const Foo = ({ name }: FooProps): JSX.Element => <p>{name}</p>;
    const Bar = ({}: BarProps): JSX.Element => <p>bar</p>;
    

    这将解决您的大部分问题。但是,将默认值定义为空对象不会让您在缺少参数的情况下失败,因为您必须在定义时将其转换为正确的类型,但在未提供时它将忽略验证(因为将使用已转换的默认值,所以它永远不会失败)。

    【讨论】:

      【解决方案2】:

      我认为你对PropsWithChildren 有一些误解。 FC 已经在幕后使用了PropsWithChildren,所以FC&lt;PropsWithChildren&lt;T&gt;&gt; 是多余的。 PropsWithChildren&lt;PropsWithChildren&lt;T&gt;&gt; 显然是多余的。我想你会在那里两次修复由于将其添加到 FC 引起的错误。

      children 可以包含在扩展运算符中。 &lt;Component { ...props }&gt;{ props?.children }&lt;/Component&gt;&lt;Component { ...props }/&gt; 相同。

      为了使第二个参数仅对T 的某些值是可选的,您需要将泛型与函数重载结合起来。

      function render(Component: FC<{}>): JSX.Element
      function render<T> (Component: FC<T>, props: PropsWithChildren<T>): JSX.Element
      function render<T> (Component: FC<T>, props = {} as PropsWithChildren<T>): JSX.Element {
          return <Component { ...props }/>;
      }
      

      这不会给出带有额外属性的错误。我可以告诉你原因,但我不确定解决方法。当您调用render(Foo, { name: 'asdd', a: '' }) 时,它会被推断为render&lt;{ name: string; a: string; }&gt;。 Foo 作为该类型的渲染组件是完全有效的。假设它只会忽略额外的属性。

      【讨论】:

      • 感谢您的建议。我认为目前最好的选择是重载函数。我可以通过额外的属性使这个版本失败的唯一方法是通过函数调用传递类型:render&lt;FooProps&gt;(Foo, { name: 'Bob', a: ' ' })
      猜你喜欢
      • 2021-06-23
      • 1970-01-01
      • 2022-12-11
      • 2020-10-02
      • 1970-01-01
      • 2019-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多