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

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

متى تستخدم modifyClass

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

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

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

يمكن استخدام api.modifyClass لتعديل وظائف وخصائص أي فئة يمكن الوصول إليها عبر محلل Ember. وهذا يشمل مسارات Discourse، ووحدات التحكم، والخدمات، والمكونات.

يأخذ 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 ... بناء جملة فئات JavaScript الفرعية. بشكل عام، يمكن تطبيق أي بناء جملة/ميزات مدعومة بواسطة الفئات الفرعية هنا. وهذا يشمل super، والخصائص/الوظائف الثابتة، والمزيد.

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

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

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

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

    // Core code:
    class Foo extends Component {
      // This core field cannot be overridden
      someField = "original";
    
      // This core tracked field can be overridden by including
      // `@tracked someTrackedField =` in the modifyClass call
      @tracked someTrackedField = "original";
    }
    

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

ترقية بناء الجملة القديم

في الماضي، تم استدعاء modifyClass باستخدام بناء جملة كائن حرفي مثل هذا:

// Outdated syntax - do not use
api.modifyClass("component:some-component", {
  someFunction() {
    const original = this._super();
    return original + " some change";
  }
  pluginId: "some-unique-id"
});

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

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

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

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

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

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

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

  • إضافة lookup() تسببت في الخطأ

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

    // Lookup service in initializer, then use it at runtime (bad!)
    export default apiInitializer("0.8", (api) => {
      const composerService = api.container.lookup("service:composer");
      api.composerBeforeSave(async () => {
        composerService.doSomething();
      });
    });
    

    إلى هذا:

    // 'Just in time' lookup of service (good!)
    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
    // or
    // (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", {
          myNewUserFunction() {
            return "hello world";
          },
        });
      },
    
      initialize() {
        withPluginApi("0.12.1", this.initializeWithApi);
      },
    };
    

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


This document is version controlled - suggest changes on github.

16 إعجابًا

أفترض أنه من المستحيل أو على الأقل غير موثوق به محاولة استخدام modifyClass (ضمن حالات الاستخدام القانونية المذكورة أعلاه) داخل مكون إضافي لمكون إضافي آخر في نفس التثبيت؟

حتى لو كان مكونًا إضافيًا مضمنًا في النواة (مثل الدردشة أو الاستطلاع)؟

إعجاب واحد (1)

إذا تم تثبيت كلا المكونين الإضافيين وتم تمكينهما، فيجب أن يعمل كل شيء بدون مشاكل. إذا لم يتم تثبيت/تمكين المكون المستهدف، فستتلقى تحذيرًا في وحدة التحكم. ولكن يمكنك استخدام ignoreMissing parameter لكتم هذا التحذير.

api.modifyClass(
  "component:some-component",
  (Superclass) => ...,
  { ignoreMissing: true }
);

بالطبع، لا تزال نصائح modifyClass القياسية سارية: يجب أن يكون الملاذ الأخير، ويمكن أن يتعطل في أي وقت، لذا يجب عليك التأكد من أن اختباراتك جيدة بما يكفي لتحديد المشكلات بسرعة. سيكون استخدام transformers استراتيجية أكثر أمانًا بكثير.

3 إعجابات

إذًا، كيف يعمل ذلك، هل يؤجل التطبيق حتى يتم تسجيل وتحميل جميع المكونات من جميع المكونات الإضافية؟

أعتقد أن لدي حالة لا تبدو أنها تعمل.

إعجاب واحد (1)

يتم تعريف جميع وحدات ES6 (بما في ذلك المكونات) أولاً، ثم نقوم بتشغيل التهيئة المسبقة، ثم نقوم بتشغيل التهيئة العادية. لذا، بحلول الوقت الذي تعمل فيه أي تهيئة، يمكن حل جميع المكونات.

يسعدني إلقاء نظرة إذا كان بإمكانك مشاركة مقتطف أو فرع :eyes:

4 إعجابات

عذراً، يجب أن تكون حذراً وتوفر المسار الكامل!

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

api.modifyClass("component:chat/modal/create-channel", :white_check_mark:

لا:

api.modifyClass("component:create-channel", :cross_mark:

ولا حتى:

api.modifyClass("component:modal/create-channel", :cross_mark:

تكفي!

5 إعجابات