web-dev-qa-db-ja.com

TypeScriptでReact高次コンポーネントを作成する

TypeScriptで React高次コンポーネント(HOC) を書いています。 HOCはラップされたコンポーネントよりも1つ多くの小道具を受け入れる必要があるので、私はこれを書きました:

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がTChildPropsChildComponentに必要なプロップの形状に置き換えていないようです。 TypeScriptでそれを行うにはどうすればよいですか?

13
mthmulders

私はそれを機能させる1つの方法を見つけました:次のように、提供されたtype引数を使用してhocを呼び出すことによって:

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

しかし、私はそれが本当に好きではありません。子供の小道具をエクスポートする必要があります(私はそれをしたくはありません)。TypeScriptに推測できるものを伝えているような気がします。

0
mthmulders

あなたが求めているのが、新しいプロップを追加できるHOCを定義することが可能である場合、コンポーネントのプロップ定義を変更して「モノ」を含めることなく、「モノ」と言いましょう。それは不可能だと思います。

これは、コードのある時点で次のようになるためです。

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

また、WrappedComponentthingyがプロップ定義に含まれていない場合は、常にエラーがスローされます。子供は何を受け取るかを知らなければなりません。一方で、私はとにかくそれについて知らないコンポーネントに小道具を渡す理由を考えることができません。エラーなしで子コンポーネントでその小道具を参照することはできません。

トリックは、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は実際にはanythingを実行しません。本当に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}/>
            );
        }
    }
}

次に、次のように1秒ごとに子を更新するコンポーネントを作成します。

const Child = AutoUpdate(ChildComponent, 1000);
6
KhalilRavanna

あなたの問題は、hocが汎用的であるということですが、パラメーターを取りません。何から推論するためのデータもありません。そのため、呼び出すときにhocのタイプを明示的に指定する必要があります。 hoc()(Foo)を実行する場合でも、TypeScriptは最初にhoc()を評価する必要があります。その時点で実行する情報はありません。したがって、hoc<FooProps>()(Foo)が必要です。

hocの本体が型を必要とせず、その点で柔軟である場合、hocnotをジェネリックにすることができますが、代わりにreturnジェネリック関数(高次コンポーネント)。

つまり、代わりに

_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自体がtypeパラメーターを使用して何かを行う必要がある場合には役立ちません。その場合、「このタイプのコンポーネントである引数のみをプロパティとして受け入れる関数を指定してください」そして、あなたはそれにタイプthenを与える必要があります。これが役立つかどうかは、実際のhoc関数が何をしているかによって異なりますが、質問では示していません。

私はあなたの実際のユースケースisがすぐに高次コンポーネント関数を返すのではなく、hocで何かをしていると思います。確かにそうでない場合は、間接参照のレイヤーを削除して、高次コンポーネントだけを用意する必要があります。あなたがarehocの中で物事をやっているとしても、あなたがより良い結果を出す可能性がかなりあると強くお勧めしますnotそれをやって、それを動かす実際の高次コンポーネント内に機能を追加し、間接参照を排除します。このアプローチは、hocを1回または数回呼び出す場合にのみ意味がありますが、結果のhoc()関数をさまざまなパラメーターで何度も使用します。最適化は時期尚早かもしれません。

0
KRyan