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 (Lit-html) بـ VDOM في React، تظهر lit-html بشكل أسرع بنسبة تتراوح بين 8% و 10% مقارنةً بأداة React في أسوأ الحالات وأسرع بنسبة +50% في حالات الاستخدام الأكثر شيوعًا.
إنّ LitElement (الفئة الأساسية لمكوّن LitElement) يضيفان الحد الأدنى من النفقات العامة لـ lit-html، ولكن يتفوقان على أداء React بنسبة 16% إلى 30% عند مقارنة ميزات المكوّنات، مثل استخدام الذاكرة وأوقات التفاعل وبدء التشغيل.
لا يحتاج إلى إصدار
مع ميزات المتصفّح الجديدة، مثل وحدات ES والقيم الحرفية للنماذج التي تم وضع علامات عليها، لا يتطلّب تطبيق Lit تجميع المحتوى. وهذا يعني أنّه يمكن إعداد بيئات المطوّرين باستخدام علامة نص برمجي ومتصفّح وخادم، ويمكنك بدء العمل.
باستخدام وحدات ES وشبكات توصيل المحتوى (CDN) الحديثة، مثل Skypack أو UNPKG، قد لا تحتاج حتى إلى NPM للبدء.
ومع ذلك، سيظل بإمكانك إنشاء رمز Lit وتحسينه إذا أردت ذلك. كان دمج المطوّرين الأخير حول وحدات ES الأصلية مفيدًا لخدمة Lit، لأنّ Lit هي مجرد JavaScript عادية ولا حاجة إلى واجهات سطر الأوامر أو معالجة الإنشاء الخاصة بالإطارات الأساسية.
غير مرتبط بإطار عمل معيّن
تستند مكوّنات 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 وميزة IntelliSense في نماذج Lit في وقتَي التطوير والإنشاء باستخدام lit-analyzer
وlit-plugin
.
أدوات المطوّرين مدمجة في المتصفّح
مكونات Lit هي مجرد عناصر HTML في DOM. وهذا يعني أنّه لإجراء فحص للمكوّنات، لست بحاجة إلى تثبيت أي أدوات أو إضافات لمتصفّحك.
ما عليك سوى فتح أدوات المطوّرين واختيار عنصر واستكشاف خصائصه أو حالته.
تم تصميمه مع الأخذ في الاعتبار العرض من جهة الخادم (SSR).
تم تصميم Lit 2 مع وضع دعم SSR في الاعتبار. في وقت كتابة هذا الدليل التعليمي حول الرموز البرمجية، لم يُصدر فريق Lit أدوات العرض من جهة الخادم بعد في شكل ثابت، ولكنّه سبق أن نشر المكوّنات المعروضة من جهة الخادم في منتجات Google واختبر العرض من جهة الخادم في تطبيقات React. يتوقع فريق Lit إطلاق هذه الأدوات خارجيًا على GitHub قريبًا.
في هذه الأثناء، يمكنك متابعة مستوى تقدّم فريق Lit هنا.
تكلفة الاشتراك منخفضة
لا يتطلب Lit التزامًا كبيرًا باستخدامه! يمكنك إنشاء مكونات في Lit وإضافتها إلى مشروعك الحالي. إذا لم تعجبك هذه الإطارات، ليس عليك تحويل التطبيق بالكامل دفعة واحدة لأنّ مكونات الويب تعمل في إطارات عمل أخرى.
هل أنشأت تطبيقًا كاملاً في Lit وتريد تغييره إلى تطبيق آخر؟ يمكنك حينها وضع تطبيق Lit الحالي في إطار العمل الجديد ونقل كل ما تريده إلى مكونات إطار العمل الجديد.
بالإضافة إلى ذلك، تتوافق العديد من الإطارات الحديثة مع الإخراج في مكوّنات الويب، ما يعني أنّه يمكن عادةً دمجها في عنصر Lit.
3- الإعداد واستكشاف "مساحة اللعب"
هناك طريقتان لتنفيذ هذا الدليل التعليمي حول الرموز البرمجية:
- يمكنك إجراء ذلك بالكامل على الإنترنت من خلال المتصفح
- (خيار متقدم) يمكنك إجراء ذلك على جهازك المحلي باستخدام رمز VS
الوصول إلى الرمز
خلال ورشة رموز البرمجة، ستظهر روابط إلى ساحة ألعاب Lit على النحو التالي:
"مساحة اللعب" هي مساحة رموز برمجية تمكّنك من تنفيذ الرموز البرمجية بالكامل في المتصفّح. ويمكنه تجميع ملفات TypeScript وJavaScript وتشغيلها، كما يمكنه أيضًا تحليل عمليات الاستيراد تلقائيًا إلى وحدات العقدة. على سبيل المثال،
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
يمكنك تنفيذ البرنامج التعليمي بالكامل في ساحة Lit، باستخدام نقاط التفتيش هذه كنقاط بداية. إذا كنت تستخدم VS Code، يمكنك استخدام نقاط التفتيش هذه لتنزيل الرمز البرمجي الأوّلي لأي خطوة، بالإضافة إلى استخدامها للتحقّق من عملك.
استكشاف واجهة مستخدم ساحة الألعاب المُضاءة
تُبرز لقطة شاشة واجهة المستخدم في "المنصة" في "المنصة" الأقسام التي ستستخدمها في هذا الدرس التطبيقي حول الترميز.
- أداة اختيار الملفات لاحظ زر الإضافة...
- محرر الملفات.
- معاينة الرمز
- زر "إعادة التحميل"
- زر التنزيل
إعداد VS Code (متقدّم)
فيما يلي فوائد استخدام إعداد VS Code هذا:
- التحقّق من نوع النموذج
- ميزة "اقتراحات الذكاء الاصطناعي" للنماذج والإكمال التلقائي
إذا كان لديك كلّ من NPM وVS Code (مع المكوّن الإضافي lit-plugin) مثبتًا وكنت تعرف كيفية استخدام تلك البيئة، يمكنك ببساطة تنزيل هذه المشاريع وبدء تشغيلها من خلال اتّباع الخطوات التالية:
- الضغط على زر التنزيل
- استخراج محتوى ملف tar في دليل
- (في حال استخدام TS) إعداد ملف tsconfig سريع يُخرج وحدات es وes2015 والإصدارات الأحدث
- ثبِّت خادم تطوير يمكنه حلّ محددات الوحدة الأساسية (يُنصح فريق Lit باستخدام @web/dev-server)
- إليك مثال
package.json
.
- إليك مثال
- شغِّل خادم المطوّرين وافتح المتصفّح (إذا كنت تستخدم @web/dev-server، يمكنك استخدام
npx web-dev-server --node-resolve --watch --open
)- إذا كنت تستخدم المثال
package.json
، استخدِمnpm run dev
.
- إذا كنت تستخدم المثال
4. JSX والنماذج
في هذا القسم، ستتعرّف على أساسيات النماذج في Lit.
نماذج JSX وLit
JSX هي إضافة بناء جملة لـ JavaScript تسمح لمستخدمي React بكتابة النماذج بسهولة في رمز JavaScript. تخدم النماذج المخصّصة للأجهزة الجوّالة غرضًا مشابهًا: التعبير عن واجهة مستخدم المكوّن كدالّة على حالته.
البنية الأساسية
في 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>
) ثم إحاطتها بتعبير نموذجي مُشار إليه. ملاحظة: احرص على استخدام حرف الفاصلة العليا المائلة (`
) وليس حرف الاقتباس الفردي ('
).
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
);
يمكن أن تقبل TemplateResult
s المُضاءة المصفوفات والسلاسل وTemplateResult
s الأخرى، بالإضافة إلى التوجيهات.
كتدريب، جرِّب تحويل رمز 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
(تلخّص React هذه الأحداث).
بنية معالج أحداث Lit
html`<my-element @event-name=${() => {...}}></my-element>`;
- البادئة
@
هي بنية الربط لأداة معالجة الحدث. - ما يعادل
inputRef.addEventListener('event-name', ...)
- لاستخدام أسماء أحداث DOM الأصلية
5- المكوّنات والعناصر
في هذا القسم، ستتعرّف على مكونات ووظائف فئة Lit. يتم تناول الحالة و"الزوابع" بمزيد من التفصيل في الأقسام اللاحقة.
مكونات الصف وLitElement
مكافئ Lit لمكوّن فئة React هو LitElement، ومفهوم Lit "للسمات التفاعلية" هو مزيج من سمات 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 لعنصر الزخرفة
@customElement
في TypeScript.
<head>
<script type="module" src="./index.js"></script>
</head>
- لاستيراد تعريف العنصر المخصّص.
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
- تُضيف هذه الوظيفة العنصر المخصّص إلى الصفحة.
- تضبط السمة
name
على'Elliott'
.
مكونات الدالة
لا تتضمّن Lit تفسيرًا مباشرًا لمكوّن الدالة لأنّها لا تستخدِم 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. - هذا هو المكان المناسب لتنفيذ إعداد المكوّن الذي يتطلّب معالجة 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);
}
- يتمّ استدعاؤه عند إدراج العنصر المخصّص في شجرة DOM.
- على عكس مكونات 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 Hooks طريقة لمكونات الدوالّ "للتعلّق" بالحالة. وهناك العديد من الفوائد وراء ذلك.
- تبسيط إعادة استخدام المنطق الذي يتضمّن حالة
- المساعدة في تقسيم مكوِّن إلى دوال أصغر
بالإضافة إلى ذلك، إنّ التركيز على المكونات المستنِدة إلى الدوال قد أدّى إلى حلّ مشاكل معيّنة في بناء الجملة المستنِد إلى الفئة في React، مثل:
- يجب تمرير
props
منconstructor
إلىsuper
- الإعداد غير المنظَّم للخصائص في
constructor
- وقد ذكر فريق React هذا السبب في ذلك الوقت، ولكن تم حلّه باستخدام ES2019.
- لم تعُد المشاكل التي تسببت بسبب
this
تشير إلى المكوِّن
مفاهيم React hooks في Lit
كما هو موضّح في قسم المكونات وعناصر العرض، لا يوفّر Lit طريقة لإنشاء عناصر مخصّصة من دالة، ولكنّ LitElement يعالج معظم المشاكل الرئيسية في مكونات فئة 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
، سيتمّ استدعاء دورة حياة وحدة التحكّم إلى جانب دورة حياة المضيف. على سبيل المثال، يمكنك استرجاع مثال الساعة من قسم الولاية ودورة الحياة:
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
كما هو موضّح في المثال في قسم الحالة ورحلة المستخدِم.
// 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. المراجع
في بعض الأحيان، قد يحتاج المطوّر إلى الوصول إلى واجهة برمجة التطبيقات الخاصة بعنصر HTML.
في هذا القسم، ستتعرّف على كيفية الحصول على مراجع العناصر في Lit.
مراجع React
يتم تحويل مكوِّن 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
(إنشاء دالة للحصول على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.
المكونات القابلة لإعادة الاستخدام
يحاكي React مسارات العرض الوظيفية من خلال تدفق البيانات من الأعلى إلى الأسفل. يوفّر الوالدان الحالة للأطفال من خلال لوازم التصوير. يتواصل الأطفال مع الوالدَين من خلال عمليات الاستدعاء المتوفّرة في العناصر.
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
. - لعرض زر يتضمن "خطوة +" أو "-خطوة" كتصنيف له
- تعديل المكوّن الرئيسي من خلال استدعاء
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 الحالة بشكل أساسي في الاتجاه المعاكس، لذا من غير الشائع رؤية مكونات 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>Σ: {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>Σ ${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
:
- يتم تحليلها مرة واحدة لكل فئة في مقابل كل مثيل
- تم التنفيذ مع مراعاة قابلية إعادة استخدام الوحدة.
- يمكن فصل الأنماط بسهولة في ملفات خاصة بها
- التوافق مع تقنية CSS Custom Properties polyfill
بالإضافة إلى ذلك، لاحظ العلامة <style>
في index.html
:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
سيحدِّد Lit نطاق أنماط المكوّنات إلى جذورها. وهذا يعني أنّ الأنماط لن تنتقل من مكان إلى آخر. لتمرير الأنماط إلى المكونات، يوصي فريق Lit باستخدام خصائص CSS المخصصة لأنها يمكنها اختراق نطاق نمط Lit.
علامات الأنماط
من الممكن أيضًا تضمين علامات <style>
في نماذجك. سيزيل المتصفّح تكرار علامات الأنماط هذه، ولكن من خلال وضعها في النماذج، سيتم تحليلها لكل مثيل مكوّن بدلاً من كل فئة كما هو الحال مع نموذج css
المميّز بعلامة. بالإضافة إلى ذلك، تتم إزالة تكرار متصفِّح CSSResult
بشكل أسرع بكثير.
علامات الروابط
يمكنك أيضًا استخدام <link rel="stylesheet">
في النموذج لتطبيق الأنماط، ولكن لا يُنصح بذلك أيضًا لأنّه قد يؤدي إلى ظهور وميض أولي للمحتوى غير المنسَّق (FOUC).
12. المواضيع المتقدّمة (اختيارية)
JSX والنماذج
Lit و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 يضيف ميزات تمييز البنية وفحص النوع وIntelliSense إلى مشاريعك في Lit.
Lit وJSX + React DOM
لا يتم تشغيل JSX في المتصفّح ويستخدم بدلاً من ذلك معالجًا مسبقًا لتحويل 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، لا يستخدم نموذج DOM افتراضيًا تقليديًا، بل يستخدم واجهة برمجة التطبيقات DOM مباشرةً، فإنّ حجم Lit 2 يقل عن 5 كيلوبايت بعد تصغيره وضغطه باستخدام برنامج gzip مقارنةً بـ React (2.8 كيلوبايت) + react-dom (39.4 كيلوبايت) الذي يبلغ حجمه 40 كيلوبايت بعد تصغيره وضغطه باستخدام برنامج gzip.
الفعاليات
يستخدم React نظام أحداث اصطناعيًا. وهذا يعني أنّه على react-dom تحديد كل حدث سيتم استخدامه في كل مكوّن وتوفير مكافئ مستمع للأحداث بتنسيق camelCase لكل نوع من أنواع العقد. نتيجةً لذلك، لا تتضمّن JSX طريقة لتحديد مستمع أحداث لحدث مخصّص، وعلى المطوّرين استخدام 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 على المشاكل الرئيسية المتعلّقة بتسجيل JavaScript. بالإضافة إلى ذلك، تستفيد بعض الشركات، مثل 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>
الحالة ودورة الحياة
طلبات الاستدعاء الأخرى في مراحل نشاط React
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
، يتم انتظار تحديث مجدول.
تعديل
تعديل المحتوى بعد نشره
Hooks
أهمية العبارات الافتتاحية
تم إدخال خطّاف في React لحالات استخدام مكونات الدوال البسيطة التي تتطلب حالة. في العديد من الحالات البسيطة، تكون مكوّنات الدوالّ التي تتضمّن علامات الربط عادةً أبسط وأسهل قراءة من نظيراتها من مكوّنات الفئات. على الرغم من ذلك، عند إدخال تحديثات حالة غير متزامنة بالإضافة إلى تمرير البيانات بين الخطّافات أو التأثيرات، فإن نمط الخطّافات لا يكفي، والحل المستند إلى الفئة مثل وحدات التحكم التفاعلية يكون أكثر تألقًا.
عناصر الربط وعناصر التحكّم في طلبات البيانات من واجهة برمجة التطبيقات
من الشائع كتابة رمز برمجي يطلب البيانات من واجهة برمجة التطبيقات. على سبيل المثال، يمكنك استخدام مكوِّن الدالة React هذا الذي يؤدي ما يلي:
index.tsx
- عرض النص
- عرض ردّ
useAPI
- رقم تعريف المستخدم + اسم المستخدم
- رسالة الخطأ
- 404 عند الوصول إلى المستخدِم 11 (حسب التصميم)
- خطأ في الإيقاف إذا تم إيقاف جلب واجهة برمجة التطبيقات
- جارٍ تحميل الرسالة
- عرض زر إجراء
- المستخدم التالي: الذي يُجلب واجهة برمجة التطبيقات للمستخدم التالي
- إلغاء: لإيقاف عملية جلب البيانات من واجهة برمجة التطبيقات وعرض خطأ
useApi.tsx
- لتحديد عنصر جذب مخصّص
useApi
- سيتم جلب كائن المستخدم من واجهة برمجة التطبيقات بشكل غير متزامن.
- الإرسال:
- اسم المستخدم
- ما إذا كان سيتم تحميل طلب الجلب أم لا
- أي رسائل خطأ
- دالة استدعاء لإيقاف عملية الجلب
- إيقاف عمليات الجلب الجارية في حال إلغاء التثبيت
- لتحديد عنصر جذب مخصّص
في ما يلي تنفيذ وحدة التحكّم التفاعلية + وحدات التحكّم بالإضاءة.
الخلاصات:
- تشبه أدوات التحكّم التفاعلية إلى حدٍ كبير أدوات الربط المخصّصة.
- تمرير البيانات غير القابلة للعرض بين عمليات الاستدعاء والتأثيرات
- يستخدم React
useRef
لنقل البيانات بينuseEffect
وuseCallback
. - يستخدم Lit خاصية فئة خاصة.
- يحاكي React بشكل أساسي سلوك خاصية فئة خاصة.
- يستخدم 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
. في المثال أدناه، سيتم تسجيل assignedNodes في وحدة التحكّم على slotchange
للفتحة الأولى التي تم العثور عليها في shadowRoot
.
@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 التي يتم عرضها لاحقًا من خلال عنصر تزيين @query
في Lit أو مرجع React.
React هي مسار عمل وظيفي ينشئ عناصر React وليس عناصر HTMLElement. لأنه يتم الإعلان عن المرجع قبل عرض 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
دالة الحصول على السمة التي تُجري بشكل أساسي عملية 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
دالة جلب لـ paragraphs
تُرجع نتائج this.shadowRoot.querySelectorAll()
. في JavaScript، يمكن تعريف دالة جلب لتنفيذ الغرض نفسه:
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.
إليك سلسلة من Vaadin توضّح كيفية الاستفادة من Redux في مكوّن Lit.
اطّلِع على lit-mobx من Adobe لمعرفة كيف يمكن لموقع إلكتروني واسع النطاق الاستفادة من MobX في Lit.
يمكنك أيضًا الاطّلاع على Apollo Elements (عناصر Apollo) لمعرفة كيف يضمِّن المطوّرون GraphQL في مكوّناتهم على الويب.
تعمل Lit مع ميزات المتصفّح الأصلية، ويمكن استخدام معظم حلول إدارة الحالة في نطاق المتصفّح في مكوّن Lit.
التصميم
نموذج 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 بعرض حِزمتَين مختلفتَين، إحداهما لمتصفّح IE 11 والأخرى للمتصفّحات الحديثة. هناك عدة فوائد لهذا الإجراء:
- إنّ عرض ES 6 أسرع وسيناسب معظم عملائك.
- تم قراءة الفيديو ES 5 بهدف زيادة حجم الحِزمة بشكل كبير.
- تمنحك الحزم الشرطية أفضل ما في الأمرين
- دعم IE 11
- لا بطء في المتصفحات الحديثة
يمكنك الاطّلاع على مزيد من المعلومات حول كيفية إنشاء حِزمة يتم عرضها بشكل مشروط على موقع المستندات هنا.