تا این قسمت از کار به خوبی جلو آمده ایم و فعلا یکی از باگ های اصلی ما این است که اگر پروژه ای را اضافه کنیم، به هر دو مجموعه لیست Active Projects و Finished Projects اضافه می شود. از نظر من بهترین قسمت برای فیلتر کردن پروژه ها همان تابع listener ما است. یعنی در متد زیر در کلاس projectList:
projectState.addListener((projects: Project[]) => { this.assignedProjects = projects; this.renderProjects(); });
به همین دلیل می گویم:
projectState.addListener((projects: Project[]) => { const relevantProjects = projects.filter(prj => { if (this.type === 'active') { return prj.status === ProjectStatus.Active; } return prj.status === ProjectStatus.Finished; }); this.assignedProjects = relevantProjects; this.renderProjects(); });
همانطور که می دانید تابع filter یک تابع را به عنوان پارامتر ورودی می گیرد و آن را روی تک تک اعضای یک آرایه پیاده می کند. بنابراین با استفاده از همین متد می توانیم تمام پروژه هایمان را به دو حالت finished و active فیلتر و جداسازی کنیم. برنامه این است که اگر تابع ما true برگرداند آن عضو آرایه را نگه داریم و در غیر این صورت آن عضو را دور می اندازیم (نه از لیست اصلی، بلکه از لیست جدیدی که می خواهیم بسازیم و درون relevantProjects قرار دهیم). بنابراین تابع بالا هر عضو را چک می کند که وضعیت active دارد یا خیر. اگر داشت، نتیجه ی مقایسه ی زیر را برمی گردانیم:
return prj.status === ProjectStatus.Active;
یعنی اگر status پروژه برابر active باشد (آن را از enum گرفته ایم) مقدار true را برمی گردانیم. در غیر این صورت این شرط را بررسی می کنیم:
return prj.status === ProjectStatus.Finished;
سپس پروژه های جدیدی را که درون relevantProjects ذخیره کرده ایم، درون assignedProjects قرار داده و نهایتا renderProjects را صدا می زنیم. اگر به مرورگر برویم و کدها را تست کنیم، بار اول هیچ مشکلی نداریم اما در دفعات دیگر باز هم همان باگ تکرار شده و پروژه ها دو بار دو بار اضافه می شوند. چرا؟ مشکل در تابع renderProjects است. درون این متد همیشه پروژه ها را به لیست خودمان append می کنیم:
listEl.appendChild(listItem);
مشکل اینجاست که شاید پروژه ای را از قبل داشته باشیم که برای کاربر نمایش داده شده است و اگر پروژه ی جدید را append کنیم باعث ایجاد پروژه های تکراری می شویم. بهترین روش حل این مشکل استفاده از انواع چک های if و غیره است تا ببینید چه پروژه هایی از قبل وجود دارند و از دوباره سازی آن ها جلوگیری کنید. عیب این روش این است که باید با آن DOM را چک کنیم تا ببینیم چه پروژه هایی نمایش داده شده اند و دسترسی به DOM کُند می باشد. به همین دلیل برای برنامه ی کوچکی مثل برنامه ی ما می توانیم تمام لیست های موجود را خالی کنیم و دوباره مقدار مورد نظر را در آن قرار دهیم:
private renderProjects() { const listEl = document.getElementById( `${this.type}-projects-list` )! as HTMLUListElement; listEl.innerHTML = ''; for (const prjItem of this.assignedProjects) { const listItem = document.createElement('li'); listItem.textContent = prjItem.title; listEl.appendChild(listItem); } }
با خالی کردن innerHTML می توانیم به راحتی از این پروژه استفاده کنیم. حالا مشکل ما حل می شود و پروژه های ما غیرتکراری خواهند بود. اگر به کدهای پروژه نگاه کنید متوجه می شوید که پروژه ی ما کدهای تکراری زیادی دارد و با استفاده از ارث بری (inheritance) می توانیم از این مسئله جلوگیری کنیم. همچنین می خواهیم به پروژه ی خودمان قابلیت drag & drop را هم اضافه کنیم و برخی از بهینه سازی های دیگر را نیز انجام بدهیم.
من قسمتی از کار inheritance را در این جلسه انجام می دهم و در قسمت های بعد آن را تکمیل و دیگر بهینه سازی ها را نیز انجام می دهیم. من کلاس پدر اصلی را بالای کلاس ProjectList تعریف می کنم تا برخی از خصوصیات و متدهای مشترک را در خود داشته باشد و از تکراری شدن کدهای ما جلوگیری کند:
class Component { templateElement: HTMLTemplateElement; hostElement: HTMLDivElement; element: HTMLElement; }
در ابتدا سه خصوصیت مشترک بین اکثر کلاس هایمان را درون آن تعریف کرده ام: templateElement و hostElement و element. البته این کار باعث ایجاد مشکلی می شود. خصوصیت templateElement در تمام کلاس ها از نوع HTMLTemplateElement خواهد بود بنابراین مشکلی از این بابت نداریم اما hostElement قرار نیست همیشه Div باشد بلکه به این بستگی دارد که درون کدام کلاس استفاده شود. مثلا زمانی که پروژه ها را در مرورگر نمایش می دهیم ممکن است آن ها را درون یک لیست نمایش بدهیم نه درون div. برای خصوصیت سوم یا element نیز می توان گفت که همیشه HTMLElement خواهد بود اما این تعریف بسیار کلی است. مثلا در کلاس PorjectInput آن را از نوع form تعریف کرده بودیم:
element: HTMLFormElement;
بنابراین اگر فقط به تایپ کلی HTMLElement بسنده کنیم، بسیاری از خصوصیات تایپ اسکریپت را از دست می دهیم. راه حل ما استفاده از generic type ها است تا هنگام ارث بری خودمان تایپ های دقیق تر را مشخص کنیم. بنابراین می گویم:
class Component <T, U> {
در این قدم (همانطور که قبلا یاد گرفته ایم) باید دو شناسه یا identifier داشته باشیم و معمولا حروف T و U زیاد استفاده می شوند بنابراین من از همین حروف استفاده کرده ام (شما می توانید بر اساس سلیقه ی خود عمل کنید). حالا این شناسه ها را دقیق تر کرده و می گوییم:
class Component <T extends HTMLElement, U extends HTMLElement> {
یعنی T و U هر دو نوعی از عناصر HTML هستند (یک تایپ کلی تا بعدا دقیق تر شود). بنابراین می توان گفت:
class Component <T extends HTMLElement, U extends HTMLElement> { templateElement: HTMLTemplateElement; hostElement: T; element: U; }
یعنی دو تایپ کلی را برای hostElement و element مشخص کنیم. این شروع کار ما است. در قسمت های بعدی کدها را کامل تر می کنیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.