هر دوازده ماه چند روزی وقت میکنم که درگیری‌های ذهنی خودم درمورد تکنولوژی‌های مختلف رو از بین ببرم. البته الزاما مواردی که در طول ۱۲ ماه گوشه ذهنم بودند رفع نمی‌شوند و مثل امسال ممکنه چیزای جدید بوجود بیان رو رفع بشوند.

اواخر امسال بحث راه اندازی یک سرویس برای rate و review شده بود بنظر میومد میشه سیستمی جدید برای این مورد نوشت که کل مجموعه بتونند از اون استفاده کنند. متاسفانه یا خوشبختانه دید کلی مجموعه یک دید یا C# یا دردسر هست. ایده بدی بنظر نمیومد که یک سرویس راه اندازی کنیم که بتونیم تجربه جدید کسب کنیم.

اینجوری بود که به برای کسب بیشتر تایپ‌اسکریپت جالب به نظر می‌رسید. اینجا میخوام تجربه خودم از تایپ اسکریپت رو بگم و مهمتر از اون شیوه ساخت مدل‌های mongoose رو باهم ببینیم.

شروع کار با تایپ اسکریپت خیلی راحته، کافیه پکیج typescript رو نصب کنید و پسوند فایل خودتون رو بجای js برابر ts قرار بدید. بعد از نوشتن کد باید کد تایپ‌اسکریپت رو کامپایل کنیم تا کد جاوااسکریپت معادل و قابل اجرا با node رو بدست بیاریم و بتونیم اجراش کنیم.

yarn add -D typescript

# create index.ts and write your code

yarn tsc index.ts

node index.js

تعیین نوع متغییرها توی TypeScript شبیه چیزی هست که توی Swift و Kotlin وجود داره، یعنی نوع متغییر بعد از اسم اون میاد و به وسیله : ازاسم متعییر جدا میشه. تعیین نوع خروجی متودها هم به همین صورت به : مشخص میشه.

class Test {
	name: string

	doSomething() : void {

	}
}

تایپ اسکریپت ویژگی‌های زیادی که همه زبان‌های برنامه نویسی دارای type دارند، مثل Genericها یا Decoratorها که من اون رو بعنوان annotation میشناسم. (خدا  پدر Sun Microsystems) رو بیامرزه.

Decoratorها تابع‌هایی هستند که قابلیت‌های وامکانات افزوده‌ای رو به کلاس‌ها و فیلد‌هاو متودهای اون اضافه می‌کنند. تعریف decorator کار سختی نیست ک کافیه یک تابع تعریف کنیم که یک ورودی گرفته  و کار خاصی رو اون انجام میده و یک خروجی بدرد بخور برمی‌گردونه. برای مثال کد زیر مدت زمان اجرای تابع مورد نظر ما رو چاپ میکنه.

function log(target: any, propertyName: string):any {
    const old = target[propertyName]
    target[propertyName] = function() {
        const time = Date.now()
        old()
        console.log(Date.now() - time);
    }
}

class MyWorker {
    @log
    work() {
        // a hard worker function
    }
}

همچنین اگه بخوایم decorator داشته باشیم و بتونیم نحوه کار اون در جای‌های مختلف رو مدیریت کنیم، میتونیم از factory functionها استفاده کنیم. برای نمونه اگه بخوایم مثال قبلی خودمون رو تغییر بدیم بصورتی که واحد زمانی مدت اجرای تابع قابل تغییر باشه. ( بتونیم تعیین کنیم که مدت اجرا رو بر حسب میلی‌ثانیه چاپ بکنه با برحس ثانیه)

enum TimeUnit {
    MiliSecond = 1,
    Second = 1000
}

function log(unit: TimeUnit): (target: any, propertyName: string) => void {
    return function lodDecorator(target: any, propertyName: string): void {
        const old = target[propertyName]
        target[propertyName] = function() {
            const time = Date.now()
            old()
            console.log((Date.now() - time) / unit);
        }
    }
}

class MyWorker {
    
    @log(TimeUnit.Second)
    work() {
        // a hard worker function
    }
}

new MyWorker().work()

با Decoratorها میشه کارای باحالی انجام داد. برای مثال اگه تابحال از mongoose استفاده کرده باشید دیدید که درست کردم یک مدل برای mongoose تا چه اندازه درهم‌برهم و شلوغه.(ممکنه شما با من هم نظر نباشید) کد زیر یک نمونه مدل mongoose رو نشون مید

const schema = mongoose.Schema({
  phone: String,
  email: String,
  active: { type: Boolean, index: true },
}, {
  timestamps: true
});

schema.pre('save', function() {
  // do something necessary
})

schema.statics.activeContacts = function () {
  const result = await this.find({ active: true }, { _id: 0, phone: 1 });
  return result.map(r => r.phone);
}

const contact = mongoose.model('contact', schema);

فرض کنید مدل ما بزرگتر بود و فیلدهای و قابلیت‌های بیشتری رو داشت. به کمک TypeScript و Decorator می‌تونیم تعریف یک کلاس رو به تعریف مدل mongoose تبدیل کنیم. چیزی مشابه کد زیر:

import { Model, Schema } from 'mongoose'

@doc({ options: { timestamps: true }})
class Contact extends Model {
  @field(String)
  phone?: String

  @field(String)
  email?: String

  @field({ type: Boolean, index: true })
  active?: Boolean

  static activeContacts() {
    const result = await this.find({ active: true }, { _id: 0, phone: 1 });
    return result.map(r => r.phone);
  }

  @pre('save')
  doSomethingNecessary() {
    // do something necessary
  }
}

نحوه پیاده سازی و توسعه این decoratorها رو می‌تونید توی ریپوزیتوری گیت‌هاب mongoose-model-decorator ببینید.

حتما توصیه می‌کنم بعد از کامپایل فایل‌های ts و تبدیل اونها به جاوااسکریپت، محتوای جاواسکریپت بدست اومده رو نگاه کنید، هرچند شاید پیچیده و غیرقابل فهم بنظر برسه ولی با دقت در این خروجی ساز کار ویژگی های مختلف TypeScript رو میشه متوجه شد.