我正在用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(我相信)是一个接受Component
的函数。由于我事先并不知道包装组件是什么,我使用泛型类型TChildProps
来定义包装组件的道具形状。该函数还返回Component
。返回的组件接受包装组件的道具(再次,使用泛型TChildProps
)键入它自己需要的一些道具(类型HocProps
)。使用返回的组件时,应提供所有道具(包括HocProps
和包裹的Component
的道具)。
现在,当我尝试使用我的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并没有将TChildProps
替换为ChildComponent
所需的道具形状。我如何让TypeScript做到这一点?
答案 0 :(得分:6)
如果您要求的是,是否可以定义一个可以添加新道具的HOC,让我们说“thingy”,对于一个组件而不修改该组件的道具定义以包含“thingy”我觉得这是不可能的。
那是因为在代码的某些时候你最终会得到:
render() {
return (
<WrappedComponent thingy={this.props.thingy} {...this.props}/>
);
}
如果WrappedComponent
在其道具定义中不包含thingy
,那么这总会引发错误。孩子必须知道它收到了什么。在手边,我无法想到将支柱传递给一个不知道它的组件的原因。如果没有错误,您将无法在子组件中引用该prop。
我认为诀窍是将HOC定义为围绕孩子的道具的通用,然后明确地包含你的道具thingy
或其他任何内容。
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);
答案 1 :(得分:1)
聚会晚了一点。 我喜欢使用Omit TypeScript实用程序类型来解决此问题。 链接到文档:https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk
import React, {ComponentType} from 'react';
export interface AdditionalProps {
additionalProp: string;
}
export function hoc<P extends AdditionalProps>(WrappedComponent: ComponentType<P>) : ComponentType<Omit<P, 'additionalProp'>> {
const additionalProp = //...
return props => (
<WrappedComponent
additionalProp={additionalProp}
{...props as any}
/>
);
}
答案 2 :(得分:0)
我发现了一种方法可以使它工作:通过调用提供类型参数的hoc
,如下所示:
import ChildComponent, { Props as ChildComponentProps } from './child';
const WrappedChildComponent = hoc<ChildComponentProps>()(ChildComponent);
但我真的不喜欢它。它需要我导出孩子的道具(我宁愿不做),我觉得我告诉TypeScript应该能够推断的东西。
答案 3 :(得分:0)
您的问题是hoc
是通用的,但是没有参数-没有数据可以推断任何内容。这就迫使您在调用hoc
时显式指定其类型。即使您进行hoc()(Foo)
,Typescript也必须先评估hoc()
,并且此时的信息为零。因此需要hoc<FooProps>()(Foo)
。
如果hoc
的主体不需要该类型并且可以灵活设置,则可以使hoc
不是是通用的,而是返回泛型函数(高阶组件)。
也就是说,而不是
const hoc = function<TChildProps>(): (component: Component<TChildProps>) => Component<HocProps & TChildProps) {
return (Child: Component<TChildProps>) => {
相反,您可以拥有
const hoc = function(): <TChildProps>(component: Component<TChildProps>) => Component<HocProps & TChildProps) {
return <TChildProps>(Child: Component<TChildProps>) => {
(请注意<TChildProps>
的位置)
然后,当您调用hoc()
时,会得到一个泛型函数,而当您调用hoc()(Foo)
时,它将被推断为等同于hoc()<FooProps>(Foo)
。
如果hoc
本身需要对类型参数做一些事情,这无济于事-如果这样做,是说“给我一个函数,该函数将只接受属于该类型的组件的参数作为属性。 ”,则需要输入 then 。是否有帮助取决于实际的hoc
函数在做什么,这在您的问题中没有得到证明。
我假设您的实际用例 正在hoc
中做某事,而不是立即返回高阶组件函数。当然,如果没有,您应该删除间接层,只保留一个高阶组件。即使您在hoc
内做事,我也强烈建议您很有可能只是不去做就可以了,然后移动该功能到实际的高阶分量内,并消除了间接。仅当您一次或几次调用hoc
,但随后使用结果的hoc()
函数并使用不同的参数多次时,您的方法才有意义-即使那样,这仍然是一个优化可能还为时过早。
答案 4 :(得分:0)
我发现以下有关Pluralsight的文章对于理解Typescript中的HOC及其基本概念非常有帮助。我碰到的所有其他文章以前都是从零开始的东西混搭在一起,而且这一步一步地工作了,而且清晰明了: