我正在用TypeScript写一个
React higher-order component (HOC). HOC应该接受比包裹组件更多的支柱,所以我写道:
type HocProps { // Contains the prop my HOC needs thingy: number } type Component<P> = React.ComponentClass<P> | React.StatelessComponent<P> interface ComponentDecorator<TChildProps> { (component: Component<TChildProps>): Component<HocProps & TChildProps>; } const hoc = function<TChildProps>(): (component: Component<TChildProps>) => Component<HocProps & TChildProps) { return (Child: Component<TChildProps>) => { class MyHOC extends React.Component<HocProps & TChildProps,void> { // Implementation skipped for brevity } return MyHOC; } } export default hoc;
换句话说,hoc是产生实际HOC的函数.这个HOC(我相信)是一个接受组件的函数.由于我事先并不知道包装组件是什么,我使用通用类型TChildProps来定义包装组件的道具形状.该函数还返回一个Component.返回的组件接受包装组件的道具(再次,使用通用TChildProps键入)以及它自己需要的一些道具(类型为HocProps).使用返回的组件时,应提供所有道具(包括HocProps和包装组件的道具).
现在,当我尝试使用我的HOC时,我会执行以下操作:
// outside parent component const WrappedChildComponent = hoc()(ChildComponent); // inside parent component render() { return <WrappedChild thingy={ 42 } // Prop `foo` required by ChildComponent foo={ 'bar' } /> }
但我得到一个TypeScript错误:
TS2339: Property 'foo' does not exist on type 'IntrinsicAttributes & HocProps & {} & { children? ReactNode; }'
在我看来,TypeScript并没有用ChildComponent所需的道具形状替换TChildProps.我如何让TypeScript做到这一点?
如果您要求的是,是否可以定义一个可以添加新道具的HOC,让我们说“thingy”,对于一个组件而不修改该组件的道具定义以包含“thingy”我觉得这是不可能的.
那是因为在代码中的某些时候你最终会得到:
render() { return ( <WrappedComponent thingy={this.props.thingy} {...this.props}/> ); }
如果WrappedComponent在其props定义中不包含thingy,那将始终抛出错误.孩子必须知道它收到了什么.另一方面,我想不出将道具传递给一个不知道它的组件的理由.如果没有错误,您将无法在子组件中引用该prop.
我认为诀窍是将HOC定义为围绕孩子的道具的通用,然后明确地在你孩子的界面中包含你的道具或任何东西.
interface HocProps { // Contains the prop my HOC needs thingy: number; } const hoc = function<P extends HocProps>( WrappedComponent: new () => React.Component<P,any> ) { return class MyHOC extends React.Component<P,any> { render() { return ( <WrappedComponent {...this.props}/> ); } } } export default hoc; // Example child class // Need to make sure the Child class includes 'thingy' in its props definition or // this will throw an error below where we assign `const Child = hoc(ChildClass)` interface ChildClassProps { thingy: number; } class ChildClass extends React.Component<ChildClassProps,void> { render() { return ( <h1>{this.props.thingy}</h1> ); } } const Child = hoc(ChildClass);
当然,这个例子HOC并没有真正做任何事情.真正的HOC应该采取某种逻辑来为儿童道具提供价值.例如,您可能有一个组件显示一些重复更新的通用数据.您可以采用不同的方式进行更新,并创建HOC以将该逻辑分离出来.
你有一个组件:
interface ChildComponentProps { lastUpdated: number; data: any; } class ChildComponent extends React.Component<ChildComponentProps,void> { render() { return ( <div> <h1>{this.props.lastUpdated}</h1> <p>{JSON.stringify(this.props.data)}</p> </div> ); } }
然后,使用setInterval以固定间隔更新子组件的示例HOC可能是:
interface AutoUpdateProps { lastUpdated: number; } export function AutoUpdate<P extends AutoUpdateProps>( WrappedComponent: new () => React.Component<P,any>,updateInterval: number ) { return class extends React.Component<P,any> { autoUpdateIntervalId: number; lastUpdated: number; componentWillMount() { this.lastUpdated = 0; this.autoUpdateIntervalId = setInterval(() => { this.lastUpdated = performance.now(); this.forceUpdate(); },updateInterval); } componentWillUnMount() { clearInterval(this.autoUpdateIntervalId); } render() { return ( <WrappedComponent lastUpdated={this.lastUpdated} {...this.props}/> ); } } }
然后我们可以创建一个每秒更新一次孩子的组件,如下所示:
const Child = AutoUpdate(ChildComponent,1000);