ريأكت ج إس - كيف تنشئ مكوّن React عام مع موفر سياق محدد النوع؟


مع واجهة برمجة التطبيقات الجديدة للسياق في React، يمكنك إنشاء منتج/مستهلك سياق مكتوب بالأنواع هكذا:

type MyContextType = string;

const { Consumer, Producer } = React.createContext<MyContextType>('foo');

ومع ذلك، لنفترض أن لدي مكوّنًا عامًا يعرض العناصر.

// To be referenced later
interface IContext<ItemType> {
    items: ItemType[];
}

interface IProps<ItemType> {
    items: ItemType[];
}

class MyList<ItemType> extends React.Component<IProps<ItemType>> {
    public render() {
        return items.map(i => <p key={i.id}>{i.text}</p>);
    }
}

إذا أردت بدلًا من ذلك عرض بعض المكونات المخصصة كبند في القائمة وتمرير السمات من MyList كسياق، كيف يمكنني تحقيق ذلك؟ هل هذا ممكن أصلاً؟

ما حاولت:

النهج #1

class MyList<ItemType> extends React.Component<IProps<ItemType>> {
    // The next line is an error.
    public static context = React.createContext<IContext<ItemType>>({
        items: []
    }
}

هذا النهج لا يعمل لأنك لا تستطيع الوصول إلى نوع الفئة من سياق ثابت، وهذا منطقي.

النهج #2

باستخدام نمط السياق القياسي، نقوم بإنشاء المستهلك والمنتج على مستوى الوحدة (أي ليس داخل الفصل). المشكلة هنا هي أننا مضطرون لإنشاء المستهلك والمنتج قبل أن نعرف نوع الوسائط الخاصة بهم.

النهج #3

لقد وجدت منشورًا على موقع Medium يعكس ما أحاول القيام به. الخلاصة الأساسية من هذا التبادل هي أننا لا نستطيع إنشاء المنتج/المستهلك حتى نعرف معلومات النوع (يبدو واضحًا أليس كذلك؟). هذا يقودنا إلى النهج التالي.

class MyList<ItemType> extends React.Component<IProps<ItemType>> {
    private localContext: React.Context<IContext<ItemType>>;

    constructor(props?: IProps<ItemType>) {
        super(props);

        this.localContext = React.createContext<IContext<ItemType>>({
            items: [],
        });
    }

    public render() {
        return (
            <this.localContext.Provider>
                {this.props.children}
            </this.localContext.Provider>
        );
    }
}

هذا (ربما) تقدم لأننا نستطيع إنشاء مزود من النوع الصحيح، لكن كيف ستتمكن المكوّن الفرعي من الوصول إلى المستهلك الصحيح؟

تحديث

كما يذكر الجواب أدناه، هذه النمط هو علامة على محاولة التعميم المفرط والتي لا تعمل بشكل جيد مع React. إذا كنت سأحاول حل هذه المشكلة، فسأقوم بإنشاء فئة عامة ListItem لتغليف العناصر نفسها. بهذه الطريقة يمكن أن يكون كائن السياق من نوع أي شكل من أشكال ListItem ولا نحتاج إلى إنشاء المستهلكين والمزودين ديناميكيًا.

الإجابة رقم 1

لقد واجهت نفس المشكلة وأعتقد أنني حللتها بطريقة أكثر أناقة: يمكنك استخدام lodash once (أو إنشاء واحدة بنفسك وهو أمر سهل جدًا) لتهيئة السياق مرة واحدة بالنوع العام ثم استدعاؤه من داخل الوظيفة وفي بقية المكونات يمكنك استخدام الخطاف المخصص useContext للحصول على البيانات:

المكون الرئيسي:

import React, { useContext } from 'react';
import { once } from 'lodash';

const createStateContext = once(<T,>() => React.createContext({} as State<T>));
export const useStateContext = <T,>() => useContext(createStateContext<T>());

const ParentComponent = <T>(props: Props<T>) => {
    const StateContext = createStateContext<T>();
    return (
        <StateContext.Provider value={[YOUR VALUE]}>
            <ChildComponent />
        </StateContext.Provider>
    );
}

المكون الفرعي:

import React from 'react';
import { useStateContext } from './parent-component';

const ChildComponent = <T>(props: Props<T>) => {
     const state = useStateContext<T>();
     ...
}

آمل أن يساعد هذا شخصًا ما

الإجابة رقم 2

تحقق كيف يفعلها Formik

بالنسبة لي، كان من المهم التحقق من الأنواع عند استخدام السياق (useContext) تمامًا مثل useFormikContext في Formik.

مثال على النوع العام

type YourContextType<T> = { state: T; setState: (s: T) => void }

إنشاء سياق (لا تهتم بالنوع)

const YourContext = createContext<YourContextType<any>>(
  undefined as any,
);

خطاف السياق بنوع عام

function useYourContext<T>() {
  const context = useContext<YourContextType<T>>(YourContext);


  if (context === undefined) {
    // assert if context is available
    throw new Error('No context provided');
  }

  return context;
}

استخدم الخطاف

 const { state, changeState } = useYourContext<{ name: string }>();

سترى أن state و changeState لهما أنواع. ربما ليست بالضبط ما تبحث عنه بنسبة 100٪، لكن بالنسبة لي كان ذلك كافيًا. تحقق من شرح @TheRubberDuck

المصدر: https://github.com/jaredpalmer/formik/blob/master/packages/formik/src/FormikContext.tsx

الإجابة رقم 3

لا أعرف TypeScript لذلك لا أستطيع الإجابة بنفس اللغة، ولكن إذا كنت تريد أن يكون مزودك "محدداً" لفئة MyList، يمكنك إنشاء كلاهما في نفس الدالة.

function makeList() {
  const Ctx = React.createContext();

  class MyList extends Component {
    // ...
    render() {
      return (
        <Ctx.Provider value={this.state.something}>
          {this.props.children}
        </Ctx.Provider>
      );
    }
  }

  return {
    List,
    Consumer: Ctx.Consumer 
  };
}

// Usage
const { List, Consumer } = makeList();

بشكل عام أعتقد أنك قد تكون تبالغ في التجريد للأشياء. استخدام الجنريك بشكل مكثف في مكونات React ليس أسلوبًا شائعًا جدًا ويمكن أن يؤدي إلى كود مربك إلى حد ما.