پروژه Drag & Drop: اضافه کردن کلاس‌های بیشتر و custom type ها

Drag & Drop Project: Adding More Classes And Custom Types

16 مرداد 1399
پروژه ی Drag & Drop: اضافه کردن کلاس های بیشتر و custom type ها

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

// Project Type
class Project {

}

این کلاس به ما کمک می کند که اشیاء پروژه را طوری بسازیم که همیشه ساختار یکسانی داشته باشند و کار ما را راحت کنند. در حال حاضر این کار را به شکل زیر انجام می دهیم:

  addProject(title: string, description: string, numOfPeople: number) {
    const newProject = {
      id: Math.random().toString(),
      title: title,
      description: description,
      people: numOfPeople
    };
    this.projects.push(newProject);
    for (const listenerFn of this.listeners) {
      listenerFn(this.projects.slice());
    }
  }

همانطور که می بینید شیء newProject را به صورت literal (دستی) نوشته ایم و خصوصیت هایش را تعیین کرده ایم اما کلاس جدیدمان به ما این اجازه را می دهد که این کار را بسیار آسان تر کنیم. روش بالا مشکلاتی را هم دارد، به طور مثال همیشه باید حفظ باشیم که به جای description به اشتباه desc ننویسیم یا مثلا همیشه به id نیاز داریم الی آخر. حالا می توانیم به شکل زیر کلاس خود را کامل کنیم:

// Project Type
enum ProjectStatus {
  Active,
  Finished
}

class Project {
  constructor(
    public id: string,
    public title: string,
    public description: string,
    public people: number,
    public status: ProjectStatus
  ) {}
}

همانطور که می بینید این کلاس بسیار ساده است و تمام خصوصیاتی که یک پروژه باید داشته باشد را درون constructor خود صدا می زند. قابل توجه است که شما می توانید این کار را با روش های مختلفی مانند interface ها نیز پیاده سازی کنید اما من این روش را انتخاب کرده ام. نکته بعدی این است که ما خصوصیت status (به معینی «وضعیت») را به این constructor اضافه کرده ایم و از قبل چنین خصوصیتی نداشتیم. دلیل اضافه کردن این خصوصیت رفع یکی از باگ های جلسه قبل است.

اگر یادتان باشد در جلسه قبل برایتان توضیح دادم که اگر فرم برنامه را کامل کرده و پروژه جدیدی را ثبت کنید، این پروژه در هر دو قسمت finished projects و active projects ظاهر می شود. دلیل این باگ این بود که هیچ معیاری برای تشخیص و مشخص کردن وضعیت پروژه ها نداشتیم. با اضافه کردن خصوصیت Status این مشکل حل خواهد شد. برای تعیین تایپ این خصوصیت می توانستیم از روش های قبلی (استفاده از union type های active یا finished) استفاده کنیم اما من می خواستم روش نوآورانه تری را پیش بگیریم. به همین دلیل یک enum را تعریف کردم و تایپ status را از آن نوع قرار دادم.

حالا که این کلاس را تعریف کرده ایم باید به قسمت های مختلف برنامه رفته و از آن استفاده کنیم. به طور مثال در کلاس projectState کدهای زیر را داشتیم:

// Project State Management

class ProjectState {
  private listeners: any[] = [];
  private projects: any[] = [];
  private static instance: ProjectState;
// بقیه کدها //

که می توانیم آن را به شکل زیر ویرایش کنیم:

class ProjectState {
  private listeners: any[] = [];
  private projects: Project[] = [];
  private static instance: ProjectState;

یعنی projects ما آرایه ای از نوع کلاس project خواهد بود. همچنین در قسمت addProject که بالاتر به شما نشان دادم و شیء آن را به شکل دستی نوشته بودیم باید به شکل زیر کدنویسی کنیم:

  addProject(title: string, description: string, numOfPeople: number) {
    const newProject = new Project(
      Math.random().toString(),
      title,
      description,
      numOfPeople,
      ProjectStatus.Active
    );
    this.projects.push(newProject);
    for (const listenerFn of this.listeners) {
      listenerFn(this.projects.slice());
    }
  }

دلیل استفاده من از یک کلاس و کنار گذاشتن روش های دیگر مانند interface ها همین بود که بتوانیم شیء مورد نظرمان را با instantiate کردن (ساختن یک نمونه از یک کلاس) ایجاد کنیم. در ضمن من فرضم بر این بوده است که هر پروژه جدیدی که ایجاد بشود از نوع active خواهد بود به همین دلیل هنگام پاس دادن وضعیت از ProjectStatus.Active استفاده کرده ام.

همچنین در کلاس projectList از any استفاده کرده بودیم:

// ProjectList Class
class ProjectList {
  templateElement: HTMLTemplateElement;
  hostElement: HTMLDivElement;
  element: HTMLElement;
  assignedProjects: any[];

که می توانیم آن را به شکل زیر ویرایش کنیم:

// ProjectList Class
class ProjectList {
  templateElement: HTMLTemplateElement;
  hostElement: HTMLDivElement;
  element: HTMLElement;
  assignedProjects: Project[];

از مزیت های این روش این است که حالا پروژه ما دارای autocompletion می شود، یعنی زمانی که بخواهیم به خصوصیت شیء دسترسی داشته باشیم، خود vscode (یا هر ویرایشگری که دارید) آن خصوصیات را به شما نشان می دهد. همچنین اگر نام خصوصیتی را اشتباه تایپ کنید سریعا خطا می گیرید:

  private renderProjects() {
    const listEl = document.getElementById(
      `${this.type}-projects-list`
    )! as HTMLUListElement;
    for (const prjItem of this.assignedProjects) {
      const listItem = document.createElement('li');
      listItem.textContent = prjItem.title;
      listEl.appendChild(listItem);
    }
  }

مثلا در این کد که در جلسات قبل نوشتیم، اگر به جای prjItem.title بنویسیم prjItem.titl سریعا خطا می گیریم در صورتی که در جلسات قبل چنین خطایی را دریافت نمی کردیم.

در مرحله بعد listener ها را داشتیم که برای آن ها هم از any استفاده کرده بودیم. اگر دقت کنید listener های ما در حقیقت تنها مجموعه ای از توابع هستند که آیتم های ما (پروژه هایمان را) دریافت کرده و در صورت ایجاد تغییر توابع خود را اجرا می کنند. بنابراین به جای نوشتن یک کلاس از custom type ها استفاده می کنم:

// Project State Management
type Listener = (items: Project[]) => void;

زمانی که تایپ برگردانده شده توسط تابع را void می گذاریم، یعنی هیچ اهمیتی به مقدار برگردانده شده توسط این تابع نمی دهیم. این انتخاب، انتخاب صحیحی است چرا که ما با نوعی event-listener ها کار می کنیم و مقدار برگشتی در کار نیست. هدف اصلی تعریف این تایپ این است که مطمئن شویم item هایی پاس داده شوند و از نوع project نیز باشند. حالا می توانیم کدهای قبلی را بهش کل زیر ویرایش کنیم:

class ProjectState {
  private listeners: Listener[] = [];
  private projects: Project[] = [];
  private static instance: ProjectState;

همچنین برای متد addListener باید بگوییم:

  addListener(listenerFn: Listener) {
    this.listeners.push(listenerFn);
  }

همچنین در محل صدا زدن addListener از any استفاده کرده بودیم که به شکل زیر ویرایش می کنیم:

    projectState.addListener((projects: Project[]) => {
      this.assignedProjects = projects;
      this.renderProjects();
    });

در حال حاضر برنامه ما بدون نقص کار می کند اما هنوز مشکل status پروژه ها را حل نکرده ایم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش جامع تایپ اسکریپت توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو