برنامه نویسی

شک و تردید نوع: چگونه یک مهاجرت مجدد TypeScript حقیقت را در مورد توربو نشان داد

در سپتامبر سال 2023 ، دیوید هینمییر هانسون اعلام کرد که “توربو 8 در حال رها کردن TypeScript است.” این تصمیم در جامعه توسعه در مورد شایستگی های استاتیک در مقابل سیستم های تایپ پویا بحث و گفتگو قابل توجهی ایجاد کرده است.

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

این تجزیه و تحلیل شواهد تجربی در مورد اثربخشی TypeScript در زمینه چارچوب توربو ، بر اساس اجرای مجدد توربو با استفاده از TypeScript (github.com/shiftyp/ts-turbo) ارائه می دهد.

مطالب

زمینه تاریخی Typescript در توربو

DHH اظهار داشت که “TypeScript فقط برای من در راه است” و “چیزهایی که باید به آسانی سخت شوند و چیزهایی که سخت می شوند any. “این دیدگاه در زمینه خاص اجرای توربو ، بررسی را دارد.

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

  1. استفاده گسترده از any انواع برای دور زدن نوع بررسی
  2. چک های تهی دقیق غیرفعال برای جلوگیری از پرداختن به منابع احتمالی تهی
  3. کاربرد متناقض از نوع ایمنی در بین اجزای مرتبط

در واقع چه TypeScript ثابت شده است

هنگامی که به درستی با تنظیمات سخت اجرا شد ، Typecript تعدادی از مسائل اصلی را در پایگاه کد توربو نشان داد:

منابع تهی

خطاهای مرجع تهی می تواند باعث شود برنامه ها با پیام هایی مانند “نمی توان ویژگی” GetAttribute “NULL را بخوانید” خراب شود. این مسائل به ویژه خطرناک است زیرا می توانند در حین آزمایش کار کنند اما در شرایط خاص در تولید شکست می خورند. آنها منجر به صفحات خالی ، کنترل های غیر عملکردی یا ناوبری شکسته برای کاربران می شوند.

در کنترل کننده های فریم ، منابع عناصر غالباً بدون بررسی وجود دسترسی پیدا می کردند. این کد JavaScript بی گناه را از اجرای اصلی توربو در نظر بگیرید:

// From commit 9f3aad7: src/core/frames/frame_controller.js
getRootLocation() {
  const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`)
  const root = meta?.content ?? "/"
  return expandURL(root)
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

چه اتفاقی می افتد this.element تهی است؟ JavaScript در زمان اجرا با یک خطای مرجع تهی خراب می شود. نسخه Typescript در TS-Turbo این امنیت را فراهم می کند:

// Current implementation in ts-turbo: src/core/frames/frame_controller.ts
get rootLocation(): Location {
  if (!this.element) return expandURL("/")
  const meta = this.element.ownerDocument.querySelector<HTMLMetaElement>(`meta[name="turbo-root"]`)
  const root = meta?.content ?? "/"
  return expandURL(root)
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

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

ناسازگاری های اجرای رابط

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

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

در frame_controller.js (خطوط 238) ، ما یک تماس به روشی را مشاهده می کنیم که ممکن است وجود نداشته باشد:

// In frame_controller.js - The method is called on the frame's delegate
frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

توربو الگوی نماینده ای دارد که در آن FrameElement نماینده خود را از کلاس سازنده ایجاد می کند:

// In frame_element.js - The delegate is created but with no explicit interface
export class FrameElement extends HTMLElement {
  static delegateConstructor = undefined  // Set elsewhere in the codebase

  constructor() {
    super()
    this.delegate = new FrameElement.delegateConstructor(this)
  }

  // The only delegate methods actually called are:
  connectedCallback() {
    this.delegate.connect()
  }

  disconnectedCallback() {
    this.delegate.disconnect()
  }

  attributeChangedCallback(name) {
    if (name == "loading") {
      this.delegate.loadingStyleChanged()
    } else if (name == "src") {
      this.delegate.sourceURLChanged()
    } else {
      this.delegate.disabledChanged()
    }
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مشکل این است که در جاوا اسکریپت ، هیچ رابط صریح وجود ندارد proposeVisitIfNavigatedWithAction برای پیاده سازی توسط هر کلاس که به آن اختصاص داده می شود FrameElement.delegateConstructorبشر این یک قرارداد ضمنی و بدون مستند بین مؤلفه ها ایجاد می کند.

این یک مسئله به خصوص شدید است که باعث می شود در هنگام بروز یک ناوبری فریم ، خطای “روش یافت نشد”. از آنجا که این خطا فقط در توالی های ناوبری خاص اتفاق می افتد ، تشخیص در هنگام آزمایش دشوار خواهد بود. در TypeScript ، این مشکل با نیاز به رابط های صریح که دقیقاً مشخص می کنند چه روش هایی را باید اجرا کرد ، حل می شود.

اجبار نوع گیج کننده

اجبار نوع ضمنی JavaScript اشکالاتی را ایجاد می کند که به نظر می رسد به درستی کار می کنند اما در شرایط خاص نتایج نادرست ایجاد می کنند. اشکال زدایی این مسائل دشوار است زیرا اغلب به نظر می رسد مشکلات زیست محیطی یا مربوط به زمان بندی هستند.

اجبار از نوع ضمنی JavaScript اشکالات ظریف ایجاد کرد که غالباً بدون توجه به آنها می رفتند تا اینکه باعث خرابی تولید شوند. در SRC/Core/Drive/History.js ما کدی را پیدا می کنیم که سؤالاتی درباره قصد ایجاد می کند:

// Original JavaScript implementation - implicit type coercion
restore(position) {
  if (this.restorationIdentifier) {
    history.replaceState(history.state, "", location.toString())
    this.location = location.href
    history.pushState(history.state, "", position)
    // Number comparison with string using == (not ===)
    if (this.currentIndex == 0) {
      window.scrollTo(0, 0)
    }
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

اجرای TypeScript در TS-Turbo مقایسه را صریح می کند و از مشکلات احتمالی اجبار جلوگیری می کند:

// TypeScript implementation in ts-turbo
restore(position: URL): void {
  if (this.restorationIdentifier) {
    history.replaceState(history.state, "", location.toString())
    this.location = location.href
    history.pushState(history.state, "", position)
    // Explicit comparison with proper typing
    if (this.currentIndex === 0) {
      window.scrollTo(0, 0)
    }
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

آیا این بررسی برای برابری یا انواع به طور تصادفی تبدیل است؟
بازگشت این. pageLoaded || Document.ReadyState == “کامل”. این ممکن است هنگامی اجرا شود که انتظار نداشته باشید به دلیل اجبار. این الگوی در سراسر ماژول مدیریت تاریخ یافت شد. مسئله عمیق تر از آنچه به نظر می رسد پیچیده تر است:

  1. الگوهای مقایسه ناسازگار: در طول پایگاه کد ، مقایسه های برابری دقیق (مقایسه)===) و برابری گشاد (==) ، ایجاد رفتار متناقض. document.readyState == "complete" در حالی که از برابری شل استفاده می کند this.restorationIdentifier === identifier از برابری دقیق در جای دیگر استفاده می کند.

  2. مسائل مربوط به ردیابی دولت: سیستم تاریخ انواع مختلفی از داده ها را ردیابی می کند: مسیرهای URL (رشته ها) ، شناسه های ترمیم (انواع مختلط) و اشیاء دولتی. در مقایسه با برابری شل ، مسیرهای URL مانند “/404” به طور تصادفی می توانند با شناسه های عددی مطابقت داشته باشند.

  3. تفاوت های اجرای مرورگر: مرورگرها با هم متناقض اشیاء تاریخ را اداره می کنند – برخی از انواع دقیقاً ، برخی دیگر سریال می شوند و از بین می روند (انواع در حال تغییر) ، و سافاری ممکن است رشته های عددی را در برخی زمینه ها به اعداد تبدیل کند.

مثال دیگر از انتقال فرم ارسال فرم آمده است:

در فرم ارسال ارسال فرم ، ما تبدیل های ضمنی مشکل ساز را می بینیم:

// Actual code from form_submission.js after TypeScript removal (commit 9f3aad7772ba8ef4080538e4e5fb175a8ad550f1)
get method() {
  const method = this.submitter?.getAttribute("formmethod") || this.formElement.getAttribute("method") || ""
  return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این الگوی مسائل بالقوه ای را ایجاد می کند که در آن یک ویژگی روش رشته خالی (که در JavaScript فال است) متفاوت از یک روش غیر موجود است ، وقتی هر دو باید به روش پیش فرض منجر شوند

این مثال ارسال فرم ، چندین چالش اجبار را نشان می دهد که بر تعامل واقعی کاربر تأثیر می گذارد:

  1. ناسازگاری های دست زدن به ویژگی ها: getAttribute() در حالی که ویژگی های خالی “” “NULL را برای ویژگی های غیر موجود برمی گرداند” “. منطقی یا (||) هر دو را به عنوان فال رفتار می کند ، اما این موارد باید متفاوت انجام شود.

  2. تعیین روش فرم: فرم های وب بر اساس روش های GET در مقابل POST رفتار می کنند ، بر ناوبری ، ذخیره سازی و کار با داده ها تأثیر می گذارند. روش اشتباه می تواند باعث از بین رفتن داده ها یا مشکلات امنیتی شود.

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

بررسی دقیق Typecript باعث می شود رسیدگی صریح این موارد مختلف باشد.

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

چه TypeScript نمی تواند برطرف شود

تایپ استاتیک یک گلوله نقره ای نیست ، و دسته های خاصی از اشکالات در برابر محافظت از TypeScript مصون هستند:

خطاهای منطقی همچنان ادامه داشت

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

مهاجرت نشان داد که TypeScript نمی تواند از الگوریتم های ناقص یا جریان کنترل نادرست جلوگیری کند. به عنوان مثال ، در اجرای Renderer پیام جریان ، حتی با TypeScript ، خطاهای منطقی در دنباله پردازش باقی مانده است:

// From src/core/drive/navigator.ts in the TypeScript codebase
visit(location: URL, options: Partial<VisitOptions> = {}) {
  this.navigator.visit(location, options)
}

navigate(event: Event) {
  if (this.isHashChangeEvent(event)) {
    this.navigator.update(this.location, "replace")
  } else {
    // Event and navigation handling run in separate async paths
    // TypeScript can't detect logical flow problems between these methods
    const linkClicked = this.getVisitOptions(event)
    this.visit(linkClicked.url, linkClicked.options)
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این مثال خطای جریان منطقی را نشان می دهد که Typecript نمی تواند تشخیص دهد:

در سیستم ناوبری توربو ، چندین مسیر اعدام ناهمزمان وجود دارد:

  1. روش پیمایش () به رویدادهای کلیک پاسخ می دهد
  2. getVisitOptions () عملیات دامنه (خواندن ویژگی های داده) را انجام می دهد که به طور ضمنی async هستند
  3. بازدید () عملیات ASYNC را آغاز می کند (درخواست های شبکه ، تماس های API تاریخ)
  4. شنوندگان چندین رویداد در سطوح مختلف (پنجره ، سند ، عناصر) می توانند به طور همزمان درخواست های ناوبری رقیب را تحریک کنند

شرایط مسابقه به این دلیل اتفاق می افتد که بین رویدادها ، URL را می توان با کد دیگر اصلاح کرد ، چندین کلیک درخواست های ناوبری رقابتی را ایجاد می کند ، و این رویدادها در چرخه های مختلف حلقه رویداد اجرا می شوند که Tipcript نمی تواند تجزیه و تحلیل کند.

این مسائل مربوط به زمان بندی منجر به اشکالات شد که کاربران روی یک پیوند کلیک می کنند اما در یک URL متفاوت قرار می گیرند ، ناوبری برگشت/رو به جلو پس از تعامل سریع شکسته می شود و مرورگر و توربو در مورد URL “فعلی” به حالت های متناقض می روند.

TypeScript فقط می تواند پارامترها و انواع بازگشت را تأیید کند ، اما نمی تواند مشکلات مربوط به زمان را در حلقه های رویداد تشخیص دهد ، هماهنگی بین مسیرهای اجرای ASYNC را تأیید کند ، یا از سازگاری دولت در یک محیط چند رویداد اطمینان حاصل کند.

این موضوعات نیاز به آزمایش تخصصی (مانند آزمایش سریع کلیک) و تجزیه و تحلیل توالی رویداد در طول بررسی کد – مواردی که بررسی نوع استاتیک نمی تواند ارائه دهد.

مدیریت حافظه هنوز هم به نظم و انضباط نیاز داشت

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

برخی از موذیانه ترین اشکالات موجود در توربو شامل مدیریت حافظه بود. تجزیه و تحلیل stream_source_element.js موضوعات قابل توجهی را نشان داد:

// From commit 9f3aad7: src/elements/stream_source_element.js
export class StreamSourceElement extends HTMLElement {
  streamSource = null

  connectedCallback() {
    this.streamSource = this.src.match(/^ws{1,2}:/) ? new WebSocket(this.src) : new EventSource(this.src)

    connectStreamSource(this.streamSource)
  }

  disconnectedCallback() {
    if (this.streamSource) {
      disconnectStreamSource(this.streamSource)
    }
  }

  get src() {
    return this.getAttribute("src") || ""
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

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

برای درک بهتر زمینه ، بیایید به اجرای TypeScript و چندین مؤلفه اصلی در آن نگاه کنیم:

// src/elements/stream_source_element.ts (TypeScript implementation)
export class StreamSourceElement extends HTMLElement {
  streamSource: StreamSource | null = null

  connectedCallback() {
    // Creates either WebSocket or EventSource based on URL pattern
    this.streamSource = this.src.match(/^ws{1,2}:/) ? 
      new WebSocket(this.src) : new EventSource(this.src)

    connectStreamSource(this.streamSource)
  }

  disconnectedCallback() {
    if (this.streamSource) {
      disconnectStreamSource(this.streamSource)
    }
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

// Example from observer pattern in frame_controller.ts
export class FrameController implements LinkClickObserverDelegate, 
  AppearanceObserverDelegate<FrameElement>, FormSubmitObserverDelegate {
  // Multiple observers that need to be started/stopped at the right times
  readonly appearanceObserver: AppearanceObserver<FrameElement>
  readonly formLinkClickObserver: FormLinkClickObserver
  readonly formSubmitObserver: FormSubmitObserver

  constructor() {
    // Create observers that register event listeners
    this.appearanceObserver = new AppearanceObserver(this, this.element)
    this.formLinkClickObserver = new FormLinkClickObserver(this, this.element)
    this.formSubmitObserver = new FormSubmitObserver(this, this.element)
  }

  connect() {
    this.appearanceObserver.start()
    this.formLinkClickObserver.start()
    this.formSubmitObserver.start()
  }

  disconnect() {
    this.appearanceObserver.stop()
    this.formLinkClickObserver.stop()
    this.formSubmitObserver.stop()
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این مثال یک چالش مدیریت حافظه بسیار بزرگتر در توربو را نشان می دهد. زمینه عمیق تر نشان می دهد:

  1. مالکیت منابع چند لایه: عنصر StreamSource کنترلرهایی را ایجاد می کند که ناظران را ثبت می کنند ، شنوندگان رویداد را ضمیمه می کنند و اتصالات شبکه را برقرار می کنند. در مثال FrameController ، سه نوع ناظر مختلف فوری می شوند که هر یک به هماهنگی مناسب شروع/توقف نیاز دارند.

  2. شکافهای قطع: قطع ارتباط فقط HostDisconnected () را صدا می کند ، اما این روش ممکن است همه شنوندگان رویداد را تمیز نکند ، اتصالات بسته را تأیید کند یا وقفه ها را در حین عملیات فعال انجام دهد. TypeScript نمی تواند پاکسازی مناسب را در زمان اجرا اجرا کند.

  3. هماهنگی مؤلفه های متقابل: TypeScript هیچ راهی برای اطمینان از منابع اختصاص داده شده توسط یک مؤلفه توسط بخش دیگر ندارد ، ناظران به درستی متوقف می شوند ، یا شنوندگان رویداد به درستی حذف می شوند. TypeScript می تواند روشهای موجود را تأیید کند اما نمی تواند آنها را در زمان یا دنباله مناسب فراخوانی کنند.

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

سایر مسائل مربوط به حافظه شامل شنوندگان رویداد به درستی حذف نشده ، منابع دایره ای از جمع آوری زباله ها و منابع منتشر نشده پس از استفاده.

TypeScript در برابر این مشکلات محافظت کمی کرد. مدیریت حافظه در JavaScript بدون در نظر گرفتن سیستم نوع ، به شیوه های برنامه نویسی انضباطی نیاز دارد و چک های زمان کامپایل Typecript نمی تواند پاکسازی مناسب را اجرا کند.

TypeScript اشتباه انجام شده است

اجرای جزئی و متناقض Typescript شرایطی را ایجاد می کند که یک پایگاه کد تمام هزینه های TypeScript (ایجاد پیچیدگی ، منحنی یادگیری ، تعاریف نوع) را در حالی که تعداد کمی از مزایای آن را دریافت می کند ، متحمل می شود. این می تواند یک احساس امنیت کاذب ایجاد کند و اصطکاک را به توسعه اضافه کند ، در حالی که اعضای تیم وقت خود را صرف مبارزه با سیستم نوع می کنند تا اینکه از آن استفاده کنند.

شاید آشکارترین یافته ترین یافته این بود که توربو در وهله اول واقعاً TypeCript را به درستی اجرا نکرد.

کاستی های پیکربندی

پیکربندی TypeScript مورد استفاده در تلاش های قبلی اساساً ناقص بود. از tsconfig.json قبل از حذف در تعهد 0826B8152C0E97F19D459C1A1A1C364FA89CC62829

{
  "compilerOptions": {
    "esModuleInterop": true,
    "lib": [ "dom", "dom.iterable", "esnext" ],
    "module": "es2015",
    "moduleResolution": "node",
    "noUnusedLocals": true,
    "rootDir": "src",
    "strict": true,
    "target": "es2017",
    "noEmit": true,
    "removeComments": true,
    "skipLibCheck": true,
    "isolatedModules": true
  },
  "exclude": [ "dist", "src/tests/fixtures", "playwright.config.ts" ]
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

موضوعات کلیدی شامل:

  • در tsconfig.json علی رغم ادعای “سختگیرانه” ، فاقد تنظیمات سختگیرانه بود
  • نوع مهم بررسی پرچم ها مانند strictNullChecks از طریق ادعاهای نوع از لحاظ عملکردی غیرفعال شدند
  • کامپایلر پیکربندی شده بود که در عمل مجازات باشد نه سختگیرانه

دریچه فرار “هر”

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

// From stream_message.ts prior to removal
// Instead of properly typing this:
function processMessage(message: any) {
  // Access properties without type checking
  if (message.content) {
    // Type safety completely bypassed
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

// From error_renderer.ts prior to removal
function renderError(error: any) {
  // Using any to avoid properly modeling the error hierarchy
  return error.message || "Unknown error"
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

// Using any to bypass element type checking
function processNode(node: any) {
  // Directly accessing element properties without verification
  if (node.id && node.hasAttribute) {
    // This crashes if node is a Text node, not an Element
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

// Promise error handling with any type
async function handleRequest(request: any): Promise<any> {
  try {
    // Generic error handling that loses error type information
    return await fetch(request)
  } catch (error: any) {
    console.error(error.message) // May fail if error isn't an Error object
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این الگوی در سراسر پایگاه کد ظاهر شد ، و به طور موثری مزایای TypeScript را خنثی می کند.

این به معنای بحث Typescript در مقابل JavaScript چیست

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

قبل از حذف TypeScript ، یک چک در کد درست 36 نمونه از موارد را نشان داد any در قسمت پایگاه کد تایپ کنید. اینها در پرونده های مهم مانند مدیریت تاریخ ، ارائه ، درخواست HTTP و دستکاری DOM یافت شد.

TypeScript با دریچه های فرار قابل توجهی اجرا شد که مزایای ایمنی آن را تضعیف می کرد. جالب اینجاست ، در حالی که 36 مورد از آن وجود داشت any انواع ، هیچ نمونه ای از نظرات دستورالعمل وجود نداشت @ts-ignoreبا @ts-expect-error، یا @ts-nocheck در پایه کد این نشان می دهد که توسعه دهندگان در درجه اول از any به جای سرکوب خطاهای خاص با دستورالعمل ها ، به عنوان دریچه اصلی فرار خود تایپ کنید.

این الگوی اجرای Typescript را نشان می دهد که احتمالاً از تنظیمات پیکربندی شل استفاده می کند تا حالت دقیق ، با توسعه دهندگان پیش فرض any هنگام مواجهه با تایپ کردن چالش ها به جای حل صحیح مشکلات نوع. نتیجه یک پایگاه کد با اصطکاک تمام TypeScript اما تعداد کمی از مزایای ایمنی آن بود.

با این حال ، این بدان معنی نیست که TypeScript به خودی خود ناقص است. شواهد مربوط به مهاجرت توربو نشان می دهد که TypeScript می تواند ارزشمند باشد وقتی:

  1. کاملاً و به طور مداوم اجرا شده است
  2. تنظیمات کامپایلر دقیق از ابتدا فعال می شود
  3. تعاریف نوع به عنوان ابزارهای طراحی رفتار می شود ، نه پس از آن
  4. فرار فرار (any، ادعاهای نوع) به طور کم استفاده می شود

درسهایی از مهاجرت مجدد

تجربه من در بازگشت توربو به TypeScript (موجود در github.com/shiftyp/ts-turbo) چشم انداز منحصر به فردی در مورد این بحث به من داده است. با استفاده از TypeScript به درستی – با بررسی های دقیق NULL فعال ، استفاده حداقل از any، و تعاریف کامل رابط – من توانستم موضوعات متعددی را که قبلاً کشف نشده بودند ، کشف و رفع کنم.

تلاش مهاجرت مجدد چندین پیشرفت کلیدی را نشان داد که TypeScript می تواند هنگام اجرای صحیح به وجود آورد:

1. قراردادهای رابط صریح

در پایگاه اصلی کد ، رابط ها ضمنی بودند. با نگاه به اعلان session.ts پرونده ، تعاریف رابط مناسب اکنون به صراحت قرارداد بین مؤلفه ها را بیان می کند:

// From src/core/session.ts in the remigration
interface PageViewDelegate {
  allowsImmediateRender({ element }: { element: Element }, options: RenderOptions): boolean;
}

interface LinkPrefetchDelegate {
  canPrefetchRequestToLocation(link: HTMLAnchorElement, location: URL): boolean;
}

export class Session implements PageViewDelegate, LinkPrefetchDelegate, FrameRedirectorSession {
  // Implementation now contractually bound to fulfill these interfaces
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این الگوی تضمین می کند که وقتی یکی از مؤلفه ها انتظار دیگری را داشته باشد ، این قرارداد در زمان کامپایل به جای عدم موفقیت مرموز در زمان اجرا ، تأیید می شود.

ترتیب. رسیدگی به حالت جامع

ارسال فرم در توربو می تواند در ایالت های مختلف وجود داشته باشد. مهاجرت مجدد این حالتها را با استفاده از ادعاهای const Typecript به درستی تایپ می کند:

// From src/core/drive/form_submission.ts in the remigration
export const FormSubmissionState = {
  initialized: "initialized",
  requesting: "requesting",
  waiting: "waiting",
  receiving: "receiving",
  stopping: "stopping",
  stopped: "stopped"
} as const

export type FormSubmissionStateType = typeof FormSubmissionState[keyof typeof FormSubmissionState]
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

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

3. حفاظت از سازگاری مرورگر

در مهاجرت ، نیروهای Typecript چک های صریح را بررسی می کنند:

// From src/core/drive/history.ts in the remigration
assumeControlOfScrollRestoration() {
  if ("scrollRestoration" in history) {
    this.previousScrollRestoration = history.scrollRestoration
    history.scrollRestoration = "manual"
  }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

با بیان این چک ها در سیستم نوع ، دسترسی به API به طور تصادفی غیرممکن است که ممکن است در همه مرورگرها وجود نداشته باشد بدون اینکه ابتدا حضور آنها را تأیید کند.

4. ایمنی داده ها را تشکیل دهید

هنگام پردازش داده های فرم چندین مسئله با منابع تهی وجود داشت. مهاجرت بررسی صحیح تهی را اضافه می کند:

// From src/core/drive/form_submission.ts in the remigration
buildFormData(formElement: HTMLFormElement, submitter?: HTMLElement): FormData | URLSearchParams {
  const formData = new FormData(formElement)
  const name = submitter?.getAttribute("name")
  const value = submitter?.getAttribute("value")

  if (name && value && formData.get(name) !== value) {
    formData.append(name, value)
  }

  return formData
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این الگوی زنجیره ای اختیاری (?.) همراه با چک های صفر صریح تضمین می کند که پردازش فرم در برابر داده های گمشده قوی است.

پایان

مهاجرت Turbo Typescript یک چشم انداز ظریف در مورد بحث JavaScript در مقابل TypeScript ارائه می دهد. TypeScript ذاتاً خوب یا بد نیست – ارزش آن کاملاً به اجرای بستگی دارد. تلاش های مهاجرت در هنگام استفاده صحیح ، هزینه ها و مزایای Typescript را نشان داده است.

سوال این نیست که “آیا باید از TypeScript یا JavaScript استفاده کنید؟” اما “آیا شما مایل به اجرای صحیح TypeScript هستید؟” اگر می خواهید چک های تهی دقیق را غیرفعال کنید ، از آن استفاده کنید any انواع آزادانه و فقط تا حدی پایگاه کد خود را تبدیل می کنید ، ممکن است با سادگی JavaScript بهتر باشید.

اعلام DHH در مورد رها کردن TypeScript از Turbo 8 ممکن است کمتر در مورد خود TypeScript باشد و بیشتر در مورد تشخیص اینکه توربو هرگز به TypeScript فرصتی عادلانه برای شروع کار نداد. تاریخ تلاش های قبلی به جای در آغوش گرفتن آنچه که TypeScript ارائه می داد ، الگویی از اقدامات نیمه کاره و کار را نشان می دهد.

پروژه مهاجرت مجدد من رویکرد متفاوتی گرفت. من به جای اینکه TypeScript را به عنوان یک لایه یا یک لایه حاشیه نویسی نوع ساده درمان کنم ، آن را به عنوان یک ابزار طراحی اساسی ادغام کردم. با تعریف رابط های دقیق ابتدا و سپس اجرای آن در برابر آن رابط ها ، TypeScript به جای مانعی که باید در اطراف کار شود ، به یک همکار در فرآیند توسعه تبدیل شد. نتیجه یک پایگاه کد قوی تر با موارد لبه کمتری و رفتار قابل پیش بینی تر بود.

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

شاید حرکت درست از دست دادن TypeScript نباشد ، بلکه اجرای صحیح آن را اجرا می کند. یا شاید ، همانطور که DHH نشان می دهد ، خود JavaScript به اندازه کافی تکامل یافته است که برای برخی از تیم ها و پروژه ها ، شبکه ایمنی اضافی TypeScript به سادگی ارزش اصطکاک را که معرفی می کند ، ندارد.

تجربه شما چیست؟ آیا TypeScript پروژه شما را از اشکالات بحرانی نجات داده است ، یا در درجه اول اصطکاک را معرفی کرده است؟ بحث ادامه دارد ، اما امیدوارم با ظرافت بیشتر و کمتر غیرت نسبت به گذشته.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا