رد فعل: فتحات مثل ابن صديق أمي

عند تكوين المكونات ، غالبًا ما تنشأ مهمة تخصيص محتوى المكون. على سبيل المثال ، لدينا مكون DatePicker ونحتاج إلى عرض أزرار تطبيق مختلفة في أجزاء مختلفة من تطبيق الويب.

لحل مثل هذه المشاكل ، تستخدم كل تقنية شائعة اليوم مفهوم "الفتحات". يحتوي Angular على ngContent و Vue و Svelte و WebComponents بها فتحات. ومكتبة React الشهيرة هي الوحيدة التي ليس لديها مفهوم كامل للفتحات اليوم.

توجد عدة طرق لحل هذه المشكلة في React:

  1. يمكن للمكون إما أن يجعل كل العناصر التابعة له بالكامل ، أو "التسلق" إليهم من خلال React.Children API والتلاعب بالأبناء

  2. يمكن للمكون الإعلان عن ما يسمى "عرض الدعائم" ، وعرض المحتوى الذي يتم إرجاعه منها في الأماكن الصحيحة:

    <MyComponent renderFooter={data => (<h1>Bye, ${data.name}</h1>)}/>

يُعد أسلوب "عرض الدعامة" معروفًا بشكل عام وليس به عيوب أساسية. ما لم يكن استخدامه غير مناسب جدًا ، مقارنةً بالفتحات الكاملة. يحتوي NPM على العديد من المكتبات ، مثل فتحة عرض التفاعل ، لكنني لا أعتقد أنها ملائمة بما فيه الكفاية ، والأهم من ذلك أنها تحل المشكلة ببساطة.

قررت أن أحاول إصلاح هذا الخلل القاتل ، والآن سأخبرك كيف.

أرى الهدف - لا أرى التنفيذ

قبل برمجة أي شيء ، من المفيد معرفة API الذي تريد الحصول عليه. هكذا بدا مخططي للنتيجة المرجوة مثل:

const Component = props => {
  Component.NameSlot = useSlot(props.children);

  return (
    <div>
      <h1>
        <Component.NameSlot.Receiver>
          Default value
        </Component.NameSlot.Receiver>
      </h1>

      Hello {props.children}
    </div>
  );
};

function App() {
  return (
    <div>
      Hello!
      <Component>
        Foo
        <Component.NameSlot>
          Bobobo
        </Component.NameSlot>
      </Component>
    </div>
  );
}

كانت الفكرة هي إنشاء الفتحات اللازمة وتخزين مكون الوظيفة في خاصية ثابتة ، ثم استخدامها بشكل مناسب على جانبي الإرسال والاستقبال.

, . – , , , -, . , , API, :

import {createSlot} from 'react-slotify';

export const MySlot = createSlot();

export const Component = ({children}) => {
  return (
    <div>
      This component contains slot:
      
      <MySlot.Renderer childs={children}>
        This is default slot content
      </MySlot.Renderer>
      
      <div>It also renders children: {children}</div>
    </div>
  );
};
import {Component, MySlot} from './component';

const App = () => {
  return (
    <div>
      <Component>
        <MySlot>Slotted content</MySlot>
        Other content
      </Component>
    </div>
  );
};

, API, , –  MySlot, {children}, , MySlot.Renderer. , JS-, :

export function createSlot() {
  const Slot = ({ children, showChildren }) => {
    return showChildren ? children : null;
  }

  const Renderer = ({ childs, children }) => {
    const slotted = React.Children.toArray(childs).find(child => {
      return React.isValidElement(child) && child.type === Slot;
    });

    if (!slotted || !React.isValidElement(slotted)) {
      return children;
    }
    return React.cloneElement(slotted, { showChildren: true });
  };

  Slot.Renderer = Renderer;

  return Slot;
}

-, 20 . , React- , . . –  Slot. , :

export function createSlot() {
  const Slot = ({ children, showChildren }) => {
    return showChildren ? children : null;
  }
  return Slot;
}

, Slot –  , showChildren={true}. , , Slot .

– Renderer. –  -, Slot-, , showChildren={true}:

  const Renderer = ({ childs, children }) => {
    const slotted = React.Children.toArray(childs).find(child => {
      return React.isValidElement(child) && child.type === Slot;
    });

    if (!slotted || !React.isValidElement(slotted)) {
      return children;
    }
    return React.cloneElement(slotted, { showChildren: true });
  };

, Renderer , , Slot . .

–  Renderer Slot, : <MySlot.Renderer/>.

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

لقد قمت بنشر التنفيذ النهائي كمكتبة تفاعلية على GitHub وكحزمة على NPM . بالفعل في TypeScript وبدعم معلمات الفتحات . سأكون سعيدا لتلقي النقد البناء.




All Articles