使用TypeScript编写React高阶组件

mth*_*ers 10 typescript reactjs higher-order-components

我正在用TypeScript 编写一个React高阶组件(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;
Run Code Online (Sandbox Code Playgroud)

换句话说,hoc是一个产生实际HOC的函数.这个HOC(我相信)是一个接受a的功能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' } />
}
Run Code Online (Sandbox Code Playgroud)

但我得到一个TypeScript错误:

TS2339: Property 'foo' does not exist on type 'IntrinsicAttributes & HocProps & {} & { children? ReactNode; }'
Run Code Online (Sandbox Code Playgroud)

在我看来,TypeScript并没有取代TChildProps所需道具的形状ChildComponent.我如何让TypeScript做到这一点?

Par*_*rip 9

聚会有点晚了。我喜欢使用 Omit TypeScript 实用程序类型来解决这个问题。文档链接:https : //www.typescriptlang.org/docs/handbook/utility-types.html#omitk

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}
        />
    );
}
Run Code Online (Sandbox Code Playgroud)


Kha*_*nna 6

如果您要求的是,是否可以定义一个可以添加新道具的HOC,让我们说"thingy",对于一个组件而不修改该组件的道具定义以包含"thingy"我觉得这是不可能的.

那是因为在代码中的某些时候你最终会得到:

render() {
    return (
        <WrappedComponent thingy={this.props.thingy} {...this.props}/>
    );
}
Run Code Online (Sandbox Code Playgroud)

如果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);
Run Code Online (Sandbox Code Playgroud)

当然,这个例子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>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,使用固定间隔更新子组件的示例HOC setInterval可能是:

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}/>
            );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我们可以创建一个每秒更新一次孩子的组件,如下所示:

const Child = AutoUpdate(ChildComponent, 1000);
Run Code Online (Sandbox Code Playgroud)


mth*_*ers 2

我找到了一种让它工作的方法:通过调用hoc提供的类型参数,如下所示:

import ChildComponent, { Props as ChildComponentProps } from './child';
const WrappedChildComponent = hoc<ChildComponentProps>()(ChildComponent);
Run Code Online (Sandbox Code Playgroud)

但我真的不喜欢它。它要求我导出子组件的 props(我不想这样做),而且我感觉我正在告诉 TypeScript 一些它应该能够推断出的东西。