نظرات مطالب
React یک کتابخانهی جاوا اسکریپتی، برای ساخت رابطهای کاربری سریع و تعاملی است. توسعهی آن از سال 2011 در فیسبوک شروع شد و در حال حاضر محبوبترین کتابخانهی جاوا اسکریپتی در این ردهاست:
به همین جهت اگر میخواهید رزومهی غنیتری را ارائه دهید، فراگیری React میتواند موقعیتهای شغلی بیشتری را نصیب شما کند.
ساختار کلی یک برنامهی React
کامپوننتها (جزئی از یک رابط کاربری) قلب هر برنامهی React ای را تشکیل میدهند. برای ساخت یک برنامهی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب میکنیم تا به رابط کاربری نهایی برسیم.
هر برنامهی React، حداقل از یک کامپوننت تشکیل میشود که به آن Root component هم میگویند. این کامپوننت بیانگر کل برنامهاست و دربرگیرندهی مابقی Child components برنامه است. بنابراین ساختار هر برنامهی React، شبیه به درختی از کامپوننتها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید میخواهیم UI برنامهای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحهی توئیتر، به کامپوننتهایی شکسته میشود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقهی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامهی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننتهای برنامه مانند nav bar ، trends ، profile و feed را انجام میدهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز میتواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننتهای tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل میدهد. هر کدام از آنها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار میگیرند تا UI نهایی را شکل دهند:
هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل میشود:
state در اینجا همان اطلاعاتی است که قرار است در زمان نمایش این کامپوننت، رندر شود. کار متد render نیز همانطور که از نام آن نیز مشخص است، بیان نحوهی تشکیل و رندر UI است. خروجی این متد، یک React Element است که در حقیقت یک شیء جاوا اسکریپتی خالص است و در نهایت به المانهای DOM، نگاشت میشود. یک React Element، یک DOM Element واقعی نیست؛ بلکه تنها یک شیء جاوا اسکریپتی بیانگر DOM Element، در حافظهاست. بنابراین یک برنامهی React تشکیل شدهاست از لیستی از React Elementها در حافظه که به آن Virtual DOM هم گفته میشود.
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام میدهد. زمانیکه در state کامپوننتی تغییری رخ میدهد، یک React Element جدید تولید میشود. سپس React این شیء جدید را با نمونهی قبلی آن مقایسه کرده و تغییرات رخداده را محاسبه میکند. در آخر این تغییرات را به DOM واقعی اعمال میکند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمیدهیم. در اینجا فقط state یک کامپوننت را تغییر میدهیم و سپس React، کار ایجاد شیء UI درون حافظهای متناظر با آن و سپس اعمال آنرا به UI نهایی قابل مشاهدهی در مرورگر، انجام میدهد. به همین جهت به این کتابخانه React میگویند! چون به تغییرات state کامپوننتها واکنش نشان میدهد و سپس DOM واقعی را به روز میکند.
Angular یا React؟!
هر دوی React و Angular از لحاظ طراحی کامپوننتها بسیار شبیه به هم هستند؛ اما Angular یک فریمورک است و React تنها یک کتابخانه. تنها کاری را که React انجام میدهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننتها. این تمام کاری است که React انجام میدهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریعتر و سادهتر از Angular است. بدیهی است یک برنامهی تک صفحهای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویسهای HTTP نیز تشکیل میشود. در React شما مختار هستید که کتابخانههای جانبی فراهم شدهی برای آنرا خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریمورک ارائه میشوند.
برپایی محیط توسعهی React
اولین برنامهای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانهی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
این دستور npm قدیمی موجود بر روی سیستم را به روز رسانی میکند (اگر پیشتر یک node.js قدیمی را نصب و اکنون آنرا به روز رسانی کردهاید).
اگر هم خیلی پیشترها node.js را نصب کردهاید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشههای قدیمی آنرا یکی یکی یافته و دستی حذف کنید . سپس مجددا آنرا نصب کنید.
در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
در اینجا سوئیچ i به معنای install است و g یعنی نصب global و سراسری بستهی create-react-app. نصب سراسری یک بسته یعنی در هر پوشهای میتوان به امکانات آن دسترسی یافت و از آن استفاده کرد. اگر از سوئیچ g استفاده نمیشد، این بسته تنها در پوشهی جاری و با سطح دید مختص به آن، نصب و قابل استفاده میشد.
ابزار دیگری که در این سری از آن استفاده خواهیم کرد، ادیتور بسیار معروف و محبوب VSCode است. پس از دریافت و نصب آن، چند افزونهی زیر را نیز به آن اضافه خواهیم کرد:
برای نصب آنها، پنل extensions را در VSCode، از نوار ابزار کنار صفحهی آن، انتخاب کرده و نامهای فوق را در آن جستجو و سپس نصب کنید.
و یا میتوانید این فایل را اجرا کرده و تعدادی از افزونههای مفید VSCode را یکجا نصب کنید: install-addons.zip
همچنین قابلیت فرمتکردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونهها به صورت خودکار صورت گیرد. برای این منظور گزینهی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:
علاوه بر اینها، جهت کار بهتر با VSCode، بهتر است بررسی کنندههای کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینهی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحهی نمایش تنظیمات آن، گزینهی Auto fix on save آنرا انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینهی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آنرا از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده میشود) تا افزونهی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).
ایجاد قالب اولین برنامهی React
در ادامه برای ایجاد اولین برنامهی React، از بستهی create-react-app که پیشتر آنرا نصب کردیم، استفاده میکنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
در اینجا sample-01 یک نام دلخواه است و در حین اجرای این دستور باید به اینترنت متصل باشید تا وابستگیهای مرتبط با پروژه را نیز دریافت کند. برای بار اول، اجرای آن ممکن است کمی طول بکشد. اما از دفعات آتی، چون بستههای مرتبط را در npm-cache سیستم نیز ذخیره میکند، اجرای آن بسیار سریع خواهد بود.
این قالب نه تنها React را نصب میکند، بلکه یک development server را برای اجرا و مشاهدهی سریع برنامه، webpack را برای یکی کردن فایلها (bundling & minification)، Babel را برای کامپایل کدهای فایلهای JSX و ... نیز نصب میکند. بنابراین به این ترتیب، یک پروژهی تنظیم شده و آمادهی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیهی آن نیست.
پس ایجاد برنامه، وارد پوشهی sample-01 شده و دستور npm start را صادر کنید:
به این ترتیب برنامه بر روی پورت 3000، قابل دسترسی و مشاهده میشود:
development server آن، تغییرات فایلهای برنامه را تحت نظر قرار میدهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.
بررسی ساختار اولین پروژهی React ایجاد شده
ساختار پوشهها و فایلهای مثال اولیهی ایجاد شده توسط قالب create-react-app به صورت زیر است:
البته شما در این تصویر پوشهی node_modules را که در کنار این پوشهها قرار دارد، مشاهده نمیکنید. وجود یک چنین پوشهی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه میکند (مصرف حافظهی بالا، به همراه کند شدن شدید آن). اگر نمیخواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:
و سپس در فایل باز شده، یک چنین تنظیمی را میتوانید اضافه و یا ویرایش و تکمیل کنید:
در ادامه پوشهی public این پروژه را مشاهده میکنید. تمام فایلهایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار میگیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آنرا بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آنها ذکر manifest.json است، مشاهده میکنید. کار فایل manifest.json، ارائهی یک سری متادیتای خاص مخصوص دستگاههای موبایل است که در آنها بجای favicon.ico، میتوان از تصاویر و یا آیکنهای بزرگتری مانند فایلهای png موجود در پوشهی public، استفاده کرد. در ادامهی این فایل، به تنظیم زیر میرسیم:
div با id مساوی root، محل ارائهی کل برنامهی React ما است.
در پوشهی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحهی مشکی پیشفرض این قالب را انجام میدهد. در این فایل، شاهد بازگشت یک چنین تگهایی هستیم:
احتمالا تابحال چنین return ای را در برنامههای جاوا اسکریپتی مشاهده نکردهاید؛ چون درج آنها در فایلهای js به این نحو، غیرمجاز است. این تگها نه رشتهای هستند و نه HTML خالص. به آن jsx گفته میشود که مخفف JavaScript XML میباشد. کار آن ارائهی ساختار UI ای است که قرار است رندر شود. یک چنین کدی برای اینکه قابل تفسیر و اجرا باشد، از درون کامپایلر ویژهای به نام Babel عبور میکند و تبدیل به کدهای جاوا اسکریپتی خالصی میشود که برای مرورگرها قابل درک و اجرا است.
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
همانطور که مشاهده میکنید، این قطعه کد jsx (که یک رشتهی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شدهاست:
بدیهی است نوشتن کدهای jsx، سادهتر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسانتر است. به همین جهت در کدهای React، ما از jsx استفاده میکنیم و تفسیر آنرا به Babel واگذار خواهیم کرد.
در پوشهی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطهی آغازین برنامه را مشخص میکند. در قسمتهای بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده میکنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شدهاست و کار آن کمک به ارائهی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.
نوشتن اولین برنامهی React
به پوشهی src ایجاد شده مراجعه کرده و تمام فایلهای موجود و پیشفرض آنرا حذف کنید. در ادامه خودمان آنها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد میکنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
در اینجا شیء React از ماژول react دریافت شده و سپس یک ثابت را با یک عبارت jsx مقدار دهی کردهایم. چون از jsx استفاده میکنیم، ذکر import ابتدای فایل الزامی است؛ از این جهت که Babel به کمک آن است که میتواند معادل React.createElement را تولید کند.
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره میکنیم، خروجی نهایی را در مرورگر نمایش میدهد (اگر هم آنرا بستهاید، یکبار از طریق خط فرمان، دستور npm start را در ریشهی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته میشود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحهی سفید را مشاهده خواهید کرد. اکنون دکمهی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.
شیءای را که در اینجا مشاهده میکنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کردهاست که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظهای DOM مختص React، جهت محاسبهی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آنها به DOM اصلی در مرورگر).
اکنون میخواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
برای این منظور نیاز است از متد ReactDOM.render استفاده کرد. این شیء در ماژول react-dom قرار دارد؛ به همین جهت در ابتدای فایل import شدهاست. سپس در متد render آن، ابتدا المانی که قرار است رندر شود ذکر خواهد شد و سپس محل درج آنرا مشخص میکنیم که دقیقا به همان div با id مساوی root در فایل public\index.html اشاره میکند.
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:
همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کردهاست.
هدف از این مثال ساده، نمایش نحوهی کارکرد React، در پشت صحنه بود. در یک برنامهی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشهای برنامه بوده و میتواند شامل درختی از کامپوننتها که UI نهایی را تشکیل میدهند، شود.
نگاهی به تنظیمات پروژهی ایجاد شده
اگر فایل package.json پروژه را باز کنید، یک چنین بستههایی در آن درج شدهاست:
در اینجا صرفا سه بستهی react، react-dom و react-scripts را در قسمت dependencies مشاهده میکنید که کل Importهای ما را تشکیل میدهند.
بستهی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام میدهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start میشود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بستهی نهایی بهینه سازی شده را تولید میکند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنهی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر میشوند). همچنین این عملیات نیز یک طرفهاست. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانیهای بعدی create-react-app را با مشکل مواجه میکند. این گزینه صرفا مختص توسعه دهندگان پیشرفتهی React است.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-01.zip
در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی میکنیم.
به همین جهت اگر میخواهید رزومهی غنیتری را ارائه دهید، فراگیری React میتواند موقعیتهای شغلی بیشتری را نصیب شما کند.
ساختار کلی یک برنامهی React
کامپوننتها (جزئی از یک رابط کاربری) قلب هر برنامهی React ای را تشکیل میدهند. برای ساخت یک برنامهی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب میکنیم تا به رابط کاربری نهایی برسیم.
هر برنامهی React، حداقل از یک کامپوننت تشکیل میشود که به آن Root component هم میگویند. این کامپوننت بیانگر کل برنامهاست و دربرگیرندهی مابقی Child components برنامه است. بنابراین ساختار هر برنامهی React، شبیه به درختی از کامپوننتها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید میخواهیم UI برنامهای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحهی توئیتر، به کامپوننتهایی شکسته میشود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقهی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامهی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننتهای برنامه مانند nav bar ، trends ، profile و feed را انجام میدهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز میتواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننتهای tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل میدهد. هر کدام از آنها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار میگیرند تا UI نهایی را شکل دهند:
هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل میشود:
class Tweet { state = {}; render() { } }
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام میدهد. زمانیکه در state کامپوننتی تغییری رخ میدهد، یک React Element جدید تولید میشود. سپس React این شیء جدید را با نمونهی قبلی آن مقایسه کرده و تغییرات رخداده را محاسبه میکند. در آخر این تغییرات را به DOM واقعی اعمال میکند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمیدهیم. در اینجا فقط state یک کامپوننت را تغییر میدهیم و سپس React، کار ایجاد شیء UI درون حافظهای متناظر با آن و سپس اعمال آنرا به UI نهایی قابل مشاهدهی در مرورگر، انجام میدهد. به همین جهت به این کتابخانه React میگویند! چون به تغییرات state کامپوننتها واکنش نشان میدهد و سپس DOM واقعی را به روز میکند.
Angular یا React؟!
هر دوی React و Angular از لحاظ طراحی کامپوننتها بسیار شبیه به هم هستند؛ اما Angular یک فریمورک است و React تنها یک کتابخانه. تنها کاری را که React انجام میدهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننتها. این تمام کاری است که React انجام میدهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریعتر و سادهتر از Angular است. بدیهی است یک برنامهی تک صفحهای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویسهای HTTP نیز تشکیل میشود. در React شما مختار هستید که کتابخانههای جانبی فراهم شدهی برای آنرا خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریمورک ارائه میشوند.
برپایی محیط توسعهی React
اولین برنامهای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانهی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
اگر هم خیلی پیشترها node.js را نصب کردهاید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشههای قدیمی آنرا یکی یکی یافته و دستی حذف کنید . سپس مجددا آنرا نصب کنید.
در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
ابزار دیگری که در این سری از آن استفاده خواهیم کرد، ادیتور بسیار معروف و محبوب VSCode است. پس از دریافت و نصب آن، چند افزونهی زیر را نیز به آن اضافه خواهیم کرد:
برای نصب آنها، پنل extensions را در VSCode، از نوار ابزار کنار صفحهی آن، انتخاب کرده و نامهای فوق را در آن جستجو و سپس نصب کنید.
و یا میتوانید این فایل را اجرا کرده و تعدادی از افزونههای مفید VSCode را یکجا نصب کنید: install-addons.zip
همچنین قابلیت فرمتکردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونهها به صورت خودکار صورت گیرد. برای این منظور گزینهی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:
علاوه بر اینها، جهت کار بهتر با VSCode، بهتر است بررسی کنندههای کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks
پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینهی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحهی نمایش تنظیمات آن، گزینهی Auto fix on save آنرا انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینهی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آنرا از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده میشود) تا افزونهی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).
ایجاد قالب اولین برنامهی React
در ادامه برای ایجاد اولین برنامهی React، از بستهی create-react-app که پیشتر آنرا نصب کردیم، استفاده میکنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
این قالب نه تنها React را نصب میکند، بلکه یک development server را برای اجرا و مشاهدهی سریع برنامه، webpack را برای یکی کردن فایلها (bundling & minification)، Babel را برای کامپایل کدهای فایلهای JSX و ... نیز نصب میکند. بنابراین به این ترتیب، یک پروژهی تنظیم شده و آمادهی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیهی آن نیست.
پس ایجاد برنامه، وارد پوشهی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01 > npm start
development server آن، تغییرات فایلهای برنامه را تحت نظر قرار میدهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.
بررسی ساختار اولین پروژهی React ایجاد شده
ساختار پوشهها و فایلهای مثال اولیهی ایجاد شده توسط قالب create-react-app به صورت زیر است:
البته شما در این تصویر پوشهی node_modules را که در کنار این پوشهها قرار دارد، مشاهده نمیکنید. وجود یک چنین پوشهی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه میکند (مصرف حافظهی بالا، به همراه کند شدن شدید آن). اگر نمیخواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:
و سپس در فایل باز شده، یک چنین تنظیمی را میتوانید اضافه و یا ویرایش و تکمیل کنید:
"files.exclude": { "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, "**/node_modules": true, "**/wwwroot": true, "**/bower_components": true, "**/**/bin": true, "**/**/obj": true, "**/packages": true },
در ادامه پوشهی public این پروژه را مشاهده میکنید. تمام فایلهایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار میگیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آنرا بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آنها ذکر manifest.json است، مشاهده میکنید. کار فایل manifest.json، ارائهی یک سری متادیتای خاص مخصوص دستگاههای موبایل است که در آنها بجای favicon.ico، میتوان از تصاویر و یا آیکنهای بزرگتری مانند فایلهای png موجود در پوشهی public، استفاده کرد. در ادامهی این فایل، به تنظیم زیر میرسیم:
<body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div>
در پوشهی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحهی مشکی پیشفرض این قالب را انجام میدهد. در این فایل، شاهد بازگشت یک چنین تگهایی هستیم:
return ( <div className="App"> <header className="App-header"> ... </header> </div> );
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;
همانطور که مشاهده میکنید، این قطعه کد jsx (که یک رشتهی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شدهاست:
"use strict"; var element = React.createElement("h1", null, "Hello World!");
بدیهی است نوشتن کدهای jsx، سادهتر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسانتر است. به همین جهت در کدهای React، ما از jsx استفاده میکنیم و تفسیر آنرا به Babel واگذار خواهیم کرد.
در پوشهی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطهی آغازین برنامه را مشخص میکند. در قسمتهای بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده میکنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شدهاست و کار آن کمک به ارائهی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.
نوشتن اولین برنامهی React
به پوشهی src ایجاد شده مراجعه کرده و تمام فایلهای موجود و پیشفرض آنرا حذف کنید. در ادامه خودمان آنها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد میکنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react"; const element = <h1>Hello World!</h1>; console.log(element);
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره میکنیم، خروجی نهایی را در مرورگر نمایش میدهد (اگر هم آنرا بستهاید، یکبار از طریق خط فرمان، دستور npm start را در ریشهی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته میشود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحهی سفید را مشاهده خواهید کرد. اکنون دکمهی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.
شیءای را که در اینجا مشاهده میکنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کردهاست که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظهای DOM مختص React، جهت محاسبهی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آنها به DOM اصلی در مرورگر).
اکنون میخواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react"; import ReactDOM from "react-dom"; const element = <h1>Hello World!</h1>; console.log(element); ReactDOM.render(element, document.getElementById("root"));
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:
همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کردهاست.
هدف از این مثال ساده، نمایش نحوهی کارکرد React، در پشت صحنه بود. در یک برنامهی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشهای برنامه بوده و میتواند شامل درختی از کامپوننتها که UI نهایی را تشکیل میدهند، شود.
نگاهی به تنظیمات پروژهی ایجاد شده
اگر فایل package.json پروژه را باز کنید، یک چنین بستههایی در آن درج شدهاست:
{ "name": "sample-01", "version": "0.1.0", "private": true, "dependencies": { "react": "^16.11.0", "react-dom": "^16.11.0", "react-scripts": "3.2.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }
بستهی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام میدهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start میشود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بستهی نهایی بهینه سازی شده را تولید میکند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنهی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر میشوند). همچنین این عملیات نیز یک طرفهاست. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانیهای بعدی create-react-app را با مشکل مواجه میکند. این گزینه صرفا مختص توسعه دهندگان پیشرفتهی React است.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-01.zip
در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی میکنیم.
با پیشرفت HTML 5 و پدید آمدن چارچوبهای مختلف JavaScript توسعهی نرم افزارهای تک صفحه ای تحت وب (Single Page Applications) محبوب شده است.
اخیرا مطالب خوبی در رابطه با AngularJS در وبسایت جاری منتشر شده است. KnockoutJS توسط Microsoft معرفی شد و در قالب پیشفرض پروژههای SPA قرار گرفت ، بنابراین احتمالا این سوال برای افرادی مطرح شده است که تفاوت بین KnockoutJS و AngularJS چیست ؟
می توان پاسخ داد این مقایسه ممکن نیست.
KnockoutJS : یک پیاده سازی مستقل JavaScript از الگوی MVVM با امکانات Databinding میباشد. Knokcout یک کتابخانهی Databinding است نه یک کتاب خانهی SPA
AngularJS : طبق معرفی در این مطلب AngularJS فریم ورکی متن باز و نوشته شده به زبان جاوا اسکریپت است. هدف از به وجود آمدن این فریم ورک، توسعه هر چه سادهتر SPAها با الگوی طراحی MVC و تست پذیری هر چه آسانتر آنها است. این فریم ورک توسط یکی از محققان Google در سال 2009 به وجود آمد. بعدها این فریم ورک تحت مجوز MIT به صورت متن باز در آمد و اکنون گوگل آن را حمایت میکند و توسط هزاران توسعه دهنده در سرتاسر دنیا، توسعه داده میشود.
بنابراین شاید بهتر باشد ذکر شود AngularJS یک Presentation Framework مخصوص برنامههای وب تک صفحه ای میباشد در حالی که KnockoutJS کتاب خانه ای با تمرکز بر Databinding میباشد ، بنابراین مقایسهی اینها چندان صحیح نیست.
اگر قصد بر بررسی گزینههای دیگر در کنار Angular باشد ، میتوان از Durandal نام برد. Durandal یک چارچوب SPA میباشد ، این چارچوب بر فراز jQuery ، RequireJS و Knockout توسعه پیدا کرده است. (سابقا برای routing از SammyJS استفاده میکرد که در نسخههای اخیر از موتور خودش استفاده میکند.)
Durandal از Knockout جهت Databinding و از RequireJS برای مدیریت وابستگیها استفاده میکند.
Angular همهی امکانات بالا را مستقل پیاده سازی کرده و حتی نیازی به jQuery ندارد. اگر jQuery وجود داشته باشد Angular از آن استفاده میکند در غیر این صورت از jQuery Lite یا jqLite استفاده میکند. jqLite پیاده سازی توابع متداول jQuery برای دستکاری DOM میباشد. اطلاعات بیشتر در اینجا
بنابراین با استفاده تنها از KnockoutJS نمیتوان یک برنامهی کامل SPA توسعه داد ، در کنار آن نیاز به کتابخانههای دیگری مثل jQuery برای مدیریت درخواستهای AJAX و استفاده از دیگر APIها ، Sammy برای routing و RequireJS برای مدیریت وابستگیها میباشد.
در Knockout و در نتیجه Durandal عمل Databinding به این صورت است :
// JavaScript var vm = { firstName = ko.observable('John') }; ko.applyBindings(vm);
<!-- HTML --> <input data-bind="value:firstName"/>
در Angular :
// JavaScript // Inside of a personController this.firstName = 'John';
در Angular همچنین از یک روش Controller As استفاده میشود :
<!-- HTML --> <div ng-controller="personController as vm"> <input ng-model="vm.firstName"/> </div>
اگر تنها نیاز به یک کتابخانهی Databinding باشد ، Knockout گزینهی مناسبی است ، به خوبی از عمل مقید سازی دادهها پشتیبانی میکند و Syntax خوش دستی دارد اما اگر نیاز به چارچوبی برای توسعهی پروژههای SPA میباشد میتوان از Angular یا Durandal استفاده کرد.
مقایسهی Knockout با Angular همانند مقایسهی موتور بنز با ماشین پورشه میباشد.
در حین کار با ارتباطات بین اشیاء و جداول، دانستن یک سری از نکات میتوانند در کم کردن تعداد رفت و برگشتهای به سرور مؤثر واقع شده و نهایتا سبب بالا رفتن سرعت برنامه شوند. از این دست میتوان به یک سری نکات ریز همراه با primary-keys و foreign-keys اشاره کرد که در ادامه به آنها پرداخته خواهد شد.
در ابتدا کلاسهای مدل و Context برنامه را به شکل زیر درنظر بگیرید:
در اینجا کلاس صورتحساب و حساب مرتبط به آن تعریف شدهاند. سپس به کمک DbContext این دو کلاس در معرض دید EF Code first قرار گرفتهاند و در کلاس Configuration نحوه آغاز بانک اطلاعاتی به همراه تعدادی رکورد اولیه مشخص شده است.
نحوه صحیح مقدار دهی کلید خارجی در EF Code first
تا اینجا یک روال متداول را مشاهده کردیم. اکنون سؤال این است که اگر بخواهیم اولین رکورد صورتحساب ثبت شده توسط متد Seed را ویرایش کرده و مثلا حساب دوم را به آن انتساب دهیم، بهینهترین روش چیست؟ بهینهترین در اینجا منظور روشی است که کمترین تعداد رفت و برگشت به بانک اطلاعاتی را داشته باشد. همچنین فرض کنید در صفحه ویرایش، اطلاعات حسابها در یک Drop down list شامل نام و id آنها نیز وجود دارد.
روش اول:
این روش مخصوص تازه واردهای EF Code first است و آنطور که مدنظر آنها است کار نمیکند.
به کمک متد Find اولین رکورد یافت شده و سپس بر اساس اطلاعات drop down در دسترس، یک شیء جدید حساب را ایجاد و سپس تغییرات لازم را اعمال میکنیم. در نهایت اطلاعات را هم ذخیره خواهیم کرد.
این روش به ظاهر کار میکنه اما حاصل آن ذخیره رکورد حساب سومی با id=3 در بانک اطلاعاتی است و سپس انتساب آن به اولین صورتحساب ثبت شده.
نتیجه: Id را دستی مقدار دهی نکنید؛ تاثیری ندارد. زیرا اطلاعات شیء جدید حساب، در سیستم tracking مرتبط با Context جاری وجود ندارد. بنابراین EF آنرا به عنوان یک شیء کاملا جدید درنظر خواهد گرفت، صرفنظر از اینکه Id را به چه مقداری تنظیم کردهاید.
روش دوم:
اینبار بر اساس Id دریافت شده از Drop down list، شیء حساب دوم را یافته و به صورتحساب اول انتساب میدهیم. این روش درست کار میکند؛ اما ... بهینه نیست. فرض کنید شیء جاری دارای 5 کلید خارجی است. آیا باید به ازای هر کلید خارجی یکبار از بانک اطلاعاتی کوئری گرفت؟
مگر نه این است که اطلاعات نهایی ذخیره شده در بانک اطلاعاتی متناظر با حساب صورتحساب جاری، فقط یک عدد بیشتر نیست. بنابراین آیا نمیشود ما تنها همین عدد متناظر را بجای دریافت کل شیء به صورتحساب نسبت دهیم؟
پاسخ: بله. میشود! ادامه آن در روش سوم.
روش سوم:
در اینجا بهترین کار و یکی از best practices طراحی مدلهای EF این است که طراحی کلاس صورتحساب را به نحو زیر تغییر دهیم:
به این ترتیب هم navigation property که سبب تعریف رابطه بین دو شیء و همچنین lazy loading اطلاعات آن میشود پابرجا خواهد بود و هم توسط خاصیت جدید AccountId که توسط ویژگی ForeignKey معرفی شده است، ویرایش اطلاعات آن دقیقا همانند کار با یک بانک اطلاعاتی واقعی خواهد شد.
اینبار به کمک خاصیت متناظر با کلید خارجی جدول، مقدار دهی و ویرایش کلیدهای خارجی یک شیء به سادگی زیر خواهد بود؛ خصوصا بدون نیاز به رفت و برگشت اضافی به بانک اطلاعاتی جهت دریافت اطلاعات متناظر با اشیاء تعریف شده به صورت navigation property :
وارد کردن یک شیء به سیستم Tracking
در قسمت قبل عنوان شد که Id را دستی مقدار دهی نکنید، چون تاثیری ندارد. سؤال: آیا میشود این شیء ویژه تعریف شده را به سیستم Tracking وارد کرد؟
پاسخ: بلی. به نحو زیر:
در اینجا شیء حساب دوم را به صورت دستی و بدون واکشی از بانک اطلاعاتی ایجاد کردهایم. بنابراین از دیدگاه Context جاری هیچ ارتباطی به بانک اطلاعاتی نداشته و یک شیء جدید درنظر گرفته میشود (صرفنظر از Id آن). اما میتوان این وضعیت را تغییر داد. فقط کافی است State آنرا به نحوی که ملاحظه میکنید به Modified تغییر دهیم. اکنون اگر اطلاعات این شیء را ذخیره کنیم، دقیقا حساب با id=2 در بانک اطلاعاتی ویرایش خواهد شد و نه اینکه حساب جدیدی ثبت گردد.
در ابتدا کلاسهای مدل و Context برنامه را به شکل زیر درنظر بگیرید:
using System; using System.Data.Entity; using System.Data.Entity.Migrations; namespace TestKeys { public class Bill { public int Id { get; set; } public decimal Amount { set; get; } public virtual Account Account { get; set; } } public class Account { public int Id { get; set; } public string Name { get; set; } } public class MyContext : DbContext { public DbSet<Bill> Bills { get; set; } public DbSet<Account> Accounts { get; set; } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { var a1 = new Account { Name = "a1" }; var a2 = new Account { Name = "a2" }; var bill1 = new Bill { Amount = 100, Account = a1 }; var bill2 = new Bill { Amount = 200, Account = a2 }; context.Bills.Add(bill1); context.Bills.Add(bill2); base.Seed(context); } } public static class Test { public static void Start() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); using (var ctx = new MyContext()) { var bill1 = ctx.Bills.Find(1); Console.WriteLine(bill1.Amount); } } } }
در اینجا کلاس صورتحساب و حساب مرتبط به آن تعریف شدهاند. سپس به کمک DbContext این دو کلاس در معرض دید EF Code first قرار گرفتهاند و در کلاس Configuration نحوه آغاز بانک اطلاعاتی به همراه تعدادی رکورد اولیه مشخص شده است.
نحوه صحیح مقدار دهی کلید خارجی در EF Code first
تا اینجا یک روال متداول را مشاهده کردیم. اکنون سؤال این است که اگر بخواهیم اولین رکورد صورتحساب ثبت شده توسط متد Seed را ویرایش کرده و مثلا حساب دوم را به آن انتساب دهیم، بهینهترین روش چیست؟ بهینهترین در اینجا منظور روشی است که کمترین تعداد رفت و برگشت به بانک اطلاعاتی را داشته باشد. همچنین فرض کنید در صفحه ویرایش، اطلاعات حسابها در یک Drop down list شامل نام و id آنها نیز وجود دارد.
روش اول:
using (var ctx = new MyContext()) { var bill1 = ctx.Bills.Find(1); var a2 = new Account { Id = 2, Name = "a2" }; bill1.Account = a2; ctx.SaveChanges(); }
به کمک متد Find اولین رکورد یافت شده و سپس بر اساس اطلاعات drop down در دسترس، یک شیء جدید حساب را ایجاد و سپس تغییرات لازم را اعمال میکنیم. در نهایت اطلاعات را هم ذخیره خواهیم کرد.
این روش به ظاهر کار میکنه اما حاصل آن ذخیره رکورد حساب سومی با id=3 در بانک اطلاعاتی است و سپس انتساب آن به اولین صورتحساب ثبت شده.
نتیجه: Id را دستی مقدار دهی نکنید؛ تاثیری ندارد. زیرا اطلاعات شیء جدید حساب، در سیستم tracking مرتبط با Context جاری وجود ندارد. بنابراین EF آنرا به عنوان یک شیء کاملا جدید درنظر خواهد گرفت، صرفنظر از اینکه Id را به چه مقداری تنظیم کردهاید.
روش دوم:
using (var ctx = new MyContext()) { var bill1 = ctx.Bills.Find(1); var a2 = ctx.Accounts.Find(2); bill1.Account = a2; ctx.SaveChanges(); }
مگر نه این است که اطلاعات نهایی ذخیره شده در بانک اطلاعاتی متناظر با حساب صورتحساب جاری، فقط یک عدد بیشتر نیست. بنابراین آیا نمیشود ما تنها همین عدد متناظر را بجای دریافت کل شیء به صورتحساب نسبت دهیم؟
پاسخ: بله. میشود! ادامه آن در روش سوم.
روش سوم:
در اینجا بهترین کار و یکی از best practices طراحی مدلهای EF این است که طراحی کلاس صورتحساب را به نحو زیر تغییر دهیم:
public class Bill { public int Id { get; set; } public decimal Amount { set; get; } [ForeignKey("AccountId")] public virtual Account Account { get; set; } public int AccountId { set; get; } }
اینبار به کمک خاصیت متناظر با کلید خارجی جدول، مقدار دهی و ویرایش کلیدهای خارجی یک شیء به سادگی زیر خواهد بود؛ خصوصا بدون نیاز به رفت و برگشت اضافی به بانک اطلاعاتی جهت دریافت اطلاعات متناظر با اشیاء تعریف شده به صورت navigation property :
using (var ctx = new MyContext()) { var bill1 = ctx.Bills.Find(1); bill1.AccountId = 2; ctx.SaveChanges(); }
وارد کردن یک شیء به سیستم Tracking
در قسمت قبل عنوان شد که Id را دستی مقدار دهی نکنید، چون تاثیری ندارد. سؤال: آیا میشود این شیء ویژه تعریف شده را به سیستم Tracking وارد کرد؟
پاسخ: بلی. به نحو زیر:
using (var ctx = new MyContext()) { var a2 = new Account { Id = 2, Name = "a2_a2" }; ctx.Entry(a2).State = System.Data.EntityState.Modified; ctx.SaveChanges(); }
عموما در برنامههای وب مرسوم است که پیغام به کاربر را در همان لابلای html صفحه نمایش میدهند. مثلا یک برچسب و سپس تنظیم متن آن در کد برنامه به صورت پویا.
با استفاده از پلاگینهای jQuery اینکار را به صورت شکیلتری میتوان انجام داد. برای مثال:
پلاگین کم حجمی برای این منظور موجود است به نام jQuery Notice (یکی از چند ده نمونه موجود)
<script type="text/javascript">
$(document).ready(function()
{
jQuery.noticeAdd({
text: 'پیغامی به کاربر',
stay: false
});
});
</script>
<div id="myElement" style="position: absolute">This stays at the top</div>
با استفاده از jQuery اینکار به صورت زیر قابل انجام است:
<script type="text/javascript">
$(document).ready(function()
{
$(window).scroll(function() {
$('#myElement').css('top', $(this).scrollTop() + "px");
});
});
</script>
ولی این روش جهت نمایش پیغامی پویا به کاربر مشکل دارد.
نیاز است به ازای هر پیغام پویا یکبار به نحوی این اسکریپت به صفحه تزریق شود که روش انجام کار در ASP.Net به صورت زیر میتواند باشد:
using System;
using System.Web.UI;
using System.Web;
public class CAddJqueryNotice
{
/// <summary>
/// نمایش یک پیغام بر اساس پلاگین نوتیس
/// </summary>
/// <param name="title">عنوان</param>
/// <param name="msg">پیغام</param>
/// <param name="rtl">راست به چپ؟</param>
/// <param name="duration">مدت زمان نمایش</param>
/// <param name="autoHide">به صورت خودکار بسته شود؟</param>
public static void Show(string title, string msg, bool rtl, int duration, bool autoHide)
{
string scriptBlock
= string.Format(@"<script type=""text/javascript"">
$(document).ready(function() {{
jQuery.noticeAdd({{
text: '<b>{0}</b><br/><div align=left dir={1}>{2}</div>',
stay: {3},
stayTime: {4}
}});
}});
</script>",
title,
(rtl ? "rtl" : "ltr"),
msg,
(autoHide ? "false" : "true"),
duration);
if (HttpContext.Current == null || HttpContext.Current.Handler == null) return;
Page page = HttpContext.Current.Handler as Page;
if (page != null)
page.ClientScript.RegisterStartupScript(
page.GetType(),
"script" + new Guid().ToString("N"),
scriptBlock,
false);
}
}
برای آزمایش آن یک دکمه را در صفحه قرار داده و در روال رخداد گردان کلیک آن کد زیر را اضافه کنید:
CAddJqueryNotice.Show( "لطفا دوباره سعی کنید", "مشکلی رخ داده است", true, 2000, true);
بدیهی است قبل از استفاده از کد فوق، باید چند سطر زیر را به هدر master page سایت خود اضافه کنید:
<script src="jquery-1.3.2.js" type="text/javascript"></script>
<link href="jquery.notice.css" type="text/css" media="screen" rel="stylesheet" />
<script src="jquery.notice.js" type="text/javascript"></script>
الگوی decorator، امکان محصور کردن یک شیء مفروض را با لایهای بر فراز آن میسر میکند. برای مثال بجای اینکه در تمام متدهای سرویسی از try/catch استفاده کنیم، میتوانیم این متدها را با یک ExceptionHandlingDecorator مزین کنیم و یا از این دست اعمال تکراری میتوان به لاگ کردن ورودی و خروجیهای یک متد و یا کش کردن اطلاعات آنها نیز اشاره کرد. حتی عملیاتی مانند تشخیص خواص تغییر یافتهی یک شیء در Entity framework نیز به کمک همین مزین کنندهها که شیء اصلی در حال استفاده را با ایجاد لایهای بر روی آنها محصور میکنند، انجام میشود. به این عملیات Aspect oriented programming و یا AOP نیز میگویند؛ در اینجا واژهی Aspect به اعمال مشترک و متداول موجود در برنامه اشاره میکند. در این مطلب قصد داریم نمونهای از این تزئین کنندهها را به کمک سیستم تزریق وابستگیهای NET Core. پیاده سازی کنیم.
پیاده سازی الگوی Decorator به کمک سیستم تزریق وابستگیهای NET Core.
مثال زیر را در نظر بگیرید که در آن یک سرویس تعریف شدهاست و در این بین استثنائی رخ دادهاست.
میخواهیم بدون تغییری در کدهای این کلاس، به متدهای آن در حین اجرای نهایی، یک try/catch را به همراه logging، اضافه کنیم. به همین جهت نیاز خواهیم داشت تا یک محصور کننده (تزئین کننده یا decorator در اینجا) را برای آن طراحی کنیم:
این محصور کننده نیز دقیقا همان ITaskService را پیاده سازی میکند؛ اما در سازندهی آن یک ITaskService را نیز دریافت میکند. علت اینجا است که توسط آن بتوان متدهای ITaskService تزریقی را اجرا کرد و بر روی آن اعمالی مانند کش کردن، لاگ کردن و مدیریت استثناءها و غیره را انجام داد. برای مثال در متد Run آن مشاهده میکنید که متد Run همان وهلهی تزریقی اجرا شدهاست؛ اما درون یک try/catch به همراه لاگ کردن جزئیات استثنای رخ داده.
مزیت اینکار، پیاده سازی اصل DRY یا Don't repeat yourself است. کاری که برای رفع این مشکل قرار است انجام دهیم، استفاده از یک تزئین کننده (محصور کننده)، کپسوله سازی اعمال تکراری و سپس اتصال آن به قسمتهای مختلف برنامه است. همچنین در این حالت اصل open closed principle نیز بهتر رعایت خواهد شد. از این جهت که کدهای تکراری برنامه به یک لایهی دیگر منتقل شدهاند و دیگر نیازی نیست برای تغییر آنها، کدهای قسمتهای اصلی برنامه را تغییر داد (کدهای برنامه باز خواهند بود برای توسعه و بسته برای تغییر).
پس از طراحی این تزئین کننده، اکنون نوبت به معرفی آن به سیستم تزریق وابستگیهای NET Core. است:
روش انجام اینکار را نیز در «قسمت ششم - دخالت در مراحل وهله سازی اشیاء توسط IoC Container» بیشتر بررسی کردهایم.
در اینجا هم میتوان در صورت نیاز اصل کلاس MyTaskService را بدون هیچ نوع تزئین کنندهای از سیستم تزریق وابستگیها دریافت کرد و یا اگر وهلهای از سرویس ITaskService را از آن درخواست کردیم، ابتدا شیء MyTaskServiceDecorator وهله سازی شده و سپس توسط آن یک نمونهی محصور شده و تزئین شدهی MyTaskService به فراخوان بازگشت داده خواهد شد.
ساده سازی معرفی تزئین کنندهها به سیستم تزریق وابستگیهای NET Core. به کمک Scrutor
در «قسمت هشتم - ساده سازی معرفی سرویسها توسط Scrutor» با کتابخانهی Scrutor آشنا شدیم. یکی دیگر از قابلیتهای آن، امکان ساده سازی تعریف تزئین کنندها است:
در اینجا معادل کدهایی را که با روش factory خود NET Core. نوشتیم، ملاحظه میکنید. ابتدا نیاز است خود سرویس اصلی غیر تزئین شده، به نحو متداولی به سیستم معرفی شود. سپس متد الحاقی جدید <,>Decorate را با همان اینترفیس و اینبار با Decorator مدنظر معرفی میکنیم. کاری که Scrutor در اینجا انجام میدهد، یافتن سرویس ITaskService معرفی شدهی پیشین و تعویض آن با MyTaskServiceDecorator میباشد. بنابراین نیاز است تعریف services.AddTransient پیش از تعریف services.Decorate انجام شده باشد. این روش تمیزتر از روش قبلی به نظر میرسد و شامل وهله سازی مستقیم MyTaskServiceDecorator به همراه فراهم آوردن تمام پارامترهای سازندهی آن توسط ما نیست.
پیاده سازی الگوی Decorator به کمک سیستم تزریق وابستگیهای NET Core.
مثال زیر را در نظر بگیرید که در آن یک سرویس تعریف شدهاست و در این بین استثنائی رخ دادهاست.
public interface ITaskService { void Run(); } public class MyTaskService : ITaskService { public void Run() { throw new InvalidOperationException("An exception from the MyTaskService!"); } }
using System; using Microsoft.Extensions.Logging; namespace CoreIocServices { public class MyTaskServiceDecorator : ITaskService { private readonly ILogger<MyTaskServiceDecorator> _logger; private readonly ITaskService _decorated; public MyTaskServiceDecorator( ILogger<MyTaskServiceDecorator> logger, ITaskService decorated) { _logger = logger; _decorated = decorated; } public void Run() { try { _decorated.Run(); } catch (Exception ex) { _logger.LogCritical(ex, "An unhandled exception has been occurred."); } } } }
مزیت اینکار، پیاده سازی اصل DRY یا Don't repeat yourself است. کاری که برای رفع این مشکل قرار است انجام دهیم، استفاده از یک تزئین کننده (محصور کننده)، کپسوله سازی اعمال تکراری و سپس اتصال آن به قسمتهای مختلف برنامه است. همچنین در این حالت اصل open closed principle نیز بهتر رعایت خواهد شد. از این جهت که کدهای تکراری برنامه به یک لایهی دیگر منتقل شدهاند و دیگر نیازی نیست برای تغییر آنها، کدهای قسمتهای اصلی برنامه را تغییر داد (کدهای برنامه باز خواهند بود برای توسعه و بسته برای تغییر).
پس از طراحی این تزئین کننده، اکنون نوبت به معرفی آن به سیستم تزریق وابستگیهای NET Core. است:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<MyTaskService>(); services.AddTransient<ITaskService>(serviceProvider => new MyTaskServiceDecorator( serviceProvider.GetService<ILogger<MyTaskServiceDecorator>>(), serviceProvider.GetService<MyTaskService>()) );
در اینجا هم میتوان در صورت نیاز اصل کلاس MyTaskService را بدون هیچ نوع تزئین کنندهای از سیستم تزریق وابستگیها دریافت کرد و یا اگر وهلهای از سرویس ITaskService را از آن درخواست کردیم، ابتدا شیء MyTaskServiceDecorator وهله سازی شده و سپس توسط آن یک نمونهی محصور شده و تزئین شدهی MyTaskService به فراخوان بازگشت داده خواهد شد.
ساده سازی معرفی تزئین کنندهها به سیستم تزریق وابستگیهای NET Core. به کمک Scrutor
در «قسمت هشتم - ساده سازی معرفی سرویسها توسط Scrutor» با کتابخانهی Scrutor آشنا شدیم. یکی دیگر از قابلیتهای آن، امکان ساده سازی تعریف تزئین کنندها است:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<ITaskService, MyTaskService>(); services.Decorate<ITaskService, MyTaskServiceDecorator>();
نظرات مطالب
کار با کلیدهای اصلی و خارجی در EF Code first
- «... Id را دستی مقدار دهی نکنید؛ تاثیری ندارد. زیرا اطلاعات شیء جدید حساب، در
سیستم tracking مرتبط با Context جاری وجود ندارد. بنابراین EF آنرا به
عنوان یک شیء کاملا جدید درنظر خواهد گرفت، صرفنظر از اینکه Id را به چه
مقداری تنظیم کردهاید ...»
+ «... وارد کردن یک شیء به سیستم Tracking »
+ «... وارد کردن یک شیء به سیستم Tracking »
نظرات مطالب
EF Code First #12
جهت اطلاعات بیشتر مراجعه کنید به مطلب MARS؛ یک شیء اتصالی با یک کانکشن با چندین درخواست و یا یک شیء اتصالی و چندین بار باز و بسته شدن اتصالهای مدیریت شدهی توسط آن. هر دو مورد با یک Context ممکن است. اما در طی یک Context یک شیء اتصالی بیشتر ایجاد نمیشود (تغییرات شماره IDهای اتصال را در DNTProfiler بررسی کنید).
افزونهها/پلاگینهای زیادی جهت کار با table استاندارد HTML وجود دارند و خروجی رندر شدهی یک ASP.Net GridView هم در نهایت یک جدول است. فرض کنید قصد داریم افزونه زیر را به GridView استاندارد ASP.Net اعمال کنیم.
jQuery quickSearch plug-in
ظاهرا بدون مشکل خاصی اعمال میگردد. برای مثال در هدر صفحه داریم: (شبیه به مثال موجود در سایت اصلی آن، جهت اعمال به GridView1)
<script src="jquery.min.js" type="text/javascript"></script>
<script src="jquery.quicksearch.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
//جستجو در جدول
$('table#<%=GridView1.ClientID %> tbody tr').quicksearch({
position: 'before',
attached: 'span#attachSearch',
labelText: 'جستجو',
isFieldset: true,
loaderText: ' ... ',
fixWidths: true
});
});
</script>
(در اینجا محل قرارگیری تکست باکس مربوط به جستجو، در span ایی با id مساوی attachSearch تنظیم شده است، میتوانید از ID خود GridView هم استفاده کنید.)
<span id="attachSearch"></span>
<br />
<asp:GridView ID="GridView1" runat="server"></asp:GridView>
1- همانطور که در مقاله مربوط به ClientID ذکر شد، هیچ الزامی ندارد که ID مربوط به GridView شما برای مثال دقیقا همان GridView1 جهت استفاده در سمت کلاینت باشد و بسته به container آن، این نام ترکیبی از ID شیء(های) در بر گیرنده و شیء مورد نظر میباشد. به همین جهت از GridView1.ClientID استفاده گردید تا اسکریپت ما با آن مشکلی نداشته باشد.
2- خصوصیات ظاهری افزونه فوق از سلکتور quicksearch فایل css شما دریافت میشوند. برای مثال:
.quicksearch
{
width:190px;
}
پس از هر بار جستجو، header مربوط به GridView محو میشود، اما نمونه موجود در سایت اصلی افزونه، این مشکل را ندارد. چرا؟!
GridView مربوط به ASP.Net پس از رندر شدن، جدولی است که تگهای thead را ندارد. اگر به سورس صفحه سایت افزونه دقت نمائید، هدر جدول با تگهای thead محصور شده است اما GridView استاندارد ASP.Net به صورت پیش فرض اینکار را انجام نمیدهد و خروجی آن چیزی شبیه به جدول زیر است: (هدر با th مشخص میشود و از تگ thead خبری نیست)
<table >
<tr >
<th scope="col">col1</th>
<th scope="col">col2</th>
<th scope="col">col3</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
.
.
.
الف) راه حل سمت سرور
برای اضافه کردن thead باید کمی کد نویسی کرد. پس از اینکه گرید شما بایند شد، چند سطر زیر را اضافه کنید:
//سازگار با افزونههای جی کوئری
if (GridView1.Rows.Count > 0)
{
//This replaces <td> with <th> and adds the scope attribute
GridView1.UseAccessibleHeader = true;
//This will add the <thead> and <tbody> elements
GridView1.HeaderRow.TableSection = TableRowSection.TableHeader;
//This adds the <tfoot> element.
//Remove if you don't have a footer row
//GridView1.FooterRow.TableSection = TableRowSection.TableFooter;
}
سطر مربوط به جستجو را به صورت زیر هم میتوان نوشت:
$('table#<%=GridView1.ClientID %> tr:has(td)').quicksearch({
نکته:
اگر میخواهید که ناحیه مربوط به جستجوی افزونه فوق در پرینت صفحه ظاهر نشود به css صفحه چند سطر زیر را اضافه کنید:
@media print
{
.quicksearch
{
display: none;
}
}
تمرین!
افزونه جی کوئری زیر را به یک ASP.Net GridView اعمال کنید:
table sorter
یکی از مهمترین خصوصیات CLR این است که نوعها، ایمن هستند و همواره میداند که هر شیء از چه نوعی است. برای اثبات این ادعا میتوانید متد GetType را صدا بزنید تا به شما بگوید این شیء از چه نوعی است. متد GetType قابلیت رونویسی ندارد و به همین علت میتوانید مطمئن باشید که خروجی برگشتی دستکاری نشده است.
یکی از نیازهای طراحان این است که مرتبا نیاز به تبدیل نوعها را به یکدیگر دارند. CLR به شما اجازه میدهد که هر آبجکتی را به نوع مربوط به خودش یا والدینش تبدیل کنید. بسته به زبانی که انتخاب میکنید، این تبدیل شکل متفاوتی دارد و در سی شارپ نیاز به سینتکس خاصی نیست.
سی شارپ برای تبدیل یک شیء به نوعهای والدش، نیازی به ذکر نوع ندارد ولی اگر قرار است از سمت والد به سمت فرزند Cast شود نیاز است که صریحا نوع آن را اعلام کنید. در این روش اگر نوع تبدیلات با شیء ما سازگاری نداشته باشد، در زمان اجرا، با خطای
یکی از نیازهای طراحان این است که مرتبا نیاز به تبدیل نوعها را به یکدیگر دارند. CLR به شما اجازه میدهد که هر آبجکتی را به نوع مربوط به خودش یا والدینش تبدیل کنید. بسته به زبانی که انتخاب میکنید، این تبدیل شکل متفاوتی دارد و در سی شارپ نیاز به سینتکس خاصی نیست.
سی شارپ برای تبدیل یک شیء به نوعهای والدش، نیازی به ذکر نوع ندارد ولی اگر قرار است از سمت والد به سمت فرزند Cast شود نیاز است که صریحا نوع آن را اعلام کنید. در این روش اگر نوع تبدیلات با شیء ما سازگاری نداشته باشد، در زمان اجرا، با خطای
InvalidCastExceptio
internal class Employee { ... } public sealed class Program { public static void Main() { // بدون ذکر نام والد تبدیل صورت میگیرد Object o = new Employee(); // برای تبدیل والد به یکی از مشتقات آن نیاز است // نوع آن به طور صریح ذکر گردد // در بعضی زبانهای مثل ویژوال بیسیک نیازی به ذکر آن نیست Employee e = (Employee) o; } }
استفاده از کلمات as و is در تبدیلات
یکی دیگر از روشهای امن برای cast کردن اشیاء، استفاده از کلمهی کلیدی is هست. این عبارت چک میکند که آیا شیء مورد نظر، از نوع تبدیلی ما پشتیبانی میکند یا خیر؛ اگر true بازگرداند به این معنی است که پشتیبانی میشود و در حین cast کردن با خطایی روبرو نمیشویم.
Object o = new Object(); Boolean b1 = (o is Object); // b1 is true. Boolean b2 = (o is Employee); // b2 is false.
پی نوشت :در این بررسی اگر شیء نال باشد، مقدار برگشتی همیشه false است. چون به هیچ نوعی قابل تبدیل نیست.نحوهی استفادهی از کلمه کلیدی is در این تبدیل به شکل زیر است:
if (o is Employee) { Employee e = (Employee) o; }
برای بهبود کد بالا، سی شارپ کلمهی کلیدی as را ارائه میکند. کلمه کلیدی as باعث میشود اگر شیء به آن نوع قابل تبدیل باشد، ارجاعی صورت بگیرد؛ در غیر این صورت مقدار نال بازگشت داده میشود. شاید شما بگویید که در خط بعدی ما نیز دوباره مجددا یک عبارت شرطی داریم و دوباره داریم عمل تاییدیه را انجام میدهیم. ولی باید گفت این if به مراتب هزینهی کمتری نسبت به بررسیهای تبدیل یا Cast به شیوهی بالاست.
Employee e = o as Employee; if (e != null) { ..... }
فضاهای نام و اسمبلی ها
همانطور که مطلع هستید، فضاهای نام به ما این اجازه را میدهند تا نوعها را به صورت منطقی گروه بندی کنیم تا دسترسی به آنان راحتتر باشد. برای مثال مطمئنا با نگاه به اسم فضای نام
System.Text
public sealed class Program { public static void Main() { System.IO.FileStream fs = new System.IO.FileStream(...); System.Text.StringBuilder sb = new System.Text.StringBuilder(); } }
using System.IO; // Try prepending "System.IO." using System.Text; // Try prepending "System.Text." public sealed class Program { public static void Main() { FileStream fs = new FileStream(...); StringBuilder sb = new StringBuilder(); } }
موقعیکه شما نوعی را در یک فضای نام استفاده میکنید، این نوع به ترتیب بررسی میکند که نوع، در کدام فضای نام و کدام اسمبلی مورد استفاده قرار گرفته است. این اسمبلیها شامل FCL و اسمبلیهای خارجی است که به آن لینک کردهاید. حال ممکن است این سؤال پیش بیاید که ممکن است نام دو نوع، در دو فضای نام متفاوت، یکی باشد و در یک جا مورد استفاده قرار گرفتهاند. چگونه میتوان تشخیص داد که کدام نوع، متعلق به دیگری است؟ نظر مایکروسافت این است که تا میتوانید سعی کنید از اسامی متفاوت استفاده کنید. ولی در بعضی شرایط این مورد ممکن نیست. به همین علت باید هر دو کلاس یا به طور کامل، به همراه فضای نام نوشته شوند؛ یا اینکه یکی از آنها بدین شکل باشد و فضای نام نوع دیگر، با using صدا زده شود.
در مثال زیر ما دو نوع را با نام Widget داریم که در دو فضای نام Microsoft و Dotnettips وجود دارند:
using Microsoft; using Dotnettips ; public sealed class Program { public static void Main() { Widget w = new Widget();// An ambiguous reference } }
'Widget' is an ambiguous reference between 'Microsoft.Widget' and 'Dotnettips.Widget
using Microsoft; using Dotnettips; public sealed class Program { public static void Main() { Dotnettips.Widget w = new Dotnettips.Widget(); // Not ambiguous } }
using Microsoft; using Dotnettips; using DotnettipsWidget = Dotnettips.Widget; public sealed class Program { public static void Main() { DotnettipsWidget w = new DotnettipsWidget (); // No error now } }