اشتراکها
اشتراکها
معرفی NET Aspire.
اشتراکها
نگران Angular 3 نباشید
in Angular 1.x we had this:
- Angular 1.0 - major version
- Angular 1.1 - major version (well, more a preview of Angular 1.2)
- Angular 1.2 - major version
- Angular 1.3 - major version (dropped IE8 support)
- Angular 1.4 - major version
- Angular 1.5 - major version
In “Angular 2”, you’re looking at this:
- Angular 2 - major version
- Angular 3 - major version
- Angular 4 - major version
- Angular 5 - major version
- Angular 6 - major version
- Angular 7 - major version
اشتراکها
اجرای دات نت در مروگر توسط Ooui
Earlier this year, Microsoft announced their support for Blazor, and now Frank A. Krueger has developed the Ooui library which allows C# or F# to be used to write applications that run in the browser. Ooui can target WASM, enabling Xamarin.Forms app to be deployed in web assembly and the result runs entirely in-browser without the need for an application server.
تقریبا تمام برنامهها نیاز دارند فرمهای مخصوصی را داشته باشند. به همین جهت در این قسمت، برنامهی نمایش لیست فیلمها را که تا این مرحله تکمیل کردیم، با افزودن تعدادی فرم بهبود میبخشیم؛ مانند فرم لاگین، فرم ثبت نام، فرمی برای ثبت و ویرایش فیلمها و یک فرم جستجوی سریع در لیست فیلمهای موجود.
ایجاد فرم لاگین
فرم لاگینی را که به برنامهی نمایش لیست فیلمهای تکمیل شدهی تا قسمت 17، اضافه خواهیم کرد، یک فرم بوت استرپی است و میتوانید جزئیات بیشتر مزین سازی المانهای این نوع فرمها را با کلاسهای بوت استرپ، در مطلب «کار با شیوهنامههای فرمها در بوت استرپ 4» مطالعه کنید.
در ابتدا فایل جدید src\components\loginForm.jsx را ایجاد کرده و سپس توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت جدید LoginForm را ایجاد میکنیم:
در ادامه یک Route جدید را در فایل app.js برای این فرم، با مسیر login/ و کامپوننت LoginForm، در ابتدای Switch موجود، تعریف میکنیم:
پس از تعریف این مسیریابی، نیاز است لینک آنرا نیز به منوی راهبری سایت اضافه کنیم. به همین جهت در فایل navBar.jsx که آنرا در قسمت قبل تکمیل کردیم، در انتهای لیست موجود و پس از Rentals، لینک لاگین را نیز قرار میدهیم:
که در نهایت حاصل این تغییرات، به صورت زیر در مرورگر ظاهر میشود:
اکنون نوبت به افزودن فرم بوت استرپی لاگین به فایل loginForm.jsx رسیدهاست:
توضیحات:
- ابتدا المان form به صفحه اضافه میشود.
- سپس هر ورودی، داخل یک div با کلاس form-group، محصور میشود. کار آن تبدیل یک برچسب و فیلد ورودی، به یک گروه از ورودیهای بوت استرپ است.
- در اینجا هر برچسب دارای یک ویژگی for است. اما چون قرار است عبارات jsx، به معادلهای جاوا اسکریپتی ترجمه شوند، نمیتوان از واژهی کلیدی for در اینجا استفاده کرد. به همین جهت از معادل react ای آن که htmlFor است، در کدهای فوق استفاده کردهایم؛ شبیه به نکتهای که در مورد تبدیل ویژگی class به className وجود دارد. مقدار هر ویژگی htmlFor نیز به id فیلد ورودی متناظر با آن تنظیم میشود. به این ترتیب اگر کاربر بر روی این برچسب کلیک کرده و آنرا انتخاب کند، فیلد متناظر با آن، دارای focus میشود.
- فیلدهای ورودی نیز دارای کلاس form-control هستند.
با این خروجی نهایی در مرورگر:
مدیریت ارسال فرمها
به صورت پیش فرض و استاندارد، دکمهی افزوده شدهی به المان form، سبب ارسال اطلاعات آن به سرور و سپس بارگذاری کامل صفحه میشود. این رفتاری نیست که در یک برنامهی SPA مدنظر باشد. برای مدیریت این حالت، میتوان از رخداد onSubmit هر المان فرم، استفاده کرد:
در اینجا یک متد رویدادگردان را برای رخداد onSubmit تعریف کردهایم که توسط آن رخداد جاری، دریافت و متد preventDefault آن فراخوانی میشود تا دیگر پس از کلیک بر روی دکمهی submit، حالت پیشفرض و استاندارد full page reload و post back به سمت سرور، رخ ندهد.
دسترسی مستقیم به المانهای فرمها
پس از فراخوانی متد preventDefault، کار مدیریت ارسال فرم به سرور را باید خودمان مدیریت کنیم و دیگر رخداد full post back استاندارد به سمت سرور را نخواهیم داشت. در جاوا اسکریپت خالص برای دریافت مقادیر وارد شدهی توسط کاربر میتوان نوشت:
اما در React و کدهای یک کامپوننت، نباید ارجاع مستقیمی را به شیء document و DOM اصلی مرورگر داشته باشیم. در برنامههای React هیچگاه نباید با شیء document کار کرد؛ چون کل فلسفهی آن ایجاد یک abstraction بر فراز DOM اصلی مرورگر است که به آن DOM مجازی گفته میشود. به این ترتیب مدیریت برنامه و همچنین آزمون نویسی برای آن نیز سادهتر میشود. اما اگر واقعا نیاز به دسترسی به یک المان DOM در React وجود داشت، چه باید کرد؟
برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت، ایجاد کرده و آنرا با React.RefObject، مقدار دهی اولیه میکنیم:
سپس ویژگی ref المان مدنظر را به این RefObject تنظیم میکنیم:
اکنون زمان submit فرم، اگر نیاز به مقدار username وجود داشت، میتوان توسط خاصیت ارجاعی username تعریف شده، به خاصیت current آن که DOM element مدنظر را بازگشت میدهد، دسترسی یافت و مانند مثال زیر، مقدار آنرا مورد استفاده قرار داد:
در life-cycle hook ای به نام componentDidMount که پس از رندر کامپوننت در DOM فراخوانی میشود، میتوان توسط RefObject تعریف شده، به شیء current که معادل DOM Element متناظر است، دسترسی یافت و سپس متد focus آنرا فراخوانی کرد. در این حالت در اولین بار نمایش فرم، یک چنین تصویری حاصل میشود:
البته روش بهتری نیز برای انجام اینکار وجود دارد. المانهای JSX دارای ویژگی autoFocus نیز هستند که دقیقا همین کار را انجام میدهد:
برای آزمایش آن، قطعه کد componentDidMount را کامنت کرده و برنامه را اجرا کنید.
تبدیل المانهای فرمها به Controlled elements
در بسیاری از اوقات، فرمهای ما state خود را از سرور دریافت میکنند. فرض کنید که در حال ایجاد یک فرم ثبت اطلاعات فیلمها هستیم. در این حالت باید بر اساس id فیلم، اطلاعات آن را از سرور دریافت و در state ذخیره کرد؛ سپس فیلدهای فرم را بر اساس آن مقدار دهی اولیه کرد. برای نمونه در فرم لاگین میتوان state را با شیء account، به صورت زیر مقدار دهی اولیه کرد:
تا اینجا فیلدهای فرم لاگین، از این state مطلع نبوده و تغییرات دادههای ورودی در آنها، به شیء account منعکس نمیشوند. علت اصلی هم اینجا است که هر کدام از فیلدهای ورودی در React، دارای state خاص خود بوده و مستقل از state کامپوننت جاری هستند. برای رفع این مشکل باید آنها را تبدیل به controlled element هایی کرد که دارای state خاص خود نبوده، تمام اطلاعات مورد نیاز خود را از طریق props دریافت میکنند و تغییرات در دادههای خود را از طریق صدور رخدادهایی اطلاع رسانی میکنند. برای اینکار باید مراحل زیر طی شوند:
ابتدا ویژگی value فیلد برای مثال username را به خاصیت username شیء account موجود در state متصل میکنیم:
به این ترتیب دیگر این المان، state خاص خود را نداشته و از طریق props، مقادیر خود را دریافت میکند. تا اینجا username، به رشتهی خالی دریافتی از شیء state و خاصیت account آن، به صورت یک طرفه متصل شدهاست. یعنی زمانیکه فرم نمایش داده میشود، دارای یک مقدار خالی است. برای اینکه تغییرات رخدادهی در این المان را به state منعکس کرد، باید رخداد change آنرا مدیریت نمود. به این ترتیب زمانیکه کاربری اطلاعاتی را در اینجا وارد میکند، رخداد change صادر شده و پس از آن میتوان اطلاعات وارد شده را دریافت و state را به روز رسانی کرد. به روز رسانی state نیز سبب رندر مجدد فرم میشود. بنابراین فیلدهای ورودی، با اطلاعات state جدید، به روز رسانی و رندر میشوند. به همین جهت ابتدا رویداد onChange را به فیلد username اضافه کرده:
و متد مدیریت کنندهی آنرا به صورت زیر تعریف میکنیم:
در اینجا، هدف به روز رسانی this.state.account، بر اساس رخداد رسیده (پارامتر e) است و چون نمیتوان state را مستقیما به روز رسانی کرد، ابتدا یک clone از آن را تهیه میکنیم. سپس توسط e.currentTarget به المان در حال به روز رسانی دسترسی یافته و مقدار آنرا به مقدار خاصیت username انتساب میدهیم. در آخر state را بر اساس این تغییرات، به روز رسانی میکنیم. این انعکاس در state را توسط افزونهی react developer tools هم میتوان مشاهده کرد:
مدیریت دریافت اطلاعات چندین فیلد ورودی
تا اینجا موفق شدیم اطلاعات state را به تغییرات فیلد username در فرم لاگین متصل کنیم؛ اما فیلد password را چگونه باید مدیریت کرد؟ برای اینکه تمام این مراحل را مجددا تکرار نکنیم، میتوان از مقدار دهی پویای خواص در جاوا اسکریپت که توسط [] انجام میشود استفاده کرد:
البته برای اینکه این قطعه کد کار کند، نیاز است ویژگی name فیلدهای ورودی را نیز تنظیم کرد تا e.currentTarget.name، به نام یکی از خواص شیء account تعریف شدهی در state اشاره کند. برای نمونه فیلد کلمهی عبور، ابتدا دارای ویژگی value متصل به خاصیت password شیء account موجود در state میشود. سپس تغییرات آن توسط رویداد onChange، به متد handleChange منتقل شده و خاصیت name آن نیز مقدار دهی شدهاست تا مقدار دهی پویای خواص، در این متد میسر شود:
که در نهایت سبب مقدار دهی صحیح state، با هر دو فیلد تغییر یافته میشود:
یک نکته: میتوان توسط Object Destructuring، تکرار e.currentTarget را حذف کرد:
ما از شیء e دریافتی، تنها به خاصیت currentTarget آن نیاز داریم. بنابراین آنرا از طریق Object Destructuring در همان پارامتر ورودی متد جاری دریافت کرده و سپس آنرا به نام input، تغییر نام میدهیم.
آشنایی با خطاهای متداول دریافتی در حین کار با فرمها
فرض کنید خاصیت username را از شیء account موجود در state حذف کردهایم. در زمان نمایش ابتدایی فرم، خطایی را دریافت نخواهیم کرد، اما اگر اطلاعاتی را در آن وارد کنیم، بلافاصله در کنسول توسعه دهندگان مرورگر چنین اخطاری ظاهر میشود:
چون خاصیت username را حذف کردهایم، اینبار که در textbox مقداری را وارد میکنیم، سبب انتساب undefined و یا null به مقدار المان خواهد شد. در این حالت React چنین المانی را به صورت controlled element درنظر نمیگیرد و دارای state خاص خودش خواهد بود. به همین جهت عنوان میکند که بین یک المان کنترل شده و نشده، یکی را انتخاب کنید.
دقیقا چنین اخطاری را با ورود null/undefined بجای "" در حین مقدار دهی اولیهی username در شیء account نیز دریافت خواهیم کرد:
بنابراین به عنوان یک قاعده در فرمهای React، المانهای یک فرم را باید توسط یک "" مقدار دهی اولیه کرد و یا با مقداری که از سمت سرور دریافت میشود.
ایجاد یک کامپوننت ورود اطلاعات با قابلیت استفادهی مجدد
هر چند در پیاده سازی فعلی سعی کردیم با بکارگیری مقداردهی پویای خواص اشیاء، تکرار کدها را کاهش دهیم، اما باز هم به ازای هر فیلد ورودی باید این مسایل تکرار شوند:
- ایجاد یک div با کلاسهای بوت استرپی.
- ایجاد label و همچنین فیلد ورودی.
- در اینجا مقدار htmlFor باید با مقدار id فیلد ورودی یکی باشد.
- مقدار دهی ویژگیهای value و onChange نیز باید تکرار شوند.
بنابراین بهتر است این تعاریف را استخراج و به یک کامپوننت با قابلیت استفادهی مجدد منتقل کرد. به همین جهت فایل جدید src\components\common\input.jsx را در پوشهی common ایجاد کرده و سپس توسط میانبرهای imrc و sfc، این کامپوننت تابعی بدون حالت را تکمیل میکنیم:
در اینجا کل تگ div مرتبط با username را از کامپوننت فرم لاگین cut کرده و در اینجا در قسمت return، قرار دادهایم. سپس شروع به تبدیل مقادیر قبلی به مقادیری که قرار است از props تامین شوند، کردهایم. یا میتوان props را به عنوان آرگومان این متد تعریف کرد و یا میتوان توسط Object Destructuring، خواصی را که از props نیاز داریم، در پارامتر متد Input ذکر کنیم که این روش چون به نوعی اینترفیس کامپوننت را نیز مشخص میکند و همچنین کدهای تکراری دسترسی به props را به حداقل میرساند، تمیزتر و با قابلیت نگهداری بالاتری است. برای مثال هر جائیکه نام username استفاده شده بود، با خاصیت name جایگزین شده و بجای برچسب از label، بجای مقدار username از متغیر value و بجای رخداد تعریف شده نیز onChange قرار گرفتهاست.
سپس به کامپوننت فرم لاگین بازگشته و ابتدا آنرا import میکنیم:
اکنون متد رندر ماژول src\components\loginForm.jsx، به صورت زیر با درج دو Input، خلاصه میشود که دیگر در آن خبری از تگها و کدهای تکراری نیست:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-18.zip
ایجاد فرم لاگین
فرم لاگینی را که به برنامهی نمایش لیست فیلمهای تکمیل شدهی تا قسمت 17، اضافه خواهیم کرد، یک فرم بوت استرپی است و میتوانید جزئیات بیشتر مزین سازی المانهای این نوع فرمها را با کلاسهای بوت استرپ، در مطلب «کار با شیوهنامههای فرمها در بوت استرپ 4» مطالعه کنید.
در ابتدا فایل جدید src\components\loginForm.jsx را ایجاد کرده و سپس توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت جدید LoginForm را ایجاد میکنیم:
import React, { Component } from "react"; class LoginForm extends Component { render() { return <h1>Login</h1>; } } export default LoginForm;
import LoginForm from "./components/loginForm"; //... function App() { return ( <React.Fragment> <NavBar /> <main className="container"> <Switch> <Route path="/login" component={LoginForm} /> <Route path="/movies/:id" component={MovieForm} /> // ... </Switch> </main> </React.Fragment> ); }
<NavLink className="nav-item nav-link" to="/login"> Login </NavLink>
اکنون نوبت به افزودن فرم بوت استرپی لاگین به فایل loginForm.jsx رسیدهاست:
import React, { Component } from "react"; class LoginForm extends Component { render() { return ( <form> <div className="form-group"> <label htmlFor="username">Username</label> <input id="username" type="text" className="form-control" /> </div> <div className="form-group"> <label htmlFor="password">Password</label> <input id="password" type="password" className="form-control" /> </div> <button className="btn btn-primary">Login</button> </form> ); } } export default LoginForm;
- ابتدا المان form به صفحه اضافه میشود.
- سپس هر ورودی، داخل یک div با کلاس form-group، محصور میشود. کار آن تبدیل یک برچسب و فیلد ورودی، به یک گروه از ورودیهای بوت استرپ است.
- در اینجا هر برچسب دارای یک ویژگی for است. اما چون قرار است عبارات jsx، به معادلهای جاوا اسکریپتی ترجمه شوند، نمیتوان از واژهی کلیدی for در اینجا استفاده کرد. به همین جهت از معادل react ای آن که htmlFor است، در کدهای فوق استفاده کردهایم؛ شبیه به نکتهای که در مورد تبدیل ویژگی class به className وجود دارد. مقدار هر ویژگی htmlFor نیز به id فیلد ورودی متناظر با آن تنظیم میشود. به این ترتیب اگر کاربر بر روی این برچسب کلیک کرده و آنرا انتخاب کند، فیلد متناظر با آن، دارای focus میشود.
- فیلدهای ورودی نیز دارای کلاس form-control هستند.
با این خروجی نهایی در مرورگر:
مدیریت ارسال فرمها
به صورت پیش فرض و استاندارد، دکمهی افزوده شدهی به المان form، سبب ارسال اطلاعات آن به سرور و سپس بارگذاری کامل صفحه میشود. این رفتاری نیست که در یک برنامهی SPA مدنظر باشد. برای مدیریت این حالت، میتوان از رخداد onSubmit هر المان فرم، استفاده کرد:
class LoginForm extends Component { handleSubmit = e => { console.log("handleSubmit", e); e.preventDefault(); // call the server }; render() { return ( <form onSubmit={this.handleSubmit}> //...
دسترسی مستقیم به المانهای فرمها
پس از فراخوانی متد preventDefault، کار مدیریت ارسال فرم به سرور را باید خودمان مدیریت کنیم و دیگر رخداد full post back استاندارد به سمت سرور را نخواهیم داشت. در جاوا اسکریپت خالص برای دریافت مقادیر وارد شدهی توسط کاربر میتوان نوشت:
const username = document.getElementById("username").value;
برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت، ایجاد کرده و آنرا با React.RefObject، مقدار دهی اولیه میکنیم:
class LoginForm extends Component { username = React.createRef();
<input ref={this.username} id="username" type="text" className="form-control" />
handleSubmit = e => { e.preventDefault(); // call the server const username = this.username.current.value; console.log("handleSubmit", username); };
البته در حالت کلی باید استفادهی از RefObjectها را به حداقل رساند (راه حل بهتری برای دریافت ورودیها وجود دارد) و جاهائی از آنها استفاده کرد که واقعا راه حل دیگری وجود ندارد؛ مانند تنظیم focus بر روی یک المان DOM. در این حالت حتما باید ارجاعی را از آن المان DOM در دسترس داشت و یا برای پویانمایی (animation) نیز مجبور به استفادهی از RefObjectها هستیم.
برای نمونه روش تنظیم focus بر روی یک فیلد ورودی توسط RefObjectها به صورت زیر است:class LoginForm extends Component { username = React.createRef(); componentDidMount = () => { this.username.current.focus(); };
البته روش بهتری نیز برای انجام اینکار وجود دارد. المانهای JSX دارای ویژگی autoFocus نیز هستند که دقیقا همین کار را انجام میدهد:
<input autoFocus ref={this.username} id="username" type="text" className="form-control" />
تبدیل المانهای فرمها به Controlled elements
در بسیاری از اوقات، فرمهای ما state خود را از سرور دریافت میکنند. فرض کنید که در حال ایجاد یک فرم ثبت اطلاعات فیلمها هستیم. در این حالت باید بر اساس id فیلم، اطلاعات آن را از سرور دریافت و در state ذخیره کرد؛ سپس فیلدهای فرم را بر اساس آن مقدار دهی اولیه کرد. برای نمونه در فرم لاگین میتوان state را با شیء account، به صورت زیر مقدار دهی اولیه کرد:
class LoginForm extends Component { state = { account: { username: "", password: "" } };
ابتدا ویژگی value فیلد برای مثال username را به خاصیت username شیء account موجود در state متصل میکنیم:
<input value={this.state.account.username}
<input value={this.state.account.username} onChange={this.handleChange}
handleChange = e => { const account = { ...this.state.account }; //cloning an object account.username = e.currentTarget.value; this.setState({ account }); };
مدیریت دریافت اطلاعات چندین فیلد ورودی
تا اینجا موفق شدیم اطلاعات state را به تغییرات فیلد username در فرم لاگین متصل کنیم؛ اما فیلد password را چگونه باید مدیریت کرد؟ برای اینکه تمام این مراحل را مجددا تکرار نکنیم، میتوان از مقدار دهی پویای خواص در جاوا اسکریپت که توسط [] انجام میشود استفاده کرد:
handleChange = e => { const account = { ...this.state.account }; //cloning an object account[e.currentTarget.name] = e.currentTarget.value; this.setState({ account }); };
<input id="password" name="password" value={this.state.account.password} onChange={this.handleChange} type="password" className="form-control" />
یک نکته: میتوان توسط Object Destructuring، تکرار e.currentTarget را حذف کرد:
handleChange = ({ currentTarget: input }) => { const account = { ...this.state.account }; //cloning an object account[input.name] = input.value; this.setState({ account }); };
آشنایی با خطاهای متداول دریافتی در حین کار با فرمها
فرض کنید خاصیت username را از شیء account موجود در state حذف کردهایم. در زمان نمایش ابتدایی فرم، خطایی را دریافت نخواهیم کرد، اما اگر اطلاعاتی را در آن وارد کنیم، بلافاصله در کنسول توسعه دهندگان مرورگر چنین اخطاری ظاهر میشود:
Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://fb.me/react-controlled-components
دقیقا چنین اخطاری را با ورود null/undefined بجای "" در حین مقدار دهی اولیهی username در شیء account نیز دریافت خواهیم کرد:
Warning: `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.
ایجاد یک کامپوننت ورود اطلاعات با قابلیت استفادهی مجدد
هر چند در پیاده سازی فعلی سعی کردیم با بکارگیری مقداردهی پویای خواص اشیاء، تکرار کدها را کاهش دهیم، اما باز هم به ازای هر فیلد ورودی باید این مسایل تکرار شوند:
- ایجاد یک div با کلاسهای بوت استرپی.
- ایجاد label و همچنین فیلد ورودی.
- در اینجا مقدار htmlFor باید با مقدار id فیلد ورودی یکی باشد.
- مقدار دهی ویژگیهای value و onChange نیز باید تکرار شوند.
بنابراین بهتر است این تعاریف را استخراج و به یک کامپوننت با قابلیت استفادهی مجدد منتقل کرد. به همین جهت فایل جدید src\components\common\input.jsx را در پوشهی common ایجاد کرده و سپس توسط میانبرهای imrc و sfc، این کامپوننت تابعی بدون حالت را تکمیل میکنیم:
import React from "react"; const Input = ({ name, label, value, onChange }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <input value={value} onChange={onChange} id={name} name={name} type="text" className="form-control" /> </div> ); }; export default Input;
سپس به کامپوننت فرم لاگین بازگشته و ابتدا آنرا import میکنیم:
import Input from "./common/input";
render() { const { account } = this.state; return ( <form onSubmit={this.handleSubmit}> <Input name="username" label="Username" value={account.username} onChange={this.handleChange} /> <Input name="password" label="Password" value={account.password} onChange={this.handleChange} /> <button className="btn btn-primary">Login</button> </form> );
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-18.zip
اشتراکها
بومی سازی در برنامههای Angular
در قسمت قبل، بخشی از تازههای ES6 را که بیشتر در برنامههای مبتنی بر React مورد استفاده قرار میگیرند، بررسی کردیم. در این قسمت نیز سایر موارد مهم باقیمانده را بررسی میکنیم.
در اینجا نیز برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
سپس تمام کدهای داخل index.js را نیز حذف میکنیم. اکنون تمام کدهای خالص جاوا اسکریپتی خود را داخل این فایل خواهیم نوشت.
همچنین چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
متد Array.map
در برنامههای مبتنی بر React، از متد Array.map برای رندر لیستها استفاده میشود و نمونههای بیشتری از آنرا در قسمتهای بعدی مشاهده خواهید کرد.
فرض کنید آرایهای از رنگها را داریم. اکنون میخواهیم لیستی را به صورت <li>color</li> به ازای هر آیتم آن، تشکیل دهیم:
برای این منظور میتوان از متد map بر روی این آرایه به نحو زیر استفاده کرد:
متد map یک callback function را دریافت میکند که با هر بار فراخوانی آن، یک عنصر از عناصر آرایه را دریافت کرده، آنرا تغییر شکل داده و بازگشت میدهد (چیزی شبیه به متد Select در LINQ).
این مثال را توسط arrow functions نیز میتوان بازنویسی کرد:
ابتدا function را حذف میکنیم. سپس { return } را تبدیل به یک <= خواهیم کرد. چون تک پارامتری است، نیازی به ذکر پرانتز color وجود ندارد. همچنین نیازی به ذکر سمیکالن انتهای return هم نیست؛ چون کل بدنهی این تابع، یک سطر return بیشتر نیست.
یک مرحلهی دیگر هم میتوانیم این قطعه کد را زیباتر کنیم؛ جمع زدن رشتهها در ES6 معادل بهتری پیدا کردهاست که template literals نام دارد:
در اینجا بجای ' و یا " از حرف back-tick استفاده میشود. سپس قالب کلی رشتهی خود را مشخص میکنیم و جائیکه قرار است متغیری را درج کنیم، از {}$ استفاده میکنیم که بسیار شبیه به ویژگی string interpolation در #C است. فقط برخلاف آن، حرف $ در ابتدای رشته قرار نمیگیرد و باید دقیقا پیش از متغیر مدنظر تعریف شود.
Object Destructuring
فرض کنید شیء آدرس را به صورت زیر تعریف کردهایم:
اکنون میخواهیم خواص آنرا به متغیرهایی نسبت دهیم. یک روش متداول آن به صورت زیر است:
برای کاهش این حجم کد تکراری که با .address شروع میشود، میتوان از ویژگی Object Destructuring استفاده کرد:
این تک سطر، دقیقا با سه سطر قبلی که نوشتیم، عملکرد یکسانی دارد. ابتدا متغیرهای مدنظر، داخل {} قرار میگیرند و سپس کل شیء آدرس به آنها نسبت داده خواهد شد.
در اینجا باید نام متغیرهای تعریف شده با نام خواص شیء آدرس یکی باشند. همچنین ذکر تمامی این متغیرها نیز ضرورتی ندارد و برای مثال اگر فقط نیاز به street بود، میتوان تنها آنرا ذکر کرد.
اگر خواستیم نام متغیر دیگری را بجای نام خواص شیء آدرس انتخاب کنیم، میتوان از یک نام مستعار ذکر شدهی پس از : استفاده کرد:
Spread Operator
فرض کنید دو آرایهی زیر را داریم:
و میخواهیم آنها را با هم ترکیب کنیم. یک روش انجام اینکار توسط متد concat آرایهها است:
در ES6 با استفاده از عملگر ... که spread نیز نام دارد، میتوان قطعه کد فوق را به صورت زیر بازنویسی کرد:
ابتدا یک آرایهی جدید را ایجاد میکنیم. سپس تمام عناصر اولین آرایه را در آن گسترده میکنیم و بعد از آن، تمام عناصر دومین آرایه را.
شاید اینطور به نظر برسد که بین دو راه حل ارائه شده آنچنانی تفاوتی نیست. اما مزیت قطعه کد دوم، سهولت افزودن المانهای جدید، به هر قسمتی از آرایه است:
کاربرد دیگر عملگر spread امکان clone سادهی یک آرایهاست:
به علاوه امکان اعمال آن به اشیاء نیز وجود دارد:
در اینجا تمام خواص شیء اول و دوم با هم ترکیب و همچنین یک خاصیت اختیاری نیز ذکر شدهاست. خروجی نهایی آن چنین شیءای خواهد بود:
و امکان clone اشیاء توسط آن هم وجود دارد:
کلاسها در ES 6
قطعه کد کلاسیک زیر را که کار ایجاد اشیاء را در جاوا اسکریپت انجام میدهد، در نظر بگیرید:
ابتدا یک شیء person را با دو عضو، ایجاد کردهایم. اکنون برای ایجاد یک شیء person دیگر، باید دقیقا همان قطعه کد را تکرار کنیم. به همین جهت برای حذف کدهای تکراری، نیاز به قالبی برای ایجاد اشیاء داریم و اینجا است که از کلاسها استفاده میشود:
برای تعریف یک کلاس ES6، با واژهی کلیدی class شروع میکنیم. نام یک کلاس با حروف بزرگ شروع میشود (pascal case) و اگر برای نمونه این نام قرار است دو قسمتی باشد، به مانند CoolPerson عمل میکنیم. در مرحلهی بعد، متد walk را از تعریف شیء شخص، به کلاس شخص انتقال دادهایم. سپس متد ویژهی constructor را در اینجا تعریف کردهایم. توسط آن زمانیکه یک نمونه از این کلاس ساخته میشود، پارامتری را دریافت و به یک خاصیت جدید در آن کلاس که توسط this.name تعریف شدهاست، انتساب میدهیم.
باید دقت داشت که class Person تنها یک قالب است و const person ای که پیشتر تعریف شد، یک شیء. برای اینکه از روی قالب تعریف شدهی Person، یک شیء را ایجاد کنیم، به صورت زیر توسط واژهی کلیدی new عمل میشود:
در اینجا اگر دقت کنید، عبارت Person("User 3") شبیه به فراخوانی یک متد است. این متد دقیقا همان متد ویژهی constructor ای است که تعریف کردیم. اکنون توسط شیء person3، میتوان به خاصیت name و یا متد walk آن دسترسی یافت.
یک نکته: در جاوا اسکریپت، کلاسها نیز شیء هستند! از این جهت که کلاسها در جاوا اسکریپت صرفا یک بیان نحوی زیبای تابع constructor هستند و توابع در جاوا اسکریپت نیز شیء میباشند!
ارث بری کلاسها در ES6
فرض کنید میخواهیم کلاس Teacher را به نحو زیر تعریف کنیم:
این کلاس دارای متد teach است؛ اما تمام معلمها باید بتوانند راه هم بروند. همچنین قصد نداریم متد walk کلاس Person را هم با توجه به اینکه Teacher یک Person نیز هست، در اینجا تکرار کنیم. یک روش حل این مشکل، استفاده از ارثبری کلاسها است که با افزودن extends Person به نحو زیر میسر میشود:
پس از این تعریف، اگر بخواهیم توسط واژهی کلیدی new، یک شیء را بر اساس این کلاس تهیه کنیم، در VSCode، تقاضای ثبت یک سازنده نیز میشود:
علت اینجا است که کلاس Teacher، نه فقط متد walk کلاس Person را به ارث بردهاست، بلکه سازندهی آنرا نیز به ارث میبرد:
اکنون میتوان با استفاده از شیء معلم ایجاد شده، نه فقط به متدهای کلاس Teacher دسترسی یافت، بلکه امکان دسترسی به خواص و متدهای کلاس پایهی Person نیز در اینجا وجود دارد:
در ادامه فرض کنید علاوه بر ذکر نام، نیاز به ذکر مدرک معلم نیز در سازندهی کلاس وجود دارد:
در این حالت اگر به کنسول توسعه دهندههای مرورگر مراجعه کنید، خطای زیر را مشاهده خواهید کرد:
عنوان میکند که نیاز است متد ویژهی super را در سازندهی سفارشی کلاس Teacher فراخوانی کنیم. در ES6، فراخوانی سازندهی کلاس پایه، در سازندههای سفارشی کلاسهای مشتق شدهی از آن، اجباری است:
با اینکار، مقدار دهی خاصیت name کلاس پایه نیز صورت خواهد گرفت. در اینجا همچنین تعریف خاصیت جدید degree و مقدار دهی آنرا نیز مشاهده میکنید. در ادامه باید این پارامتر دوم سازنده را نیز در حین نمونه سازی از کلاس Teacher تعریف کنیم:
در برنامههای React، هر زمانیکه یک کامپوننت جدید تعریف میشود، کلاس آن، از کلاس پایهی کامپوننت، ارث بری خواهد کرد. به این ترتیب میتوان به تمام امکانات این کلاس پایه، بدون نیاز به تکرار آنها در کلاسهای مشتق شدهی از آن، دسترسی یافت.
ماژولها در ES 6
تا اینجا اگر مثالها را دنبال کرده باشید، تمام آنها را داخل همان فایل index.js درج کردهایم. به این ترتیب کم کم دارد مدیریت این فایل از دست خارج میشود. امکان تقسیم کدهای index.js به چندین فایل، مفهوم ماژولها را در ES6 تشکیل میدهد. برای این منظور قصد داریم هر کلاس تعریف شده را به یک فایل جداگانه که ماژول نامیده میشود، منتقل کنیم. از کلاس Person شروع میکنیم و آنرا به فایل جدید person.js و کلاس Teacher را به فایل جدید teacher.js منتقل میکنیم.
البته اگر از افزونههای VSCode استفاده میکنید، اگر کرسر را بر روی نام کلاس قرار دهید، یک آیکن لامپ مانند ظاهر میشود. با کلیک بر روی آن، منویی که شامل گزینهی move to a new file هست، برای انجام سادهتر این عملیات (ایجاد یک فایل جدید js، سپس انتخاب و cut کردن کل کلاس و در آخر کپی کردن آن در این فایل جدید) پیشبینی شدهاست.
هرچند این عملیات تا به اینجا خاتمه یافته به نظر میرسد، اما نیاز به اصلاحات زیر را نیز دارد:
- هنگام کار با ماژولها، اشیاء تعریف شدهی در آن به صورت پیشفرض، خصوصی و private هستند و خارج از آنها قابل دسترسی نمیباشند. به این معنا که class Teacher ما که اکنون در یک ماژول جدید قرار گرفتهاست، توسط سایر قسمتهای برنامه قابل مشاهده و دسترسی نیست.
- برای public تعریف کردن یک کلاس تعریف شدهی در یک ماژول، نیاز است آنرا export کنیم. انجام این کار نیز سادهاست. فقط کافی است واژهی کلیدی export را به پیش از class اضافه کنیم:
- اگر افزونهی eslint را نصب کرده باشید، اکنون در فایل یا ماژول جدید teacher.js، زیر کلمهی Person خط قرمز کشیدهاست و عنوان میکند که کلاس Person را نمیشناسد:
برای رفع این مشکل، باید این وابستگی را import کرد:
در اینجا شیء Person، از فایل محلی واقع شدهی در پوشهی جاری Person.js تامین میشود. نیازی به ذکر پسوند فایل در اینجا نیست.
- مرحلهی آخر، اصلاح فایل index.js است؛ چون اکنون تعاریف Person و Teacher را نمیشناسد.
دو سطر فوق را نیز به ابتدای فایل index.js اضافه میکنیم تا بتوان new Person و new Teacher نوشته شدهی در آنرا کامپایل کرد.
Exportهای پیشفرض و نامدار در ES6
اشیاء تعریف شدهی در یک ماژول، به صورت پیشفرض private هستند؛ مگر اینکه export شوند. برای مثال export class Teacher و یا export function xyz. به اینها named exports گویند. حال اگر ماژول ما تنها یک شیء عمومی شده را داشت (کلاسها هم شیء هستند!)، میتوان از واژهی کلیدی default نیز در اینجا استفاده کرد:
پس از این دیگر نیازی به ذکر {} در حین import چنین شیءای نخواهد بود:
در ادامه اگر یک export نامدار دیگر را به این ماژول اضافه کنیم (مانند تابع testTeacher):
نحوهی import آن به صورت زیر تغییر میکند:
یک default export و یک named export را در اینجا داریم که اولی بدون {} و دومی با {} تعریف شدهاست. این الگویی است که در برنامههای React زیاد دیده میشود؛ مانند:
یک نکته: اگر در VSCode داخل {}، دکمههای ctrl+space را فشار دهید، میتوانید منوی exportهای ماژول تعریف شده را مشاهده کنید.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-03.zip
در اینجا نیز برای بررسی ویژگیهای جاوا اسکریپت مدرن، یک پروژهی جدید React را ایجاد میکنیم.
> create-react-app sample-03 > cd sample-03 > npm start
همچنین چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود میتوانید مشاهده کنید (فشردن دکمهی F12).
متد Array.map
در برنامههای مبتنی بر React، از متد Array.map برای رندر لیستها استفاده میشود و نمونههای بیشتری از آنرا در قسمتهای بعدی مشاهده خواهید کرد.
فرض کنید آرایهای از رنگها را داریم. اکنون میخواهیم لیستی را به صورت <li>color</li> به ازای هر آیتم آن، تشکیل دهیم:
const colors = ["red", "green", "blue"];
const items = colors.map(function(color) { return "<li>" + color + "</li>"; }); console.log(items);
این مثال را توسط arrow functions نیز میتوان بازنویسی کرد:
const items2 = colors.map(color => "<li>" + color + "</li>"); console.log(items2);
یک مرحلهی دیگر هم میتوانیم این قطعه کد را زیباتر کنیم؛ جمع زدن رشتهها در ES6 معادل بهتری پیدا کردهاست که template literals نام دارد:
const items3 = colors.map(color => `<li>${color}</li>`); console.log(items3);
Object Destructuring
فرض کنید شیء آدرس را به صورت زیر تعریف کردهایم:
const address = { street: "street 1", city: "city 1", country: "country 1" };
const street1 = address.street; const city1 = address.city; const country1 = address.country;
const { street, city, country } = address;
در اینجا باید نام متغیرهای تعریف شده با نام خواص شیء آدرس یکی باشند. همچنین ذکر تمامی این متغیرها نیز ضرورتی ندارد و برای مثال اگر فقط نیاز به street بود، میتوان تنها آنرا ذکر کرد.
اگر خواستیم نام متغیر دیگری را بجای نام خواص شیء آدرس انتخاب کنیم، میتوان از یک نام مستعار ذکر شدهی پس از : استفاده کرد:
const { street: st } = address; console.log(st);
Spread Operator
فرض کنید دو آرایهی زیر را داریم:
const first = [1, 2, 3]; const second = [4, 5, 6];
const combined = first.concat(second); console.log(combined);
در ES6 با استفاده از عملگر ... که spread نیز نام دارد، میتوان قطعه کد فوق را به صورت زیر بازنویسی کرد:
const combined2 = [...first, ...second]; console.log(combined2);
شاید اینطور به نظر برسد که بین دو راه حل ارائه شده آنچنانی تفاوتی نیست. اما مزیت قطعه کد دوم، سهولت افزودن المانهای جدید، به هر قسمتی از آرایه است:
const combined2 = [...first, "a", ...second, "b"]; console.log(combined2);
کاربرد دیگر عملگر spread امکان clone سادهی یک آرایهاست:
const clone = [...first]; console.log(clone);
به علاوه امکان اعمال آن به اشیاء نیز وجود دارد:
const firstObject = { name: "User 1" }; const secondObject = { job: "Job 1" }; const combinedObject = { ...firstObject, ...secondObject, location: "Here" }; console.log(combinedObject);
{name: "User 1", job: "Job 1", location: "Here"}
و امکان clone اشیاء توسط آن هم وجود دارد:
const clonedObject = { ...firstObject }; console.log(clonedObject);
کلاسها در ES 6
قطعه کد کلاسیک زیر را که کار ایجاد اشیاء را در جاوا اسکریپت انجام میدهد، در نظر بگیرید:
const person = { name: "User 1", walk() { console.log("walk"); } }; const person2 = { name: "User 2", walk() { console.log("walk"); } };
class Person { constructor(name) { this.name = name; } walk() { console.log("walk"); } }
باید دقت داشت که class Person تنها یک قالب است و const person ای که پیشتر تعریف شد، یک شیء. برای اینکه از روی قالب تعریف شدهی Person، یک شیء را ایجاد کنیم، به صورت زیر توسط واژهی کلیدی new عمل میشود:
const person3 = new Person("User 3"); console.log(person3.name); person3.walk();
یک نکته: در جاوا اسکریپت، کلاسها نیز شیء هستند! از این جهت که کلاسها در جاوا اسکریپت صرفا یک بیان نحوی زیبای تابع constructor هستند و توابع در جاوا اسکریپت نیز شیء میباشند!
ارث بری کلاسها در ES6
فرض کنید میخواهیم کلاس Teacher را به نحو زیر تعریف کنیم:
class Teacher { teach() { console.log("teach"); } }
class Teacher extends Person { teach() { console.log("teach"); } }
علت اینجا است که کلاس Teacher، نه فقط متد walk کلاس Person را به ارث بردهاست، بلکه سازندهی آنرا نیز به ارث میبرد:
const teacher = new Teacher("User 4");
console.log(teacher.name); teacher.teach(); teacher.walk();
در ادامه فرض کنید علاوه بر ذکر نام، نیاز به ذکر مدرک معلم نیز در سازندهی کلاس وجود دارد:
class Teacher extends Person { constructor(name, degree) {}
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
class Teacher extends Person { constructor(name, degree) { super(name); this.degree = degree; } teach() { console.log("teach"); } }
const teacher = new Teacher("User 4", "MSc");
در برنامههای React، هر زمانیکه یک کامپوننت جدید تعریف میشود، کلاس آن، از کلاس پایهی کامپوننت، ارث بری خواهد کرد. به این ترتیب میتوان به تمام امکانات این کلاس پایه، بدون نیاز به تکرار آنها در کلاسهای مشتق شدهی از آن، دسترسی یافت.
ماژولها در ES 6
تا اینجا اگر مثالها را دنبال کرده باشید، تمام آنها را داخل همان فایل index.js درج کردهایم. به این ترتیب کم کم دارد مدیریت این فایل از دست خارج میشود. امکان تقسیم کدهای index.js به چندین فایل، مفهوم ماژولها را در ES6 تشکیل میدهد. برای این منظور قصد داریم هر کلاس تعریف شده را به یک فایل جداگانه که ماژول نامیده میشود، منتقل کنیم. از کلاس Person شروع میکنیم و آنرا به فایل جدید person.js و کلاس Teacher را به فایل جدید teacher.js منتقل میکنیم.
البته اگر از افزونههای VSCode استفاده میکنید، اگر کرسر را بر روی نام کلاس قرار دهید، یک آیکن لامپ مانند ظاهر میشود. با کلیک بر روی آن، منویی که شامل گزینهی move to a new file هست، برای انجام سادهتر این عملیات (ایجاد یک فایل جدید js، سپس انتخاب و cut کردن کل کلاس و در آخر کپی کردن آن در این فایل جدید) پیشبینی شدهاست.
هرچند این عملیات تا به اینجا خاتمه یافته به نظر میرسد، اما نیاز به اصلاحات زیر را نیز دارد:
- هنگام کار با ماژولها، اشیاء تعریف شدهی در آن به صورت پیشفرض، خصوصی و private هستند و خارج از آنها قابل دسترسی نمیباشند. به این معنا که class Teacher ما که اکنون در یک ماژول جدید قرار گرفتهاست، توسط سایر قسمتهای برنامه قابل مشاهده و دسترسی نیست.
- برای public تعریف کردن یک کلاس تعریف شدهی در یک ماژول، نیاز است آنرا export کنیم. انجام این کار نیز سادهاست. فقط کافی است واژهی کلیدی export را به پیش از class اضافه کنیم:
export class Teacher extends Person {
برای رفع این مشکل، باید این وابستگی را import کرد:
import { Person } from "./Person"; export class Teacher extends Person {
- مرحلهی آخر، اصلاح فایل index.js است؛ چون اکنون تعاریف Person و Teacher را نمیشناسد.
import { Person } from "./Person"; import { Teacher } from "./Teacher";
Exportهای پیشفرض و نامدار در ES6
اشیاء تعریف شدهی در یک ماژول، به صورت پیشفرض private هستند؛ مگر اینکه export شوند. برای مثال export class Teacher و یا export function xyz. به اینها named exports گویند. حال اگر ماژول ما تنها یک شیء عمومی شده را داشت (کلاسها هم شیء هستند!)، میتوان از واژهی کلیدی default نیز در اینجا استفاده کرد:
export default class Teacher extends Person {
import Teacher from "./Teacher";
در ادامه اگر یک export نامدار دیگر را به این ماژول اضافه کنیم (مانند تابع testTeacher):
import { Person } from "./Person"; export function testTeacher() { console.log("Test Teacher"); } export default class Teacher extends Person {
import Teacher, { testTeacher } from "./Teacher";
import React, { Component } from 'react';
یک نکته: اگر در VSCode داخل {}، دکمههای ctrl+space را فشار دهید، میتوانید منوی exportهای ماژول تعریف شده را مشاهده کنید.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-03.zip
18 Innovative Ways to Use Docker Daily
- Running GUI Applications in Docker Containers
- How to Run a Web Browser Inside Docker
- Using Obsidian Note-Taking App via Docker
- LibreOffice in Docker: Open-Source Office Suite Containerized
- Contribute to Science: Folding@Home with Docker
- Effortless Docker Management with Docker Desktop and Portainer
- Secure Document Handling Using Dangerzone and Docker
- Containerizing CLI Tools for a Clean System
- Enhance Docker Security with Docker Scout
- Set Up an Isolated Hacking Lab with Docker and Kali Linux
- Master Docker Networks for Container Isolation
- Automate Your Docker Workflows with Docker Compose
- Explore New Operating Systems Using Docker Containers
- Running macOS in a Docker Container (Educational Purposes)
- How to Run Raspberry Pi OS via Docker
- Access a Suite of IT Tools Inside a Docker Container
- Using Docker to Containerize AI Tools like Fabric
- Safely Test Applications with Docker Containers
- Building Custom Docker Images from Scratch
- Unconventional Docker Use Cases You Need to Know
- Daily Docker Tips and Tricks for Enhanced Productivity
- Beginner's Guide to Advanced Docker Techniques
- Boost Your IT Workflow with These Docker Hacks
- Docker for Everyday Tasks: Beyond Development