تازه های تایپ اسکریپت ۳

بعد از ۲ سال از انتشار تایپ اسکریپت ۲ و محبوب شدنش اخیرا مایکروسافت تایپ اسکریپت ۳ رو منتشر کرده که توی این مطلب می خوایم ببینیم چه چیزای جدیدی نسبت به نسخه های قبل بهش اضافه شده و چه تاثیری میتونه روی دولوپ کردن ما داشته باشه.


تایپ های tuple قوی تر

تاپل ها توی تایپ اسکریپت در واقع همون strongly-typed آرایه های توی جاوا اسکریپت با طول ثابت هستن و اینطوری ازشون استفاده می شد:

type Triple = [boolean, number, string];
const a: Triple = [true,1,'lia'];

توی تایپ اسکریپت ۳ میتونیم tuple ها رو با طول تعریف نشده بزاریم و فقط بهش یه مقدار حداقل بدیم. به عنوان مثال:

type StringAndNumbers = [string, ...number[ ]];
const a1: StringAndNumbers = ['lia',0 , 1, 2, 3];
const a2: StringAndNumbers = ['lia'];

همون طور که میبینیم قسمت اول یه string عه که خب حتما باید داشته باشیمش اما قسمت دوم که اعداد هستن میتونن حذف بشن. ما حتی میتونیم tuple هایی داشته باشیم که اون قسمت اول و لازم و حداقل رو هم ندارن یعنی یه چیزی مثل مثال بالا با این تفاوت که نیازی نیست حتما اون آیتم string رو توی تاپلمون داشته باشیم که این میشه همون یه آرایه یا یه tuple خالی، به مثال توجه کنید:

type Strings = [...string];
type Empty = [ ];
const b1: Strings = ['lia', 'liaa'];
const b2: Strings = [ ];
const b3: Empty = [ ];
const b4: Empty = ['lia', 'liaa']; // تایپ اشتباه و غیرقابل قبول

تاپل ها میتونن استفاده های مفیدی داشته باشن برای مثال میتونیم یه آرایه رو مجبور کنیم که حداقل یه المنت تو خودش داشته باشه!

انتشار و جدا کردن Argument های تابع با استفاده از همین tuple type ها

همونطور که میدونین توی جاوا اسکریپت ما میتونیم از argument های تابع استفاده کنیم بدون اینکه هیچ کدومو نام گذاری کرده باشیم ولی تایپ اسکریپت به صورت دیفالت این اجازه رو به ما نمیده و تک تک چیزها رو برای استفاده باید از قبل به کامپایلر بشناسونیم:

// JavaScriptfunction 
jsHello() { 
    return [...arguments].join(',');
} 
// JavaScript, second way
function jsHello1(...args) { 
    return args.join(',');
} 
// TypeScript
function tsHello(...args: any[]): string { 
    return args.join(',');
}

یه زمانی هست که خب ما نمی خایم از any استفاده کنیم برای argument های تابعمون مثلن میخایم بگیم که argument امون number باشه یا string که خب به راحتی میتونیم بنویسم (string | number) اما یک زمانی هم هست که ما یه دیتایی داریم متشکل از یه سری number و یه سری string و خب مشخص هم نیست که اینا چند تا هستن و یا به ترتیب نوعشون چیه پس نمیتونیم argument تابعمون رو به صورت ( string | number ) هم تعریف کنیم. توی تایپ اسکریپت ۳ این مشکل به کمک همین tuple ها به راحتی حل میشه:

function hello(...args: [string, number, number, ...string[ ]]): string { 
    return args.join(',');
}

یه مثال متفاوت تر :

function compose2<T1 extends any[], T1R, T2>(f1: (...args1: T1) => T1R,  f2: (arg: T1R) => T2) { 
     return (...a: T1) => f2(f1(...a));
 } 
 const add = (x: number, y: number) => x + y;
 const sqr = (x: number) => x * x;
 const addAndSqr = compose2(add, sqr); 
 addAndSqr(1, 2); // valid
 addAndSqr('a', 2); // invalid type

آیتم های اختیاری در tuple types

در ورژن جدید tuple type ها مجاز هستند از کاراکتر "?" برای آیتم های اختیاری استفاده کنند:

let t: [number, string?, boolean?]; 
t = [42, "hello", true]; 
t = [42, "hello"]; 
t = [42];

ناشناخته : "unknown"

تایپ اسکریپت یه تایپ any داره که تقریبا به معنای هر تایپیه و همه چیزو میتونه هندل کنه ولی پرکتیک خوبی نیست که بخوایم ازش به دفعات زیاد استفاده کنیم . یه بار هست که ما از any استفاده می کنیم زمانی که نمیدونیم واقعا چی داریم ولی بعدن قراره بفهمیم و تایپشو دقیق تر کنیم و در واقع به صورت strongly typed ازش استفاده کنیم. وقتی از any استفاده می کنیم حتی لازم نیست بعدا تایپ متغیرمون رو چک کنیم و به راحتی بدون هیچ ارور کامپایلی میتونیم ازش استفاده کنیم. توی تایپ اسکریت ۳ یه تایپ جدید به اسم unknown اضافه شده که به کامپایلر میگه "ما واقعا نمیدونیم در حال حاضر چی داریم پس تا زمانی که نفهمیدیم چی داریم نمیتونیم ازش استفاده کنیم." مثال رو نگاه کنید تا بهتر متوجه بشین:

const a1: any = 'lia';
const a2: unknown = 'lia';
a1.length; // = 1
a2.length; // compiler error
a1(); // error during execution
a2(); // compiler error
new a1(); // error during execution
new a2(); // compiler error
const a3 = a1 + 3; // = 'lia3'
const a4 = a2 + 3; // compiler error

const b: unknown = { a: 'b' };
console.log(b.a); // compiler error
function hasA(obj: any): obj is { a: any } {
    return !!obj
        && typeof obj === 'object'
        && 'lia' in obj;
}
if (hasA(b)) {
    console.log(b.a); // = 'b'
}

// unknown in conditional types 
type T30<T> = unknown extends T ? true : false;  // Deferred 
type T31<T> = T extends unknown ? true : false;  // Deferred (so it distributes) 
type T32<T> = never extends T ? true : false;  // true 
type T33<T> = T extends never ? true : false;  // Deferred 

// keyof unknown 
type T40 = keyof any;  // string | number | symbol 
type T41 = keyof unknown;  // never

دقت کنین که این یه تغییر بزرگ تلقی میشه و از نسخه ی ۳ به بعد unknown جز کلمات کلیدی زبان میشه و دیگه نمیتونیم به عنوان اسم ازش استفاده کنیم!

پشتیبانی از defaultProps در JSX

تایپ اسکریپت ۲.۹ و قبل از اون نمیتونست توی اعلان defualtProps در کامپوننت های JSX نفوذ کنه و کاربرها مجبور بودن که ویژگی ها رو مشخص کنن و از تکرار های non-null داخل render استفاده کنن، یا ازtype-assertions برای مشخص کردن تایپ کامپوننت قبل از اکسپورت کردن اون استفاده کنن. از ویژگی های جدید تایپ اسکریپت ۳ پشتیبانی language-level از defaultProps عه ری اکت جی اس عه. برای اونایی که تا حالا چیزی از defaultProps نشنیدن باید بگم که به صورت معمول توی جاوا اسکریپت (ECMAScript 6) اگر ما می بخوایم پارامترهای مولفه پیش فرض React رو ارائه بدیم، می تونیم با استفاده از defaultProps object static مثل مثال پایین این کارو انجام بدیم:

import * as React from 'react';
import * as ReactDOM from 'react-dom'; 

class Heading extends React.Component { 
        render() { 
            return <h1>{this.props.title.toUpperCase()}</h1>  
        }
} 
Heading.defaultProps = { title: 'Hello!' };
const elem = document.querySelector('#target');
ReactDOM.render(<Heading />, elem);

همانطور که می بینیم، به لطف استفاده از defaultProps نیازی به انجام null check روی title نداریم، زیرا ما مطمئن هستیم که همیشه یه ارزش خاصی داره. در گذشته TypeScript نمی تونست این رو درک کنه و ما مجبور بودیم حتما یک null check انجام بدیم. در مثال های زیر نشون میدیم که چگونه می تونیم این موضوع رو در TypeScript 2.x هندل کنیم و چگونه می تونیم توی ورژن جدید انجامش بدیم:

type Props = {  title?: string;}; 
class Heading extends React.Component<Props> { 
    static defaultProps = {   title: 'Hello!'  };  
    render() { 
        const title = this.props.title; return <h1>{title && title.toUpperCase()}</h1> 
    }    
}


type Props = {  title?: string;}; 
class Heading extends React.Component<Props> { 
    static defaultProps = {    title: 'Hello!'  };  
    render() { 
        const title = this.props.title; return <h1>{title.toUpperCase()}</h1>  
    }   
}

همانطور که می بینید، در جدیدترین نسخه TypeScript نیازی به بررسی null نیست، زیرا کامپایلر می تونه مقدار پارامتر تعریف شده در defaultProps رو ببینه!

مراجع پروژه(Project references)

این ویژگی احتمالن مهم ترین ویژگی تایپ اسکریپت ۳ باشه، ما ازین به بعد قادر خواهیم بود که cross‑project references تعریف کنیم. Project references به پروژه TypeScript اجازه می ده که به سایر پروژه های TypeScript وابسته باشه - به طور خاص، اجازه می ده فایل tsconfig.json به سایر فایل های tsconfig.json مراجعه کنه. مشخص کردن این وابستگی ها باعث می شه که کد شما به پروژه های کوچکتر تقسیم بشه، زیرا TypeScript (و ابزارهای اطراف اون) راهی برای درک ترتیب build و ساختار خروجی هستش.
به لطف این تغییر، ما میتونیم از برخی از سناریوهای معماری جدید پروژه مانند:

  • استفاده از کدهای مشترک برای سمت کاربر(کلاینت) و سمت سرور. خروجی کامپایل شده کد رو هم شیر میکنه نه اینکه یه کپی جدا از هر فایل کد داشته باشه.
  • یونیت تست ها شامل کپی خود از فایل های اصلی پروژه نیستن.
  • بسیاری از پروژه های وابسته به یکدیگر(MonoRepos). همراه با راه حل های تست مثل Lerna و Yarn Workspaces.

استفاده کنیم. همه ی این ها به لطف ورودی های جدید توی tsconfig امکان پذیر میشه: فلگ composite برای گزینه های کامپایلر و references. همچنین ما یک پارامتر جدید برای tsc (کامپایلر TypeScript) داریم: - - build که می تونه تمام پروژه های TypeScript را با توجه به tsconfig داده شده بسازه.

برای نمونه فرض کنیم که یه همچنین ساختاری برای پروژه داریم:

در پروژه کلاینت tsconfig.json، می شه از دستور زیر برای بیلد گرفتن استفاده کرد:

tsc -b composite / client

برای استفاده از shared project ما به راحتی چیزهایی از آن رو که نیاز داریم بدون هیچ ساختار اضافی در کد وارد می کنیم:

{
    "compilerOptions": { 
        "target": "es5", 
        "module": "commonjs", 
        "lib": ["es2015", "dom"], 
        "outDir": "../../lib/client", 
        "composite": true, 
        "strict": true, 
        "esModuleInterop": true, 
    }, 
    "references": [ 
        { "path": "../shared" }  
    ]
}

بعد از کامپایل کردن هر دوی پروژه های کلاینت و سرور ما به همچین ساختاری می رسیم:

همینطور که می بینید دایرکتوری shared در واقع بین کلاینت و سرور تقسیم شده. در نسخه های قدیمی تر، دایرکتوری "shared" را به هر دو client و server کپی می کردیم، بنابراین ساختار خروجی متفاوت از ساختار منبع ایجاد می شه. این تغییر در واقع بزرگترین تغییر ورژن جدید تایپ اسکریپت هست با این وجود باید ببینیم که توی پروژه های جدید و ساختار های متفاوت چطوری قراره ازین ویژگی استفاده بشه.

خب توی نسخه ی ۳ بنظر میاد این زبان جذاب در حال هر روز بهتر شدنه و من خودم به شخصه واقعا از استفادش لذت میبرم:‌ Love for TypeScript