استخدام modifyClass لتغيير السلوك الأساسي

للمظاهر والإضافات المتقدمة، يوفر Discourse نظام modifyClass. يسمح لك هذا بتوسيع وتجاوز الوظائف في العديد من فئات جافاسكريبت الأساسية.

متى تستخدم modifyClass

يجب أن يكون modifyClass الملاذ الأخير، عندما لا يمكن إجراء التخصيص الخاص بك عبر واجهات برمجة تطبيقات التخصيص الأكثر استقرارًا في Discourse (على سبيل المثال، أساليب plugin-api، أو منافذ الإخراج (outlets)، أو المحولات (transformers)).

يمكن أن يتغير الكود الأساسي في أي وقت. وبالتالي، يمكن أن تتوقف التخصيصات التي تتم عبر modifyClass في أي وقت. عند استخدام واجهة برمجة التطبيقات هذه، يجب عليك التأكد من وجود ضوابط لديك لالتقاط تلك المشكلات قبل وصولها إلى موقع الإنتاج. على سبيل المثال، يمكنك إضافة اختبارات آلية إلى المظهر/الإضافة، أو يمكنك استخدام موقع مرحلي لاختبار تحديثات Discourse الواردة مقابل المظهر/الإضافة الخاص بك.

الاستخدام الأساسي

يمكن استخدام api.modifyClass لتعديل وظائف وخصائص أي فئة يمكن الوصول إليها عبر مُحَلِّل Ember (Ember resolver). ويشمل ذلك مسارات Discourse، ووحدات التحكم (controllers)، والخدمات (services)، والمكونات (components).

تأخذ modifyClass وسيطين:

  • resolverName (سلسلة نصية) - قم بإنشاء هذا باستخدام النوع (على سبيل المثال، component/controller/etc.)، متبوعًا بنقطتين رأسيتين، متبوعًا باسم الفئة (المُصاغ بالشرطات) للملف. على سبيل المثال: component:d-button، component:modal/login، controller:user، route:application، إلخ.

  • callback (دالة) - دالة تستقبل تعريف الفئة الحالي، ثم تُرجع نسخة موسعة.

على سبيل المثال، لتعديل إجراء click() على d-button:

api.modifyClass(
  "component:d-button",
  (Superclass) =>
    class extends Superclass {
      @action
      click() {
        console.log("button was clicked");
        super.click();
      }
    }
);

صيغة class extends ... تحاكي فئات JS الفرعية. بشكل عام، يمكن تطبيق أي صيغة/ميزات مدعومة من قبل الفئات الفرعية هنا. ويشمل ذلك super، والخصائص/الوظائف الثابتة، والمزيد.

ومع ذلك، هناك بعض القيود. يكتشف نظام modifyClass التغييرات فقط على prototype جافاسكريبت الخاص بالفئة. عمليًا، هذا يعني:

  • تقديم أو تعديل constructor() غير مدعوم

    api.modifyClass(
      "component:foo",
      (Superclass) =>
        class extends Superclass {
          constructor() {
            // هذا غير مدعوم. سيتم تجاهل المُنشئ
          }
        }
    );
    
  • تقديم أو تعديل حقول الفئة غير مدعوم (على الرغم من أنه يمكن استخدام بعض حقول الفئة المزينة، مثل @tracked)

    api.modifyClass(
      "component:foo",
      (Superclass) =>
        class extends Superclass {
          someField = "foo"; // غير مدعوم - لا تنسخ
          @tracked someOtherField = "foo"; // هذا مقبول
        }
    );
    
  • لا يمكن تجاوز حقول الفئة البسيطة في التنفيذ الأصلي بأي شكل من الأشكال (على الرغم من أنه، كما ذكرنا أعلاه، يمكن تجاوز حقول @tracked بواسطة حقل @tracked آخر)

    // الكود الأساسي:
    class Foo extends Component {
      // لا يمكن تجاوز حقل الأساس هذا
      someField = "original";
    
      // يمكن تجاوز حقل التتبع الأساسي هذا عن طريق تضمين
      // `@tracked someTrackedField =` في استدعاء modifyClass
      @tracked someTrackedField = "original";
    }
    

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

ترقية الصيغة القديمة (Legacy Syntax)

في الماضي، كان يتم استدعاء modifyClass باستخدام صيغة حرفية للكائن (object-literal) على النحو التالي:

// صيغة قديمة - لا تستخدم
api.modifyClass("component:some-component", {
  someFunction() {
    const original = this._super();
    return original + " some change";
  }
  pluginId: "some-unique-id"
});

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

  1. إزالة pluginId - لم يعد هذا مطلوبًا
  2. التحديث إلى صيغة الفئة الأصلية الحديثة الموضحة أعلاه
  3. اختبار التغييرات الخاصة بك

استكشاف الأخطاء وإصلاحها

تم تهيئة الفئة بالفعل

عند استخدام modifyClass في مُهيِّئ (initializer)، قد ترى هذا التحذير في وحدة التحكم:

Attempted to modify "{name}", but it was already initialized earlier in the boot process

في تطوير السمات/الإضافات، يتم تقديم طريقتين عادةً لهذا الخطأ:

  • أدى إضافة lookup() إلى حدوث الخطأ

    إذا قمت بـ lookup() لـ singleton في وقت مبكر جدًا أثناء عملية التمهيد، فسيؤدي ذلك إلى فشل أي استدعاءات لاحقة لـ modifyClass. في هذه الحالة، يجب أن تحاول نقل البحث ليحدث لاحقًا. على سبيل المثال، ستقوم بتغيير شيء مثل هذا:

    // البحث عن الخدمة في المُهيِّئ، ثم استخدامها في وقت التشغيل (سيئ!)
    export default apiInitializer("0.8", (api) => {
      const composerService = api.container.lookup("service:composer");
      api.composerBeforeSave(async () => {
        composerService.doSomething();
      });
    });
    

    إلى هذا:

    // بحث "في الوقت المناسب" للخدمة (جيد!)
    export default apiInitializer("0.8", (api) => {
      api.composerBeforeSave(async () => {
        const composerService = api.container.lookup("service:composer");
        composerService.doSomething();
      });
    });
    
  • أدى إضافة modifyClass جديد إلى حدوث الخطأ

    إذا تم تقديم الخطأ بواسطة السمة/الإضافة الخاصة بك التي تضيف استدعاء modifyClass، فستحتاج إلى نقله إلى وقت أبكر في عملية التمهيد. يحدث هذا بشكل شائع عند تجاوز أساليب الخدمات (على سبيل المثال، topicTrackingState)، وعلى النماذج التي يتم تهيئتها مبكرًا في عملية تمهيد التطبيق (على سبيل المثال، يتم تهيئة model:user لـ service:current-user).

    عادةً ما يعني نقل استدعاء modifyClass إلى وقت أبكر في عملية التمهيد نقله إلى “ما قبل المُهيِّئ” (pre-initializer)، وتكوينه للتشغيل قبل مُهيِّئ Discourse المسمى ‘inject-discourse-objects’. على سبيل المثال:

    // (plugin)/assets/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js
    // أو
    // (theme)/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js
    
    import { withPluginApi } from "discourse/lib/plugin-api";
    
    export default {
      name: "extend-user-for-my-plugin",
      before: "inject-discourse-objects",
    
      initializeWithApi(api) {
        api.modifyClass("model:user", (Superclass) => class extends Superclass {
          myNewUserFunction() {
            return "hello world";
          },
        });
      },
    
      initialize() {
        withPluginApi(this.initializeWithApi);
      },
    };
    

    يجب أن يعمل تعديل نموذج المستخدم هذا الآن دون طباعة تحذير، وستكون الدالة الجديدة متاحة على كائن currentUser.


يتم التحكم في إصدار هذه الوثيقة - اقترح تغييرات على github.

16 إعجابًا