我需要创建一个通用的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);