از Web Component تا Lit Element

1. مقدمه

آخرین به روز رسانی: 2021-08-10

اجزای وب

Web Components مجموعه‌ای از APIهای پلتفرم وب هستند که به شما امکان می‌دهند تگ‌های HTML سفارشی، قابل استفاده مجدد و کپسوله‌شده جدیدی را برای استفاده در صفحات وب و برنامه‌های وب ایجاد کنید. اجزای سفارشی و ویجت‌های ساخته شده بر اساس استانداردهای Web Component در مرورگرهای مدرن کار می‌کنند و می‌توانند با هر کتابخانه یا چارچوب جاوا اسکریپتی که با HTML کار می‌کند استفاده شوند.

Lit چیست

Lit یک کتابخانه ساده برای ساخت کامپوننت های وب سریع و سبک است که در هر چارچوبی یا اصلاً بدون فریمورک کار می کنند. با Lit می توانید اجزای قابل اشتراک گذاری، برنامه های کاربردی، سیستم های طراحی و موارد دیگر را بسازید.

Lit APIهایی را برای ساده کردن وظایف متداول Web Components مانند مدیریت ویژگی ها، ویژگی ها و رندر ارائه می دهد.

چیزی که یاد خواهید گرفت

  • کامپوننت وب چیست؟
  • مفاهیم اجزای وب
  • چگونه یک کامپوننت وب بسازیم
  • lit-html و LitElement چیست
  • آنچه Lit در بالای یک مؤلفه وب انجام می دهد

چیزی که خواهی ساخت

  • یک کامپوننت وب وانیلی که انگشت شست بالا/پایین است
  • یک کامپوننت وب مبتنی بر روشن/شست بالا/پایین

آنچه شما نیاز دارید

  • هر مرورگر مدرن به روز شده (Chrome، Safari، Firefox، Chromium Edge). اجزای وب در همه مرورگرهای مدرن کار می‌کنند و پلی‌فیل‌ها برای Microsoft Internet Explorer 11 و غیر کرومی مایکروسافت اج در دسترس هستند.
  • آشنایی با HTML، CSS، جاوا اسکریپت و ابزار توسعه کروم .

2. راه اندازی و کاوش در زمین بازی

دسترسی به کد

در سرتاسر Codelab پیوندهایی به زمین بازی Lit وجود دارد که به شرح زیر است:

زمین بازی یک جعبه ماسه ای کد است که به طور کامل در مرورگر شما اجرا می شود. می‌تواند فایل‌های TypeScript و JavaScript را کامپایل و اجرا کند، و همچنین می‌تواند به طور خودکار واردات به ماژول‌های گره را حل کند. به عنوان مثال

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

// after
import './my-file.js';
import 'https://unpkg.com/lit?module';

شما می توانید کل آموزش را در زمین بازی Lit با استفاده از این نقاط بازرسی به عنوان نقطه شروع انجام دهید. اگر از VS Code استفاده می کنید، می توانید از این نقاط بازرسی برای دانلود کد شروع برای هر مرحله و همچنین برای بررسی کار خود استفاده کنید.

کاوش در رابط کاربری روشنایی زمین بازی

نوار برگه انتخابگر فایل دارای برچسب بخش 1، بخش ویرایش کد به عنوان بخش 2، پیش نمایش خروجی به عنوان بخش 3، و دکمه پیش نمایش بارگذاری مجدد به عنوان بخش 4 است.

اسکرین شات رابط کاربری زمین بازی Lit بخش هایی را که در این کد لبه استفاده خواهید کرد برجسته می کند.

  1. انتخابگر فایل به دکمه پلاس توجه کنید...
  2. ویرایشگر فایل.
  3. پیش نمایش کد.
  4. دکمه بارگذاری مجدد
  5. دکمه دانلود.

تنظیم VS Code (پیشرفته)

در اینجا مزایای استفاده از این تنظیمات VS Code وجود دارد:

  • بررسی نوع الگو
  • هوشمندسازی الگو و تکمیل خودکار

اگر NPM، VS Code (با افزونه lit-plugin ) را قبلاً نصب کرده‌اید و می‌دانید چگونه از آن محیط استفاده کنید، می‌توانید به سادگی این پروژه‌ها را با انجام کارهای زیر دانلود و شروع کنید:

  • دکمه دانلود را فشار دهید
  • محتویات فایل tar را در یک دایرکتوری استخراج کنید
  • سرور توسعه دهنده ای را نصب کنید که بتواند مشخصه های ماژول خالی را حل کند (تیم Lit @web/dev-server را توصیه می کند)
  • سرور توسعه دهنده را اجرا کرده و مرورگر خود را باز کنید (اگر از @web/dev-server استفاده می کنید، می توانید از npx web-dev-server --node-resolve --watch --open استفاده کنید)
    • اگر از نمونه package.json استفاده می کنید از npm run serve استفاده کنید

3. یک عنصر سفارشی را تعریف کنید

عناصر سفارشی

Web Components مجموعه ای از 4 API وب بومی است. آنها عبارتند از:

  • ماژول های ES
  • عناصر سفارشی
  • سایه DOM
  • قالب های HTML

شما قبلاً از مشخصات ماژول‌های ES استفاده کرده‌اید، که به شما امکان می‌دهد ماژول‌های جاوا اسکریپت را با واردات و صادرات ایجاد کنید که با <script type="module"> در صفحه بارگذاری می‌شوند.

تعریف یک عنصر سفارشی

مشخصات عناصر سفارشی به کاربران اجازه می دهد تا عناصر HTML خود را با استفاده از جاوا اسکریپت تعریف کنند. نام ها باید دارای خط فاصله ( - ) باشند تا آنها را از عناصر اصلی مرورگر متمایز کند. فایل index.js را پاک کنید و یک کلاس عنصر سفارشی تعریف کنید:

index.js

class RatingElement extends HTMLElement {}

customElements.define('rating-element', RatingElement);

یک عنصر سفارشی با مرتبط کردن کلاسی که HTMLElement با نام تگ خط فاصله گسترش می‌دهد، تعریف می‌شود. فراخوانی به customElements.define به مرورگر می‌گوید که کلاس RatingElement با تگName 'rating-element' مرتبط کند. این بدان معنی است که هر عنصر در سند شما با نام <rating-element> با این کلاس مرتبط خواهد بود.

یک <rating-element> را در بدنه سند قرار دهید و ببینید چه چیزی رندر می شود.

index.html

<body>
 <rating-element></rating-element>
</body>

اکنون، با نگاه کردن به خروجی، خواهید دید که هیچ چیزی ارائه نشده است. این مورد انتظار است، زیرا شما به مرورگر نگفتید که چگونه <rating-element> را رندر کند. می‌توانید تأیید کنید که تعریف عنصر سفارشی با انتخاب <rating-element> در انتخابگر عنصر Chrome Dev Tools و فراخوانی در کنسول:

$0.constructor

که باید خروجی داشته باشد:

class RatingElement extends HTMLElement {}

چرخه عمر عنصر سفارشی

عناصر سفارشی با مجموعه ای از قلاب های چرخه حیات ارائه می شوند. آنها عبارتند از:

  • constructor
  • connectedCallback
  • disconnectedCallback
  • attributeChangedCallback
  • adoptedCallback

constructor زمانی که عنصر برای اولین بار ایجاد می شود فراخوانی می شود: برای مثال، با فراخوانی document.createElement('rating-element') یا new RatingElement() . سازنده مکان خوبی برای تنظیم عنصر شما است، اما معمولاً انجام دستکاری های DOM در سازنده به دلایل عملکرد "بوت کردن" عنصر، عمل بدی در نظر گرفته می شود.

connectedCallback زمانی فراخوانی می شود که عنصر سفارشی به DOM متصل شود. این معمولاً جایی است که دستکاری های اولیه DOM اتفاق می افتد.

پس از حذف عنصر سفارشی از DOM disconnectedCallback فراخوانی می شود.

attributeChangedCallback(attrName, oldValue, newValue) زمانی فراخوانی می شود که هر یک از ویژگی های مشخص شده توسط کاربر تغییر کند.

هنگامی که عنصر سفارشی از یک documentFragment دیگر از طریق adoptNode در سند اصلی مانند HTMLTemplateElement استفاده می شود، adoptedCallback فراخوانی می شود.

DOM را رندر کنید

اکنون به عنصر سفارشی برگردید و مقداری DOM را با آن مرتبط کنید. وقتی عنصر به DOM متصل شد، محتوای آن را تنظیم کنید:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   this.innerHTML = `
     <style>
       rating-element {
         display: inline-flex;
         align-items: center;
       }
       rating-element button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

در constructor ، یک ویژگی نمونه به نام rating روی عنصر ذخیره می کنید. در connectedCallback ، فرزندان DOM را به <rating-element> اضافه می‌کنید تا رتبه‌بندی فعلی را همراه با دکمه‌های شست بالا و پایین نمایش دهد.

4. Shadow DOM

چرا Shadow DOM؟

در مرحله قبل، متوجه خواهید شد که انتخابگرها در تگ سبکی که درج کرده اید، هر عنصر رتبه بندی را در صفحه و همچنین هر دکمه ای را انتخاب می کنند. این ممکن است منجر به نشت سبک ها از عنصر و انتخاب گره های دیگری شود که ممکن است قصد سبک دادن به آنها را نداشته باشید. علاوه بر این، سایر سبک‌های خارج از این عنصر سفارشی ممکن است ناخواسته به گره‌های داخل عنصر سفارشی شما استایل دهی کنند. به عنوان مثال، سعی کنید یک برچسب سبک در سر سند اصلی قرار دهید:

index.html

<!DOCTYPE html>
<html>
 <head>
   <script src="./index.js" type="module"></script>
   <style>
     span {
       border: 1px solid red;
     }
   </style>
 </head>
 <body>
   <rating-element></rating-element>
 </body>
</html>

خروجی شما باید یک کادر حاشیه قرمز در اطراف دهانه برای رتبه بندی داشته باشد. این یک مورد بی اهمیت است، اما فقدان محصورسازی DOM ممکن است منجر به مشکلات بزرگتر برای برنامه های پیچیده تر شود. اینجاست که Shadow DOM وارد می شود.

چسباندن ریشه سایه

یک Shadow Root را به عنصر متصل کنید و DOM را در داخل آن ریشه رندر کنید:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});

   shadowRoot.innerHTML = `
     <style>
       :host {
         display: inline-flex;
         align-items: center;
       }
       button {
         background: transparent;
         border: none;
         cursor: pointer;
       }
     </style>
     <button class="thumb_down" >
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
     </button>
     <span class="rating">${this.rating}</span>
     <button class="thumb_up">
       <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
     </button>
   `;
 }
}

customElements.define('rating-element', RatingElement);

وقتی صفحه را به‌روزرسانی می‌کنید، متوجه می‌شوید که استایل‌های موجود در سند اصلی دیگر نمی‌توانند گره‌های داخل Shadow Root را انتخاب کنند.

چگونه این کار را انجام دادی؟ در connectedCallback شما this.attachShadow نامیدید.attachShadow که یک ریشه سایه را به یک عنصر متصل می کند. حالت open به این معنی است که محتوای سایه قابل بازرسی است و ریشه سایه از طریق this.shadowRoot نیز قابل دسترسی است. به مؤلفه وب در بازرس کروم نیز نگاهی بیندازید:

درخت dom در بازرس کروم. یک <rating-element> با یک#shadow-root (open) به عنوان فرزند آن و DOM از قبل در داخل آن shadowroot وجود دارد.

اکنون باید یک ریشه سایه قابل گسترش ببینید که محتویات را در خود نگه می دارد. هر چیزی که در آن ریشه سایه قرار دارد Shadow DOM نامیده می شود. اگر بخواهید عنصر رتبه‌بندی را در Chrome Dev Tools انتخاب کنید و با $0.children تماس بگیرید، متوجه می‌شوید که هیچ فرزندی برنمی‌گرداند. این به این دلیل است که Shadow DOM بخشی از همان درخت DOM به عنوان فرزندان مستقیم در نظر گرفته نمی شود، بلکه درخت سایه است.

DOM روشن

یک آزمایش: یک گره به عنوان فرزند مستقیم <rating-element> اضافه کنید:

index.html

<rating-element>
 <div>
   This is the light DOM!
 </div>
</rating-element>

صفحه را بازخوانی کنید، و خواهید دید که این گره DOM جدید در Light DOM این عنصر سفارشی در صفحه نمایش داده نمی شود. این به این دلیل است که Shadow DOM دارای ویژگی هایی برای کنترل نحوه نمایش گره های Light DOM به سایه dom از طریق عناصر <slot> است.

5. قالب های HTML

چرا قالب ها

استفاده از innerHTML و رشته های تحت اللفظی الگو بدون پاکسازی ممکن است باعث ایجاد مشکلات امنیتی در تزریق اسکریپت شود. روش‌ها در گذشته شامل استفاده از DocumentFragment می‌شدند، اما این روش‌ها با مسائل دیگری مانند بارگیری تصاویر و اجرای اسکریپت‌ها هنگام تعریف الگوها و همچنین ایجاد موانع برای قابلیت استفاده مجدد همراه بود. اینجاست که عنصر <template> وارد می شود. قالب‌ها DOM بی‌اثر، روشی بسیار کارآمد برای شبیه‌سازی گره‌ها، و قالب‌های قابل استفاده مجدد را ارائه می‌کنند.

استفاده از قالب ها

در مرحله بعد، مؤلفه را به استفاده از قالب های HTML منتقل کنید:

index.html

<body>
 <template id="rating-element-template">
   <style>
     :host {
       display: inline-flex;
       align-items: center;
     }
     button {
       background: transparent;
       border: none;
       cursor: pointer;
     }
   </style>
   <button class="thumb_down" >
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
   </button>
   <span class="rating"></span>
   <button class="thumb_up">
     <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
   </button>
 </template>

 <rating-element>
   <div>
     This is the light DOM!
   </div>
 </rating-element>
</body>

در اینجا شما محتوای DOM را به یک تگ الگو در DOM سند اصلی منتقل کردید. اکنون تعریف عنصر سفارشی را مجدداً تنظیم کنید:

index.js

class RatingElement extends HTMLElement {
 constructor() {
   super();
   this.rating = 0;
 }
 connectedCallback() {
   const shadowRoot = this.attachShadow({mode: 'open'});
   const templateContent = document.getElementById('rating-element-template').content;
   const clonedContent = templateContent.cloneNode(true);
   shadowRoot.appendChild(clonedContent);

   this.shadowRoot.querySelector('.rating').innerText = this.rating;
 }
}

customElements.define('rating-element', RatingElement);

برای استفاده از این عنصر الگو، الگو را پرس و جو می کنید، محتویات آن را دریافت می کنید و آن گره ها را با templateContent.cloneNode که آرگومان true یک کلون عمیق انجام می دهد، کلون می کنید. سپس dom را با داده ها مقداردهی اولیه می کنید.

تبریک می‌گوییم، شما اکنون یک کامپوننت وب دارید! متأسفانه هنوز هیچ کاری انجام نمی دهد، بنابراین در مرحله بعد، برخی از قابلیت ها را اضافه کنید.

6. افزودن قابلیت

صحافی اموال

در حال حاضر، تنها راه برای تنظیم رتبه بندی بر روی عنصر درجه بندی، ساختن عنصر، تنظیم ویژگی rating روی شی و سپس قرار دادن آن در صفحه است. متأسفانه، عناصر بومی HTML اینگونه عمل نمی کنند. عناصر HTML بومی تمایل به به روز رسانی با تغییرات ویژگی و ویژگی دارند.

وقتی ویژگی rating تغییر می کند، با افزودن خطوط زیر، عنصر سفارشی را به روز کنید:

index.js

constructor() {
  super();
  this._rating = 0;
}

set rating(value) {
  this._rating = value;
  if (!this.shadowRoot) {
    return;
  }

  const ratingEl = this.shadowRoot.querySelector('.rating');
  if (ratingEl) {
    ratingEl.innerText = this._rating;
  }
}

get rating() {
  return this._rating;
}

شما یک تنظیم‌کننده و دریافت‌کننده برای ویژگی رتبه‌بندی اضافه می‌کنید، و سپس متن عنصر رتبه‌بندی را در صورت موجود بودن، به‌روزرسانی می‌کنید. این بدان معناست که اگر بخواهید ویژگی رتبه بندی را روی عنصر تنظیم کنید، view به روز می شود. آن را در کنسول Dev Tools خود آزمایش کنید!

پیوندهای صفت

اکنون، هنگامی که ویژگی تغییر می کند، نمای را به روز کنید. این شبیه به ورودی است که هنگام تنظیم <input value="newValue"> نمای خود را به روز می کند. خوشبختانه، چرخه عمر مؤلفه وب شامل attributeChangedCallback است. رتبه بندی را با افزودن خطوط زیر به روز کنید:

index.js

static get observedAttributes() {
 return ['rating'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
 if (attributeName === 'rating') {
   const newRating = Number(newValue);
   this.rating = newRating;
 }
}

برای اینکه attributeChangedCallback فعال شود، باید یک گیرنده ثابت برای RatingElement.observedAttributes which defines the attributes to be observed for changes . سپس رتبه بندی را به صورت اعلامی در DOM تنظیم می کنید. آن را امتحان کنید:

index.html

<rating-element rating="5"></rating-element>

رتبه بندی اکنون باید به صورت اعلامی به روز شود!

عملکرد دکمه

اکنون تنها چیزی که از دست رفته عملکرد دکمه است. رفتار این مؤلفه باید به کاربر این امکان را بدهد که یک امتیاز رأی بالا یا پایین ارائه کند و بازخورد بصری به کاربر بدهد. می‌توانید این مورد را با برخی شنوندگان رویداد و یک ویژگی بازتابی پیاده‌سازی کنید، اما ابتدا سبک‌ها را برای ارائه بازخورد بصری با پیوست کردن خطوط زیر به‌روزرسانی کنید:

index.html

<style>
...

 :host([vote=up]) .thumb_up {
   fill: green;
 }
  :host([vote=down]) .thumb_down {
   fill: red;
 }
</style>

در Shadow DOM، انتخابگر :host به گره یا عنصر سفارشی اشاره دارد که Shadow Root به آن متصل است. در این حالت، اگر ویژگی vote "up" باشد، دکمه شست به بالا سبز می‌شود، اما اگر vote "down", then it will turn the thumb-down button red . اکنون، منطق این مورد را با ایجاد یک ویژگی/ویژگی بازتاب دهنده برای vote ، مشابه نحوه اجرای rating پیاده سازی کنید. با تنظیم کننده و گیرنده ویژگی شروع کنید:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }
  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }
  this._vote = newValue;
  this.setAttribute('vote', newValue);
}

get vote() {
  return this._vote;
}

شما ویژگی نمونه _vote با null در constructor مقداردهی اولیه می کنید و در تنظیم کننده بررسی می کنید که آیا مقدار جدید متفاوت است. اگر چنین است، رتبه بندی را بر اساس آن تنظیم می کنید و مهمتر از همه، ویژگی vote را با this.setAttribute به میزبان بازتاب می دهید.

سپس، ویژگی binding را تنظیم کنید:

index.js

static get observedAttributes() {
  return ['rating', 'vote'];
}

attributeChangedCallback(attributeName, oldValue, newValue) {
  if (attributeName === 'rating') {
    const newRating = Number(newValue);

    this.rating = newRating;
  } else if (attributeName === 'vote') {
    this.vote = newValue;
  }
}

باز هم، این همان فرآیندی است که با اتصال ویژگی rating انجام دادید. شما vote به observedAttributes اضافه می کنید و ویژگی vote در attributeChangedCallback تنظیم می کنید. و در نهایت، تعدادی شنونده رویداد کلیک اضافه کنید تا عملکرد دکمه ها را ارائه دهید!

index.js

constructor() {
 super();
 this._rating = 0;
 this._vote = null;
 this._boundOnUpClick = this._onUpClick.bind(this);
 this._boundOnDownClick = this._onDownClick.bind(this);
}

connectedCallback() {
  ...
  this.shadowRoot.querySelector('.thumb_up')
    .addEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .addEventListener('click', this._boundOnDownClick);
}

disconnectedCallback() {
  this.shadowRoot.querySelector('.thumb_up')
    .removeEventListener('click', this._boundOnUpClick);
  this.shadowRoot.querySelector('.thumb_down')
    .removeEventListener('click', this._boundOnDownClick);
}

_onUpClick() {
  this.vote = 'up';
}

_onDownClick() {
  this.vote = 'down';
}

در constructor شما برخی از شنوندگان کلیک را به عنصر متصل می کنید و مراجع را در اطراف نگه می دارید. در connectedCallback به رویدادهای کلیک روی دکمه ها گوش می دهید. در disconnectedCallback شما این شنوندگان را پاک می‌کنید و روی خود شنونده‌های کلیکی، vote مناسب را تنظیم می‌کنید.

تبریک می‌گوییم، شما اکنون یک کامپوننت وب با امکانات کامل دارید. سعی کنید روی چند دکمه کلیک کنید! اکنون مسئله این است که فایل JS من اکنون به 96 خط رسیده است، فایل HTML من به 43 خط رسیده است و کد برای چنین کامپوننت ساده ای کاملاً پیچیده و ضروری است. اینجاست که پروژه لایت گوگل وارد می شود!

7. Lit-html

ایست بازرسی کد

چرا lit-html

اول از همه، تگ <template> مفید و کارآمد است، اما با منطق کامپوننت بسته بندی نشده است، بنابراین توزیع الگو با بقیه منطق دشوار می شود. علاوه بر این، نحوه استفاده از عناصر الگو به طور ذاتی به کدهای ضروری کمک می کند، که در بسیاری از موارد، در مقایسه با الگوهای کدگذاری اعلانی، منجر به کد خوانایی کمتری می شود.

اینجاست که lit-html وارد می شود! Lit html سیستم رندر Lit است که به شما امکان می‌دهد قالب‌های HTML را در جاوا اسکریپت بنویسید، سپس آن قالب‌ها را همراه با داده‌ها برای ایجاد و به‌روزرسانی DOM به طور موثر رندر و دوباره رندر کنید. این شبیه به کتابخانه های محبوب JSX و VDOM است اما به صورت بومی در مرورگر اجرا می شود و در بسیاری از موارد بسیار کارآمدتر است.

استفاده از Lit HTML

سپس، rating-element برای استفاده از الگوی Lit که از Tagged Template Literals استفاده می‌کند، منتقل کنید، که توابعی هستند که رشته‌های الگو را به‌عنوان آرگومان‌هایی با یک نحو خاص می‌گیرند. سپس Lit از عناصر قالب در زیر هود برای ارائه رندر سریع و همچنین ارائه برخی ویژگی‌های ضدعفونی برای امنیت استفاده می‌کند. با انتقال <template> در index.html به یک الگوی Lit با افزودن یک متد render() به وبکامپوننت شروع کنید:

index.js

// Dont forget to import from Lit!
import {render, html} from 'lit';

class RatingElement extends HTMLElement {
  ...
  render() {
    if (!this.shadowRoot) {
      return;
    }

    const template = html`
      <style>
        :host {
          display: inline-flex;
          align-items: center;
        }
        button {
          background: transparent;
          border: none;
          cursor: pointer;
        }

       :host([vote=up]) .thumb_up {
         fill: green;
       }

       :host([vote=down]) .thumb_down {
         fill: red;
       }
      </style>
      <button class="thumb_down">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
      </button>
      <span class="rating">${this.rating}</span>
      <button class="thumb_up">
        <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
      </button>`;

    render(template, this.shadowRoot);
  }
}

همچنین می توانید الگوی خود را از index.html حذف کنید. در این روش رندر شما متغیری به نام template تعریف می‌کنید و تابع تحت اللفظی قالب برچسب‌گذاری شده html را فراخوانی می‌کنید. همچنین متوجه خواهید شد که با استفاده از نحو درونیابی تحت اللفظی الگوی ${...} یک اتصال ساده داده در عنصر span.rating انجام داده اید. این بدان معنی است که در نهایت دیگر نیازی به به روز رسانی اجباری آن گره نخواهید داشت. علاوه بر این، شما روش render روشن را فراخوانی می کنید که به طور همزمان الگو را در ریشه سایه رندر می کند.

مهاجرت به نحو اعلانی

اکنون که از شر عنصر <template> خلاص شده‌اید، کد را تغییر شکل دهید تا متد render جدید را فراخوانی کنید. برای پاک کردن کد شنونده، می‌توانید با استفاده از اتصال شنونده رویداد lit شروع کنید:

index.js

<button
    class="thumb_down"
    @click=${() => {this.vote = 'down'}}>
...
<button
    class="thumb_up"
    @click=${() => {this.vote = 'up'}}>

الگوهای روشن می‌توانند شنونده رویداد را به گره‌ای با نحو الزام آور @EVENT_NAME اضافه کنند که در این حالت، هر بار که این دکمه‌ها کلیک می‌شوند، ویژگی vote را به‌روزرسانی می‌کنید.

سپس، کد اولیه شنونده رویداد را در constructor و connectedCallback و disconnectedCallback پاک کنید:

index.js

constructor() {
  super();
  this._rating = 0;
  this._vote = null;
}

connectedCallback() {
  this.attachShadow({mode: 'open'});
  this.render();
}

// remove disonnectedCallback and _onUpClick and _onDownClick

شما توانستید منطق کلیک شنونده را از هر سه تماس بک حذف کنید و حتی disconnectedCallback به طور کامل حذف کنید! شما همچنین توانستید تمام کدهای اولیه DOM را از connectedCallback حذف کنید و بسیار زیباتر به نظر برسد. این همچنین به این معنی است که می توانید از شر روش های شنونده _onUpClick و _onDownClick خلاص شوید!

در نهایت، تنظیم‌کننده‌های ویژگی را برای استفاده از روش render به‌روزرسانی کنید تا زمانی که ویژگی‌ها یا ویژگی‌ها تغییر می‌کنند، dom بتواند به‌روزرسانی شود:

index.js

set rating(value) {
  this._rating = value;
  this.render();
}

...

set vote(newValue) {
  const oldValue = this._vote;
  if (newValue === oldValue) {
    return;
  }

  if (newValue === 'up') {
    if (oldValue === 'down') {
      this.rating += 2;
    } else {
      this.rating += 1;
    }
  } else if (newValue === 'down') {
    if (oldValue === 'up') {
      this.rating -= 2;
    } else {
      this.rating -= 1;
    }
  }

  this._vote = newValue;
  this.setAttribute('vote', newValue);
  // add render method
  this.render();
}

در اینجا، شما توانستید منطق به‌روزرسانی dom را از تنظیم‌کننده rating حذف کنید و یک تماس برای render از تنظیم‌کننده vote اضافه کردید. اکنون الگو بسیار خواناتر است زیرا اکنون می‌توانید ببینید که در کجا اتصال‌ها و شنوندگان رویداد اعمال می‌شوند.

صفحه را بازخوانی کنید، و باید یک دکمه رتبه‌بندی عملکردی داشته باشید که وقتی رأی موافق فشار داده می‌شود، شبیه این باشد!

لغزنده رتبه بندی انگشت شست بالا و پایین با مقدار 6 و انگشت شست بالا سبز رنگ

8. LitElement

چرا LitElement

برخی از مشکلات هنوز در کد وجود دارد. ابتدا، اگر ویژگی یا ویژگی vote را تغییر دهید، ممکن است ویژگی rating را تغییر دهد که منجر به دو بار فراخوانی render می شود. علی‌رغم اینکه تماس‌های تکراری رندر اساساً بدون عملیات و کارآمد هستند، جاوا اسکریپت VM هنوز هم برای فراخوانی آن تابع دو بار به صورت همزمان زمان می‌گذراند. دوم، افزودن ویژگی‌ها و ویژگی‌های جدید خسته‌کننده است، زیرا به کدهای دیگ بخار زیادی نیاز دارد. اینجاست که LitElement وارد می شود!

LitElement کلاس پایه Lit برای ایجاد کامپوننت های وب سریع و سبک است که می توانند در فریمورک ها و محیط ها استفاده شوند. در مرحله بعد، نگاهی به آنچه LitElement می تواند برای ما در rating-element با تغییر پیاده سازی برای استفاده از آن انجام دهد، بیندازید!

با استفاده از LitElement

با وارد کردن و زیر کلاس بندی کلاس پایه LitElement از بسته lit شروع کنید:

index.js

import {LitElement, html, css} from 'lit';

class RatingElement extends LitElement {
// remove connectedCallback()
...

شما LitElement را وارد می کنید که کلاس پایه جدید برای rating-element است. سپس وارد کردن html و در نهایت css خود را حفظ می‌کنید که به ما امکان می‌دهد واژه‌های قالب برچسب‌گذاری شده با css را برای ریاضیات css، قالب‌بندی و سایر ویژگی‌ها در زیر هود تعریف کنیم.

سپس، استایل ها را از روش رندر به شیوه نامه استاتیک Lit منتقل کنید:

index.js

class RatingElement extends LitElement {
  static get styles() {
    return css`
      :host {
        display: inline-flex;
        align-items: center;
      }
      button {
        background: transparent;
        border: none;
        cursor: pointer;
      }

      :host([vote=up]) .thumb_up {
        fill: green;
      }

      :host([vote=down]) .thumb_down {
        fill: red;
      }
    `;
  }
 ...

این جایی است که اکثر سبک ها در Lit زندگی می کنند. Lit از این سبک‌ها استفاده می‌کند و از ویژگی‌های مرورگر مانند Constructable Stylesheets برای ارائه زمان‌های رندر سریع‌تر و همچنین در صورت لزوم از طریق Web Components polyfill در مرورگرهای قدیمی‌تر استفاده می‌کند.

چرخه زندگی

Lit مجموعه‌ای از روش‌های بازگشت به تماس چرخه حیات رندر را در بالای فراخوان‌های اصلی Web Component معرفی می‌کند. این تماس‌های برگشتی زمانی فعال می‌شوند که ویژگی‌های Lit اعلام شده تغییر می‌کنند.

برای استفاده از این ویژگی، باید به صورت ایستا مشخص کنید که کدام ویژگی چرخه حیات رندر را راه اندازی می کند.

index.js

static get properties() {
  return {
    rating: {
      type: Number,
    },
    vote: {
      type: String,
      reflect: true,
    }
  };
}

// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()

در اینجا، شما تعریف می‌کنید که rating و vote چرخه حیات رندر LitElement و همچنین تعریف انواعی که برای تبدیل ویژگی‌های رشته به ویژگی‌ها استفاده می‌شوند، راه‌اندازی می‌کند.

<user-profile .name=${this.user.name} .age=${this.user.age}>
  ${this.user.family.map(member => html`
        <family-member
             .name=${member.name}
             .relation=${member.relation}>
        </family-member>`)}
</user-profile>

علاوه بر این، پرچم reflect در ویژگی vote به طور خودکار ویژگی vote عنصر میزبان را که به صورت دستی در تنظیم کننده vote فعال کرده اید، به روز می کند.

اکنون که بلوک ویژگی های ثابت را دارید، می توانید تمام منطق به روز رسانی رندر ویژگی و ویژگی را حذف کنید. این بدان معنی است که می توانید روش های زیر را حذف کنید:

  • connectedCallback
  • observedAttributes
  • attributeChangedCallback
  • rating (تنظیم کننده ها و دریافت کنندگان)
  • vote (تنظیم کننده ها و دریافت کننده ها اما منطق تغییر را از تنظیم کننده حفظ کنید)

چیزی که نگه می دارید constructor و همچنین اضافه کردن یک روش چرخه حیات جدید willUpdate است:

index.js

constructor() {
  super();
  this.rating = 0;
  this.vote = null;
}

willUpdate(changedProps) {
  if (changedProps.has('vote')) {
    const newValue = this.vote;
    const oldValue = changedProps.get('vote');

    if (newValue === 'up') {
      if (oldValue === 'down') {
        this.rating += 2;
      } else {
        this.rating += 1;
      }
    } else if (newValue === 'down') {
      if (oldValue === 'up') {
        this.rating -= 2;
      } else {
        this.rating -= 1;
      }
    }
  }
}

// remove set vote() and get vote()

در اینجا، شما به سادگی rating و vote را مقداردهی اولیه می‌کنید و منطق تنظیم‌کننده vote را به روش چرخه حیات willUpdate منتقل می‌کنید. متد willUpdate قبل از render می‌شود، هرگاه ویژگی به‌روزرسانی تغییر کند، زیرا LitElement تغییرات ویژگی را دسته‌بندی می‌کند و رندر را ناهمزمان می‌کند. تغییرات در ویژگی‌های واکنشی (مانند this.rating ) در willUpdate باعث ایجاد تماس‌های چرخه حیات render غیرضروری نمی‌شود.

در نهایت، render یک روش چرخه حیات LitElement است که از ما می‌خواهد یک الگوی Lit را برگردانیم :

index.js

render() {
  return html`
    <button
        class="thumb_down"
        @click=${() => {this.vote = 'down'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
    </button>
    <span class="rating">${this.rating}</span>
    <button
        class="thumb_up"
        @click=${() => {this.vote = 'up'}}>
      <svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
    </button>`;
}

دیگر لازم نیست ریشه سایه را بررسی کنید، و دیگر لازم نیست تابع render که قبلاً از بسته 'lit' وارد شده است را فراخوانی کنید.

عنصر شما باید اکنون در پیش نمایش ارائه شود. آن را یک کلیک کنید!

9. تبریک می گویم

تبریک می گوییم، شما با موفقیت یک کامپوننت وب را از ابتدا ساخته اید و آن را به یک LitElement تبدیل کرده اید!

Lit بسیار کوچک است (< 5 کیلوبایت کوچک شده + gzipped)، فوق العاده سریع، و کدنویسی با آن واقعا سرگرم کننده است! می توانید کامپوننت هایی را بسازید که توسط فریمورک های دیگر مصرف شوند یا می توانید با آن برنامه های کامل بسازید!

اکنون می‌دانید که Web Component چیست، چگونه می‌توان آن را ساخت و چگونه Lit ساخت آنها را آسان‌تر می‌کند!

ایست بازرسی کد

آیا می خواهید کد نهایی خود را با کد ما بررسی کنید؟ اینجا را مقایسه کنید.

بعدش چی؟

برخی از کدهای دیگر را بررسی کنید!

در ادامه مطلب

جامعه