Lit للمطوّرين في React

1. مقدمة

ما هو Lit؟

Lit هي مكتبة بسيطة لإنشاء مكوّنات ويب سريعة وخفيفة تعمل ضمن أي إطار عمل أو بدون أي إطار عمل على الإطلاق. باستخدام Lit، يمكنك إنشاء مكونات وتطبيقات وأنظمة تصميم قابلة للمشاركة والمزيد.

ما ستتعرَّف عليه

كيفية ترجمة عدة مفاهيم React إلى Lit مثل:

  • JSX و النماذج
  • المكونات مساعدات
  • الولاية مراحل النشاط
  • Hooks
  • أطفال
  • الحكام
  • حالة التوسط

ما الذي ستقوم ببنائه

في نهاية هذا الدرس التطبيقي حول الترميز، سيكون بإمكانك تحويل مفاهيم مكوّنات React إلى نظيراتها في Lit.

المتطلبات

  • أحدث إصدار من Chrome أو Safari أو Firefox أو Edge.
  • معرفة HTML وCSS وJavaScript وأدوات مطوري البرامج في Chrome
  • المعرفة بالتفاعل
  • (خيار متقدم) يمكنك تنزيل VS Code إذا كنت ترغب في الحصول على أفضل تجربة تطوير. ستحتاج أيضًا إلى lit- Plugin لـ VS Code وNPM.

2. Lit مقابل React

تتشابه مفاهيم Lit وقدراتها الأساسية مع React في عدة نواحٍ، ولكن لدى Lit أيضًا بعض الاختلافات والاختلافات الرئيسية:

إنه صغير

أداة Lit صغيرة جدًا: إذ يتم تصغيرها إلى حوالي 5 كيلوبايت وضغطها بتنسيق gzip مقارنةً بـ React + ReactDOM الذي يزيد عن 40 كيلوبايت.

رسم بياني شريطي لحجم الحزمة تم تصغيره وضغطه بالكيلوبايت. يبلغ حجم شريط Lit 5 كيلوبايت في حين يبلغ حجم React + React 42.2 كيلوبايت.

سريع

في مقاييس الأداء العامة التي تقارن نظام نماذج Lit (Lit-html) بـ VDOM في React، تظهر lit-html بشكل أسرع بنسبة تتراوح بين 8% و 10% مقارنةً بأداة React في أسوأ الحالات وأسرع بنسبة +50% في حالات الاستخدام الأكثر شيوعًا.

إنّ LitElement (الفئة الأساسية لمكوّن LitElement) يضيفان الحد الأدنى من النفقات العامة إلى lit-html، ولكنه يتفوقان على أداء React بنسبة 16% إلى 30% عند مقارنة ميزات المكوّنات مثل استخدام الذاكرة وأوقات التفاعل وبدء التشغيل.

رسم بياني شريطي مجمّع للأداء مقارنةً بمقياس "التفاعل" بالمللي ثانية (من الأفضل استخدام الأقل)

لا يحتاج إلى إصدار

مع ميزات المتصفّح الجديدة، مثل وحدات ES والقيم الحرفية للنماذج التي تم وضع علامات عليها، لا يتطلّب تطبيق Lit تجميع المحتوى. يعني هذا أنّه يمكن إعداد بيئات مطوّري البرامج باستخدام علامة نص برمجي + متصفّح + خادم، ومن ثمّ تنفيذ الخطوات اللازمة.

باستخدام وحدات اللغة الإسبانية وشبكات توصيل المحتوى (CDN) الحديثة، مثل Skypack أو UNPKG، قد لا تحتاجون حتى إلى استخدام NPM للبدء.

ومع ذلك، لا يزال بإمكانك إنشاء رموز Lit وتحسينها، إن أردت. إنّ عملية الدمج الأخيرة التي جرت على وحدات مطوّري البرامج بشأن وحدات اللغة الإسبانية الأصلية كانت مناسبة بالنسبة إلى تطبيق Lit، فهو مجرد إصدار بسيط من JavaScript لا يحتاج إلى معرّفات CLI خاصة بإطار العمل أو معالجة التصميم.

غير مرتبط بإطار العمل

تعتمد مكونات Lit على مجموعة من معايير الويب تسمى "مكونات الويب". ويعني هذا أنّ إنشاء مكوّن في Lit سيعمل في إطارات العمل الحالية والمستقبلية. إذا كان يدعم عناصر HTML، فإنه يتوافق مع مكونات الويب.

المشكلة الوحيدة في إمكانية التشغيل المتداخل لإطار العمل هي عندما يكون هناك دعم محدود لنموذج DOM في أطر العمل. React هو أحد أطر العمل هذه، ولكنّه يتيح فتح أبواب الهروب من خلال المراجع، ولا تكون المراجع في React تجربة جيدة للمطوّرين.

يعمل فريق Lit على مشروع تجريبي يُسمى @lit-labs/react والذي سيحلّل تلقائيًا مكوّنات Lit وينشئ برنامج تضمين React كي لا تضطر إلى استخدام refs.

بالإضافة إلى ذلك، سيعرض لك قسم Custom Elements Everywhere أطر العمل والمكتبات التي تعمل بشكل جيد مع العناصر المخصّصة.

دعم من الدرجة الأولى لـ TypeScript

على الرغم من أنّه من الممكن كتابة جميع رموز Lit في JavaScript، فإنّ Lit مكتوب بلغة TypeScript، ويوصي فريق Lit مطوّري البرامج باستخدام TypeScript أيضًا.

يعمل فريق Lit مع منتدى Lit للمساعدة في صيانة المشاريع التي توفّر إمكانية التحقّق من نوع TypeScript وتزويد نماذج Lit بمعلومات مفيدة في وقت التطوير والتصميم باستخدام lit-analyzer وlit-plugin.

لقطة شاشة لبيئة تطوير متكاملة (IDE) تعرض عملية فحص من النوع غير الصحيح لضبط القيمة المنطقية على رقم

لقطة شاشة لبيئة تطوير متكاملة (IDE) تعرض اقتراحات الذكاء

أدوات المطوّرين مضمّنة في المتصفّح

المكوّنات البسيطة هي عناصر HTML في نموذج العناصر في المستند (DOM). وهذا يعني أنه لفحص المكوّنات، لن تحتاج إلى تثبيت أي أدوات أو تنفيذات للمتصفّح.

يمكنك بكل بساطة فتح أدوات مطوّري البرامج واختيار عنصر واستكشاف خصائصه أو حالته.

صورة لأدوات مطوري البرامج في Chrome تعرض $0 إرجاع <mwc-textfield>، و$0.value تعرض hello world، و$0.outlined يعرض true، و{$0} يعرض توسيع الخاصية

وهي مصمَّمة مع أخذ العرض من جهة الخادم في الاعتبار

تم تصميم Lit 2 مع وضع دعم SSR في الاعتبار. حتى وقت كتابة هذا الدرس التطبيقي حول الترميز، لم يُصدر فريق Lit بعد أدوات SSR في شكل ثابت، إلا أنّ فريق Lit كان ينشر مكوّنات معروضة من جهة الخادم على مختلف منتجات Google واختبر SSR في تطبيقات React. يتوقع فريق Lit إطلاق هذه الأدوات خارجيًا على GitHub قريبًا.

في هذه الأثناء، يمكنك متابعة مستوى تقدّم فريق Lit من هنا.

قبول قليل

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

هل أنشأت تطبيقًا كاملاً في Lit وتريد التبديل إلى تطبيق آخر؟ يمكنك حينها وضع تطبيق Lit الحالي في إطار العمل الجديد ونقل كل ما تريده إلى مكونات إطار العمل الجديد.

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

3- بدء الإعداد استكشاف Playground

هناك طريقتان لإجراء هذا الدرس التطبيقي حول الترميز:

  • يمكنك إجراء ذلك بالكامل على الإنترنت من خلال المتصفح
  • (خيار متقدم) يمكنك إجراء ذلك على جهازك المحلي باستخدام رمز VS

الوصول إلى الرمز

خلال الدرس التطبيقي حول الترميز، ستكون هناك روابط إلى ملعب Lit على النحو التالي:

الملعب هو وضع حماية للرموز يتم تشغيله بالكامل في متصفحك. ويمكنه تجميع ملفات TypeScript وJavaScript وتشغيلها، كما يمكنه أيضًا تحليل عمليات الاستيراد تلقائيًا إلى وحدات العقدة. مثلاً:

// before
import './my-file.js';
import 'lit';

// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';

يمكنك تنفيذ البرنامج التعليمي بالكامل في ملعب Lit، باستخدام نقاط التفتيش هذه كنقاط بداية. إذا كنت تستخدم رمز VS، يمكنك استخدام نقاط التحقق هذه لتنزيل رمز البداية لأي خطوة، بالإضافة إلى استخدامها للتحقق من عملك.

استكشِف واجهة المستخدم في ساحة مضيئة

تتم تسمية شريط علامة تبويب &quot;ملف تحديد الملف&quot; على أنه القسم 1، وقسم تعديل التعليمات البرمجية في القسم 2، ومعاينة الناتج في القسم 3، وزر إعادة تحميل المعاينة باعتباره القسم 4

تُبرز لقطة شاشة واجهة المستخدم في "المنصة" في "المنصة" الأقسام التي ستستخدمها في هذا الدرس التطبيقي حول الترميز.

  1. أداة اختيار الملفات لاحظ زر الإضافة...
  2. محرر الملفات.
  3. معاينة الرمز
  4. زر "إعادة التحميل"
  5. زر التنزيل

إعداد رمز VS (متقدّم)

فيما يلي فوائد استخدام إعداد VS Code هذا:

  • التحقّق من نوع النموذج
  • ذكاء النماذج الإكمال التلقائي

إذا سبق لك تثبيت NPM وVS Code (مع المكوّن الإضافي lit- Plugin) وكنت تعرف كيفية استخدام تلك البيئة، يمكنك ببساطة تنزيل هذه المشاريع وبدء تشغيلها عن طريق اتّباع الخطوات التالية:

  • اضغط على زر التنزيل.
  • استخراج محتوى ملف tar إلى دليل
  • (في حال تحديد المشاكل وحلّها) يجب إعداد عملية ضبط سريعة تؤدي إلى إخراج الوحدات والإصدار es2015 والإصدارات الأحدث.
  • تثبيت خادم مطوّر يمكنه حل محددات الوحدات المجرّدة (يوصي فريق Lit بـ @web/dev-server)
  • تشغيل خادم dev وفتح المتصفح (إذا كنت تستخدم @web/dev-server يمكنك استخدام npx web-dev-server --node-resolve --watch --open)
    • إذا كنت تستخدم المثال package.json، استخدِم npm run dev

4. JSX و النماذج

في هذا القسم، ستتعلم أساسيات إنشاء النماذج باستخدام Lit.

JSX و نماذج مُضاءة

JSX هي إضافة بناء جملة لـ JavaScript تسمح لمستخدمي React بكتابة النماذج بسهولة في رمز JavaScript. تؤدي نماذج Lite الغرض نفسه، وهو التعبير عن واجهة مستخدم مكوِّن كدالة لحالتها.

البنية الأساسية

في React، يمكنك عرض عالم JSX hello على النحو التالي:

import 'react';
import ReactDOM from 'react-dom';

const name = 'Josh Perez';
const element = (
  <>
    <h1>Hello, {name}</h1>
    <div>How are you?</div>
  </>
);

ReactDOM.render(
  element,
  mountNode
);

في المثال أعلاه، يوجد عنصران و"اسم" مضمن المتغير. في Lit، يمكنك إجراء ما يلي:

import {html, render} from 'lit';

const name = 'Josh Perez';
const element = html`
  <h1>Hello, ${name}</h1>
  <div>How are you?</div>`;

render(
  element,
  mountNode
);

لاحظ أن قوالب Lit لا تحتاج إلى جزء React لتجميع عناصر متعددة في قوالبها.

في Lit، يتم تغليف النماذج باستخدام html نموذج يحمل علامة LITeral، وهو النموذج الذي حصل فيه Lit على اسم هذا النموذج.

قيم النموذج

يمكن أن تقبل نماذج Lit الأخرى نماذج Lit الأخرى المعروفة باسم TemplateResult. على سبيل المثال، عليك التفاف name بعلامات الفاصلة المائلة (<i>) والتفافه بنموذج حرفي N.B مع وضع علامة عليهما.احرص على استخدام حرف الفاصلة العليا المائلة (`) وليس حرف علامة الاقتباس المفرد (').

import {html, render} from 'lit';

const name = html`<i>Josh Perez</i>`;
const element = html`
  <h1>Hello, ${name}</h1>
  <div>How are you?</div>`;

render(
  element,
  mountNode
);

يمكن أن يقبل Lit TemplateResult الصفائف والسلاسل وTemplateResult الأخرى، بالإضافة إلى الأوامر.

لإجراء تمرين، حاول تحويل تعليمة React التالية إلى Lit:

const itemsToBuy = [
  <li>Bananas</li>,
  <li>oranges</li>,
  <li>apples</li>,
  <li>grapes</li>
];
const element = (
  <>
    <h1>Things to buy:</h1>
    <ol>
      {itemsToBuy}
    </ol>
  </>);

ReactDOM.render(
  element,
  mountNode
);

الإجابة:

import {html, render} from 'lit';

const itemsToBuy = [
  html`<li>Bananas</li>`,
  html`<li>oranges</li>`,
  html`<li>apples</li>`,
  html`<li>grapes</li>`
];
const element = html`
  <h1>Things to buy:</h1>
  <ol>
    ${itemsToBuy}
  </ol>`;

render(
  element,
  mountNode
);

لوازم التمرير والوضع

أحد أكبر الاختلافات بين بناء جملة JSX وLit هو بناء جملة ربط البيانات. على سبيل المثال، خذ الإدخال React هذا مع الروابط:

const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
  <input
      disabled={disabled}
      className={`static-class ${myClass}`}
      defaultValue={value}/>;

ReactDOM.render(
  element,
  mountNode
);

في المثال أعلاه، يتم تعريف المُدخل الذي يؤدي ما يلي:

  • يتم الضبط على متغيّر محدَّد (خطأ في هذه الحالة)
  • تُعين الفئة على static-class بالإضافة إلى متغير ("static-class my-class" في هذه الحالة).
  • لضبط قيمة تلقائية

في Lit، يمكنك إجراء ما يلي:

import {html, render} from 'lit';

const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
  <input
      ?disabled=${disabled}
      class="static-class ${myClass}"
      .value=${value}>`;

render(
  element,
  mountNode
);

في مثال Lit، تتم إضافة ربط منطقي لتبديل السمة disabled.

بعد ذلك، هناك ربط مباشر بالسمة class بدلاً من className. يمكن إضافة عمليات ربط متعددة إلى السمة class، ما لم تكن تستخدم التوجيه classMap، وهو مساعد وصفي لتبديل الفئات.

أخيرًا، يتم ضبط السمة value على الإدخال. وعلى عكس React، لن يؤدي ذلك إلى ضبط عنصر الإدخال ليكون للقراءة فقط لأنّه يتّبع التنفيذ الأصلي وسلوك الإدخال.

بنية ربط دعائم Lit

html`<my-element ?attribute-name=${booleanVar}>`;
  • البادئة ? هي بنية الربط لتبديل سمة على عنصر.
  • ما يعادل inputRef.toggleAttribute('attribute-name', booleanVar)
  • مفيد للعناصر التي تستخدم disabled كـ disabled="false" لا تزال تقرأها دالة DOM على أنّها صحيحة لأن inputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
  • البادئة . هي بنية الربط لضبط سمة لعنصر.
  • ما يعادل inputRef.propertyName = anyVar
  • جيدة لتمرير البيانات المعقدة مثل الكائنات أو الصفائف أو الفئات
html`<my-element attribute-name=${stringVar}>`;
  • ترتبط بسمة عنصر
  • ما يعادل inputRef.setAttribute('attribute-name', stringVar)
  • يصلح للقيم الأساسية وأدوات اختيار قواعد الأنماط وأدوات اختيار الاستعلام

معالِجات التمرير

const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
  <input
      onClick={() => console.log('click')}
      onChange={e => console.log(e.target.value)} />;

ReactDOM.render(
  element,
  mountNode
);

في المثال أعلاه، يتم تعريف المُدخل الذي يؤدي ما يلي:

  • تسجيل كلمة "النقر" عند النقر على الإدخال
  • تسجيل قيمة الإدخال عندما يكتب المستخدم حرفًا

في Lit، يمكنك إجراء ما يلي:

import {html, render} from 'lit';

const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
  <input
      @click=${() => console.log('click')}
      @input=${e => console.log(e.target.value)}>`;

render(
  element,
  mountNode
);

في مثال Lit، تمت إضافة مستمع إلى الحدث click باستخدام @click.

بعد ذلك، بدلاً من استخدام onChange، يتم الربط بحدث input الأصلي لـ <input> لأنّ حدث change الأصلي يتم تنشيطه فقط في blur (مجموعات التفاعلات خلال هذه الأحداث).

بنية معالج أحداث Lit

html`<my-element @event-name=${() => {...}}></my-element>`;
  • البادئة @ هي بنية الربط الخاصة بأداة معالجة الأحداث.
  • ما يعادل inputRef.addEventListener('event-name', ...)
  • لاستخدام أسماء أحداث DOM الأصلية

5- المكونات مساعدات

ستتعرف في هذا القسم على مكونات ودوال Lit. يتم تناول معلومات الحالة والعناصر الخطية بمزيد من التفصيل في الأقسام اللاحقة.

مكوّنات الصف LitElement

مكافئ Lit لمكون فئة React هو LitElement ومفهوم ليت الخاص بـ "الخصائص التفاعلية" هي مزيج من لوازم موقع React وحالاته. على سبيل المثال:

import React from 'react';
import ReactDOM from 'react-dom';

class Welcome extends React.Component {
  constructor(props) {
    super(props);
    this.state = {name: ''};
  }

  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

const element = <Welcome name="Elliott"/>
ReactDOM.render(
  element,
  mountNode
);

في المثال أعلاه، يوجد مكوِّن React:

  • يعرض name
  • تضبط القيمة التلقائية للسياسة name على سلسلة فارغة ("").
  • إعادة تعيين name إلى "Elliott"

هذه هي طريقة تنفيذ ذلك في LitElement

في TypeScript:

import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
  @property({type: String})
  name = '';

  render() {
    return html`<h1>Hello, ${this.name}</h1>`
  }
}

في JavaScript:

import {LitElement, html} from 'lit';

class WelcomeBanner extends LitElement {
  static get properties() {
    return {
      name: {type: String}
    }
  }

  constructor() {
    super();
    this.name = '';
  }

  render() {
    return html`<h1>Hello, ${this.name}</h1>`
  }
}

customElements.define('welcome-banner', WelcomeBanner);

وفي ملف HTML:

<!-- index.html -->
<head>
  <script type="module" src="./index.js"></script>
</head>
<body>
  <welcome-banner name="Elliott"></welcome-banner>
</body>

مراجعة لما يحدث في المثال أعلاه:

@property({type: String})
name = '';
  • تحدّد هذه السمة خاصية تفاعلية عامة، وهي جزء من واجهة برمجة التطبيقات العامة الخاصة بالمكوّن.
  • تعرض سمة (بشكل تلقائي) بالإضافة إلى خاصية على المكوِّن
  • تحدد كيفية ترجمة سمة المكون (وهي سلاسل) إلى قيمة
static get properties() {
  return {
    name: {type: String}
  }
}
  • يؤدي هذا الإجراء الوظيفة نفسها التي يؤديها مصمم تزيين الأخطاء في @property، ولكن يتم تشغيله محليًا باستخدام JavaScript.
render() {
  return html`<h1>Hello, ${this.name}</h1>`
}
  • يتم استدعاء هذه العملية عند تغيير أي خاصية تفاعلية.
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
  ...
}
  • يؤدي هذا إلى ربط اسم علامة عنصر HTML بتعريف الفئة
  • وفقًا لمعيار العناصر المخصصة، يجب أن يحتوي اسم العلامة على واصلة (-)
  • تشير "this" في LitElement إلى مثيل العنصر المخصص (<welcome-banner> في هذه الحالة)
customElements.define('welcome-banner', WelcomeBanner);
  • هذا هو ما يكافئ JavaScript لمصمم TS في @customElement
<head>
  <script type="module" src="./index.js"></script>
</head>
  • لاستيراد تعريف العنصر المخصّص.
<body>
  <welcome-banner name="Elliott"></welcome-banner>
</body>
  • إضافة العنصر المخصص إلى الصفحة
  • تضبط السمة name على 'Elliott'.

مكونات الدالة

لا يحتوي Lit على تفسير 1:1 لمكوِّن الدالة لأنه لا يستخدم JSX أو معالجًا مسبقًا. على الرغم من ذلك، من السهل جدًا إنشاء دالة تستخدم الخصائص وتعرض DOM بناءً على تلك السمات. على سبيل المثال:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Elliott"/>
ReactDOM.render(
  element,
  mountNode
);

في Lit يكون هذا كالتالي:

import {html, render} from 'lit';

function Welcome(props) {
  return html`<h1>Hello, ${props.name}</h1>`;
}

render(
  Welcome({name: 'Elliott'}),
  document.body.querySelector('#root')
);

6- الولاية مراحل النشاط

ستتعرف في هذا القسم على حالة ليت ودورة حياتها.

الحالة

مفهوم ليت الخاص بـ "الخصائص التفاعلية" هي مزيج من حالة React ولوازم موقع React. عند تغييرها، يمكن للخصائص التفاعلية بدء دورة حياة المكوّنات. تتوفّر الخصائص التفاعلية بصيغتين:

السمات التفاعلية العامة

// React
import React from 'react';

class MyEl extends React.Component {
  constructor(props) {
    super(props)
    this.state = {name: 'there'}
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.name !== nextProps.name) {
      this.setState({name: nextProps.name})
    }
  }
}

// Lit (TS)
import {LitElement} from 'lit';
import {property} from 'lit/decorators.js';

class MyEl extends LitElement {
  @property() name = 'there';
}
  • محددة بواسطة @property
  • هذه اللعبة مشابهة لعناصر React وحالاتها، ولكن يمكن تغييرها
  • واجهة برمجة التطبيقات العامة التي يتم الدخول إليها وتعيينها من قبل المستهلكين للمكون

حالة الاستجابة الداخلية

// React
import React from 'react';

class MyEl extends React.Component {
  constructor(props) {
    super(props)
    this.state = {name: 'there'}
  }
}

// Lit (TS)
import {LitElement} from 'lit';
import {state} from 'lit/decorators.js';

class MyEl extends LitElement {
  @state() name = 'there';
}
  • محددة بواسطة @state
  • هذه الحالة تشبه حالة React ولكنّها قابلة للتغيير.
  • حالة داخلية خاصة يتم الوصول إليها عادةً من داخل المكون أو الفئات الفرعية

دورة الحياة

تشبه دورة حياة Lit إلى حد كبير مرحلة React، ولكن هناك بعض الاختلافات الملحوظة.

constructor

// React (js)
import React from 'react';

class MyEl extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this._privateProp = 'private';
  }
}

// Lit (ts)
class MyEl extends LitElement {
  @property({type: Number}) counter = 0;
  private _privateProp = 'private';
}

// Lit (js)
class MyEl extends LitElement {
  static get properties() {
    return { counter: {type: Number} }
  }
  constructor() {
    this.counter = 0;
    this._privateProp = 'private';
  }
}
  • قيمة الإضاءة المكافئة هي أيضًا constructor
  • ليست هناك حاجة إلى تمرير أي شيء إلى المكالمة المميزة
  • تم استدعاءه (غير شامل تمامًا):
    • document.createElement
    • document.innerHTML
    • new ComponentClass()
    • في حال توفّر اسم علامة لم تتم ترقيتها على الصفحة وتم تحميل التعريف وتسجيله في @customElement أو customElements.define.
  • تشبه وظيفتها constructor في React

render

// React
render() {
  return <div>Hello World</div>
}

// Lit
render() {
  return html`<div>Hello World</div>`;
}
  • قيمة الإضاءة المكافئة هي أيضًا render
  • يمكنه عرض أي نتيجة قابلة للعرض، مثل TemplateResult أو string وما إلى ذلك
  • على غرار React، يجب أن تكون الدالة render() دالة نقية.
  • سيتم عرضه على أي عُقدة تعرضها createRenderRoot() (ShadowRoot تلقائيًا)

componentDidMount

تشبه السمة componentDidMount مزيجًا من عمليات معاودة الاتصال بمراحل نشاط firstUpdated وconnectedCallback في Lit.

firstUpdated

import Chart from 'chart.js';

// React
componentDidMount() {
  this._chart = new Chart(this.chartElRef.current, {...});
}

// Lit
firstUpdated() {
  this._chart = new Chart(this.chartEl, {...});
}
  • يتم استدعاء هذا الإجراء في المرة الأولى التي يتم فيها عرض نموذج المكوِّن في جذر المكوِّن
  • لن يتم استدعاؤه إلا إذا كان العنصر مرتبطًا، على سبيل المثال لم يتم استدعاء ذلك عبر document.createElement('my-component') حتى يتم إلحاق هذه العقدة في شجرة نموذج العناصر في المستند
  • يعد ذلك مكانًا جيدًا لإجراء إعداد المكون الذي يتطلب عرض نموذج DOM بواسطة المكون
  • على عكس تغييرات componentDidMount في React التي يتم إجراؤها على الخصائص التفاعلية في firstUpdated، ستتم إعادة العرض، إلا أنّ المتصفّح سيجمع عادةً التغييرات في الإطار نفسه. إذا كانت هذه التغييرات لا تتطلّب الوصول إلى نموذج العناصر في المستند (DOM) للجذر، يجب إجراؤها عادةً في willUpdate.

connectedCallback

// React
componentDidMount() {
  this.window.addEventListener('resize', this.boundOnResize);
}

// Lit
connectedCallback() {
  super.connectedCallback();
  this.window.addEventListener('resize', this.boundOnResize);
}
  • يتم استدعاء هذا الإجراء عند إدراج العنصر المخصّص في شجرة نموذج العناصر في المستند
  • على عكس مكونات React، عند فصل العناصر المخصصة عن DOM، لا يتم إتلافها وبالتالي يمكن "اتصالها" مرات متعددة
    • لن يتم الاتصال بـ "firstUpdated" مرة أخرى
  • يكون مفيدًا لإعادة إعداد DOM أو إعادة إرفاق أدوات معالجة الأحداث التي تم تنظيفها عند قطع الاتصال
  • ملاحظة: قد يتم استدعاء connectedCallback قبل firstUpdated، لذلك قد لا يكون DOM متاحًا عند إجراء المكالمة الأولى.

componentDidUpdate

// React
componentDidUpdate(prevProps) {
  if (this.props.title !== prevProps.title) {
    this._chart.setTitle(this.props.title);
  }
}

// Lit (ts)
updated(prevProps: PropertyValues<this>) {
  if (prevProps.has('title')) {
    this._chart.setTitle(this.title);
  }
}
  • الحد المكافئ للضوء هو updated (باستخدام مصطلح "تحديث") باللغة الإنجليزية.
  • على عكس React، يتم استدعاء updated أيضًا في العرض الأولي
  • تشبه وظيفتها componentDidUpdate في React

componentWillUnmount

// React
componentWillUnmount() {
  this.window.removeEventListener('resize', this.boundOnResize);
}

// Lit
disconnectedCallback() {
  super.disconnectedCallback();
  this.window.removeEventListener('resize', this.boundOnResize);
}
  • بيانات Lit المكافئة تشبه disconnectedCallback
  • على عكس مكونات React، عند فصل العناصر المخصصة عن DOM، لا يتم إتلاف المكوِّن
  • على عكس componentWillUnmount، يتم استدعاء disconnectedCallback بعد إزالة العنصر من الشجرة.
  • لا يزال نموذج العناصر في المستند (DOM) داخل الجذر مرتبطًا بالشجرة الفرعية للجذر.
  • يكون ذلك مفيدًا في إزالة المستمعين للأحداث والمراجع غير المشروعة كي يتمكّن المتصفّح من جمع المكوّنات بطريقة غير مرغوب فيها.

تمارين

.

import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

في المثال أعلاه، هناك ساعة بسيطة تعمل على ما يلي:

  • يعرض "مرحبًا بالعالم! إنّه ثم تعرض الوقت
  • سيتم تحديث الساعة كل ثانية
  • عند إلغاء تثبيته، يتم محو الفاصل الزمني الذي يستدعي علامة التجزئة

ابدأ أولاً بتعريف فئة المكوِّن:

// Lit (TS)
// some imports here are imported in advance
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';

@customElement('lit-clock')
class LitClock extends LitElement {
}

// Lit (JS)
// `html` is imported in advance
import {LitElement, html} from 'lit';

class LitClock extends LitElement {
}

customElements.define('lit-clock', LitClock);

بعد ذلك، عليك إعداد date وتعريفه كخاصية تفاعلية داخلية باستخدام @state لأنّ مستخدمي المكوِّن لن يضبطوا date مباشرةً.

// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';

@customElement('lit-clock')
class LitClock extends LitElement {
  @state() // declares internal reactive prop
  private date = new Date(); // initialization
}

// Lit (JS)
import {LitElement, html} from 'lit';

class LitClock extends LitElement {
  static get properties() {
    return {
      // declares internal reactive prop
      date: {state: true}
    }
  }

  constructor() {
    super();
    // initialization
    this.date = new Date();
  }
}

customElements.define('lit-clock', LitClock);

بعد ذلك، اعرض القالب.

// Lit (JS & TS)
render() {
  return html`
    <div>
      <h1>Hello, World!</h1>
      <h2>It is ${this.date.toLocaleTimeString()}.</h2>
    </div>
  `;
}

الآن، قم بتنفيذ طريقة التجزئة.

tick() {
  this.date = new Date();
}

يأتي بعد ذلك تنفيذ componentDidMount. مرة أخرى، تناظري Lit هو مزيج من firstUpdated وconnectedCallback. في حال استخدام هذا المكوِّن، لا يتطلّب استدعاء الدالة tick باستخدام setInterval الوصول إلى نموذج العناصر في المستند (DOM) داخل الجذر. بالإضافة إلى ذلك، سيتم محو الفاصل الزمني عند إزالة العنصر من شجرة المستند، لذلك إذا تمت إعادة إرفاقه، يجب بدء الفاصل الزمني مرة أخرى. وبالتالي، يعد connectedCallback خيارًا أفضل هنا.

// Lit (TS)
@customElement('lit-clock')
class LitClock extends LitElement {
  @state()
  private date = new Date();
  // initialize timerId for TS
  private timerId = -1 as unknown as ReturnType<typeof setTimeout>;

  connectedCallback() {
    super.connectedCallback();
    this.timerId = setInterval(
      () => this.tick(),
      1000
    );
  }

  ...
}

// Lit (JS)
constructor() {
  super();
  // initialization
  this.date = new Date();
  this.timerId = -1; // initialize timerId for JS
}

connectedCallback() {
  super.connectedCallback();
  this.timerId = setInterval(
    () => this.tick(),
    1000
  );
}

أخيرًا، قم بتنظيف الفاصل الزمني بحيث لا ينفذ علامة التجزئة بعد فصل العنصر من شجرة المستند.

// Lit (TS & JS)
disconnectedCallback() {
  super.disconnectedCallback();
  clearInterval(this.timerId);
}

بوضع كل شيء معًا، يُفترض أن يبدو على النحو التالي:

// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';

@customElement('lit-clock')
class LitClock extends LitElement {
  @state()
  private date = new Date();
  private timerId = -1 as unknown as ReturnType<typeof setTimeout>;

  connectedCallback() {
    super.connectedCallback();
    this.timerId = setInterval(
      () => this.tick(),
      1000
    );
  }

  tick() {
    this.date = new Date();
  }

  render() {
    return html`
      <div>
        <h1>Hello, World!</h1>
        <h2>It is ${this.date.toLocaleTimeString()}.</h2>
      </div>
    `;
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    clearInterval(this.timerId);
  }
}

// Lit (JS)
import {LitElement, html} from 'lit';

class LitClock extends LitElement {
  static get properties() {
    return {
      date: {state: true}
    }
  }

  constructor() {
    super();
    this.date = new Date();
  }

  connectedCallback() {
    super.connectedCallback();
    this.timerId = setInterval(
      () => this.tick(),
      1000
    );
  }

  tick() {
    this.date = new Date();
  }

  render() {
    return html`
      <div>
        <h1>Hello, World!</h1>
        <h2>It is ${this.date.toLocaleTimeString()}.</h2>
      </div>
    `;
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    clearInterval(this.timerId);
  }
}

customElements.define('lit-clock', LitClock);

7. Hooks

في هذا القسم، ستتعلم كيفية ترجمة مفاهيم React Hook إلى Lit.

مفهوم عناصر الجذب في React

توفر عناصر الجذب التفاعل وسيلة لمكونات الدالة "للجذب" إلى حالته. وهناك العديد من الفوائد وراء ذلك.

  • تبسّط إعادة استخدام منطق الحالة
  • المساعدة في تقسيم مكوِّن إلى دوال أصغر

بالإضافة إلى ذلك، إنّ التركيز على المكونات المستنِدة إلى الدوال قد أدّى إلى معالجة مشاكل معيّنة في بناء الجملة المستنِد إلى الفئة في React، مثل:

  • يجب تمرير props من constructor إلى super
  • الإعداد غير المنظَّم للخصائص في constructor
    • ذكر فريق React هذا السبب في ذلك الوقت، ولكن تم حلّه في مؤتمر ES2019
  • لم تعُد المشاكل التي تسببت بسبب this تشير إلى المكوِّن

التفاعل مع مفاهيم عناصر الجذب في Lit

كما ذكرنا في قسم المكونات Props، لا يوفّر Lit طريقة لإنشاء عناصر مخصّصة من دالة، ولكنّه يعالج معظم المشاكل الرئيسية في مكوّنات فئة React. على سبيل المثال:

// React (at the time of making hooks)
import React from 'react';
import ReactDOM from 'react-dom';

class MyEl extends React.Component {
  constructor(props) {
    super(props); // Leaky implementation
    this.state = {count: 0};
    this._chart = null; // Deemed messy
  }

  render() {
    return (
      <>
        <div>Num times clicked {count}</div>
        <button onClick={this.clickCallback}>click me</button>
      </>
    );
  }

  clickCallback() {
    // Errors because `this` no longer refers to the component
    this.setState({count: this.count + 1});
  }
}

// Lit (ts)
class MyEl extends LitElement {
  @property({type: Number}) count = 0; // No need for constructor to set state
  private _chart = null; // Public class fields introduced to JS in 2019

  render() {
    return html`
        <div>Num times clicked ${count}</div>
        <button @click=${this.clickCallback}>click me</button>`;
  }

  private clickCallback() {
    // No error because `this` refers to component
    this.count++;
  }
}

كيف يعالج Lit هذه المشاكل؟

  • لا تستخدم constructor أي وسيطات.
  • ترتبط جميع عمليات ربط @event تلقائيًا بـ this.
  • تشير السمة this في الغالبية العظمى من الحالات إلى مرجع العنصر المخصّص.
  • يمكن الآن إنشاء مثيل لخصائص الفئة كأعضاء للصف. التخلص من عمليات التنفيذ المستندة إلى الدالة الإنشائية

أدوات التحكّم التفاعلية

.

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

وحدة التحكّم التفاعلية هي واجهة كائن يمكنها التفاعل مع دورة حياة التحديث لمضيف وحدة التحكّم، مثل LitElement.

دورة حياة ReactiveController وreactiveControllerHost هي:

interface ReactiveController {
  hostConnected(): void;
  hostUpdate(): void;
  hostUpdated(): void;
  hostDisconnected(): void;
}
interface ReactiveControllerHost {
  addController(controller: ReactiveController): void;
  removeController(controller: ReactiveController): void;
  requestUpdate(): void;
  readonly updateComplete: Promise<boolean>;
}

من خلال إنشاء وحدة تحكّم تفاعلية وإرفاقها بمضيف باستخدام addController، يتم استدعاء دورة حياة وحدة التحكّم إلى جانب دورة حياة المضيف. على سبيل المثال، يمكنك استدعاء مثال الساعة من العمود State & قسم "رحلة المستخدِم":

import React from 'react';
import ReactDOM from 'react-dom';

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

في المثال أعلاه، توجد ساعة بسيطة تقوم بما يلي:

  • يعرض "مرحبًا بالعالم! إنّه ثم تعرض الوقت
  • سيتم تحديث الساعة كل ثانية
  • عند إلغاء تثبيته، يتم محو الفاصل الزمني الذي يستدعي علامة التجزئة

بناء سقّالة المكونات

ابدأ أولاً بتعريف فئة المكوِّن وأضِف الدالة render.

// Lit (TS) - index.ts
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';

@customElement('my-element')
class MyElement extends LitElement {
  render() {
    return html`
      <div>
        <h1>Hello, world!</h1>
        <h2>It is ${'time to get Lit'}.</h2>
      </div>
    `;
  }
}

// Lit (JS) - index.js
import {LitElement, html} from 'lit';

class MyElement extends LitElement {
  render() {
    return html`
      <div>
        <h1>Hello, world!</h1>
        <h2>It is ${'time to get Lit'}.</h2>
      </div>
    `;
  }
}

customElements.define('my-element', MyElement);

بناء وحدة التحكم

يمكنك الآن التبديل إلى "clock.ts" وإنشاء صف لـ "ClockController" وإعداد constructor:

// Lit (TS) - clock.ts
import {ReactiveController, ReactiveControllerHost} from 'lit';

export class ClockController implements ReactiveController {
  private readonly host: ReactiveControllerHost;

  constructor(host: ReactiveControllerHost) {
    this.host = host;
    host.addController(this);
  }

  hostConnected() {
  }

  private tick() {
  }

  hostDisconnected() {
  }
}

// Lit (JS) - clock.js
export class ClockController {
  constructor(host) {
    this.host = host;
    host.addController(this);
  }

  hostConnected() {
  }

  tick() {
  }

  hostDisconnected() {
  }
}

يمكن إنشاء وحدة تحكُّم تفاعلية بأي طريقة طالما أنّها تشترك في الواجهة ReactiveController، ولكن يُفضَّل استخدام فئة مع constructor ويمكنها التعامل مع واجهة ReactiveControllerHost بالإضافة إلى أي خصائص أخرى مطلوبة لإعداد وحدة التحكّم، النمط الذي يفضّل فريق Lit استخدامه في معظم الحالات الأساسية.

عليك الآن ترجمة عمليات الاستدعاء في مراحل نشاط React إلى استدعاءات وحدة التحكّم. باختصار:

  • componentDidMount
    • إلى connectedCallback في LitElement
    • إلى hostConnected لمسؤول التحكّم بالبيانات
  • ComponentWillUnmount
    • إلى disconnectedCallback في LitElement
    • إلى hostDisconnected لمسؤول التحكّم بالبيانات

لمزيد من المعلومات حول ترجمة دورة نشاط React إلى مراحل نشاط Lit، راجع الولاية دورة حياة المنتج.

بعد ذلك، نفِّذ طريقة استدعاء hostConnected وtick، واحذف الفاصل الزمني في hostDisconnected على النحو الوارد في المثال في State & دورة الحياة.

// Lit (TS) - clock.ts
export class ClockController implements ReactiveController {
  private readonly host: ReactiveControllerHost;
  private interval = 0 as unknown as ReturnType<typeof setTimeout>;
  date = new Date();

  constructor(host: ReactiveControllerHost) {
    this.host = host;
    host.addController(this);
  }

  hostConnected() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  private tick() {
    this.date = new Date();
  }

  hostDisconnected() {
    clearInterval(this.interval);
  }
}

// Lit (JS) - clock.js
export class ClockController {
  interval = 0;
  host;
  date = new Date();

  constructor(host) {
    this.host = host;
    host.addController(this);
  }

  hostConnected() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  tick() {
    this.date = new Date();
  }

  hostDisconnected() {
    clearInterval(this.interval);
  }
}

استخدام وحدة التحكم

لاستخدام وحدة التحكّم في الساعة، عليك استيراد وحدة التحكّم وتحديث المكوِّن في index.ts أو index.js.

// Lit (TS) - index.ts
import {LitElement, html, ReactiveController, ReactiveControllerHost} from 'lit';
import {customElement} from 'lit/decorators.js';
import {ClockController} from './clock.js';

@customElement('my-element')
class MyElement extends LitElement {
  private readonly clock = new ClockController(this); // Instantiate

  render() {
    // Use controller
    return html`
      <div>
        <h1>Hello, world!</h1>
        <h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
      </div>
    `;
  }
}

// Lit (JS) - index.js
import {LitElement, html} from 'lit';
import {ClockController} from './clock.js';

class MyElement extends LitElement {
  clock = new ClockController(this); // Instantiate

  render() {
    // Use controller
    return html`
      <div>
        <h1>Hello, world!</h1>
        <h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
      </div>
    `;
  }
}

customElements.define('my-element', MyElement);

ولاستخدام وحدة التحكّم، عليك إنشاء مثيل لوحدة التحكّم من خلال إرسال إشارة إلى مضيف وحدة التحكّم (وهو المكوّن <my-element>)، ثم استخدام وحدة التحكّم بالطريقة render.

تشغيل عمليات إعادة العرض في وحدة التحكّم

لاحظ أنه سيظهر الوقت، ولكن لا يتم تحديث الوقت. وذلك لأن وحدة التحكم تضبط التاريخ كل ثانية، ولكن المضيف لا يتم تحديثه. وذلك لأن date تتغير في الفئة ClockController ولم تعد المكوِّن. ويعني ذلك أنّه بعد ضبط date على وحدة التحكّم، يجب أن يُطلَب من المضيف بدء دورة حياة التحديث باستخدام host.requestUpdate().

// Lit (TS & JS) - clock.ts / clock.js
private tick() {
  this.date = new Date();
  this.host.requestUpdate();
}

من المفترض أن تمر الساعة الآن.

لمزيد من المقارنة التفصيلية لحالات الاستخدام الشائعة مع عناصر الجذب، يُرجى الاطّلاع على قسم مواضيع متقدمة - عناصر الجذب.

8. أطفال

في هذا القسم، ستتعلّم كيفية استخدام الخانات لإدارة الأطفال في Lit.

ألعاب المقامرة الأبناء

تتيح الخانات تركيب العناصر من خلال السماح لك بتضمين المكوّنات.

في React، يكتسب الأطفال من خلال لوازم التصوير. الخانة التلقائية هي props.children وتحدِّد الوظيفة render مكان وضع الخانة التلقائية. على سبيل المثال:

const MyArticle = (props) => {
 return <article>{props.children}</article>;
};

يُرجى العِلم أنّ props.children هي مكوِّنات React وليست عناصر HTML.

في Lit، يتم إنشاء العناصر الثانوية في دالة العرض باستخدام عناصر الخانة. لاحظ أن العناصر الثانوية لا يتم اكتسابها بنفس طريقة React. في Lit، تكون العناصر الثانوية هي HTMLElements مرفقة بالفتحات. يُطلق على هذا المرفق اسم التوقّع.

@customElement("my-article")
export class MyArticle extends LitElement {
  render() {
    return html`
      <article>
        <slot></slot>
      </article>
   `;
  }
}

فتحات متعددة

في React، إضافة عدّة خانات هي في الأساس تمامًا مثل اكتساب المزيد من لوازم التصوير.

const MyArticle = (props) => {
  return (
    <article>
      <header>
        {props.headerChildren}
      </header>
      <section>
        {props.sectionChildren}
      </section>
    </article>
  );
};

وبالمثل، تؤدي إضافة المزيد من عناصر <slot> إلى إنشاء المزيد من الخانات في Lit. تم تحديد خانات متعددة باستخدام السمة name: <slot name="slot-name">. يسمح هذا الإجراء للأطفال بتعريف الخانة التي سيتم تخصيصها لهم.

@customElement("my-article")
export class MyArticle extends LitElement {
  render() {
    return html`
      <article>
        <header>
          <slot name="headerChildren"></slot>
        </header>
        <section>
          <slot name="sectionChildren"></slot>
        </section>
      </article>
   `;
  }
}

محتوى الشريحة التلقائية

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

@customElement("my-element")
export class MyElement extends LitElement {
  render() {
    return html`
      <section>
        <div>
          <slot name="slotWithDefault">
            <p>
             This message will not be rendered when children are attached to this slot!
            <p>
          </slot>
        </div>
      </section>
   `;
  }
}

تعيين الأطفال إلى خانات

في React، يتم تعيين العناصر الثانوية إلى خانات من خلال خصائص المكوِّن. في المثال أدناه، يتم نقل عناصر React إلى شريحتي headerChildren وsectionChildren.

const MyNewsArticle = () => {
 return (
   <MyArticle
     headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
     sectionChildren={<p>Children are props in React!</p>}
   />
 );
};

في Lit، يتم تخصيص الأطفال إلى خانات باستخدام سمة slot.

@customElement("my-news-article")
export class MyNewsArticle extends LitElement {
  render() {
    return html`
      <my-article>
        <h3 slot="headerChildren">
          Extry, Extry! Read all about it!
        </h3>
        <p slot="sectionChildren">
          Children are composed with slots in Lit!
        </p>
      </my-article>
   `;
  }
}

إذا لم يكن هناك خانة تلقائية (مثل <slot>) ولم تكن هناك خانة تحتوي على السمة name (مثل <slot name="foo">) التي تتطابق مع السمة slot للعناصر الثانوية للعنصر المخصّص (مثل <div slot="foo">)، لن يتم عرض هذه العقدة.

9. الحكام

قد يحتاج مطوِّر أحيانًا إلى الوصول إلى واجهة برمجة التطبيقات الخاصة بعنصر HTMLElement.

في هذا القسم، ستتعلم كيفية الحصول على مراجع العناصر في Lit.

مراجع التفاعل

.

يتم تحويل مكوِّن React إلى سلسلة من استدعاءات الدوال التي تنشئ DOM افتراضي عند استدعائه. يفسّر ReactDOM نموذج DOM الافتراضي هذا ويعرض عناصر HTMLElements.

في React، تكون المراجع عبارة عن مساحة في الذاكرة تحتوي على HTMLElement الذي تم إنشاؤه.

const RefsExample = (props) => {
 const inputRef = React.useRef(null);
 const onButtonClick = React.useCallback(() => {
   inputRef.current?.focus();
 }, [inputRef]);

 return (
   <div>
     <input type={"text"} ref={inputRef} />
     <br />
     <button onClick={onButtonClick}>
       Click to focus on the input above!
     </button>
   </div>
 );
};

في المثال أعلاه، سينفِّذ مكوِّن React ما يلي:

  • عرض حقل إدخال نص فارغ وزر يحتوي على نص
  • التركيز على الإدخال عند النقر على الزر

بعد العرض الأولي، ستضبط React inputRef.current على HTMLInputElement الذي تم إنشاؤه من خلال السمة ref.

إضاءة "المراجع" مع "@query"

يقع Lit بالقرب من المتصفح ويخلق تجريدًا ضئيلاً للغاية على ميزات المتصفح الأصلية.

التفاعل الذي يعادل refs في Lit هو عنصر HTMLElement الذي يعرضه الزخرفيان @query و@queryAll.

@customElement("my-element")
export class MyElement extends LitElement {
  @query('input') // Define the query
  inputEl!: HTMLInputElement; // Declare the prop

  // Declare the click event listener
  onButtonClick() {
    // Use the query to focus
    this.inputEl.focus();
  }

  render() {
    return html`
      <input type="text">
      <br />
      <!-- Bind the click listener -->
      <button @click=${this.onButtonClick}>
        Click to focus on the input above!
      </button>
   `;
  }
}

في المثال أعلاه، يقوم مكون Lit بما يلي:

  • تحدد سمة على MyElement باستخدام مصمم @query (إنشاء Getter لـ HTMLInputElement).
  • بيان استدعاء حدث ناتج عن النقر وإرفاقه باسم onButtonClick.
  • التركيز على الإدخال عند النقر على الزر

في JavaScript، يؤدي مصمما التصميم @query و@queryAll أداء querySelector وquerySelectorAll على التوالي. هذا الرمز هو JavaScript مكافئ لـ @query('input') inputEl!: HTMLInputElement;.

get inputEl() {
  return this.renderRoot.querySelector('input');
}

بعد أن يلتزم مكوِّن Lit بنموذج طريقة render مع جذر my-element، سيسمح مسؤول الزخرفة @query الآن لـ inputEl بعرض أول عنصر input موجود في جذر العرض. سيعرض null إذا لم يتمكن @query من العثور على العنصر المحدد.

إذا كان هناك عدة عناصر input في جذر العرض، ستعرض السمة @queryAll قائمة بالعُقد.

10. حالة التوسط

في هذا القسم، ستتعلم كيفية وسيط الحالة بين المكونات في Lit.

المكونات القابلة لإعادة الاستخدام

يحاكي التفاعل مسارات العرض الوظيفية مع تدفق البيانات من أعلى إلى أسفل. يوفّر الوالدان الحالة للأطفال من خلال لوازم التصوير. يتواصل الأطفال مع والديهم من خلال معاودة الاتصال المتوفّرة في لوازم التصوير.

const CounterButton = (props) => {
  const label = props.step < 0
    ? `- ${-1 * props.step}`
    : `+ ${props.step}`;


  return (
    <button
      onClick={() =>
        props.addToCounter(props.step)}>{label}</button>
  );
};

في المثال أعلاه، يقوم مكوِّن React بما يلي:

  • تنشئ تصنيفًا استنادًا إلى القيمة props.step.
  • لعرض زر يتضمن "+step" أو "-step" كتصنيف له
  • يمكنك تعديل العنصر الرئيسي من خلال استدعاء props.addToCounter مع استخدام props.step كوسيطة عند النقر.

على الرغم من إمكانية تمرير استدعاءات في Lit، فإن الأنماط التقليدية مختلفة. يمكن كتابة مكون React في المثال أعلاه على أنه مكون Lit في المثال أدناه:

@customElement('counter-button')
export class CounterButton extends LitElement {
  @property({type: Number}) step: number = 0;

  onClick() {
    const event = new CustomEvent('update-counter', {
      bubbles: true,
      detail: {
        step: this.step,
      }
    });

    this.dispatchEvent(event);
  }

  render() {
    const label = this.step < 0
      ? `- ${-1 * this.step}`  // "- 1"
      : `+ ${this.step}`;      // "+ 1"

    return html`
      <button @click=${this.onClick}>${label}</button>
    `;
  }
}

في المثال أعلاه، سيقوم مكون Lit بما يلي:

  • إنشاء السمة التفاعلية step
  • إرسال حدث مخصّص يُسمّى update-counter مع إضافة القيمة step للعنصر عند النقر

تظهر فقاعات الأحداث في المتصفّح من العناصر الثانوية إلى العناصر الرئيسية. تتيح الأحداث للأطفال بث أحداث التفاعل وتغييرات الحالة. يمرر التفاعل بشكل أساسي الحالة في الاتجاه المعاكس، لذلك من غير المألوف رؤية إرسال مكونات React والاستماع إلى الأحداث بنفس الطريقة التي يتم بها نقل مكونات Lit.

مكونات الحالة

في React، من الشائع استخدام عناصر الجذب لإدارة الحالة. يمكن إنشاء مكوِّن MyCounter من خلال إعادة استخدام المكوِّن CounterButton. لاحِظ كيفية تمرير addToCounter إلى كلتا المثيلتين من CounterButton.

const MyCounter = (props) => {
 const [counterSum, setCounterSum] = React.useState(0);
 const addToCounter = useCallback(
   (step) => {
     setCounterSum(counterSum + step);
   },
   [counterSum, setCounterSum]
 );

 return (
   <div>
     <h3>&Sigma;: {counterSum}</h3>
     <CounterButton
       step={-1}
       addToCounter={addToCounter} />
     <CounterButton
       step={1}
       addToCounter={addToCounter} />
   </div>
 );
};

يقوم المثال أعلاه بما يلي:

  • تنشئ حالة count.
  • إنشاء معاودة اتصال تضيف رقمًا إلى حالة count.
  • يتم استخدام addToCounter من قِبل "CounterButton" لتعديل "count" بحلول step عند كل نقرة.

يمكن تحقيق تنفيذ مماثل لـ MyCounter في Lit. لاحظ كيفية عدم تمرير addToCounter إلى counter-button. بدلاً من ذلك، يتم ربط عملية الاستدعاء للحدث @update-counter بعنصر استماع للأحداث في العنصر الرئيسي.

@customElement("my-counter")
export class MyCounter extends LitElement {
  @property({type: Number}) count = 0;

  addToCounter(e: CustomEvent<{step: number}>) {
    // Get step from detail of event or via @query
    this.count += e.detail.step;
  }

  render() {
    return html`
      <div @update-counter="${this.addToCounter}">
        <h3>&Sigma; ${this.count}</h3>
        <counter-button step="-1"></counter-button>
        <counter-button step="1"></counter-button>
      </div>
    `;
  }
}

يقوم المثال أعلاه بما يلي:

  • تنشئ خاصية تفاعلية تسمى count والتي ستعدّل المكوِّن عند تغيير القيمة.
  • ربط معاودة الاتصال addToCounter بأداة معالجة الحدث @update-counter
  • يمكنك تعديل "count" من خلال إضافة القيمة الواردة في detail.step لحدث update-counter.
  • تضبط قيمة step في counter-button من خلال السمة step.

من المعتاد استخدام الخصائص التفاعلية في Lit لبث التغييرات من العناصر الرئيسية إلى الأطفال. وبالمثل، من الممارسات الجيدة استخدام نظام الأحداث في المتصفّح لعرض التفاصيل من الأسفل إلى الأعلى.

يتبع هذا النهج أفضل الممارسات ويلتزم بهدف Lit المتمثل في توفير دعم عبر المنصات لمكونات الويب.

11. التصميم

ستتعرف في هذا القسم على معلومات حول التصميم في Lit.

التصميم

يوفر Lit طرقًا متعددة لتصميم العناصر بالإضافة إلى حل مدمج.

الأنماط المضمّنة

يدعم Lit الأنماط المضمَّنة بالإضافة إلى الربط بها.

import {LitElement, html, css} from 'lit';
import {customElement} from 'lit/decorators.js';

@customElement('my-element')
class MyElement extends LitElement {
  render() {
    return html`
      <div>
        <h1 style="color:orange;">This text is orange</h1>
        <h1 style="color:rebeccapurple;">This text is rebeccapurple</h1>
      </div>
    `;
  }
}

في المثال أعلاه، هناك عنوانان لكل منهما نمط مضمَّن.

الآن قم باستيراد وربط حد من border-color.js بالنص البرتقالي:

...
import borderColor from './border-color.js';

...

html`
  ...
  <h1 style="color:orange;${borderColor}">This text is orange</h1>
  ...`

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

styleMap

يسهّل توجيه styleMap استخدام JavaScript لضبط الأنماط المضمَّنة. على سبيل المثال:

import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {styleMap} from 'lit/directives/style-map.js';

@customElement('my-element')
class MyElement extends LitElement {
  @property({type: String})
  color = '#000'

  render() {
    // Define the styleMap
    const headerStyle = styleMap({
      'border-color': this.color,
    });

    return html`
      <div>
        <h1
          style="border-style:solid;
          <!-- Use the styleMap -->
          border-width:2px;${headerStyle}">
          This div has a border color of ${this.color}
        </h1>
        <input
          type="color"
          @input=${e => (this.color = e.target.value)}
          value="#000">
      </div>
    `;
  }
}

يفعل المثال أعلاه ما يلي:

  • عرض h1 مع حدود وأداة اختيار ألوان
  • تغيير border-color إلى القيمة من أداة اختيار الألوان

بالإضافة إلى ذلك، يتم استخدام styleMap لضبط أنماط h1. يتّبع styleMap بنية مشابهة لبنية ربط السمات style في React.

CSSResult

الطريقة الموصى بها لتصميم المكوّنات هي استخدام القيمة الحرفية للنموذج css الذي تم وضع علامة عليه.

import {LitElement, html, css} from 'lit';
import {customElement} from 'lit/decorators.js';

const ORANGE = css`orange`;

@customElement('my-element')
class MyElement extends LitElement {
  static styles = [
    css`
      #orange {
        color: ${ORANGE};
      }

      #purple {
        color: rebeccapurple;
      }
    `
  ];

  render() {
    return html`
      <div>
        <h1 id="orange">This text is orange</h1>
        <h1 id="purple">This text is rebeccapurple</h1>
      </div>
    `;
  }
}

يفعل المثال أعلاه ما يلي:

  • تعريف حرفي لنموذج CSS الذي تم وضع علامة عليه باستخدام رابط
  • تحدِّد هذه السياسة لونَين h1 باستخدام المعرّفات.

مزايا استخدام علامة نموذج css:

  • تم التحليل مرة واحدة لكل صف مقابل كل مثيل
  • تم التنفيذ مع مراعاة قابلية إعادة استخدام الوحدة.
  • يمكن فصل الأنماط بسهولة في ملفات خاصة بها
  • متوافق مع رمز polyfill للخصائص المخصصة في CSS

بالإضافة إلى ذلك، لاحظ العلامة <style> في index.html:

<!-- index.html -->
<style>
  h1 {
    color: red !important;
  }
</style>

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

علامات الأنماط

من الممكن أيضًا تضمين علامات <style> في النماذج. سيزيل المتصفّح تكرار علامات الأنماط هذه، ولكن من خلال وضعها في النماذج، سيتم تحليلها لكل مثيل مكوّن بدلاً من كل فئة كما هو الحال مع نموذج css المميّز بعلامة. بالإضافة إلى ذلك، تتم إزالة تكرار متصفِّح CSSResult بشكل أسرع بكثير.

يمكن أيضًا استخدام <link rel="stylesheet"> في النموذج الخاص بك، ولكن لا ننصح بذلك أيضًا لأنّه قد يتسبب في ظهور وميض أولي للمحتوى الذي لا نمط له (FOUC).

12. المواضيع المتقدمة (اختياري)

JSX و النماذج

إضاءة نموذج كائن افتراضي (Virtual DOM)

لا يتضمن Lit-html كائن DOM افتراضي تقليدي يختلف عن كل عقدة على حدة. بدلاً من ذلك، يستخدم ميزات الأداء الأساسية في المواصفات الحرفية للنموذج المميّز في ES2015. القيم الحرفية للنموذج التي تم وضع علامات عليها هي سلاسل حرفية للنموذج مع إرفاق دوال علامات بها.

فيما يلي مثال على نموذج حرفي:

const str = 'string';
console.log(`This is a template literal ${str}`);

إليك مثال على نموذج حرفي للنموذج الذي تم وضع علامة عليه:

const tag = (strings, ...values) => ({strings, values});
const f = (x) => tag`hello ${x} how are you`;
console.log(f('world')); // {strings: ["hello ", " how are you"], values: ["world"]}
console.log(f('world').strings === f(1 + 2).strings); // true

في المثال أعلاه، العلامة هي الدالة tag وتعرض الدالة f استدعاء حرفي للنموذج الذي تم وضع علامة عليه.

يرجع الكثير من سحر الأداء في Lit إلى أنّ مصفوفات السلسلة التي تم تمريرها إلى دالة العلامة تحتوي على المؤشر نفسه (كما هو موضّح في console.log الثانية). لا يُنشئ المتصفّح مصفوفة strings جديدة في كل استدعاء لدالة علامة، لأنّها تستخدم النموذج الحرفي نفسه (أي في الموقع نفسه في AST). لذلك، يمكن لعملية الربط والتحليل والتخزين المؤقت للنماذج في Lit الاستفادة من هذه الميزات بدون اختلافات كبيرة في وقت التشغيل.

إنّ هذا السلوك المضمَّن في المتصفح للقيم الحرفية للنموذج الذي تم وضع علامة عليه يمنح Lit ميزة كبيرة في الأداء. وتُنفذ معظم أوامر DOM الافتراضية معظم أعمالها باستخدام JavaScript. ومع ذلك، فإنّ القيم الحرفية للنماذج التي تم وضع علامات عليها تؤدي معظم الاختلافات بين لغة C++ في المتصفّح.

إذا أردت البدء في استخدام القيم الحرفية للنماذج التي تم وضع علامة HTML عليها باستخدام React أو Preact، ينصح فريق Lit باستخدام مكتبة htm.

ومع ذلك، كما هو الحال مع موقع Google Codelabs والعديد من أدوات تحرير الرموز على الإنترنت، ستلاحظ أنّ تمييز البنية الحرفية للنموذج الذي تم وضع علامة عليه ليس أمرًا شائعًا. وتتيح بعض بيئات IDE وأدوات تحرير النصوص استخدام هذه الأدوات بشكل تلقائي، مثل أداة تمييز كتلة الرموز في Atom وGitHub. يعمل فريق Lit أيضًا عن كثب مع المنتدى لصيانة مشاريع، مثل lit-plugin، وهو مكوّن إضافي لـ VS Code سيضيف تمييز البنية والتحقّق من الكتابة وذكاء لمشاريعك على Lit.

إضاءة JSX + React DOM

لا يتم تشغيل JavaScript في المتصفّح وتستخدم بدلاً من ذلك معالجًا مسبقًا لتحويل JSX إلى استدعاءات وظائف JavaScript (يتم ذلك عادةً من خلال Babel).

على سبيل المثال، ستعمل Babel على تحويل ما يلي:

const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);

في ما يلي:

const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);

بعد ذلك، يأخذ React DOM مخرجات React وتترجمه إلى DOM الفعلي، أي الخصائص والسمات وأدوات معالجة الأحداث وغير ذلك.

يستخدم Lit-html قيمًا حرفية للنموذج ذي العلامات والتي يمكن تشغيلها في المتصفح بدون ترجمة أو معالج مسبق. وهذا يعني أنه لبدء استخدام Lit، كل ما تحتاج إليه هو ملف HTML ونص برمجي لوحدة ES وخادم. إليك نص برمجي يمكن تشغيله بالكامل من خلال المتصفح:

<!DOCTYPE html>
<html>
  <head>
    <script type="module">
      import {html, render} from 'https://cdn.skypack.dev/lit';

      render(
        html`<div>Hello World!</div>`,
        document.querySelector('.root')
      )
    </script>
  </head>
  <body>
    <div class="root"></div>
  </body>
</html>

بالإضافة إلى ذلك، بما أنّ نظام إنشاء النماذج في Lit (lit-html)، لا يستخدم كائن Virtual DOM تقليديًا، بل يستخدم واجهة برمجة تطبيقات DOM API مباشرةً، يتم تصغير حجم Lit 2 إلى أقل من 5 كيلوبايت وضغطه مقارنةً بأداة React (2.8 كيلوبايت) + Interact-dom (39.4 كيلوبايت) 40 كيلوبايت مصغَّرة ومُضغوطة.

فعاليات

تستخدم React نظامًا اصطناعيًا للأحداث. هذا يعني أنه يجب أن تحدد عناصر filter-dom كل حدث سيتم استخدامه في كل مكون وتوفر مكافئ مستمع حدث CamlCase لكل نوع من العُقد. نتيجةً لذلك، لا تتضمن لغة JavaScript طريقة لتحديد أداة معالجة الحدث للأحداث المخصّصة، وعلى المطوّرين استخدام السمة ref ثم تطبيق أداة معالجة الحدث بشكل ضروري. سينتج عن ذلك تجربة مطوّرين دون المستوى المطلوب عند دمج المكتبات التي لا تراعي React، ما يؤدي إلى الحاجة إلى كتابة برنامج تضمين خاص بـ React.

يمكن لـ Lit-html الوصول مباشرةً إلى DOM واستخدام الأحداث الأصلية، لذلك فإن إضافة أدوات معالجة الأحداث أمر سهل ولا يقل عن @event-name=${eventNameListener}. وهذا يعني أنه يتم تحليل وقت التشغيل بشكل أقل لإضافة أدوات معالجة الأحداث، فضلاً عن تنشيط الأحداث.

المكونات مساعدات

عناصر التفاعل العناصر المخصصة

بشكل غير مرئي، يستخدم LitElement عناصر مخصصة لحزم مكوناته. تقدّم العناصر المخصّصة بعض المُفاضلات بين مكوّنات React في ما يتعلق بتقسيم المكوّنات (تتم مناقشة الحالة ودورة الحياة في القسم الحالة ودورة الحياة).

بعض مزايا العناصر المخصصة كنظام مكونات:

  • تطبيق أصلي للمتصفح ولا يتطلب أي أدوات
  • التوافق مع جميع واجهات برمجة التطبيقات الخاصة بالمتصفِّح، من innerHTML وdocument.createElement إلى querySelector
  • يمكن استخدامها عادةً في أُطر العمل
  • يمكن التسجيل الكسول لدى customElements.define و"Hydrate". نموذج العناصر في المستند (DOM)

بعض عيوب العناصر المخصّصة مقارنةً بمكوّنات React:

  • لا يمكن إنشاء عنصر مخصص بدون تحديد فئة (وبالتالي لا توجد مكونات وظيفية تشبه JSX)
  • يجب أن يحتوي على علامة ختام
    • ملاحظة: على الرغم من أنّ مورِّدي المتصفحات الملائمة للمطوّرين يعبّرون عن تقديرهم لمواصفات علامة الإغلاق الذاتي، ولهذا السبب لا تتضمّن المواصفات الجديدة علامات إغلاق ذاتي.
  • إدخال عقدة إضافية في شجرة نموذج العناصر في المستند والتي قد تتسبب في حدوث مشاكل في التنسيق
  • يجب التسجيل باستخدام JavaScript.

لقد استخدم Lit العناصر المخصصة بدلاً من نظام عناصر مخصص لأن العناصر المخصصة مدمجة في المتصفح، ويعتقد فريق Lit أن فوائد العمل عبر الإطارات تفوق المزايا التي توفرها طبقة التجريد المكون. وفي الواقع، نجحت جهود فريق Lit في مجال lit-ssr في حل المشكلات الرئيسية المتعلقة بتسجيل جافا سكريبت. علاوة على ذلك، تستفيد بعض الشركات مثل GitHub من التسجيل الكسول للعناصر المخصصة لتحسين الصفحات تدريجيًا بلمسة اختيارية.

تمرير البيانات إلى العناصر المخصصة

هناك اعتقاد خاطئ شائع عن العناصر المخصّصة وهو أنّه لا يمكن تمرير البيانات إلا كسلاسل. ربما يأتي هذا الاعتقاد الخاطئ من حقيقة أن سمات العناصر لا يمكن كتابتها إلا كسلاسل. على الرغم من أنه صحيح أن Lit ستبث سمات السلسلة إلى أنواعها المحددة، يمكن للعناصر المخصصة أيضًا قبول البيانات المعقدة كخصائص.

على سبيل المثال، مع مراعاة تعريف LitElement التالي:

// data-test.ts
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('data-test')
class DataTest extends LitElement {
  @property({type: Number})
  num = 0;

  @property({attribute: false})
  data = {a: 0, b: null, c: [html`<div>hello</div>`, html`<div>world</div>`]}

  render() {
    return html`
      <div>num + 1 = ${this.num + 1}</div>
      <div>data.a = ${this.data.a}</div>
      <div>data.b = ${this.data.b}</div>
      <div>data.c = ${this.data.c}</div>`;
  }
}

يتم تحديد الخاصية التفاعلية الأولية num التي ستحوِّل قيمة سلسلة السمة إلى number، ثم يتم تقديم بنية بيانات معقّدة باستخدام attribute:false تؤدي إلى إيقاف معالجة سمات Lit.

هذه هي طريقة تمرير البيانات إلى هذا العنصر المخصص:

<head>
  <script type="module">
    import './data-test.js'; // loads element definition
    import {html} from './data-test.js';

    const el = document.querySelector('data-test');
    el.data = {
      a: 5,
      b: null,
      c: [html`<div>foo</div>`,html`<div>bar</div>`]
    };
  </script>
</head>
<body>
  <data-test num="5"></data-test>
</body>

الولاية مراحل النشاط

عمليات معاودة الاتصال بمراحل نشاط التفاعل الأخرى

static getDerivedStateFromProps

لا يوجد مكافئ في "الإضاءة" حيث إن الاكسسوارات والحالة وكلاهما نفس خصائص الفئة

shouldComponentUpdate

  • مكافئ الإضاءة هو shouldUpdate
  • يتم استدعاء الإجراء عند العرض الأول على عكس React.
  • تشبه وظيفتها shouldComponentUpdate في React

getSnapshotBeforeUpdate

في Lit، يتشابه getSnapshotBeforeUpdate مع كل من update وwillUpdate.

willUpdate

  • تم الاتصال قبل update.
  • على عكس getSnapshotBeforeUpdate، يتم استدعاء willUpdate قبل render
  • لا تؤدي التغييرات التي يتم إجراؤها على السمات التفاعلية في willUpdate إلى إعادة بدء دورة التحديث.
  • يُعدّ هذا أفضل خيار لاحتساب قيم الخصائص التي تعتمد على خصائص أخرى وتُستخدَم في بقية عملية التحديث.
  • يتم استدعاء هذه الطريقة على الخادم في SSR، لذلك لا يُنصح بالوصول إلى DOM هنا

update

  • تم الاتصال بعد willUpdate.
  • على عكس getSnapshotBeforeUpdate، يتم استدعاء update قبل render
  • إنّ التغييرات التي تطرأ على السمات التفاعلية في update لا تؤدي إلى إعادة تفعيل دورة التحديث في حال تغييرها قبل طلب super.update.
  • مكان جيد للحصول على المعلومات من DOM المحيطة بالمكوِّن قبل الالتزام بالإخراج المعروض في DOM
  • لم يتم استدعاء هذه الطريقة على الخادم في SSR

عمليات الاستدعاء الأخرى لمراحل نشاط Lit

هناك العديد من استدعاءات مراحل النشاط التي لم يتم ذكرها في القسم السابق بسبب عدم توفّر تناظرية في React. وهي:

attributeChangedCallback

ويتم استدعاء هذه الدالة عند تغيير قيمة observedAttributes للعنصر. يُعَدّ كل من observedAttributes وattributeChangedCallback جزءًا من مواصفات العناصر المخصّصة، ويتم تنفيذهما بواسطة "Lit" في التفاصيل لتوفير واجهة برمجة تطبيقات سمة لعناصر Lit.

adoptedCallback

يتم استدعاؤها عند نقل المكوِّن إلى مستند جديد، مثل من documentFragment الخاص بـ HTMLTemplateElement إلى document الرئيسي. يُعد رد الاتصال هذا أيضًا جزءًا من مواصفات العناصر المخصصة ويجب استخدامه فقط في حالات الاستخدام المتقدمة عندما يغيّر المكوِّن المستندات.

خصائص وطُرق دورة الحياة الأخرى

هذه الطرق والخصائص هي عناصر تابعة للفئة يمكنك الاتصال بها أو إلغاؤها أو انتظار المساعدة في معالجة عملية مراحل النشاط.

updateComplete

وهذا النوع من Promise يتم التعامل معه عند الانتهاء من تعديل العنصر لأنّ مراحل نشاط التحديث والعرض غير متزامنة. مثال:

async nextButtonClicked() {
  this.step++;
  // Wait for the next "step" state to render
  await this.updateComplete;
  this.dispatchEvent(new Event('step-rendered'));
}

getUpdateComplete

وهذه طريقة يجب تجاوزها لتخصيصها عند حلّ updateComplete. وهذا أمر شائع عندما يعرض المكوِّن مكونًا فرعيًا ويجب أن تكون دورات العرض متزامنة. مثلاً:

class MyElement extends LitElement {
  ...
  async getUpdateComplete() {
    await super.getUpdateComplete();
    await this.myChild.updateComplete;
  }
}

performUpdate

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

hasUpdated

هذه السمة هي true إذا تم تحديث المكوِّن مرة واحدة على الأقل.

isConnected

كجزء من مواصفات العناصر المخصّصة، ستكون هذه السمة true إذا كان العنصر مرفقًا حاليًا بشجرة المستند الرئيسية.

عرض مرئي لمراحل نشاط تعديل Lit

تشمل دورة حياة التحديث 3 أجزاء:

  • التحديث المسبق
  • تعديل
  • بعد التحديث

تحديث مسبق

رسم بياني دائري موجّه للعُقد التي تحتوي على أسماء معاودة الاتصال. الدالة الإنشائية لـ requestUpdate. @property to Property Setter. السمةChangedCallback إلى أداة تعيين الخصائص. أداة تعيين الخاصية إلى hasChanged. تم تغيير إلى requestUpdate. يشير requestUpdate إلى الرسم البياني التالي لمراحل تحديث النشاط.

بعد requestUpdate، يتم انتظار تحديث مجدول.

تعديل

رسم بياني دائري موجّه للعُقد التي تحتوي على أسماء معاودة الاتصال. سهم من الصورة السابقة لنقاط دورة حياة التحديث المسبق لإجراء التحديث. reportUpdate to shouldUpdate. يجب أن تشير النقاط إلى &quot;complete Update if false&quot; (تحديث كامل إذا كان false) بالإضافة إلى willUpdate. سيتم التحديث للتحديث. تحديث لكل من العرض بالإضافة إلى الرسم البياني لمراحل نشاط ما بعد التحديث التالي. العرض يشير أيضًا إلى الرسم البياني لدورة الحياة التالية بعد التحديث.

بعد التحديث

رسم بياني دائري موجّه للعُقد التي تحتوي على أسماء معاودة الاتصال. سهم من الصورة السابقة لنقاط دورة حياة التحديث إلى أول تحديث. تم التحديث أولاً إلى التحديث. تم التحديث إلى updateComplete.

Hooks

أهمية الجذب

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

عناصر الجذب لطلب واجهة برمجة التطبيقات وحدات التحكّم

من الشائع كتابة عنصر جذب يطلب البيانات من واجهة برمجة التطبيقات. على سبيل المثال، يمكنك استخدام مكوِّن الدالة React هذا الذي يؤدي ما يلي:

  • index.tsx
    • يعرض النص
    • عرض رد useAPI
      • رقم تعريف المستخدم + اسم المستخدم
      • رسالة الخطأ
        • 404 عندما يتم الوصول إلى المستخدم 11 (حسب التصميم)
        • إلغاء الخطأ في حال إلغاء عملية استرجاع البيانات من واجهة برمجة التطبيقات
      • جارٍ تحميل الرسالة
    • لعرض زر إجراء
      • المستخدم التالي: الذي يجلب واجهة برمجة التطبيقات للمستخدم التالي
      • إلغاء: يلغي استرجاع واجهة برمجة التطبيقات ويعرض رسالة خطأ
  • useApi.tsx
    • لتحديد عنصر جذب مخصّص useApi
    • هل سيتم جلب كائن المستخدم من واجهة برمجة التطبيقات بشكل غير متزامن
    • الإرسال:
      • اسم المستخدم
      • ما إذا كان سيتم تحميل طلب الجلب أم لا
      • أي رسائل خطأ
      • معاودة الاتصال لإلغاء الجلب
    • عمليات إلغاء الجلب قيد التقدم في حال إلغاء تحميلها

في ما يلي تنفيذ وحدة التحكّم التفاعلية + وحدات التحكّم بالإضاءة.

الخلاصات:

  • وحدات التحكم التفاعلية تشبه إلى حد كبير عناصر الجذب المخصصة
  • تمرير البيانات غير القابلة للعرض بين عمليات الاستدعاء والتأثيرات
    • تستخدم React useRef لتمرير البيانات بين useEffect وuseCallback.
    • يستخدم Lit خاصية صف خاص
    • في الأساس، React هو محاكاة سلوك ملكية الفئة الخاصة.

بالإضافة إلى ذلك، إذا كنت تحب بنية مكوِّن الدالة React باستخدام عناصر الجذب ولكن مع بيئة Lit التي لا يمكنها إنشاء إصدار، يقترح فريق Lit استخدام مكتبة Haunted بشدة.

أطفال

الشريحة التلقائية

عند عدم توفير السمة slot لعناصر HTML، يتم تعيينها إلى الخانة التلقائية بدون اسم. في المثال أدناه، سيضع MyApp فقرة واحدة في خانة مُسَمّاة. سيتم ضبط الفقرة الأخرى تلقائيًا على الخانة غير المُسمّاة".

@customElement("my-element")
export class MyElement extends LitElement {
  render() {
    return html`
      <section>
        <div>
          <slot></slot>
        </div>
        <div>
          <slot name="custom-slot"></slot>
        </div>
      </section>
   `;
  }
}

@customElement("my-app")
export class MyApp extends LitElement {
  render() {
    return html`
      <my-element>
        <p slot="custom-slot">
          This paragraph will be placed in the custom-slot!
        </p>
        <p>
          This paragraph will be placed in the unnamed default slot!
        </p>
      </my-element>
   `;
  }
}

تحديثات الخانة

عندما تتغير بنية العناصر التابعة للخانة، يتم تنشيط حدث slotchange. يمكن لمكوِّن Lit ربط مستخدم مستمع للحدث بحدث slotchange. في المثال أدناه، إنّ الخانة الأولى المتوفّرة في shadowRoot سيتم تسجيل assignedNodes فيها بوحدة التحكّم على slotchange.

@customElement("my-element")
export class MyElement extends LitElement {
  onSlotChange(e: Event) {
    const slot = this.shadowRoot.querySelector('slot');
    console.log(slot.assignedNodes({flatten: true}));
  }

  render() {
    return html`
      <section>
        <div>
          <slot @slotchange="{this.onSlotChange}"></slot>
        </div>
      </section>
   `;
  }
}

الحكام

إنشاء مراجع

يعرض كل من Lit وReact مرجعًا إلى عنصر HTMLElement بعد استدعاء دوال render الخاصة بهما. يُرجى مراجعة كيفية إنشاء React وLit لنموذج DOM الذي يتم عرضه لاحقًا من خلال مصمم زخرفي Lit @query أو مرجع React.

React هو مسار وظيفي ينشئ مكونات React وليس HTMLElements. لأنه يتم الإعلان عن المرجع قبل عرض HTMLElement، يتم تخصيص مسافة في الذاكرة. هذا هو السبب في أنّك ترى null كقيمة أولية للمرجع، لأنّ عنصر DOM الفعلي لم يتم إنشاؤه (أو عرضه) بعد، أي useRef(null).

بعد أن يحوّل ReactDOM مكوِّن React إلى HTMLElement، فإنه يبحث عن سمة تُسمى ref في ReactComponent. يضع ReactDOM مرجع HTMLElement في حال توفّره، ref.current.

يستخدم LitElement دالة علامة النموذج html من lit-html لإنشاء عنصر نموذج في المقدمة. يختم LitElement محتوى النموذج إلى shadow DOM للعنصر المخصّص بعد العرض. shadow DOM هو شجرة DOM ذات نطاق مُحاطة بجذر ظل. ينشئ مزخرف @query بعد ذلك دالة getter للبحث عن السمة، ما يؤدي بشكلٍ أساسي إلى تنفيذ this.shadowRoot.querySelector على الجذر ذي النطاق.

طلب بحث عن عناصر متعددة

في المثال أدناه، سيعرض أداة التصميم @queryAll الفقرتَين في جذر الظل كالفقرتين NodeList.

@customElement("my-element")
export class MyElement extends LitElement {
  @queryAll('p')
  paragraphs!: NodeList;

  render() {
    return html`
      <p>Hello, world!</p>
      <p>How are you?</p>
   `;
  }
}

بشكل أساسي، تنشئ @queryAll دالة getter لـ paragraphs تعرض نتائج this.shadowRoot.querySelectorAll(). في JavaScript، يمكن الإعلان عن دالة getter لتنفيذ الغرض نفسه:

get paragraphs() {
  return this.renderRoot.querySelectorAll('p');
}

عناصر تغيير طلب البحث

إنّ أداة تصميم @queryAsync أكثر ملاءمةً للتعامل مع العُقدة التي يمكن أن تتغير استنادًا إلى حالة خاصية عنصر آخر.

في المثال أدناه، سيعثر @queryAsync على عنصر الفقرة الأول. ومع ذلك، لن يتم عرض عنصر الفقرة إلا عندما يُنشئ renderParagraph رقمًا فرديًا بشكل عشوائي. سيعرض التوجيه @queryAsync وعدًا سيحلّ عند توفّر الفقرة الأولى.

@customElement("my-dissappearing-paragraph")
export class MyDisapppearingParagraph extends LitElement {
  @queryAsync('p')
  paragraph!: Promise<HTMLElement>;

  renderParagraph() {
    const randomNumber = Math.floor(Math.random() * 10)
    if (randomNumber % 2 === 0) {
      return "";
    }

    return html`<p>This checkbox is checked!`
  }

  render() {
    return html`
      ${this.renderParagraph()}
   `;
  }
}

حالة التوسط

في React، يُستخدَم الاصطلاح في استخدام استدعاءات لأن الحالة تتوسّطها React نفسها. من الأفضل أن يعتمد React على الحالة التي توفرها العناصر. ويشكّل نموذج العناصر في المستند (DOM) ببساطة أحد تأثيرات عملية العرض.

الحالة الخارجية

يمكن استخدام Redux أو MobX أو أي مكتبة إدارة حالة أخرى إلى جانب Lit.

يتم إنشاء مكوّنات Lit في نطاق المتصفّح. وبالتالي، تكون أي مكتبة موجودة أيضًا في نطاق المتصفح متاحة في Lit. تم إنشاء العديد من المكتبات الرائعة لاستخدام أنظمة إدارة الدولة الحالية في Lit.

في ما يلي سلسلة من إعداد فاادين تشرح كيفية الاستفادة من Redux في مكوِّن Lit.

يمكنك إلقاء نظرة على lit-mobx من Adobe لمعرفة كيف يمكن لموقع إلكتروني واسع النطاق الاستفادة من MobX في Lit.

يمكنك أيضًا الاطّلاع على عناصر Apollo لمعرفة كيف يضمِّن المطوّرون GraphQL في مكوّناتهم على الويب.

يعمل تطبيق Lit مع ميزات المتصفّح الأصلية، ويمكن استخدام معظم حلول إدارة الحالات في نطاق المتصفّح في أحد مكوّنات Lit.

التصميم

نموذج Shadow DOM

لتغليف الأنماط ونموذج العناصر في المستند (DOM) في الأصل داخل عنصر مخصّص، يستخدم Lit دالة Shadow DOM. تنشئ جذور الظل شجرة ظل منفصلة عن شجرة المستند الرئيسية. يعني هذا أن معظم الأنماط تم تحديدها لهذا المستند. هناك أنماط معينة تتسرب من خلال مثل اللون والأنماط الأخرى المتعلقة بالخطوط.

يقدّم Shadow DOM أيضًا مفاهيم وأدوات اختيار جديدة لمواصفات CSS:

:host, :host(:hover), :host([hover]) {
  /* Styles the element in which the shadow root is attached to */
}

slot[name="title"]::slotted(*), slot::slotted(:hover), slot::slotted([hover]) {
  /*
   * Styles the elements projected into a slot element. NOTE: the spec only allows
   * styling the direcly slotted elements. Children of those elements are not stylable.
   */
}

أنماط المشاركة

يسهّل Lit مشاركة الأنماط بين المكوّنات في شكل CSSTemplateResults باستخدام علامات نماذج css. على سبيل المثال:

// typography.ts
export const body1 = css`
  .body1 {
    ...
  }
`;

// my-el.ts
import {body1} from './typography.ts';

@customElement('my-el')
class MyEl Extends {
  static get styles = [
    body1,
    css`/* local styles come after so they will override bod1 */`
  ]

  render() {
    return html`<div class="body1">...</div>`
  }
}

المظهر

تمثل جذور الظل تحديًا كبيرًا للمواضيع التقليدية التي عادةً ما تكون مناهج علامات من أعلى إلى أسفل. الطريقة التقليدية لمعالجة السمات باستخدام مكونات الويب التي تستخدم Shadow DOM هي عرض واجهة برمجة تطبيقات للنمط عبر خصائص CSS المخصصة. على سبيل المثال، هذا هو النمط الذي يستخدمه Material Design:

.mdc-textfield-outline {
  border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
  caret-color: var(--mdc-theme-primary, #...);
}

ويمكن للمستخدم عندئذٍ تغيير موضوع الموقع الإلكتروني من خلال تطبيق قيم الخصائص المخصّصة على النحو التالي:

html {
  --mdc-theme-primary: #F00;
}
html[dark] {
  --mdc-theme-primary: #F88;
}

إذا كان من الضروري استخدام مظاهر من أعلى إلى أسفل ولا يمكنك عرض الأنماط، من الممكن دائمًا إيقاف Shadow DOM من خلال إلغاء createRenderRoot لعرض this، ما سيؤدي بعد ذلك إلى عرض المكوّنات. بالعنصر المخصص نفسه بدلاً من جذر الظل المرفق بالعنصر المخصص. في هذه الحالة، ستفقد: تغليف الأنماط، وتغليف DOM، والخانات.

الإنتاج

الإصدار 11 من IE

إذا كنت بحاجة إلى التوافق مع المتصفحات القديمة مثل IE 11، فعليك تحميل بعض رموز polyfill التي تأتي بحجم 33 كيلوبايت آخر تقريبًا. يمكنك الاطّلاع على مزيد من المعلومات هنا.

الحِزم المشروطة

يوصي فريق Lit بعرض حزمتين مختلفتين، إحداهما للإصدار 11 من IE والأخرى للمتصفّحات الحديثة. وهناك العديد من الفوائد وراء ذلك:

  • يتم عرض إعلانات ES 6 بشكل أسرع وستخدم معظم عملائك
  • تم قراءة الفيديو ES 5 بهدف زيادة حجم الحزمة بشكل كبير.
  • تمنحك الحزم الشرطية أفضل ما في الأمرين
    • دعم IE 11
    • لا بطء في المتصفحات الحديثة

يمكنك العثور على مزيد من المعلومات حول كيفية إنشاء حزمة معروضة بشكل مشروط على موقع المستندات هنا.