如何定义具有附加接口的泛型HOC以添加到props



我需要创建一个通用的HOC,它将接受一个将被添加到组件道具的接口。

我实现了下面的函数,但是它需要两个参数而不是一个。我想要第二个参数从它传递给函数的组件中获取。

export const withMoreProps = <NewProps, Props>(WrappedComponent: React.FC<Props>): React.FC<Props & NewProps> => {
const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
const ComponentWithMoreProps = (props: Props & NewProps) => <WrappedComponent {...props} />;
ComponentWithMoreProps.displayName = `withMoreProps(${displayName})`;
return ComponentWithMoreProps;
};

当前当我尝试使用这个:

const Button = (props: { color: string }) => <button style={{ color: props.color }}>BTN</button>;
export const Button2 = withMoreProps<{ newProperty: string }>(Button);

我得到这个错误信息

期望有2个类型参数,但得到1个。

它应该像styled-components一样工作,你只能定义额外的道具。

export const StyledButton = styled(Button)<{ withPadding?: boolean }>`
padding: ${({ withPadding }) => (withPadding ? '8px' : 0)};
`;

编辑:

这是我在应用中创建的HOC的简化版本。真正的HOC要复杂得多,而且还能做其他事情,但为了简化,我让这个例子只关注我遇到的问题。

一般情况下,您希望使用infer关键字。你可以在这里阅读更多信息,但简而言之,你可以把它看作是"提取"的助手。泛型之外的类型

让我们定义一个从react组件中提取prop类型的类型。

type InferComponentProps<T> = T extends React.FC<infer R> ? R : never;

功能示例:

const Button = (props: { color: string }) => <button style={{ color: props.color }}>BTN</button>;
type ButtonProps = InferComponentProps<typeof Button>; // hover over ButtonProps, see {color: string}

现在我们有了这个"助手类型",我们可以继续实现您想要的东西-但是我们确实遇到了一个问题。在typescript中调用泛型函数时,有些类型不能指定,有些则不能指定。你要么指定与这个函数调用匹配的所有具体类型,要么什么都不指定,让Typescript找出这些类型。

function genericFunction<T1, T2>(t1: T2) {
//...
}
const a = genericFunction('foo') //ok
const b = genericFunction<number, string>('foo') //ok
const c = genericFunction<string>('foo') //error

你可以在这里跟踪打字问题。


所以要解决这个问题,我们需要对代码做一个小的改变,并做一个函数,返回一个返回新组件的函数。如果您注意到,这正是styled的工作方式,因为使用标记模板字面量实际上是一个函数调用。所以在你上面发布的样式组件代码中有2个函数调用。

所以最后的代码看起来像这样:
export const withMoreProps =
<C extends React.FC<any>>(WrappedComponent: C) =>
<NewProps extends Object>(): React.FC<InferComponentProps<C> & NewProps> => {
const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';

//need to re-type the component
let WrappedComponentNew = WrappedComponent as React.FC<InferComponentProps<C> & NewProps>;
const ComponentWithMoreProps = (props: InferComponentProps<C> & NewProps) => <WrappedComponentNew {...props} />;
ComponentWithMoreProps.displayName = `withMoreProps(${displayName})`;
return ComponentWithMoreProps;
};
const Button = (props: { color: string }) => <button style={{ color: props.color }}>BTN</button>;
export const Button2 = withMoreProps(Button)<{ newProperty: string }>(); //Notice the function call at the end

如果你只是想用一种通用的方式向组件中添加更多的道具,你就不需要HOC的开销了。您可以使用rest和spread操作符来传递道具,从而轻松实现这一点。(我在HOC上使用了颜色,不像OP的例子,它在主按钮上,这只是一个很好的例子)

const ColoredButton = ({color, ...other}) => <Button {...other, style: {color}/>

它可能比HOC版本稍微多一点代码,它基本上为您处理传递...other。然而作为回报:

  • 你不会得到一个奇怪的显示名称(withMoreProps(Button)这可能是任何道具)。相反,它只会像其他React组件一样使用函数名(例如ColoredButton)。在调试时,您更希望使用后者。
  • 生成的组件和其他React组件一样灵活。如果需要,只需在函数体中添加逻辑即可。但是hoc没有功能体。您可以添加多个HOC,但这很快就会变得笨拙。

类似地,声明类型的问题也就不存在了。它的工作原理与主按钮类型完全相同。

const Button = (props: { color: string }) => <button style={{ color: props.color }}>BTN</button>;
export const Button2 = ({ newProperty: string, ...other }) => <Button {...other, newProperty}/>
// Original for comparison.
export const Button3 = withMoreProps<{ newProperty: string }>(Button);

相关内容

  • 没有找到相关文章

最新更新