چهارشنبه, 22 آذر 1396

آموزش تایپ اسکریپت

همه چیز در باره تایپ اسکریپت

اینترفیس ها در تایپ اسکریپت (2#)

Function Types

 اینتر فیس قادر به توصیف طیف گسترده ای از اشکال است که اشیاء جاوا اسکریپت می تواند داشته باشد. اینتر فیس ها علاوه بر توصیف اشیا با صفات آنها، قادر به تعریف انواع تابع نیز هستند. درست مثل اعلان تابع، با مشخص شدن پارامتر های ورودی و نوع خروجی تابع است. هر پارامتر در لیست ورودی پارامتر های تابع نیاز به نام و نوع دارد.

interface SearchFunc {
    (source: string, subString: string): boolean;
}

بعد از تعریف ، درست همانند دیگر اینترفیس ها میتوانیم اینترفیس نوع تابع را استفاده کنیم. در اینجا به شما نشان خواهیم داد که چگونه یک متغیر از نوع تابع را ایجاد و چگونه یک مقدار از همان نوع را به تابع  اختصاص دهید(assign) کنید.

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    let result = source.search(subString);
    return result > -1;
}

 برای type-check تابع، احتیاجی به یکسان بودن( match بودن) نام پارامتر ها با نام آنها در اینترفیس نیست. برای مثال ما می توانیم مثال بالا را به صورت زیر بنویسیم.

let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
    let result = src.search(sub);
    return result > -1;
}

پارامتر های ارسال به تابع با توجه به موقعیت متناظر در تعریف تابع در یک زمان بررسی می شوند. اگر شما نمی خواهید نوع پارامتر ها را به یک باره در تابع مشخص کنید، موتور نوشتار تایپ اسکریپت(Typescript’s contextual typing) ، می تواند نوع پارامتر ارسال به تابع را از روی مقادیری که به متغیر های تابع SearchFunc اختصاص داده می شود، استنباط کند. در اینجا نوع خروجی تابع به صورت ضمنی از متغییری که تابع بر می گرداند، مشخص می شود(در این مثال مقدار درست یا نادرست). در صورتی که تابع string و یا عدد برگرداند، type-checker به ما اخطار می دهد که نوع بازگشتی تابع با نوع تعریف شده در اینترفیس تابع مطابقت ندارد.

 

let mySearch: SearchFunc;
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}

Indexable Types

مشابه به نحوه استفاده از اینترفیس برای توصیف تابع، می توانیم انواع فهرست راهنما (index into)  برای چیزهایی  مثل a[10] یا ageMap["daniel"] ایجاد کنیم. انواع indexable   یک مشخصه فهرست دارند که ما می توانیم نوع این index  را توصیف کنیم.(نوع پارامتری که در جایگاه index قرار می گیرد از چه نوعی باشد) و هم چنین می توانیم نوع مقداری که در آدرس index قرار دارد، را مشخص نمایم. اجازه دهید یک مثال بزنیم:

interface StringArray {
    [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

در مثال بالا ، ما یک اینترفیس با نام  StringArray داریم که یک فهرست راهنما (index signature) دارد. این فهرست راهنما بیان می کند که زمانی که StringArray با عدد نشان داده می شود، مقداری از نوع string برمی گرداند.

 

دو فهرست راهنمای معمول وجود دارد.  فهرست بندی شده بر اساس عدد و یا بر اساس string.  اگرچه هر نوع فهرست عددی و رشته ای ممکن است اما نوعی داده ای که فهرست عددی بر می گرداند باید زیرنوعی از نوعی که فهرست رشته ای بر می گرداند، باشد. این به خاطر این است که موقع ای که شما با عدد فهرست بندی می کنید، تایپ اسکریپت در واقع، قبل از نمایه سازی به آن شی، شاخص را به string تبدیل می کند. این بدین معناست که نمایه سازی با 100 (به صورت عددی) همان نمایه سازی با 100 (به صورت string) است بنابراین هر دو باید سازگار باشند.

 

class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

// Error: indexing with a 'string' will sometimes get you a Dog!
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}

در حال که استفاده از فهرست بندی رشته ای (string)  یک روش قدرتمند برای ساختن الگوی دیکشنری هست، این فهرست بندی تاکید می کند که تمامی ویژگی ها، با نوع بازگشتی آن ها باید مطابقت داشته باشد. دلیل این است که شاخص string ، بیان می کند که obj.property همانند obj["property"] قابل دسترسی است.در مثال زیر نوع name با نوع فهرست بندی مطابقت ندارد و type-checker خطا می دهد.

 

interface NumberDictionary {
    [index: string]: number;
    length: number;    // ok, length is a number
    name: string;      // error, the type of 'name' is not a subtype of the indexer
}

 

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

 

interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!

شما نمی توانید به myArray[2] مقداردهی کنید چرا که فهرست راهنما به صورت فقط خواندی هست.

 

 انواع کلاس Class Types

پیاده سازی یک اینترفیس

یکی از رایج ترین استفاده از اینترفیس در زبان هایی مثل #C و جاوا، بیان پارامتر های خاص یک کلاس است درست مثل تایپ اسکریپت.

 

interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

شما هم چنین می توانید متد هایی  در که کلاس پیاده سازی می شوند را در اینتر فیس بیان کنید. مثل کاری که ما با settime در مثال زیر انجام می دهیم.

 

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

اینترفیس به جای جنبه های عمومی و خصوصی یک کلاس تنها جنبه های عمومی یک کلاس را توصیف می کند . این ممنوعیت استفاده از جنبه های خصوصی یک کلاس به منظور بررسی کردن انواع خاص یک نمونه کلاس است.

 

تفاوت بین استاتیک و نمونه کلاس

Difference between the static and instance sides of classes

 

موقع کار با کلاس ها و اینترفیس ها این نکته را به خاطر بسپارید که کلاس ها دو وجه دارند: وجه استاتیک و وجه نمونه کلاس. توجه کنید که اگر شما اینترفیس را با construct ایجاد کنید و سعی کنید که کلاسی برای پیاده سازی این اینترفیس بسازید به خطا برخورد می کنید.

 

interface ClockConstructor {
    new (hour: number, minute: number);
}

class Clock implements ClockConstructor {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

این خطا به این دلیل است که وقتی کلاس اینترفیس را پیاده سازی می کند، تنها وجه های نمونه کلاس را بررسی می کند در حالیکه سازنده ها ( constructor) ها جزء وجه های استایتک است و بررسی نمی شود.

This is because when a class implements an interface, only the instance side of the class is checked. Since the constructor sits in the static side, it is not included in this check.

در عوض ممکنه شما نیاز داشته باشید که با وجه های استاتیک به صورت مستقیم کار کنید. در این مثال ما دو اینترفیس تعریف کردیم ClockConstructor با سازنده و ClockInterface برای نمونه متد. سپس برای راحتی کار ما تابع سازنده createClock را تعریف کردیم که نمونه هایی از نوعی که به آن پاس داده می شود را ایجاد می کند.

 

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

از آنجا که اولین پارامتر CreateClock از نوع  ClockConstrator است در CreateClock(AnalogClock,7,32) بررسی می شود که آیا AnlogClock مطابق با ساختار صحیح می باشد یا خیر.

 

گسترس اینترفیس ها

Extending Interfaces

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

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

 

یک اینترفیس می تواند با ایجاد ترکیبی از همه اینترفیس ها، در چندین اینترفیس گسترش پیدا کند.

 

 

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

 

انواع ترکیبی

Hybrid Types

 

قبلا همان طور که ذکر شد،اینترفیس ها می تواند انواع وسیعی که در دنیای جاوااسکریپت وجود دارد، را بیان کند.

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

 

 

یک شی که به دو صورت تابع و شی عمل می کند با ویژگی های اضافی در مثال زیر آمده است:

 

 

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

 

در هنگام کار کردن با  3rd-party جاوا اسکریپت، شما ممکن است نیاز داشته باشید برای توصیف شکل نوع از الگوهایی همانند بالا را استفاده کنید.

 

کلاس های توسعه اینترفیس

Interfaces Extending Classes

 

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

 

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

 

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control {
    select() { }
}

class TextBox extends Control {
    select() { }
}

class Image {
    select() { }
}

class Location {
    select() { }
}

 

در مثال بالا، SelectableControl تمامی اعضای Control را شامل می شود از جمله ویژگی اختصاصی State.از آنجا که State یک عضو خصوصی است، تنها برای فرزندان Control برای پیاده سازی SelectableControl قابل دسترس می باشد.دلیل این است که تنها فرزندان کنترل عضو خصوصی state  را دارند که از تعریف یکسان سرچشمه می گیرند که ملزم به سازگاری با اعضای خصوصی می باشد.

 

 

در کلاس control دسترسی به عضو خصوصی state در نمونه ای از SelectableControl میسر است. به طور میسر،SelectableControl مثل control عمل می کند که متد select را می شناسد. کلاس های Button  و TextBox زیرنوعی از SelectableControl هستند(بدلیل ارث بری از control و داشتن متد Select) ولی کلاس های Image و Location  چنین نیستند.