پروژه Drag & Drop: ارث‌بری و Generic Type ها (1)

Drag & Drop Project: Inheritance and Generic Types - Part 1

19 مرداد 1399
پروژه ی Drag & Drop: ارث بری و generic type ها (1)

در جلسه قبل فرآیند نوشتن کلاس اصلی پدر را شروع کردیم و در حد ساده ای از generic type ها کمک گرفتیم تا به کد ساده زیر رسیدیم:

// Component Base Class
class Component<T extends HTMLElement, U extends HTMLElement> {
    templateElement: HTMLTemplateElement;
    hostElement: T;
    element: U;

}

در مرحله بعد باید constructor ای اضافه کنیم. اولین پارامتری که به آن پاس می دهیم id مربوط به Template است تا بتوانیم با استفاده از همان id هر Template مورد نظری را انتخاب کنیم. سپس id مربوط به hostElement را می خواهیم تا بدانیم کامپوننت خود را در کجا render کنیم. در نهایت یک پارامتر غیرالزامی به نام newElementId خواهیم داشت که id عنصر تازه اضافه شده (render شده در مرورگر) است بنابراین:

// Component Base Class
class Component<T extends HTMLElement, U extends HTMLElement> {
    templateElement: HTMLTemplateElement;
    hostElement: T;
    element: U;

    constructor(
        templateId: string,
        hostElementId: string,
        newElementId?: string
    ) {

    }
}

برای کدهای درون این constructor می توانیم از همان کدهای درون constructor کلاس ProjectList کمک بگیریم:

    constructor(
        templateId: string,
        hostElementId: string,
        newElementId?: string
    ) {
        this.templateElement = document.getElementById(
            templateId
        )! as HTMLTemplateElement;
        this.hostElement = document.getElementById(hostElementId)! as T;
    }

یعنی در همان ابتدا templateElement را دریافت کرده و می سازیم. id آن را هم برابر پارامتر ورودی مناسب اش (templateId) قرار داده ایم. از آنجایی که تایپ خاصی برای این عنصر مشخص نکرده ایم به type casting آن دست نمی زنیم تا روی HTMLTemplateElement بماند. در مرحله بعد باید hostElement را تعریف کنیم که آن را با id مناسب خودش دریافت کرده اما تایپ T را برایش مشخص کرده ایم. سپس کدها را به شکل زیر تکمیل می کنیم:

    constructor(
        templateId: string,
        hostElementId: string,
        newElementId?: string
    ) {
        this.templateElement = document.getElementById(
            templateId
        )! as HTMLTemplateElement;
        this.hostElement = document.getElementById(hostElementId)! as T;

        const importedNode = document.importNode(
            this.templateElement.content,
            true
        );
        this.element = importedNode.firstElementChild as U;
        if (newElementId) {
            this.element.id = newElementId;
        }

        this.attach(insertAtStart);
    }

این کدها را در جلسات قبل نوشته و کار کردیم بنابراین قصد توضیح مکررات را ندارم. تنها نکات مهم این کدها این است که element را روی تایپ U تنظیم کرده و همچنین element.id را روی newElementId گذاشته ایم. از آنجایی که newElementId اختیاری است من تعیین id را در یک شرط if گذاشته ام تا مطمئن شویم پارامتر newElementId پاس داده شده است و سپس آن را به عنوان id تعریف کنیم. در نهایت نیز متد attach را صدا زده ام که البته به من خطا خواهد داد چرا که چنین متدی را هنوز در این کلاس تعریف نکرده ایم. من متد attach را هم از کلاس ProjectList کپی کرده و درون این کلاس پدر می آورم:

    constructor(
        templateId: string,
        hostElementId: string,
        insertAtStart: boolean,
        newElementId?: string
    ) {
        this.templateElement = document.getElementById(
            templateId
        )! as HTMLTemplateElement;
        this.hostElement = document.getElementById(hostElementId)! as T;

        const importedNode = document.importNode(
            this.templateElement.content,
            true
        );
        this.element = importedNode.firstElementChild as U;
        if (newElementId) {
            this.element.id = newElementId;
        }

        this.attach(insertAtStart);
    }

    private attach(insertAtBeginning: boolean) {
        this.hostElement.insertAdjacentElement(
            insertAtBeginning ? 'afterbegin' : 'beforeend',
            this.element
        );
    }
}

البته همانطور که می بینید این متد را تغییر داده ام تا مناسب کلاس component باشد. attach قرار بود عنصر ما را به UI اضافه کند اما مطمئن نیستیم که باید به کجا اضافه شود (بستگی دارد چه کلاس فرزندی از این کلاس پدر استفاده کند) بنابراین ابتدا یک پارامتر جدید را به constructor معرفی کرده ام که insertAtStart نام داشته و یک مقدار Boolean خواهد بود. حالا درون attach می گوییم اگر insertAtBeginning (همان پارامتر ورودی خودمان) تعریف شده بود مقدار 'afterbegin' را به متد insertAdjacentElement بده در غیر این صورت 'beforeend' پاسخ ما خواهد بود.

نکته بعدی اینجاست که کلاس component باید Abstract باشد. چرا؟ به دلیل اینکه این کلاس، یک کلاس پدر و اصلی برای بقیه کلاس ها است. به زبان دیگر یک طرح کلی است که باید توسط بقیه کلاس ها پیاده شود بنابراین هیچ دلیلی برای instantiate کردن آن (ساخت نمونه یا شیء از کلاس) نیست. با abstract کردن این کلاس مطمئن می شویم که هیچ کس نمی تواند این کلاس را مستقیما instantiate کند.

abstract class Component<T extends HTMLElement, U extends HTMLElement> {
// بقیه کدها //

در مرحله بعد به دو متد دیگر نیز نیاز داریم: Configure و متد renderContent:

// بقیه کدهای کلاس //
    private attach(insertAtBeginning: boolean) {
        this.hostElement.insertAdjacentElement(
            insertAtBeginning ? 'afterbegin' : 'beforeend',
            this.element
        );
    }

    abstract configure(): void;
    abstract renderContent(): void;
}

همانطور که می بینید این متدها را به صورت Abstract و بدون بدنه تعریف کرده ام. دلیلش این است که نحوه پیاده سازی متدها بستگی به کلاس فرزند دارد بنابراین با این کار تمام کلاس های فرزند را مجبور کرده ایم که خودشان این دو متد را تعریف کنند.

نکته: در تایپ اسکریپت متدهای abstract نمی توانند private باشند بنابراین اگر بگویید private abstract configure با خطا روبرو خواهید شد.

حالا می توانیم به کلاس ProjectList رفته و ارث بری از component را در آن پیاده سازی کنیم. در ابتدا می گوییم:

// ProjectList Class
class ProjectList extends Component<HTMLDivElement, HTMLElement> {
  assignedProjects: Project[];

مشخص است که با کلیدواژه extends از component ارث بری کرده ایم. همچنین از کدهای قبلی می دانیم که یک HTMLDivElement و یک HTMLElement خواهیم داشت بنابراین این تایپ ها را نیز برایش مشخص کرده ایم. از آنجایی که تعریف خصوصیات پایه کار کلاس component بود دیگر نیازی به تعریف templateElement و hostElement و element نداریم و همه آن ها را پاک کرده ایم. درون constructor این کلاس نیز باید مثل همیشه متد super را صدا بزنیم تا constructor کلاس پدر (component) صدا زده شود. در قسمت بعدی این کار را انجام خواهیم داد. سعی کنید بدون اینکه به جلسه بعد بروید، کدها را خودتان بنویسید و سپس جواب ها را چک کنید تا به اشکالات خود پی ببرید.

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

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

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

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