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 تجميع المحتوى. يعني هذا أنّه يمكن إعداد بيئات مطوّري البرامج باستخدام علامة نص برمجي + متصفّح + خادم، ومن ثمّ تنفيذ الخطوات اللازمة.
باستخدام وحدات اللغة الإسبانية وشبكات توصيل المحتوى (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
.
أدوات المطوّرين مضمّنة في المتصفّح
المكوّنات البسيطة هي عناصر HTML في نموذج العناصر في المستند (DOM). وهذا يعني أنه لفحص المكوّنات، لن تحتاج إلى تثبيت أي أدوات أو تنفيذات للمتصفّح.
يمكنك بكل بساطة فتح أدوات مطوّري البرامج واختيار عنصر واستكشاف خصائصه أو حالته.
وهي مصمَّمة مع أخذ العرض من جهة الخادم في الاعتبار
تم تصميم 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، يمكنك استخدام نقاط التحقق هذه لتنزيل رمز البداية لأي خطوة، بالإضافة إلى استخدامها للتحقق من عملك.
استكشِف واجهة المستخدم في ساحة مضيئة
تُبرز لقطة شاشة واجهة المستخدم في "المنصة" في "المنصة" الأقسام التي ستستخدمها في هذا الدرس التطبيقي حول الترميز.
- أداة اختيار الملفات لاحظ زر الإضافة...
- محرر الملفات.
- معاينة الرمز
- زر "إعادة التحميل"
- زر التنزيل
إعداد رمز VS (متقدّم)
فيما يلي فوائد استخدام إعداد VS Code هذا:
- التحقّق من نوع النموذج
- ذكاء النماذج الإكمال التلقائي
إذا سبق لك تثبيت NPM وVS Code (مع المكوّن الإضافي lit- Plugin) وكنت تعرف كيفية استخدام تلك البيئة، يمكنك ببساطة تنزيل هذه المشاريع وبدء تشغيلها عن طريق اتّباع الخطوات التالية:
- اضغط على زر التنزيل.
- استخراج محتوى ملف tar إلى دليل
- (في حال تحديد المشاكل وحلّها) يجب إعداد عملية ضبط سريعة تؤدي إلى إخراج الوحدات والإصدار es2015 والإصدارات الأحدث.
- تثبيت خادم مطوّر يمكنه حل محددات الوحدات المجرّدة (يوصي فريق Lit بـ @web/dev-server)
- إليك مثال
package.json
.
- إليك مثال
- تشغيل خادم 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>Σ: {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
:
- تم التحليل مرة واحدة لكل صف مقابل كل مثيل
- تم التنفيذ مع مراعاة قابلية إعادة استخدام الوحدة.
- يمكن فصل الأنماط بسهولة في ملفات خاصة بها
- متوافق مع رمز 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
، يتم انتظار تحديث مجدول.
تعديل
بعد التحديث
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
. في المثال أدناه، إنّ الخانة الأولى المتوفّرة في 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
- لا بطء في المتصفحات الحديثة
يمكنك العثور على مزيد من المعلومات حول كيفية إنشاء حزمة معروضة بشكل مشروط على موقع المستندات هنا.