reactjs - كيف تستخدم الأنواع العامة (generics) في الخصائص (props) في React داخل المكون الوظيفي؟


في مكون معتمد على الفئة، يمكنني بسهولة كتابة بعض الشيفرة مثل هذا:

import * as React from 'react';
import { render } from 'react-dom';

interface IProps<T> {
    collapsed: boolean;
    listOfData: T[];
    displayData: (data: T, index: number) => React.ReactNode;
}

class CollapsableDataList<T> extends React.Component<IProps<T>> {
    render () {
        if (!this.props.collapsed) {
            return <span>total: {this.props.listOfData.length}</span>
        } else {
            return (
                <>
                    {
                        this.props.listOfData.map(this.props.displayData)
                    }
                </>
            )
        }
    }
}

render(
    <CollapsableDataList
        collapsed={false}
        listOfData={[{a: 1, b: 2}, {a: 3, b: 4}]}
        displayData={(data, index) => (<span key={index}>{data.a + data.b}</span>)}
    />,
    document.getElementById('root'),
)

في الواقع، يجب أن يكون هذا المكون CollapsableDataList مكونًا وظيفيًا لأنه بلا حالة، لكنني لا أستطيع معرفة كيفية كتابة مكون دالي واستخدام الأنواع العامة في الخصائص، هل لديك أي نصيحة لي؟

الإجابة رقم 1

لا يمكنك إنشاء مكون وظيفي مع تعليق نوع وجعله عامًا. لذلك، هذا لن يعمل لأن T غير معرف ولا يمكنك تعريفه على مستوى المتغير:

const CollapsableDataList : React.FunctionComponent<IProps<T>> = p => { /*...*/ } 

ومع ذلك، يمكنك تخطي تحديد نوع البيانات، وجعل الدالة عامة وتحديد نوع props بشكل صريح.

import * as React from 'react';
import { render } from 'react-dom';

interface IProps<T> {
    collapsed: boolean;
    listOfData: T[];
    displayData: (data: T, index: number) => React.ReactNode;
}
const CollapsableDataList = <T extends object>(props: IProps<T> & { children?: ReactNode }) => {
    if (!props.collapsed) {
        return <span>total: {props.listOfData.length}</span>
    } else {
        return (
            <>
                {
                    props.listOfData.map(props.displayData)
                }
            </>
        )
    }
}


render(
    <CollapsableDataList
        collapsed={false}
        listOfData={[{a: 1, b: 2}, {a: 3, c: 4}]}
        displayData={(data, index) => (<span key={index}>{data.a + (data.b || 0)}</span>)}
    />,
    document.getElementById('root'),
)

الإجابة رقم 2

type Props<T> = {
    active: T;
    list: T[];
    onChange: (tab: T) => void;
};

export const Tabs = <T,>({ active, list, onChange }: Props<T>): JSX.Element => {
    return (
        <>
            {list.map((tab) => (
                <Button onClick={() => onChange(tab)} active={tab === active}>
                    {tab} 
                </Button>
            ))}
        </>
    );
};

الإجابة رقم 3

النوع React.FC هو في الأساس هذا:

<P = {}>(props: PropsWithChildren<P>, context?: any) => ReactElement | null

لذا بدلاً من هذا (الذي ليس مسموحًا به):

const Example: React.FC<Props<P>> = (props) => {
  // return a React element or null
}

يمكنك استخدام هذا:

const Example = <P extends unknown>(props: PropsWithChildren<Props<P>>): ReactElement | null => {
  // return a React element or null
}

على سبيل المثال:

const Example = <P extends unknown>({ value }: PropsWithChildren<{ value: P }>): ReactElement | null => {
  return <pre>{JSON.stringify(value)}</pre>
}

أو، بشكل أكثر صرامة، إذا كان المكوّن لا يستخدم الخاصية children ولن يعيد null:

const Example = <P>({ value }: { value: P }): ReactElement => {
  return <pre>{value}</pre>
}

ثم استخدم المكوّن المكتوب كما <Example<string> value="foo"/>

الإجابة رقم 4

ريأكت 18، تايب سكريبت 4.6.3

interface IProps<T> {
  data: T[];
}
export const YourComponent = <T,>(props: IProps<T>) => {}

الاستخدام:

const App = () => {
  const data : IDataProps[] = [
    {
      id:"1",
      label:"first banu"
    }
  ]
  return (
    <div className="App">
      <YourComponent data={data}/>
    </div>
  );
}

الإجابة رقم 5

قبل معالجة المكوّن الوظيفي، أفترض أن مثال الكود الأصلي يفتقد النوع العام في مكوّن JSX حيث لا أراه ممرّرًا إلى واجهة IProps. أي:

interface Ab {
  a: number;
  b: number;
}

...

// note passing the type <Ab> which will eventually make it to your IProps<T> interface and cascade the type for listOfData
return (
<CollapsableDataList<Ab>
  collapsed={false}
  listOfData={[{a: 1, b: 2}, {a: 3, c: 4}]}
  ...
/>
)

حسناً، الآن مع قليل من الجهد يمكنك في الواقع الحصول على مكون وظيفي بخصائص عامة.

أنت عالق في استخدام الصياغة 'الحديثة' رغم أنها تستخدم التعيين ودالة السهم والتي لا فائدة لها في حالتك العامة:

// using this syntax there is no way to pass generic props
const CollapsableDataList: React.FC<IProps> = ({ collapsed, listOfData }) => {
  // logic etc.
  return (
  // JSX output
  );
}

لنعد كتابة تعيين المتغير كـ function القديمة الجيدة:

// we are now able to to write our function component with generics
function CollapsableDataList<T>({ collapsed, listOfData }: IProps<T> & { children?: React.ReactNode }): React.ReactElement {
  // logic etc.
  return (
  // JSX output
  );
}

الحل البديل children ليس بالضرورة مطلوبًا إذا كان المكون لا يستخدم الخاصية children، لكنني أضفتها لتسليط الضوء على حقيقة أنه يجب إعادة كتابتها يدويًا حيث أن React.FC كان يفعل ذلك لنا من قبل.