روال‌های مطلوب عنصر سفارشی

عناصر سفارشی به شما امکان می‌دهند تگ‌های HTML خودتان را بسازید. این بازبینه شامل روال‌های مطلوب برای کمک به شما در ساختن عناصر با کیفیت بالا است.

عناصر سفارشی به شما امکان می‌دهند HTML را گسترش دهید و برچسب‌های خودتان را تعریف کنید. آن‌ها ویژگی بسیار قدرتمندی هستند، اما در سطح پایین نیز قرار دارند، به این معنی که همیشه مشخص نیست چگونه بهترین روش برای پیاده‌سازی عنصر خود را انتخاب کنید.

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

بازبینه

DOM سایه

برای کپسوله‌سازی سبک‌ها، ریشه سایه ایجاد کنید.

چرا؟ کپسوله‌سازی سبک‌ها در ریشه سایه عنصرتان تضمین می‌کند که بدون توجه به جایی که استفاده می‌شود، کار خواهد کرد. این امر به‌ویژه زمانی مهم است که توسعه‌دهنده بخواهد عنصر شما را در ریشه سایه عنصر دیگری قرار دهد. این موضوع حتی برای عناصر ساده‌ای مثل چارگوش انتخاب یا دکمه رادیویی نیز صدق می‌کند. It might be the case that the only content inside of your shadow root will be the styles themselves.
نمونه عنصر <howto-checkbox>.

ریشه سایه را در سازنده ایجاد کنید.

چرا؟ سازنده زمانی است که شما دانش انحصاری از عنصر خود دارید. اکنون زمان خوبی برای راه‌اندازی جزئیات پیاده‌سازی است که نمی‌خواهید عناصر دیگر با آن تداخل داشته باشند. انجام این کار در یک فراخوانی بعدی، مانند connectedCallback، به این معنی است که باید در برابر موقعیت‌هایی که عنصر شما جدا شده و سپس دوباره به سند متصل می‌شود، محافظت کنید.
نمونه عنصر <howto-checkbox>.

هر فرزندی را که عنصر ایجاد می‌کند در ریشه سایه آن قرار دهید.

چرا؟ فرزندان ایجادشده توسط عنصر شما بخشی از پیاده‌سازی آن هستند و باید خصوصی باشند. بدون محافظت ریشه سایه، جاوا اسکریپت خارجی ممکن است به‌طور ناخواسته با این فرزندان تداخل داشته باشد.
نمونه عنصر <howto-tabs>.

از <slot> برای نمایش فرزندان DOM سبک در DOM سایه استفاده کنید

چرا؟ اجازه دادن به کاربران مؤلفه شما برای مشخص کردن محتوا در مؤلفه شما به‌عنوان فرزندان HTML، مؤلفه شما را ترکیب‌پذیرتر می‌کند. وقتی مرورگری از عناصر سفارشی پشتیبانی نمی‌کند، محتوای تودرتو دردسترس، قابل‌مشاهده، و قابل‌دسترسی باقی می‌ماند.
نمونه عنصر <howto-tabs>.

سبک نمایش :host را تنظیم کنید (مثلاً block، inline-block، flex) مگراینکه پیش‌فرض inline را ترجیح دهید.

چرا؟ عناصر سفارشی به‌طور پیش‌فرض display: inline هستند، بنابراین تنظیم width یا height آن‌ها بی‌تأثیر خواهد بود. این موضوع اغلب برای توسعه‌دهندگان غافلگیرکننده است و ممکن است مشکلاتی دررابطه‌با طرح‌بندی صفحه ایجاد کند. مگراینکه نمایش inline را ترجیح دهید، همیشه باید مقدار پیش‌فرض display را تنظیم کنید.
نمونه عنصر <howto-checkbox>.

سبک نمایش :host را اضافه کنید که به مشخصه پنهان احترام می‌گذارد.

چرا؟ عنصر سفارشی با سبک پیش‌فرض display، مثلاً :host { display: block }، ویژگی hidden توکار با ویژگی پایین‌تر را لغو می‌کند. اگر انتظار دارید تنظیم کردن مشخصه hidden در عنصرتان باعث شود display: none شود، ممکن است این موضوع شما را شگفت‌زده کند. علاوه‌بر سبک پیش‌فرض display، پشتیبانی از hidden با :host([hidden]) { display: none } را اضافه کنید.
نمونه عنصر <howto-checkbox>.

مشخصه‌ها و خصوصیت‌ها

مشخصه‌های سراسری تنظیم‌شده توسط نویسنده را لغو نکنید.

چرا؟ مشخصه‌های سراسری مشخصه‌هایی هستند که در همه عناصر HTML وجود دارند. چند نمونه عبارت‌اند از tabindex و role. یک عنصر سفارشی ممکن است بخواهد tabindex اولیه خود را روی 0 تنظیم کند تا بتواند با صفحه‌کلید قابل‌تمرکز باشد. اما همیشه باید ابتدا بررسی کنید که آیا توسعه‌دهنده‌ای که از عنصر شما استفاده می‌کند، این مقدار را به مقدار دیگری تنظیم کرده است یا خیر. برای مثال، اگر tabindex را روی -1 تنظیم کرده باشند، این نشانه‌ای است که آن‌ها نمی‌خواهند عنصر تعاملی باشد.
نمونه عنصر <howto-checkbox>. این موضوع در نویسنده صفحه را لغو نکنید بیشتر توضیح داده شده است.

همیشه داده‌های اولیه (رشته‌ها، اعداد، بولی‌ها) را به‌عنوان مشخصه یا ویژگی بپذیرید.

چرا؟ عناصر سفارشی، مانند همتایان داخلی خود، باید قابل پیکربندی باشند. پیکربندی را می‌توان به‌صورت اعلانی، ازطریق مشخصه‌ها، یا به‌صورت امری ازطریق ویژگی‌های جاوا اسکریپت منتقل کرد. در حالت ایده‌آل، هر مشخصه باید به یک ویژگی متناظر نیز پیوند داده شود.
نمونه عنصر <howto-checkbox>.

هدف این است که مشخصه‌ها و ویژگی‌های داده‌های اولیه را همگام نگه دارید و از ویژگی به مشخصه و برعکس منعکس کنید.

چرا؟ هرگز نمی‌دانید کاربر چگونه با عنصر شما تعامل خواهد داشت. ممکن است دارایی‌ای را در جاوا اسکریپت تنظیم کنند و سپس انتظار داشته باشند که آن مقدار را بااستفاده از میانای برنامه‌سازی کاربردی‌ای مثل getAttribute() بخوانند. اگر هر مشخصه یک ویژگی متناظر داشته باشد و هر دو منعکس شوند، کار با عنصر شما برای کاربران آسان‌تر خواهد بود. به عبارت دیگر، فراخوانی setAttribute('foo', value) باید ویژگی foo متناظر را نیز تنظیم کند و برعکس. البته استثناهایی برای این قانون وجود دارد. نباید ویژگی‌های با فرکانس بالا را منعکس کنید، مثلاً currentTime در پخش‌کننده ویدیو. با بهترین تشخیص خود عمل کنید. اگر به‌نظر می‌رسد کاربر با ویژگی یا مشخصه‌ای تعامل خواهد داشت و منعکس کردن آن دشوار نیست، این کار را انجام دهید.
نمونه عنصر <howto-checkbox>. این موضوع در اجتناب از مشکلات ورود مجدد بیشتر توضیح داده شده است.

هدف این است که فقط داده‌های غنی (اشیا، آرایه‌ها) به‌عنوان ویژگی پذیرفته شوند.

چرا؟ به‌طور کلی، هیچ نمونه‌ای از عناصر اچ‌تی‌ام‌ال داخلی وجود ندارد که داده‌های غنی (آرایه‌ها و اشیاء جاوا اسکریپت ساده) را ازطریق ویژگی‌هایشان بپذیرند. داده‌های غنی درعوض ازطریق فراخوانی روش یا ویژگی‌ها پذیرفته می‌شود. پذیرفتن داده‌های غنی به‌عنوان مشخصه چند عیب آشکار دارد: سریالی کردن شیء بزرگ به رشته می‌تواند پرهزینه باشد و هرگونه ارجاع شیء در این فرایند رشته‌سازی ازبین خواهد رفت. برای مثال، اگر شیئی را که به شیء دیگری ارجاع می‌دهد، یا شاید یک گره DOM را رشته‌ای کنید، آن ارجاع‌ها ازبین خواهند رفت.

ویژگی‌های داده‌های غنی را به مشخصه‌ها منعکس نکنید.

چرا؟ انعکاس ویژگی‌های داده‌های غنی به مشخصه‌ها بی‌دلیل پرهزینه است، زیرا نیاز به سریال‌سازی و سریال‌زدایی همان اشیاء جاوا اسکریپت دارد. مگر اینکه مورد استفاده‌ای داشته باشید که فقط با این ویژگی قابل‌حل باشد، احتمالاً بهتر است از آن اجتناب کنید.

بررسی کنید که آیا ویژگی‌هایی وجود دارد که ممکن است قبل‌از ارتقای عنصر تنظیم شده باشد.

چرا؟ توسعه‌دهنده‌ای که از عنصر شما استفاده می‌کند ممکن است قبل‌از بار شدن تعریف عنصر، تلاش کند ویژگی‌ای را برای آن تنظیم کند. این امر به‌ویژه زمانی صادق است که توسعه‌دهنده از چارچوبی استفاده می‌کند که بارگیری مؤلفه‌ها، مهر زدن آن‌ها به صفحه، و اتصال ویژگی‌های آن‌ها به یک مدل را مدیریت می‌کند.
نمونه عنصر <howto-checkbox>. توضیحات بیشتر در تنبل کردن ویژگی‌ها.

کلاس‌ها را خودتان اعمال نکنید.

چرا؟ عناصری که باید وضعیت خود را بیان کنند باید این کار را بااستفاده از مشخصه‌ها انجام دهند. مشخصه class معمولاً متعلق به توسعه‌دهنده‌ای درنظر گرفته می‌شود که از عنصر شما استفاده می‌کند، و نوشتن در آن توسط خودتان ممکن است به‌طور ناخواسته کلاس‌های توسعه‌دهنده را خراب کند.

رویدادها

رویدادها را در پاسخ به فعالیت مؤلفه داخلی ارسال کنید.

چرا؟ مؤلفه شما ممکن است ویژگی‌هایی داشته باشد که در پاسخ به فعالیتی که فقط مؤلفه شما از آن مطلع است تغییر کند، برای مثال، اگر زمان‌سنج یا پویانمایی تکمیل شود، یا بارگیری منبعی به‌پایان برسد. ارسال رویدادها در پاسخ به این تغییرات برای مطلع کردن میزبان از متفاوت بودن وضعیت مؤلفه مفید است.

Do not dispatch events in response to the host setting a property (downward data flow).

چرا؟ ارسال رویداد در پاسخ به تنظیم دارایی توسط میزبان زائد است (میزبان وضعیت فعلی را می‌داند چون به‌تازگی آن را تنظیم کرده است). ارسال رویدادها در پاسخ به تنظیم ویژگی توسط میزبان ممکن است باعث ایجاد حلقه‌های بی‌نهایت با سیستم‌های اتصال داده شود.
نمونه عنصر <howto-checkbox>.

توضیح‌دهنده

نویسنده صفحه را لغو نکنید

ممکن است توسعه‌دهنده‌ای که از عنصر شما استفاده می‌کند بخواهد برخی‌از حالت‌های اولیه آن را لغو کند. برای مثال، تغییر دادن ARIA role یا قابلیت تمرکز آن با tabindex. پیش‌از اعمال مقادیر خودتان، بررسی کنید که آیا این مشخصه‌ها و مشخصه‌های جهانی دیگر تنظیم شده‌اند یا نه.

connectedCallback() {
  if (!this.hasAttribute('role'))
    this.setAttribute('role', 'checkbox');
  if (!this.hasAttribute('tabindex'))
    this.setAttribute('tabindex', 0);

تنبل کردن ویژگی‌ها

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

در مثال زیر، Angular به‌صورت اعلانی ویژگی isChecked مدل خود را به ویژگی checked چارگوش انتخاب متصل می‌کند. اگر تعریف howto-checkbox بارگذاری کُند شده باشد، ممکن است Angular تلاش کند ویژگی checked را قبل‌از ارتقای عنصر تنظیم کند.

<howto-checkbox [checked]="defaults.isChecked"></howto-checkbox>

عنصر سفارشی باید این سناریو را با بررسی اینکه آیا ویژگی‌هایی قبلاً در نمونه آن تنظیم شده است یا نه، مدیریت کند. <howto-checkbox> این الگو را بااستفاده از روشی به‌نام _upgradeProperty() نشان می‌دهد.

connectedCallback() {
  ...
  this._upgradeProperty('checked');
}

_upgradeProperty(prop) {
  if (this.hasOwnProperty(prop)) {
    let value = this[prop];
    delete this[prop];
    this[prop] = value;
  }
}

_upgradeProperty() مقدار را از نمونه ارتقانیافته ضبط می‌کند و ویژگی را حذف می‌کند تا بر تنظیم‌کننده ویژگی خود عنصر سفارشی سایه نیندازد. به‌این‌ترتیب، وقتی تعریف عنصر درنهایت بار می‌شود، می‌تواند فوراً وضعیت صحیح را منعکس کند.

اجتناب از مشکلات ورود مجدد

وسوسه‌انگیز است که از attributeChangedCallback() برای منعکس کردن وضعیت به یک ویژگی اساسی استفاده کنید، برای مثال:

// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'checked')
    this.checked = newValue;
}

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

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    // OOPS! This will cause an infinite loop because it triggers the
    // attributeChangedCallback() which then sets this property again.
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

یک جایگزین این است که به تنظیم‌کننده ویژگی اجازه دهید تا به مشخصه منعکس شود، و دریافت‌کننده مقدار آن را براساس مشخصه تعیین کند.

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

get checked() {
  return this.hasAttribute('checked');
}

در این مثال، افزودن یا برداشتن مشخصه باعث تنظیم ویژگی نیز می‌شود.

درنهایت، می‌توان از attributeChangedCallback() برای مدیریت عوارض جانبی مانند اعمال حالت‌های ARIA استفاده کرد.

attributeChangedCallback(name, oldValue, newValue) {
  const hasValue = newValue !== null;
  switch (name) {
    case 'checked':
      // Note the attributeChangedCallback is only handling the *side effects*
      // of setting the attribute.
      this.setAttribute('aria-checked', hasValue);
      break;
    ...
  }
}