خبرنامه هفتگی React Digest
استخراج جدول فیلمها
در طراحی فعلی کامپوننت movies، مشکل کوچکی وجود دارد: این کامپوننت تا اینجا، ترکیبی شدهاست از دو کامپوننت صفحه بندی و نمایش لیست گروهها، به همراه جزئیات کامل یک جدول بسیار طولانی. به این مشکل، mixed levels of abstractions میگویند. در اینجا دو کامپوننت سطح بالا را داریم، به همراه یک جدول سطح پایین که تمام مشخصات آن در معرض دید هستند و با هم مخلوط شدهاند. یک چنین کدی، یکدست به نظر نمیرسد. به همین جهت اولین کاری را که در ادامه انجام خواهیم داد، تعریف یک کامپوننت جدید و انتقال تمام جزئیات جدول نمایش ردیفهای فیلمها، به آن است. برای این منظور فایل جدید src\components\moviesTable.jsx را ایجاد کرده و توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت MoviesTable را تولید میکنیم. این کامپوننت را در پوشهی common قرار ندادیم؛ از این جهت که قابلیت استفادهی مجدد در سایر برنامهها را ندارد. کار آن تنها مرتبط و مختص به اشیاء فیلمی است که در سرویسهای برنامه داریم. البته در ادامه، این جدول را نیز به چندین کامپوننت با قابلیت استفادهی مجدد، خواهیم شکست؛ اما فعلا در اینجا با اصل کدهای سطح پایین جدول نمایش داده شدهی در کامپوننت movies، شروع میکنیم، آنها را cut کرده و به متد رندر کامپوننت جدید MoviesTable منتقل میکنیم.
پس از انتقال کامل تگ table از کامپوننت movies به داخل متد رندر کامپوننت MoviesTable، در ابتدای آن توسط Object Destructuring، یک آرایه و دو رخدادی را که برای مقدار دهی قسمتهای مختلف آن نیاز داریم، از props فرضی، استخراج میکنیم. اینکار کمک میکند تا بتوان اینترفیس این کامپوننت را به خوبی مشخص و طراحی کرد:
class MoviesTable extends Component { render() { const { movies, onDelete, onLike } = this.props;
import Like from "./common/like";
// ... <Like liked={movie.liked} onClick={() => onLike(movie)} />
<button onClick={() => onDelete(movie)} className="btn btn-danger btn-sm" > Delete </button>
import MoviesTable from "./moviesTable";
<MoviesTable movies={movies} onDelete={this.handleDelete} onLike={this.handleLike} />
پس از این تغییرات، متد رندر کامپوننت movies چنین شکلی را پیدا کردهاست که در آن سه کامپوننت سطح بالا درج شدهاند و در یک سطح از abstraction قرار دارند و دیگر مخلوطی از المانهای سطح بالا و سطح پایین را نداریم:
return ( <div className="row"> <div className="col-3"> <ListGroup items={this.state.genres} onItemSelect={this.handleGenreSelect} selectedItem={this.state.selectedGenre} /> </div> <div className="col"> <p>Showing {totalCount} movies in the database.</p> <MoviesTable movies={movies} onDelete={this.handleDelete} onLike={this.handleLike} /> <Pagination itemsCount={totalCount} pageSize={this.state.pageSize} onPageChange={this.handlePageChange} currentPage={this.state.currentPage} /> </div> </div> );
اکنون نوبت فعالسازی کلیک بر روی سرستونهای جدول نمایش داده شده و مرتب سازی اطلاعات جدول بر اساس ستون انتخابی است. به همین جهت در کامپوننت MoviesTable، رویداد onSort را هم به لیستی از خواصی که از props انتظار داریم، اضافه میکنیم که در نهایت در کامپوننت movies، به یک متد رویدادگردان متصل میشود:
class MoviesTable extends Component { render() { const { movies, onDelete, onLike, onSort } = this.props;
return ( <table className="table"> <thead> <tr> <th style={{ cursor: "pointer" }} onClick={() => onSort("title")}>Title</th> <th style={{ cursor: "pointer" }} onClick={() => onSort("genre.name")}>Genre</th> <th style={{ cursor: "pointer" }} onClick={() => onSort("numberInStock")}>Stock</th> <th style={{ cursor: "pointer" }} onClick={() => onSort("dailyRentalRate")}>Rate</th> <th /> <th /> </tr> </thead>
<MoviesTable movies={movies} onDelete={this.handleDelete} onLike={this.handleLike} onSort={this.handleSort} />
handleSort = column => { console.log("handleSort", column); };
پیاده سازی مرتب سازی اطلاعات
تا اینجا اگر دقت کرده باشید، هر زمانیکه شماره صفحهای تغییر میکند یا گروه فیلم خاصی انتخاب میشود، ابتدا state را به روز رسانی میکنیم که در نتیجهی آن، کار رندر مجدد کامپوننت در DOM مجازی React صورت میگیرد. سپس در متد رندر، کار تغییر اطلاعات آرایهی فیلمها را جهت نمایش به کاربر، انجام میدهیم.
بنابراین ابتدا در متد رویدادگران handleSort، با فراخوانی متد setState، مقدار path دریافتی حاصل از کلیک بر روی یک سرستون را به همراه صعودی و یا نزولی بودن مرتب سازی، در state کامپوننت جاری تغییر میدهیم:
handleSort = path => { console.log("handleSort", path); this.setState({ sortColumn: { path, order: "asc" } }); };
class Movies extends Component { state = { // ... sortColumn: { path:"title", order: "asc" } };
getPagedData() { const { pageSize, currentPage, selectedGenre, movies: allMovies, sortColumn } = this.state; let filteredMovies = selectedGenre && selectedGenre._id ? allMovies.filter(m => m.genre._id === selectedGenre._id) : allMovies; filteredMovies = filteredMovies.sort((movie1, movie2) => movie1[sortColumn.path] > movie2[sortColumn.path] ? sortColumn.order === "asc" ? 1 : -1 : movie2[sortColumn.path] > movie1[sortColumn.path] ? sortColumn.order === "asc" ? -1 : 1 : 0 ); const first = (currentPage - 1) * pageSize; const last = first + pageSize; const pagedMovies = filteredMovies.slice(first, last); return { totalCount: filteredMovies.length, data: pagedMovies }; }
همچنین میخواهیم اگر با کلیک بر روی ستونی، روش و جهت مرتب سازی آن صعودی بود، نزولی شود و یا برعکس که یک روش پیاده سازی آنرا در اینجا مشاهده میکنید:
handleSort = path => { console.log("handleSort", path); const sortColumn = { ...this.state.sortColumn }; if (sortColumn.path === path) { sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc"; } else { sortColumn.path = path; sortColumn.order = "asc"; } this.setState({ sortColumn }); };
بهبود کیفیت کدهای مرتب سازی اطلاعات
اگر قرار باشد کامپوننت MoviesTable را در جای دیگری مورد استفادهی مجدد قرار دهیم، زمانیکه این جدول سبب صدور رخدادی میشود، باید منطقی را که در متد handleSort فوق مشاهده میکنید، مجددا به همین شکل تکرار کنیم. بنابراین این منطق متعلق به کامپوننت MoviesTable است و زمانیکه onSort را فراخوانی میکند، بهتر است بجای ارسال path یا همان نام فیلدی که قرار است مرتب سازی بر اساس آن انجام شود، شیء sortColumn را به عنوان خروجی بازگشت دهد. به همین جهت، این منطق را به کلاس MoviesTable منتقل میکنیم:
class MoviesTable extends Component { raiseSort = path => { console.log("raiseSort", path); const sortColumn = { ...this.props.sortColumn }; if (sortColumn.path === path) { sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc"; } else { sortColumn.path = path; sortColumn.order = "asc"; } this.props.onSort(sortColumn); };
<MoviesTable movies={movies} onDelete={this.handleDelete} onLike={this.handleLike} onSort={this.handleSort} sortColumn={this.state.sortColumn} />
return ( <table className="table"> <thead> <tr> <th style={{ cursor: "pointer" }} onClick={() => this.raiseSort("title")}>Title</th> <th style={{ cursor: "pointer" }} onClick={() => this.raiseSort("genre.name")}>Genre</th> <th style={{ cursor: "pointer" }} onClick={() => this.raiseSort("numberInStock")}>Stock</th> <th style={{ cursor: "pointer" }} onClick={() => this.raiseSort("dailyRentalRate")}>Rate</th> <th /> <th /> </tr> </thead>
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-13.zip
وبلاگها ، سایتها و مقالات ایرانی (داخل و خارج از ایران)
- محاسبه کاربران آنلاین در ASP.NET
- نحوه جابجایی tempdb در SQL Server 2005 و ضرورت آن
- Firefox فارسی در دست تهیه
- تست نرمافزار در Agile Software Development
- محافظت از فرمهای اینترنتی با ریکپچا
- تبادل NTFS و رجیستری
- غیر فعال کردن اشتراک درایوهای مخفی در ویندوز
- تحت نظر قرار دادن پوشههای سیستمی در سی شارپ
- نحوه استفاده از jQuery در ASP.Net
- پلاگین کیبورد مجازی فارسی برای jQuery
- بکارگیری ویژگیهای CSS3 در طراحی وب
- ۹ قابلیت پنهان گوگل که باید بدانید
- فیدبرنر و گوگل
- پشتیبانی چیست و چگونه باید انجام شود
- دانشآموزان ایرانی در IT از معلمان خود باسوادترند
- امنیت وبلاگ وردپرس خود را بالا ببرید
- سرعت تبدیل اعداد انگلیسی به فارسی
- آیا پنگوئن میتواند پنجره را بشکند؟ و نصیحت لینوکسی
- دل پر خون کاربران از عملکرد وزارت ICT در حوزه اینترنت
- انتخاب افقی قسمتی از کد!
Visual Studio
ASP. Net
- از سیر تا پیاز نحوهی کارکرد سشن در ASP.Net
- آشنایی با برنامه Fiddler
- jQuery, JSON, and ASP.NET
- ASP کلاسیک مرده است!
- دستکاری ظاهر شیرپوینت با استفاده از jQuery
- بررسی سایت whitehouse.gov
طراحی و توسعه وب
- ایجاد گوشههای گرد
- استفاده از فایرفاکس برای غلط یابی یک صفحه وب
- کتابخانه DateJs (و اگر وقت کردید سری به این پست بزنید)
- 45 تکنیک JQuery
- نحوه نمایش لینکهای خود را بهبود بخشید
- Formy CSS Form Framework
- Top list of ajax upload scripts
اسکیوال سرور
- به روز رسانی سوم اس کیوال سرور 2008 ، لیست موارد فیکس شده، دریافت
- طراحی جدید سایت اس کیوال سرور 2008
- آشنایی با لاگ فایلها در SQL Server
- تولید اعداد منحصربفرد در اس کیوال سرور
- از sp_detach_db استفاده نکنید!
سی شارپ
عمومی دات نت
- UTF8Encoding.Default != Encoding.UTF8
- مقایسه Nhibernate و Linq
- مقایسه Entity Framework و LINQ to SQL
- بررسی اندازه یک رشته در دات نت
- مونو و Android
- Google Maps for Windows Forms
- برگههای تقلب دات نت
- دایرکتوری کتاب خانههای ثبت وقایع در دات نت
ویندوز
مسایل اجتماعی و انسانی برنامه نویسی
- اثر رکود اقتصادی بر بازار کار برنامه نویسی
- دلایل استعفای تعدادی از کارمندان گوگل
- تهیهی برنامههایی با مقیاس گسترده در چند روز بجای چند هفته
- از دست همکاران خود خسته شدهاید؟!
- هنر مصاحبه کردن
متفرقه
ترکیب کامپوننتها
در ادامه، همان برنامهی تا قسمت 5 را که کار نمایش یک counter را انجام میدهد، تکمیل میکنیم. در این برنامه اگر به فایل index.js دقت کنید، کار رندر تک کامپوننت Counter را انجام میدهیم:
ReactDOM.render(<Counter />, document.getElementById("root"));
برای نمایش این مفهوم، کامپوننت جدید src\components\counters.jsx را ایجاد میکنیم. قصد داریم در این کامپوننت، لیستی از کامپوننتهای Counter را رندر کنیم. سپس در index.js، بجای رندر کامپوننت Counter، کامپوننت جدید Counters را رندر میکنیم. به این ترتیب درخت کامپوننتهای برنامه، در سطح بالایی خودش از کامپوننت Counters شروع میشود و سپس فرزندان آنرا کامپوننتهای Counter تشکیل میدهند. به همین جهت فایل index.js را به صورت زیر ویرایش میکنیم تا به کامپوننت Counters اشاره کند:
import Counters from "./components/counters"; ReactDOM.render(<Counters />, document.getElementById("root"));
import React, { Component } from "react"; import Counter from "./counter"; class Counters extends Component { state = {}; render() { return ( <div> <Counter /> <Counter /> <Counter /> <Counter /> </div> ); } } export default Counters;
در مرحلهی بعد، بجای رندر و درج دستی این کامپوننتها، آرایهای از اشیاء counter را ایجاد کرده و سپس آنها را توسط متد Array.map رندر میکنیم:
import React, { Component } from "react"; import Counter from "./counter"; class Counters extends Component { state = { counters: [ { id: 1, value: 0 }, { id: 2, value: 0 }, { id: 3, value: 0 }, { id: 4, value: 0 } ] }; render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} /> ))} </div> ); } } export default Counters;
ارسال دادهها به کامپوننتها
مشکل! مقدار value هر شیء شمارشگر تعریف شده، به کامپوننتهای مرتبط رندر شده اعمال نشدهاست. برای مثال اگر value اولین شیء را به 4 تغییر دهیم، هنوز هم این کامپوننت با همان مقدار صفر شروع به کار میکند. برای رفع این مشکل، به همان روشی که ویژگی key کامپوننت Counter را مقدار دهی کردیم، میتوان ویژگیهای سفارشی دیگری را تعریف و مقدار دهی کرد:
render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true} /> ))} </div> );
class Counter extends Component { state = { count: 0 }; render() { console.log("props", this.props); //...
خاصیت this.props، یک شیء سادهی جاوا اسکریپتی است و شامل تمام ویژگیهایی میباشد که ما در کامپوننت Counters برای هر کدام از کامپوننتهای Counter رندر شدهی توسط آن، تعریف کردیم. برای نمونه دو ویژگی جدید value و selected را که به تعاریف المانهای Counter در کامپوننت Counters اضافه کردیم، در اینجا به همراه مقادیر منتسب به آنها، قابل مشاهده هستند. البته در این خروجی، key را ملاحظه نمیکنید؛ چون هدف اصلی آن، معرفی یکتای المانها در DOM مجازی React است.
بنابراین اکنون میتوان به value تنظیم شدهی در کامپوننت Counters به صورت this.props.value در کامپوننت Counter دسترسی یافت و سپس از آن جهت مقدار دهی اولیهی counter استفاده کرد.
class Counter extends Component { state = { count: this.props.value };
یک نکته: در اینجا selected={true} را داریم. اگر مقدار آنرا حذف کنیم، یعنی selected تنها درج شود، مقدار آن، همان true دریافت خواهد شد.
تعریف فرزند برای المانهای کامپوننتها
ویژگیهای اضافه شدهی به تعاریف المانهای کامپوننتها، توسط خاصیت this.props، به هر کدام از آن کامپوننتها منتقل میشوند. این خاصیت props، یک خاصیت ویژه را به نام children، نیز دارا است و از آن برای دسترسی به المانهای تعریف شدهی بین تگهای یک المان اصلی استفاده میشود:
render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true}> <h4>Counter #{counter.id}</h4> </Counter> ))} </div> ); }
یک نمونه مثال واقعی این قابلیت، امکان تعریف محتوای دیالوگ باکسها، توسط استفاده کنندهی از آن است.
روش دیباگ برنامههای React
افزونهی مفید React developer tools را میتوانید برای مرورگرهای کروم و فایرفاکس، دریافت و نصب کنید. برای نمونه پس از نصب آن در مرورگر کروم، یک برگهی جدید به لیست برگههای کنسول توسعه دهندگان آن اضافه میشود:
همانطور که مشاهده کنید، درخت کامپوننتهای برنامه را در برگهی جدید Components، میتوان مشاهده کرد. در اینجا با انتخاب هر کدام از فرزندان این درخت، مشخصات آن نیز مانند props و state، در کنار صفحه ظاهر میشوند. همچنین در بالای همین قسمت، 4 آیکن مشاهدهی سورس، مشاهدهی DOM و یا لاگ کردن جزئیات شیء کامپوننت انتخابی در کنسول هم درج شدهاند:
که برای نمونه چنین خروجی را لاگ میکند:
بررسی تفاوتهای خواص props و state
در کامپوننت Counter، از props برای مقدار دهی اولیهی state استفاده میکنیم:
class Counter extends Component { state = { count: this.props.value };
- props حاوی اطلاعاتی است که به یک کامپوننت ارسال میکنیم؛ اما state حاوی اطلاعاتی است که مختص به آن کامپوننت بوده و private است. یعنی سایر کامپوننتها نمیتوانند به state کامپوننت دیگری دسترسی پیدا کنند. برای مثال در کامپوننت Counters، تمام attributes سفارشی تنظیم شدهی بر روی تعاریف المانهای کامپوننت Counter، جزئی از اطلاعات props خواهند بود. در اینجا نمیتوان به state کامپوننت مدنظری دسترسی یافت و آنرا مقدار دهی کرد. به همین ترتیب state کامپوننت Counters نیز در سایر کامپوننتها قابل دسترسی نیست.
- همچنین باید درنظر داشت که props، در مقایسه با state، فقط خواندنی است. به عبارتی مقدار ورودی به یک کامپوننت را داخل آن کامپوننت نمیتوان تغییر داد. برای مثال سعی کنید در داخل متد رویدادگردان کلیک موجود در کامپوننت Counter، مقدار this.props.value را به صفر تنظیم کنید. در این حالت با کلیک بر روی دکمهی Increment، بلافاصله خطای readonly بودن خواص شیء منتسب به props را دریافت میکنیم. در اینجا اگر نیاز است این مقدار را داخل کامپوننت تغییر دهیم، باید ابتدا این مقدار را دریافت کرده و سپس آنرا داخل state قرار دهیم. پس از آن امکان ویرایش اطلاعات منتسب به state، داخل یک کامپوننت وجود خواهد داشت.
صدور و مدیریت رخدادها
در ادامه میخواهیم در کنار هر دکمهی Increment کامپوننت شمارشگر، یک دکمهی Delete هم قرار دهیم:
مشکل! اگر کد مدیریتی handleDelete را در کامپوننت Counter قرار دهیم، چگونه باید به لیست آرایهی اشیاء counters والد آن، یعنی کامپوننت Counters که سبب رندر شدن کامپوننتهای شمارشگر شده (state = { counters: [ ] })، دسترسی یافت و شیءای را از آن حذف کرد؟ در React، کامپوننتی که state ای را تعریف میکند، باید کامپوننتی باشد که قرار است آنرا تغییر دهد و اطلاعات state هر کامپوننت، صرفا متعلق به آن کامپوننت بوده و جزو اطلاعات خصوصی آن است. بنابراین مدیریت حذف و یا افزودن کامپوننتها در لیست نمایش داده شده، باید جزو وظایف کامپوننت Counters باشد و نه Counter.
برای حل این مشکل، کامپوننت Counter تعریف شده (کامپوننت فرزند) باید سبب بروز رخداد onDelete شود تا کامپوننت Counters (کامپوننت والد)، آنرا توسط متد handleDelete مدیریت کند. بنابراین ابتدا به کامپوننت Counters (کامپوننت والد) مراجعه کرده و متد رویدادگردان handleDelete را به آن اضافه میکنیم:
handleDelete = () => { console.log("handleDelete called."); };
<Counter key={counter.id} value={counter.value} selected={true} onDelete={this.handleDelete} />
<button onClick={this.props.onDelete} className="btn btn-danger btn-sm m-2" > Delete </button>
اکنون اگر برنامه را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر بر روی دکمهی Delete کلیک کنیم، پیام «handleDelete called» در کنسول توسعه دهندگان مرورگر لاگ میشود. به این ترتیب کامپوننت فرزند سبب بروز رخدادی شده و والد آن، این رخداد را مدیریت میکند.
به روز رسانی state
تا اینجا دکمهی Delete فرزند، به متد handleDelete والد متصل شدهاست. مرحلهی بعد، پیاده سازی واقعی حذف یک المان از DOM مجازی و به روز رسانی state است. برای اینکار ابتدا به رخدادگردان onClick، در کامپوننت شمارشگر، مراجعه کرده و id دریافتی را به سمت والد ارسال میکنیم:
onClick={() => this.props.onDelete(this.props.id)}
<Counter key={counter.id} value={counter.value} selected={true} onDelete={this.handleDelete} id={counter.id} />
handleDelete = counterId => { console.log("handleDelete called.", counterId); const counters = this.state.counters.filter( counter => counter.id !== counterId ); this.setState({ counters }); // = this.setState({ counters: counters }); };
البته پیاده سازی ما تا به اینجا بدون مشکل کار میکند، اما به ازای هر خاصیت counter، یک ویژگی جدید را به تعریف المان مرتبط اضافه کردهایم که در طول زمان بیش از اندازه طولانی خواهد شد. برای رفع این مشکل، خود شیء counter را به صورت یک ویژگی جدید به کامپوننت مرتبط با آن ارسال میکنیم. به این ترتیب اگر در آینده خاصیتی را به این شیء اضافه کردیم، دیگر نیازی نیست تا آنرا به صورت دستی و مجزا تعریف کنیم. به همین جهت ابتدا تعریف المان Counter را به صورت زیر خلاصه میکنیم که در آن ویژگی جدید counter، حاوی کل شیء counter است:
<Counter key={counter.id} counter={counter} onDelete={this.handleDelete} />
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-07.zip
This project is the next generation of the ASP.NET Boilerplate web application framework.
Modular Architecture
Designed as modular and extensible from the bottom to the top.
Microservice Focused
Designed to support microservice architecture and helps to build autonomous microservices.
Domain Driven Design
Designed and developed based on DDD patterns and principles. Provides a layered model for your application.
Authorization
Advanced authorization with user, role and fine-grained permission system. Built on the Microsoft Identity library.
Multi-Tenancy
SaaS applications made easy! Integrated multi-tenancy from database to UI.
Cross Cutting Concerns
Complete infrastructure for authorization, validation, exception handling, caching, audit logging, transaction management and so on.
We are excited to announce the release of .NET Core 1.0, ASP.NET Core 1.0 and Entity Framework 1.0, available on Windows, OS X and Linux! .NET Core is a cross-platform, open source, and modular .NET platform for creating modern web apps, microservices, libraries and console applications.
This release includes the .NET Core runtime, libraries and tools and the ASP.NET Core libraries. We are also releasing Visual Studio and Visual Studio Code extensions that enable you to create .NET Core projects. You can get started at https://dot.net/core. Read the release notes for detailed release information.
Use Tailwind CSS with RN for awesome styling!
Use the Google Distance Matrix API to calculate Travel time and Distance (+ Cost!)
Use the Directions Google API for real navigation!
Use Google places API for real navigations!
Use apple & google maps for iOS & Android
Use React Native Navigation to navigate between screens!
Use React Native Elements to elevate your app design!