ایجاد پروژههای React مبتنی بر TypeScript
برای ایجاد ساختار ابتدایی پروژههای React که جهت استفادهی از TypeScript تنظیم شدهاند، دستور زیر را در خط فرمان اجرا میکنیم:
npx create-react-app tssample --template typescript
بنابراین npx create-react-app کار اجرای آخرین نسخهی برنامهی ایجاد ساختار پروژههای React را انجام میدهد. پس از آن یک نام دلخواه ذکر شدهاست و در آخر توسط سوئیچ template typescript-- سبب خواهیم شد تا این ساختار بجای استفادهی از ES6 پیشفرض، بر اساس TypeScript ایجاد و تنظیم شود.
بررسی ساختار پروژهی TypeScript ای ایجاد شده
در تصویر فوق، نمونهای از این ساختار ابتدایی ایجاد شدهی مبتنی بر TypeScript را مشاهده میکنید. اولین تفاوت مهم این ساختار، با ساختار پیشفرض پروژههای React مبتنی بر ES6، وجود فایل جدید tsconfig.json است. کار آن تنظیم پارامترهای کامپایلر TypeScript است. همچنین اینبار بجای پسوندهای js و jsx، پسوندهای ts و tsx قابل مشاهده هستند؛ مانند فایلهای serviceWorker.ts ، index.tsx و App.tsx. البته اگر به ساختار این فایلها دقت کنید، آنچنان تفاوت مهمی را با نمونههای قبلی ES6 خود ندارند و تقریبا یکی هستند. روش اجرای آنها نیز مانند قبل است و با همان دستور npm start صورت میگیرد:
قابلیت استفادهی از کدهای جاوا اسکریپتی موجود، در پروژههای تایپ اسکریپتی جدید
داخل پوشهی src، پوشهی جدید components را ایجاد کرده و داخل آن فایل جدید Head.js را اضافه میکنیم. سپس داخل آن rfac را نوشته و دکمهی tab را فشار میدهیم تا ساختار ابتدایی یک react arrow functional component جدید ایجاد شود:
import React from "react"; export const Head = () => { return ( <div> <h1>Hello</h1> </div> ); };
import { Head } from "./components/Head"; // ... function App() { return ( <div className="App"> <Head /> // ... </div> ); } export default App;
به همین جهت پس از مشاهدهی این قابلیت، پسوند فایل کامپوننت جدید js ایجاد شده را به tsx تغییر میدهیم (src\components\Head.tsx). البته در یک چنین حالتی اگر هنوز دستور npm start در حال اجرا است، نیاز خواهید داشت یکبار آنرا بسته و مجددا اجرا کنید. پس از آن، باز هم برنامه بدون مشکل کامپایل میشود و نشان دهندهی این است که کدهای نوشته شدهی در کامپوننت Head، کدهای کاملا معتبر تایپ اسکریپتی نیز هستند. علت اینجا است که TypeScript، در حقیقت Superset جاوا اسکریپت بهشمار میرود و قابلیتهای جدیدی را به TypeScript استفاده میکند. بنابراین کدهای جاوااسکریپتی موجود، کدهای معتبر تایپ اسکریپتی نیز بهشمار میروند.
مشخص کردن نوع props کامپوننتها توسط TypeScript
اولین استفادهی ما از TypeScript در اینجا، مشخص کردن نوع props یک کامپوننت است:
import React from "react"; export const Head = ({ title, isActive }) => { return ( <div> <h1>{title}</h1> {isActive && <h3>Active</h3>} </div> ); };
علت اینجا است که این فایل، tsx است و نه js. به همین جهت نوع این متغیرها را، همان حالت پیشفرض جاوا اسکریپت که any است، درنظر گرفتهاست و ... این مورد بر اساس تنظیمات فایل tsconfig.json برنامه، ممنوع است. البته اگر به این فایل دقت کنید، شاید چنین گزینهای را به صورت صریح نتوانید در آن پیدا کنید. علت اینجا است که تعداد گزینههای قابل تنظیم در فایل tsconfig روز به روز بیشتر میشوند. به همین جهت برای ساده سازی فعالسازی آنها، از TypeScript 2.3 به بعد، پرچم strict نیز به این تنظیمات اضافه شدهاست. کار آن فعالسازی یکجای تمام بررسیهای strict است؛ مانند noImplicitAny، strictNullChecks و غیره.
{ "compilerOptions": { "strict": true /* Enable all strict type-checking options. */ } }
{ "compilerOptions": { "strict": true, "noImplicitAny": false } }
بنابراین چون در فایل tsconfig.json برنامهی React ما گزینهی strict به true تنظیم شدهاست، گزینهی فعال noImplicitAny نیز جزئی از آن است و دیگر نمیتوان متغیر یا خاصیتی را بدون ذکر صریح نوع آن، رها کرد.
برای رفع خطای noImplicitAny موجود، به ابتدای فایل src\components\Head.tsx، نوع جدید Props را اضافه میکنیم (نام آن کاملا دلخواه است):
type Props = { title: string; isActive: boolean; };
export const Head = ({ title, isActive }: Props) => {
یک نکته: البته اگر از سری React 16x بخاطر داشته باشید، میتوان یک چنین قابلیتی را توسط propTypes خود React نیز پیاده سازی کرد:
Head.propTypes = { title: PropTypes.string, isActive: PropTypes.bool }
پس از این تغییر، اگر به فایل src\App.tsx مراجعه کنیم، مشاهده میکنیم که ذیل تعریف المان کامپوننت Head، مجددا خط قرمزی کشیده شدهاست:
عنوان میکند که بر اساس نوع جدید Props ای که تعریف کردهاید، نیاز است دو خاصیت اجباری title و isActive را نیز در اینجا ذکر کنید؛ وگرنه تعریف این المان، بدون آنها ناقص است.
امکان جالب دیگری که با تعریف نوع props توسط تایپاسکریپت رخ میدهد، فعال شدن intellisense متناظر با تعریف این خواص و ویژگیها است:
در ادامه با تعریف این دو ویژگی جدید، خط قرمز رنگ ذیل کامپوننت Head برطرف میشود:
<Head title="Hello" isActive={true} />
همچنین در این حالت، کد برنامه نیز کامپایل نمیشود و ذکر این خطاها صرفا منحصر به ادیتور مورد استفاده نیست.
بنابراین به صورت خلاصه مزیتهای کار با TypeScript برای تعاریف نوع props به شرح زیر است:
- auto-complete و داشتن intellisense خودکار.
- اگر نام المان کامپوننتی و یا نام یکی از props را به اشتباه وارد کنیم، بلافاصله خطای یافت نشدن آنها را نمایش میدهد.
- اگر ذکر یک prop اجباری را فراموش کنیم، بلافاصله خطای متناظری را دریافت میکنیم.
- اگر نوع مقدار یکی از props را به اشتباه وارد کنیم، باز هم خطایی را جهت گوشزد کردن آن مشاهده خواهیم کرد.
- فعال بودن TypeScript، امکان refactoring بسیار قویتری را میسر میکند. برای مثال با فشردن دکمهی F2 میتوان نام یک کامپوننت را در کل برنامه به سادگی تغییر داد. همچنین یک چنین قابلیتی برای تغییر نام props نیز میسر است و به صورت خودکار تمام کاربردهای آنرا نیز به روز میکند.
- اگر نوع prop ای را در تعریف آن تغییر دادیم، اما مقدار منتسب به آنرا خیر، باز هم بلافاصله متوجه این مشکل خواهیم شد.
به این ترتیب با دسترسی به بررسیهای دقیق زمان کامپایل برنامه، میتوان مشکلات بسیار کمتری را در زمان اجرای آن شاهد بود.
پس از به روز رسانی کامپایلر تایپاسکریپت با دستور npm install -g typescript، اگر دستور tsc --init را در یک پوشهی جدید اجرا کنید، سبب تولید یک فایل tsconfig.json از پیش تنظیم شده، بر اساس آخرین قابلیتهای کامپایلر TypeScript میشود. برای مثال تنظیمات ذیل قسمتی از تنظیمات نگارش 2.7.1 است و برای نمونه strictPropertyInitialization به آن اضافه شدهاست:
/* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
به لطف استاندارد مدرن و هنوز فراگیر نشدهی WebAssembly ، امروزه همهی مرورگرهای مدرن میتوانند بجای اجرای جاوا اسکریپت، یک زبان bytecode استانداردِ سطح پایین و شبیه به زبان اسمبلی را اجرا کنند. استفاده از WebAssembly میتواند موجب اجرای سریعتر کد و کاهش حجم آن شود. اما مهمترین مزیت این هست که امروز میتوانیم همهی زبانهای قدرتمند، نظیر سی شارپ را به نحوی کامپایل کنیم که خروجیِ نهایی، منطبق با استاندارد webassembly باشد و به صورت native در مرورگرها، دات نت را اجرا کنیم.
کامپایل سی شارپ به WebAssembly توسط تیم Mono مایکروسافت انجام شده و عمده مشکلات فنی سر راه برداشته شدهاند. اما برای اینکه عملا بشود از دات نت در مرورگرها استفاده کرد، مایکروسافت در پی پیاده سازی پروژهی جاه طلبانهای به نام Blazor میباشد. در واقع Blazor فریم ورک Client-Side مبتنی بر دات نت خواهد بود؛ الهام گرفته از فریم ورکهای کنونی (مانند Angular و React) و رقیبی جدید برای آنها. فریم ورک Blazor هم مانند آنها حول مفهوم Component شکل گرفتهاست. کامپوننتهایی که کلاسهای سی شارپی هستند و با زبان Razor توسعه داده شدهاند.
استفاده از دات نت در مرورگرها میتواند موجب این شود که کد بیشتری را بین سرور و کلاینت بتوانیم به اشتراک بگذاریم و نیاز به دوباره کاری در هر دو سمت را نداشته باشیم. علاوه بر این توسعه دهندگان سی شارپ کمی بیشتر به مفهوم Full Stack Developer نزدیک خواهند شد.
همچنین با استفاده از WebAssembly میتوانیم به تمام کتابخانههای موجود جاوااسکریپتی هم دسترسی داشته باشیم و محدودیتی در این زمینه وجود ندارد. همچنین میتوان DOM را هم از این طریق مدیریت و دستکاری کرد.
در حال حاضر تیم AspNet عهده دار کار بر روی پروژهی Blazor شدهاست. از نوشتههای آنها چنین بر میآید که تا نهایی شدن این پروژه هنوز باید صبر کنیم.
به همراه نگارش Angular 4.3، روش جدیدی برای کار با HTTP، توسط ماژول جدید HTTP Client آن ارائه شدهاست که ساختار آن بسیار شبیه به ماژول فعلی HTTP آن است و کدهای فعلی را به سادگی میتوان به آن انتقال داد. یکی از تغییرات آن داشتن رخدادهای درصد آپلود و دانلود یک درخواست است:
import { HttpEventType, HttpClient, HttpRequest } from '@angular/common/http'; http.request(new HttpRequest( 'POST', URL, body, { reportProgress: true })).subscribe(event => { if (event.type === HttpEventType.DownloadProgress) { } if (event.type === HttpEventType.UploadProgress) { } if (event.type === HttpEventType.Response) { console.log(event.body); } })
برای مطالعهی بیشتر:
The Angular HTTP Client - Quickstart Guide
A Taste From The New Angular HTTP Client
راهاندازی Http Interceptor در Angular
به همراه نگارش Angular 4.3، روش جدیدی برای کار با HTTP، توسط ماژول جدید HTTP Client آن ارائه شدهاست که ساختار آن بسیار شبیه به ماژول فعلی HTTP آن است و کدهای فعلی را به سادگی میتوان به آن انتقال داد. یکی از تغییرات آن داشتن HttpInterceptor به صورت توکار است:
import { HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; @Injectable() class JWTInterceptor implements HttpInterceptor { constructor(private userService: UserService) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const JWT = `Bearer ${this.userService.getToken()}`; req = req.clone({ setHeaders: { Authorization: JWT } }); return next.handle(req); } }
برای مطالعهی بیشتر:
The Angular HTTP Client - Quickstart Guide
A Taste From The New Angular HTTP Client
AngularJS #1
تمام توسعه دهندههای JavaScript با واژهی کلیدی var آشنایی دارند؛ اما TypeScript واژههای کلیدی let و const را نیز اضافه کردهاست (که جزئی از ES 6 نیز میباشند). تفاوت مهم بین var و let، در میدان دید متغیرهای تعریف شدهی توسط آنها خلاصه میشود. پیشتر در سری مباحث بررسی ES 6، مطلب «متغیرها در ES 6» را نیز بررسی کردیم که در TypeScript نیز صادق میباشند؛ با این تفاوت که TypeScript را میتوان به ES 5 نیز کامپایل کرد و بدون مشکل با تمام مرورگرهای موجود، اجرا نمود.
- متغیرهایی که با var تعریف میشوند، به صورت سراسری در متدی که تعریف شدهاند، قابل دسترسی هستند؛ حتی اگر 5 سطح داخل ifهای تو در تو تعریف شده باشند. اما let و const تنها در block و قطعهای که معرفی شدهاند، معتبر بوده و خارج از آن تعریف نشدهاند. در اینجا یک block توسط {} معرفی میشود.
- متغیرهای از نوع var به دلیل مفهومی به نام hoisting توسط runtime جاوا اسکریپت، به بالاترین سطح متد منتقل میشوند. به همین دلیل عمق تعریف آنها، اثری در دسترسی به این متغیرها ندارد. اما hoisting در مورد let و const اعمال نمیشود.
- متغیرهای var را میتوان چندبار مجددا تعریف کرد (هرچند این روش توصیه نمیشود؛ اما مجاز است). یک چنین تعریف مجددی با متغیرهای از نوع let و const مجاز نیست.
برای توضیحات بیشتر به مثال ذیل دقت کنید:
function ScopeTest() { if (true) { var foo = 'use anywhere'; let bar = 'use in this block'; } console.log(foo); // works! console.log(bar); // error! }
نوعهای پایهی TypeScript
نوعهای پایهی TypeScript شامل موارد ذیل هستند:
Boolean: برای ذخیره سازی true یا false.
let isDone: boolean = false;
let decimal: number = 6; let hex: number = 0xf00d; let binary: number = 0b1010; let octal: number = 0o744;
let name: string = "bob"; name = 'smith';
let list: number[] = [1, 2, 3];
enum Color {Red, Green, Blue}; let c: Color = Color.Green;
let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean
function warnUser(): void { alert("This is my warning message"); }
یک نکته: قابلیت Template string در ES 6 نیز در TypeScript پشتیبانی میشود.
مفهوم Type Inference در TypeScript
در TypeScript الزاما نیازی نیست تا نوع متغیری را صریحا مشخص کرد. در اینجا اگر نوع متغیری را در ابتدای کار تعریف نکنید، نوع آن در اولین باری که مقدار دهی میشود، مشخص خواهد شد که به آن Type Inference نیز میگویند.
let myString= 'this is a string'; myString= 42; // error!
و یا در مثال ذیل، نوع خروجی متد ReturnNumber به صورت صریح مشخص نشدهاست:
function ReturnNumber() { return 42; } let anotherString = 'this is also a string'; anotherString = ReturnNumber(); // error!
تعریف صریح نوعها در TypeScript با استفاده از Type Annotations
برای لغو Type Inference و تعیین صریح نوعها در TypeScript میتوان به صورت زیر عمل کرد:
let myString : string = 'this is a string'; myString = 42; // error! function ReturnNumber() : number { return 42; } let anotherString : string = 'this is also a string'; anotherString = ReturnNumber(); // error!
نوعهای شمارشی در TypeScript
Enums در بسیاری از زبانهای برنامه نویسی متداول وجود دارند و هدف از آنها، دادن نامهایی بهتر و مشخص، به مجموعهای از مقادیر است:
enum Category { Biography, Poetry, Fiction }; // 0, 1, 2
اگر میخواهید این مقادیر با اعداد دیگری شروع شوند (بجای صفر پیش فرض)، میتوان مقدار اولین نام را به صورت صریحی مشخص کرد:
enum Category { Biography = 1, Poetry, Fiction }; // 1, 2, 3
enum Category{ Biography = 5, Poetry = 8, Fiction = 9 }; // 5, 8, 9
let favoriteCategory: Category = Category.Biography;
در این حالت اگر مقدار favoriteCategory را چاپ کنیم، خروجی عددی 5 نمایش داده میشود:
console.log(favoriteCategory); // 5
let categoryString= Category[favoriteCategory]; // Biography
آرایهها در TypeScript
در حالت عمومی، آرایهها در TypeScript همانند جاوا اسکریپت تعریف میشوند؛ البته به همراه تعدادی استثناء. در TypeScript سه روش برای تعریف آرایهها وجود دارند:
الف) در مثال زیر آرایهای از رشتهها تعریف شدهاست. در اینجا نوع آرایه به همراه [] مشخص میشود:
let strArray1: string[] = ['here', 'are', 'strings'];
let strArray2: Array<string> = ['more', 'strings', 'here'];
let anyArray: any[] = [42, true, 'banana'];
نوع Tuples در TypeScript
Tuples در TypeScript نوع خاصی از آرایهها هستند که نوع مقادیر اعضای آنها به صورت صریح مشخص میشوند:
let myTuple: [number, string] = [25, 'truck'];
اکنون برای دسترسی به مقادیر این المانها، همانند کار با آرایههای معمولی، از ایندکسهای آرایهی تعریف شده استفاده میشود:
let firstElement= myTuple[0]; // 25 let secondElement= myTuple[1]; // truck
یک نکته: میتوان به آرایهی تعریف شده، عناصر جدیدی را نیز افزود؛ با این شرط که نوع آنها، یکی از نوعهای مشخص شدهی در تعریف Tuple باشند:
// other elements can have numbers or strings myTuple[2] = 100; myTuple[2] = 'this works!';
مفهوم Type assertions در TypeScript
حتما با مفهوم cast و تبدیل نوعهای مختلف به یکدیگر، در زبانهای دیگر برنامه نویسی آشنا هستید. در TypeScript نیز این مفهوم تحت عنوان Type assertions پشتیبانی میشود و دو روش برای تعریف آن وجود دارد:
الف) تعریف cast توسط angle-bracket syntax که در آن نوع مدنظر داخل یک <> قرار میگیرد:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
ب) تعریف cast توسط as syntax به نحو ذیل:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
Microsoft.DotNet.Web.Spa.ProjectTemplates در آخرین نگارش آن، پشتیبانی از Angular CLI را هم افزودهاست. برای کار با آن و ایجاد یک پروژهی جدید بر مبنای آن دستورات ذیل را صادر کنید:
> dotnet new --install Microsoft.DotNet.Web.Spa.ProjectTemplates::2.0.0-preview1-final > dotnet new angular
توضیحات بیشتر:
About The Updated SPA Templates From ASP.NET Core
Migrating from the old ASP.NET Core Angular Spa template to the newer one