ريأكت ج إس - كتابة مكون أعلى ترتيب في ريأكت باستخدام تايب سكريبت


أقوم بكتابة مكون عالي المستوى (HOC) في React باستخدام 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' } />
}

لكنني أحصل على خطأ في TypeScript:

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

يبدو لي أن TypeScript لا يستبدل TChildProps بالشكل الخاص بالخصائص المطلوبة لـ ChildComponent. كيف يمكنني جعل TypeScript يفعل ذلك؟

الإجابة رقم 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

إذا كان ما تسأله هو ما إذا كان من الممكن تعريف 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 لا يفعل حقًا أي شيء. في الواقع، يجب أن تقوم HOCs ببعض المنطق لتقديم قيمة للخاصية child. على سبيل المثال، قد يكون لديك مكون يعرض بعض البيانات العامة التي يتم تحديثها بشكل متكرر. يمكنك أن يكون لديك طرق مختلفة لتحديثه وإنشاء HOCs لفصل هذا المنطق.

لديك مكوّن:

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);