تایپ اسکریپت یا جاوا اسکریپت؟ توسعهدهندگان این انتخاب را برای پروژههای گرینفیلد وب یا Node.js در نظر میگیرند، اما برای پروژههای موجود نیز باید به آن توجه کرد. مجموعه ای از جاوا اسکریپت، TypeScript همه ویژگی های جاوا اسکریپت را به همراه برخی امتیازات اضافی ارائه می دهد. TypeScript ذاتاً ما را به کدنویسی تمیز تشویق می کند و کد را مقیاس پذیرتر می کند. با این حال، پروژه ها می توانند هر چقدر که ما دوست داریم حاوی جاوا اسکریپت ساده باشند، بنابراین استفاده از TypeScript یک پیشنهاد همه یا هیچ نیست.
رابطه بین TypeScript و JavaScript
TypeScript یک سیستم نوع صریح را به جاوا اسکریپت اضافه می کند که امکان اجرای دقیق انواع متغیر را فراهم می کند. TypeScript بررسی های نوع خود را در حالی اجرا می کند انتقال دادن— شکلی از کامپایل که کد TypeScript را به کد جاوا اسکریپت تبدیل می کند که مرورگرهای وب و Node.js آن را درک می کنند.
نمونه های TypeScript در مقابل جاوا اسکریپت
بیایید با یک قطعه جاوا اسکریپت معتبر شروع کنیم:
let var1 = "Hello";
var1 = 10;
console.log(var1);
اینجا، var1
به عنوان یک شروع می شود string
، سپس تبدیل به a می شود number
.
از آنجایی که جاوا اسکریپت فقط به صورت آزاد تایپ می شود، می توانیم دوباره تعریف کنیم var1
به عنوان یک متغیر از هر نوع – از رشته تا تابع – در هر زمان.
اجرای این کد خروجی می دهد 10
.
حالا بیایید این کد را به TypeScript تغییر دهیم:
let var1: string = "Hello";
var1 = 10;
console.log(var1);
در این صورت ما اعلام می کنیم var1
بودن یک string
. سپس سعی می کنیم یک عدد به آن اختصاص دهیم، که توسط سیستم نوع سخت گیر TypeScript مجاز نیست. Transpiling منجر به خطا می شود:
TSError: ⨯ Unable to compile TypeScript:
src/snippet1.ts:2:1 - error TS2322: Type 'number' is not assignable to type 'string'.
2 var1 = 10;
اگر بخواهیم به ترانسپایلر دستور دهیم که با قطعه اصلی جاوا اسکریپت به گونه ای رفتار کند که گویی TypeScript است، ترانسپایلر به طور خودکار نتیجه می گیرد که var1
باید یک باشد string | number
. این یک TypeScript است نوع اتحادیه، که به ما امکان تخصیص می دهد var1
آ string
یا الف number
هروقت. پس از حل تعارض نوع، کد TypeScript ما با موفقیت ترجمه می شود. اجرای آن نتیجه ای مشابه با مثال جاوا اسکریپت خواهد داشت.
TypeScript در مقابل جاوا اسکریپت از 30000 فوت: چالش های مقیاس پذیری
جاوا اسکریپت همه جا حاضر است و پروژه هایی در همه اندازه ها را تقویت می کند که به روش هایی استفاده می شود که در دوران ابتدایی آن در دهه 1990 غیرقابل تصور بود. در حالی که جاوا اسکریپت بالغ شده است، اما در مورد پشتیبانی از مقیاس پذیری کوتاهی می کند. بر این اساس، توسعه دهندگان با برنامه های جاوا اسکریپت دست و پنجه نرم می کنند که هم از نظر بزرگی و هم از نظر پیچیدگی رشد کرده اند.
خوشبختانه، TypeScript به بسیاری از مسائل مربوط به مقیاس پروژه های جاوا اسکریپت می پردازد. ما بر روی سه چالش اصلی تمرکز خواهیم کرد: اعتبار سنجی، بازسازی مجدد و مستندسازی.
اعتبار سنجی
ما به محیطهای توسعه یکپارچه (IDE) برای کمک به کارهایی مانند افزودن، اصلاح و آزمایش کد جدید متکی هستیم، اما IDEها نمیتوانند منابع جاوا اسکریپت خالص را تأیید کنند. ما این نقص را با نظارت دقیق هنگام کدنویسی کاهش می دهیم تا از احتمال اشتباهات املایی در متغیرها و نام توابع جلوگیری کنیم.
زمانی که کد از یک شخص ثالث سرچشمه می گیرد، بزرگی این مسئله به طور تصاعدی افزایش می یابد، جایی که مراجع شکسته در شاخه های کد اجرا شده به ندرت می توانند به راحتی شناسایی نشوند.
در مقابل، با TypeScript، میتوانیم تلاشهای خود را بر روی کدنویسی متمرکز کنیم، با اطمینان از اینکه هر گونه خطا در زمان transpile شناسایی میشود. برای نشان دادن این موضوع، اجازه دهید با چند کد جاوا اسکریپت قدیمی شروع کنیم:
const moment = require('moment');
const printCurrentTime = (format) => {
if (format === 'ISO'){
console.log("Current ISO TS:", moment().toISO());
} else {
console.log("Current TS: ", moment().format(format));
}
}
را .toISO()
call یک اشتباه تایپی از moment.js است toISOString()
روش اما کد کار می کند، به شرطی که format
استدلال نیست ISO
. اولین باری که سعی می کنیم پاس کنیم ISO
به تابع، این خطای زمان اجرا را افزایش می دهد: TypeError: moment(...).toISO is not a function
.
پیدا کردن کد غلط املایی ممکن است دشوار باشد. پایگاه کد فعلی ممکن است مسیری به خط شکسته نداشته باشد، در این صورت ما شکسته است .toISO()
مرجع با آزمایش دستگیر نمی شود.
اگر این کد را به TypeScript منتقل کنیم، IDE مرجع شکسته را برجسته میکند و از ما میخواهد اصلاحاتی را انجام دهیم. اگر کاری انجام ندهیم و سعی در ترانسپایل کردن داشته باشیم، مسدود می شویم و ترانسپایلر خطای زیر را ایجاد می کند:
TSError: ⨯ Unable to compile TypeScript:
src/catching-mistakes-at-compile-time.ts:5:49 - error TS2339: Property 'toISO' does not exist on type 'Moment'.
5 console.log("Current ISO TS:", moment().toISO());
Refactoring
در حالی که اشتباهات تایپی در مراجع کد شخص ثالث غیر معمول نیست، مجموعه متفاوتی از مشکلات مرتبط با اشتباهات تایپی در مراجع داخلی وجود دارد، مانند این:
const myPhoneFunction = (opts) => {
// ...
if (opts.phoneNumbr)
doStuff();
}
یک توسعهدهنده انحصاری میتواند همه نمونههای آن را پیدا و برطرف کند phoneNumbr
برای پایان دادن به er
به راحتی به اندازه کافی
اما هر چه تیم بزرگتر باشد، این اشتباه ساده و رایج به طور غیرمنطقی هزینه بر است. در حین انجام کار، همکاران باید از این گونه اشتباهات تایپی آگاه و منتشر کنند. از طرف دیگر، افزودن کد برای پشتیبانی از هر دو املا، پایگاه کد را به طور غیرضروری خراب می کند.
با TypeScript، هنگامی که ما یک اشتباه تایپی را برطرف می کنیم، کد وابسته دیگر ترجمه نمی شود، و به همکاران سیگنال می دهد که اصلاح را در کد خود منتشر کنند.
مستندات
مستندات دقیق و مرتبط کلید برقراری ارتباط درون و بین تیم های توسعه دهندگان است. توسعه دهندگان جاوا اسکریپت اغلب از JSDoc برای مستندسازی انواع روش ها و ویژگی های مورد انتظار استفاده می کنند.
ویژگی های زبان TypeScript (به عنوان مثال، کلاس های انتزاعی، رابط ها، و تعاریف نوع) برنامه نویسی طراحی به قرارداد را تسهیل می کند و منجر به مستندات با کیفیت می شود. علاوه بر این، داشتن یک تعریف رسمی از روشها و ویژگیهایی که یک شی باید به آنها پایبند باشد، به شناسایی تغییرات شکسته، ایجاد آزمایشها، انجام درونبینی کد و پیادهسازی الگوهای معماری کمک میکند.
برای TypeScript، ابزار رفتن به TypeDoc (بر اساس TSDoc پیشنهاد) به طور خودکار اطلاعات نوع (مانند کلاس، رابط، متد و ویژگی) را از کد ما استخراج می کند. بنابراین، ما بدون زحمت اسنادی را ایجاد می کنیم که بسیار جامع تر از JSDoc است.
مزایای TypeScript در مقابل JavaScript
اکنون، بیایید بررسی کنیم که چگونه می توانیم از TypeScript برای رسیدگی به این چالش های مقیاس پذیری استفاده کنیم.
کد پیشرفته / پیشنهادات بازآفرینی
بسیاری از IDE ها می توانند اطلاعات را از سیستم نوع TypeScript پردازش کنند و اعتبار مرجع را هنگام کدگذاری ارائه می دهند. حتی بهتر است، همانطور که ما تایپ می کنیم، IDE می تواند مستندات مرتبط و در یک نگاه (به عنوان مثال، آرگومان هایی که یک تابع انتظار دارد) را برای هر مرجع ارائه دهد و نام متغیرهای صحیح را به صورت متنی پیشنهاد کند.
در این قطعه TypeScript، IDE تکمیل خودکار نام کلیدهای داخل مقدار بازگشتی تابع را پیشنهاد می کند:
/**
* Simple function to parse a CSV containing people info.
* @param data A string containing a CSV with 3 fields: name, surname, age.
*/
const parsePeopleData = (data: string) => {
const people: {name: string, surname: string, age: number}[] = [];
const errors: string[] = [];
for (let row of data.split('\n')){
if (row.trim() === '') continue;
const tokens = row.split(',').map(i => i.trim()).filter(i => i != '');
if (tokens.length < 3){
errors.push(`Row "${row}" contains only ${tokens.length} tokens. 3 required`);
continue;
}
people.push({ name: tokens[0], surname: tokens[1], age: +tokens[2] })
}
return {people, errors};
};
const exampleData = `
Gordon,Freeman,27
G,Man,99
Alyx,Vance,24
Invalid Row,,
Again, Invalid
`;
const result = parsePeopleData(exampleData);
console.log("Parsed People:");
console.log(result.people.
map(p => `Name: ${p.name}\nSurname: ${p.surname}\nAge: ${p.age}`)
.join('\n\n')
);
if (result.errors.length > 0){
console.log("\nErrors:");
console.log(result.errors.join('\n'));
}
وقتی شروع به فراخوانی تابع کردم (خط 31)، IDE من، کد ویژوال استودیو، این پیشنهاد را (در فراخوانی) ارائه کرد:
علاوه بر این، پیشنهادات تکمیل خودکار IDE (در فراخوانی) از نظر زمینه درست هستند و فقط نامهای معتبر را در یک موقعیت کلید تودرتو نشان میدهند (خط 34):
چنین پیشنهادهای بلادرنگ منجر به کدنویسی سریعتر می شود. علاوه بر این، IDEها میتوانند برای بازسازی کد در هر مقیاسی به اطلاعات نوع دقیق TypeScript تکیه کنند. عملیاتی مانند تغییر نام یک ویژگی، تغییر مکان فایل، یا حتی استخراج یک سوپرکلاس زمانی که ما 100% از صحت مراجع خود اطمینان داریم، بی اهمیت می شوند.
پشتیبانی از رابط
برخلاف جاوا اسکریپت، TypeScript توانایی تعریف انواع با استفاده از آن را ارائه می دهد رابط ها. یک رابط به طور رسمی روش ها و ویژگی هایی را که یک شی باید شامل شود لیست می کند – اما اجرا نمی کند. این ساختار زبان به ویژه برای همکاری با توسعه دهندگان دیگر مفید است.
مثال زیر نشان میدهد که چگونه میتوانیم از ویژگیهای TypeScript برای پیادهسازی دقیق الگوهای OOP رایج استفاده کنیم—در این مورد، استراتژی و زنجیره مسئولیت– به این ترتیب مثال قبلی بهبود می یابد:
export class PersonInfo {
constructor(
public name: string,
public surname: string,
public age: number
){}
}
export interface ParserStrategy{
/**
* Parse a line if able.
* @returns The parsed line or null if the format is not recognized.
*/
(line: string): PersonInfo | null;
}
export class PersonInfoParser{
public strategies: ParserStrategy[] = [];
parse(data: string){
const people: PersonInfo[] = [];
const errors: string[] = [];
for (let row of data.split('\n')){
if (row.trim() === '') continue;
let parsed;
for (let s of this.strategies){
parsed = s(row);
if (parsed) break;
}
if (!parsed){
errors.push(`Unable to find a strategy capable of parsing "${row}"`);
} else {
people.push(parsed);
}
}
return {people, errors};
}
}
const exampleData = `
Gordon,Freeman,27
G;Man;99
{"name":"Alyx", "surname":"Vance", "age":24}
Invalid Row,,
Again, Invalid
`;
const parser = new PersonInfoParser();
const createCSVStrategy = (fieldSeparator=","): ParserStrategy => (line) => {
const tokens = line.split(fieldSeparator).map(i => i.trim()).filter(i => i != '');
if (tokens.length < 3) return null;
return new PersonInfo(tokens[0], tokens[1], +tokens[2]);
};
parser.strategies.push(
(line) => {
try {
const {name, surname, age} = JSON.parse(line);
return new PersonInfo(name, surname, age);
} catch(err){
return null;
}
},
createCSVStrategy(),
createCSVStrategy(';')
);
const result = parser.parse(exampleData);
console.log("Parsed People:");
console.log(result.people.
map(p => `Name: ${p.name}\nSurname: ${p.surname}\nAge: ${p.age}`)
.join('\n\n')
);
if (result.errors.length > 0){
console.log("\nErrors:");
console.log(result.errors.join('\n'));
}
ماژولهای ES6—هرجا
در زمان نگارش این مقاله، همه زمانهای اجرا JavaScript front-end و back-end از ماژولهای ES6 پشتیبانی نمیکنند. با این حال، با TypeScript، می توانیم از نحو ماژول ES6 استفاده کنیم:
import * as _ from 'lodash';
export const exampleFn = () => console.log(_.reverse(['a', 'b', 'c']));
خروجی ترانسفیل شده با محیط انتخابی ما سازگار خواهد بود. مثلا با استفاده از گزینه کامپایلر --module CommonJS
، ما گرفتیم:
"use strict";
exports.__esModule = true;
exports.exampleFn = void 0;
var _ = require("lodash");
var exampleFn = function () { return console.log(_.reverse(['a', 'b', 'c'])); };
exports.exampleFn = exampleFn;
استفاده كردن --module UMD
در عوض، TypeScript الگوی پرمخاطبتر UMD را خروجی میدهد:
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "lodash"], factory);
}
})(function (require, exports) {
"use strict";
exports.__esModule = true;
exports.exampleFn = void 0;
var _ = require("lodash");
var exampleFn = function () { return console.log(_.reverse(['a', 'b', 'c'])); };
exports.exampleFn = exampleFn;
});
کلاس های ES6 – در هر کجا
محیط های قدیمی اغلب از کلاس های ES6 پشتیبانی نمی کنند. Transpile TypeScript با استفاده از ساختارهای خاص هدف، سازگاری را تضمین می کند. در اینجا یک قطعه منبع TypeScript آمده است:
export class TestClass {
hello = 'World';
}
خروجی جاوا اسکریپت به هر دو بستگی دارد ماژول و هدف، که TypeScript به ما اجازه می دهد آن را مشخص کنیم.
در اینجا چیست --module CommonJS --target es3
بازده – محصول:
"use strict";
exports.__esModule = true;
exports.TestClass = void 0;
var TestClass = /** @class */ (function () {
function TestClass() {
this.hello = 'World';
}
return TestClass;
}());
exports.TestClass = TestClass;
استفاده كردن --module CommonJS --target es6
در عوض، ما نتیجه ترانسفیل شده زیر را دریافت می کنیم. را class
کلمه کلیدی برای هدف قرار دادن ES6 استفاده می شود:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestClass = void 0;
class TestClass {
constructor() {
this.hello = 'World';
}
}
exports.TestClass = TestClass;
قابلیت Async/Await – در هر کجا
Async/wait درک و نگهداری کد جاوا اسکریپت ناهمزمان را آسانتر میکند. TypeScript این قابلیت را برای همه زمانهای اجرا ارائه میکند، حتی برای آنهایی که به صورت بومی async/wait را ارائه نمیدهند.
توجه داشته باشید که برای اجرای async/wait در زمانهای اجرا قدیمیتر مانند ES3 و ES5، به پشتیبانی خارجی نیاز دارید Promise
خروجی مبتنی بر (به عنوان مثال، از طریق Bluebird یا ES2015 polyfill). را Promise
polyfill که با TypeScript ارسال می شود به راحتی در خروجی ترانسفیل شده ادغام می شود – فقط باید پیکربندی lib
بر این اساس گزینه کامپایلر
پشتیبانی از فیلدهای کلاس خصوصی – در هر کجا
حتی برای اهداف قدیمی، TypeScript پشتیبانی می کند private
فیلدها تقریباً مانند زبانهای تایپ شده قوی (مانند جاوا یا سی شارپ). در مقابل، بسیاری از زمان های اجرا جاوا اسکریپت پشتیبانی می کنند private
زمینه ها از طریق پیشوند هش نحو، که یک پیشنهاد تمام شده از ES2022.
معایب TypeScript در مقابل JavaScript
اکنون که مزایای اصلی پیادهسازی TypeScript را برجسته کردهایم، بیایید سناریوهایی را بررسی کنیم که در آن TypeScript ممکن است مناسب نباشد.
Transpilation: بالقوه برای ناسازگاری گردش کار
گردش کار یا الزامات پروژه خاص ممکن است با مرحله انتقال TypeScript ناسازگار باشد: به عنوان مثال، اگر ما نیاز به استفاده از یک ابزار خارجی برای تغییر کد پس از استقرار داشته باشیم یا اگر خروجی تولید شده باید مناسب توسعهدهندگان باشد.
به عنوان مثال، من اخیراً یک تابع AWS Lambda برای یک محیط Node.js نوشتم. TypeScript مناسب نبود زیرا نیاز به ترجمه باعث میشود من و سایر اعضای تیم نتوانیم عملکرد را با استفاده از ویرایشگر آنلاین AWS ویرایش کنیم. این برای مدیر پروژه یک معامله شکنی بود.
نوع سیستم فقط تا زمان Transpile کار می کند
خروجی جاوا اسکریپت TypeScript حاوی اطلاعات نوع نیست، بنابراین بررسی نوع را انجام نمی دهد و بنابراین، ایمنی نوع ممکن است در زمان اجرا خراب شود. به عنوان مثال، فرض کنید تابعی تعریف شده است که همیشه یک شی را برمی گرداند. اگر null
از استفاده آن در یک باز می گردد .js
فایل، یک خطای زمان اجرا رخ خواهد داد.
تایپ کردن ویژگیهای وابسته به اطلاعات (مثلاً فیلدهای خصوصی، رابطها یا ژنریکها) به هر پروژه ارزش میافزاید اما در حین انتقال از بین میرود. مثلا، private
اعضای کلاس دیگر پس از ترجمه خصوصی نخواهند بود. برای روشن بودن، مسائل زمان اجرا با این ماهیت منحصر به TypeScript نیستند و میتوانید انتظار داشته باشید که با مشکلات مشابهی در جاوا اسکریپت نیز مواجه شوید.
ترکیب TypeScript و JavaScript
با وجود مزایای فراوان TypeScript، گاهی اوقات نمی توانیم تبدیل کل یک پروژه جاوا اسکریپت را به یکباره توجیه کنیم. خوشبختانه، ما میتوانیم به ترانسپایلر TypeScript – بر اساس فایل به فایل – مشخص کنیم که چه چیزی به عنوان جاوا اسکریپت ساده تفسیر شود. در واقع، این رویکرد ترکیبی میتواند به کاهش چالشهای فردی که در طول چرخه عمر پروژه ایجاد میشوند، کمک کند.
ممکن است ترجیح دهیم جاوا اسکریپت را بدون تغییر بگذاریم اگر کد:
- توسط یک همکار سابق نوشته شده است و برای تبدیل به TypeScript به تلاش های مهندسی معکوس قابل توجهی نیاز دارد.
- از تکنیک(های) غیر مجاز در TypeScript استفاده می کند (به عنوان مثال، یک ویژگی پس از نمونه سازی شی اضافه می کند) و برای پایبندی به قوانین TypeScript نیاز به refactoring دارد.
- متعلق به تیم دیگری است که همچنان از جاوا اسکریپت استفاده می کند.
در چنین مواردی، الف اعلام فایل (.d.ts
فایل، که گاهی اوقات فایل تعریف یا فایل تایپ نامیده میشود) به TypeScript دادههای نوع کافی میدهد تا پیشنهادات IDE را فعال کند در حالی که کد جاوا اسکریپت را همانطور که هست باقی میگذارد.
بسیاری از کتابخانههای جاوا اسکریپت (مثلا Lodash، Jest و React) فایلهای تایپ TypeScript را در بستههای نوع جداگانه ارائه میکنند، در حالی که بقیه (مثلاً Moment.js، Axios و Luxon) فایلهای تایپ را در بسته اصلی ادغام میکنند.
تایپ اسکریپت در مقابل جاوا اسکریپت: سوالی در مورد ساده سازی و مقیاس پذیری
پشتیبانی بیرقیب، انعطافپذیری و پیشرفتهایی که از طریق TypeScript در دسترس است، بهطور قابلتوجهی تجربه توسعهدهنده را بهبود میبخشد و پروژهها و تیمها را قادر میسازد تا مقیاس شوند. هزینه اصلی گنجاندن TypeScript در یک پروژه، اضافه کردن مرحله ساخت transpilation است. برای اکثر برنامه ها، انتقال به جاوا اسکریپت مشکلی نیست. بلکه پله ای برای مزایای فراوان TypeScript است.