Написание компонента React более высокого порядка с помощью TypeScript

Я пишу React компонент более высокого порядка (HOC) С TypeScript. 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' } />
}

но я получаю машинописный текст ошибка:

TS2339: Property 'foo' does not exist on type 'IntrinsicAttributes & HocProps & {} & { children? ReactNode; }'

мне кажется, TypeScript не заменяет TChildProps С формой реквизита, необходимого для ChildComponent. Как я могу сделать TypeScript?

2 ответов


если вы спрашиваете, можно ли определить HOC, который может добавить новую опору, скажем, "thingy", к компоненту без изменения определения реквизита этого компонента, чтобы включить "thingy", я думаю, что это невозможно.

это потому, что в какой-то момент кода Вы получите:

render() {
    return (
        <WrappedComponent thingy={this.props.thingy} {...this.props}/>
    );
}

и это всегда будет вызывать ошибку, если WrappedComponent не входит thingy в его определении реквизита. Ребенок должен знать, что он получает. Без рук, я не могу. подумайте о причине передачи опоры компоненту, который все равно не знает об этом. Вы не сможете ссылаться на эту опору в дочернем компоненте без ошибки.

Я думаю, что трюк состоит в том, чтобы определить 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>
        );
    }
}

а затем пример 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}/>
            );
        }
    }
}

тогда мы могли бы создать компонент это обновляет нашего ребенка раз в секунду, как это:

const Child = AutoUpdate(ChildComponent, 1000);

Я нашел один способ заставить его работать: вызывая hoc с предоставленным аргументом типа, например:

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

но мне это не очень нравится. Это требует, чтобы я экспортировал реквизит ребенка (чего я бы предпочел не делать), и у меня такое чувство, что я говорю TypeScript что-то, что он должен быть в состоянии сделать.