این خطا عنوان کرده که با فرمت استاندارد «ایجاد پروژهی «کتابخانه» توسط Angular CLI 6.0» سازگاری ندارد. بهتر است با توجه به سورس باز بودن پروژه، این فرمت خاص را ایجاد کنید و به عنوان یک pull request جدید ارسال نمائید:
«انجام تنظیمات مسیریابی پیش فرض پروژه جدید توسط Angular CLI» را در قسمت دوم این سری بررسی کردیم. در ادامه با قابلیتهای بیشتری از امکانات تنظیمات مسیریابی موجود در Angular CLI، آشنا خواهیم شد.
یک مثال: در ادامه یک پروژهی جدید مبتنی بر Angular CLI را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
همانطور که در قسمت دوم نیز ذکر شد، پرچم routing در اینجا، سبب ایجاد فایل app-routing.module.ts نیز خواهد گردید:
و تنظیمات مرتبط با آن به صورت خودکار به قسمت imports فایل app.module.ts اضافه میشوند و آمادهی استفاده هستند.
همچنین اگر به فایل src\app\app.component.html مراجعه کنیم، router-outlet نیز به آن افزوده شدهاست و مدیریت نمایش مسیریابیها در این قسمت انجام خواهد شد.
در ادامهی این مثال، دو کامپوننت جدید را به نامهای dashboard و customer ایجاد میکنیم:
هدف این است که مسیریابیهایی را جهت کار و نمایش این کامپوننتها ایجاد کنیم. به همین جهت به فایل src\app\app-routing.module.ts مراجعه کرده و تغییرات ذیل را اعمال کنید:
در اینجا ابتدا کامپوننتهای جدید، import شده و سپس یک مسیریابی پیش فرض به کامپوننت dashboard و دو مسیریابی جدید دیگر به کامپوننتهای dashboard و customer ایجاد شدهاند.
البته باید دقت داشت که چون پیشتر با اجرای دستورات ng g c، این کامپوننتها به صورت خودکار به تعاریف فایل app.module.ts اضافه شدهاند، امکان استفادهی از آنها در اینجا میسر است:
پس از تعریف مسیریابیها، به فایل src\app\app.component.html مراجعه کرده و لینکهایی را به این مسیریابیهای جدید ایجاد میکنیم:
اکنون اگر دستور کامپایل و گشودن برنامه را در مرورگر پیش فرض سیستم صادر کنیم:
یک چنین تصویری حاصل خواهد شد:
با توجه به تنظیمات مسیریابی پیش فرض انجام شده، ابتدا مسیر http://localhost:4200/dashboard بارگذاری شدهاست.
ایجاد ماژولهای جدید به همراه تنظیمات مسیریابی آنها
در قسمت قبل با نحوهی ایجاد ماژولهای جدید توسط Angular CLI آشنا شدیم:
این فرمان، فایل admin.module.ts را تولید میکند. در اینجا میتوان پرچم مسیریابی را نیز ذکر کرد (برای اینکار یک پنجرهی خط فرمان دیگر را باز کنید و اجازه دهید تا پنجرهی خط فرمان ng serve -o باز باقی بماند و مدام مشغول بررسی تغییرات و کامپایل پشت صحنهی کار باشد):
در این حالت دو فایل admin.module.ts و همچنین admin-routing.module.ts تولید میشوند.
سپس داخل این ماژول یک کامپوننت جدید را به نام admin ایجاد میکنیم:
در اینجا چون این کامپوننت، هم نام پوشهی admin است، داخل همان پوشه ایجاد خواهد شد.
برای مثال اگر نیاز به ایجاد کامپوننت دیگری به نام emails داخل این پوشه بود، باید به نحو ذیل عمل کرد:
در این حالت پوشهی جدید email داخل پوشهی admin ایجاد شده و فایلهای کامپوننت جدید email به آن اضافه میشوند. همچنین اگر دقت کنید، اینبار سطر update آخری، فایل admin.module.ts را به روز رسانی کردهاست و در قسمت declarations آن، دو کامپوننت ایجاد شده را تعریف کردهاست:
تا اینجا ماژول جدید admin را ایجاد کردهایم؛ اما برنامهی اصلی از آن اطلاعی ندارد. به همین جهت به فایل src\app\app.module.ts مراجعه کرده و این ماژول جدید را به آن معرفی میکنیم:
ابتدا کلاس این ماژول import شده و سپس آنرا پیش از AppRoutingModule تعریف میکنیم.
در ادامه برای تعریف مسیریابیهای این ماژول جدید، به فایل src\app\admin\admin-routing.module.ts آن مراجعه کرده و ثابت routes آنرا مقدار دهی میکنیم:
در اینجا مسیریابی admin، دارای فرزند email نیز میباشد و پیش فرض آن نیز به email تنظیم شدهاست.
سپس به فایل app\admin\admin.component.html نیز مراجعه کرده و router-outlet آنرا به آن اضافه میکنیم:
تا اینجا هرچند لینک جدیدی را به ناحیهی ادمین تعریف نکردهایم، اما مسیریابی تعریف شدهی آن کار میکند:
یک نکته: امکان تولید route guards نیز توسط Angular CLI برای محافظت از مسیریابی خاصی وجود دارد. برای این منظور میتوان دستور ذیل را صادر کرد:
که سبب تولید فایل auth.guard.ts میشود.
یک مثال: در ادامه یک پروژهی جدید مبتنی بر Angular CLI را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
> ng new angular-routing --routing
و تنظیمات مرتبط با آن به صورت خودکار به قسمت imports فایل app.module.ts اضافه میشوند و آمادهی استفاده هستند.
همچنین اگر به فایل src\app\app.component.html مراجعه کنیم، router-outlet نیز به آن افزوده شدهاست و مدیریت نمایش مسیریابیها در این قسمت انجام خواهد شد.
در ادامهی این مثال، دو کامپوننت جدید را به نامهای dashboard و customer ایجاد میکنیم:
>ng g c dashboard >ng g c customer
هدف این است که مسیریابیهایی را جهت کار و نمایش این کامپوننتها ایجاد کنیم. به همین جهت به فایل src\app\app-routing.module.ts مراجعه کرده و تغییرات ذیل را اعمال کنید:
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { DashboardComponent } from './dashboard/dashboard.component'; import { CustomerComponent } from './customer/customer.component'; const routes: Routes = [ { path: '', pathMatch: 'full', redirectTo: 'dashboard' }, { path: 'dashboard', component: DashboardComponent }, { path: 'customer', component: CustomerComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
البته باید دقت داشت که چون پیشتر با اجرای دستورات ng g c، این کامپوننتها به صورت خودکار به تعاریف فایل app.module.ts اضافه شدهاند، امکان استفادهی از آنها در اینجا میسر است:
@NgModule({ declarations: [ AppComponent, DashboardComponent, CustomerComponent ],
پس از تعریف مسیریابیها، به فایل src\app\app.component.html مراجعه کرده و لینکهایی را به این مسیریابیهای جدید ایجاد میکنیم:
<h1> {{title}} </h1> <nav> <ul> <li><a href="" [routerLink]="['/dashboard']">Dashboard</a></li> <li><a href="" [routerLink]="['/customer']">Customer</a></li> </ul> </nav> <router-outlet></router-outlet>
اکنون اگر دستور کامپایل و گشودن برنامه را در مرورگر پیش فرض سیستم صادر کنیم:
> ng serve -o
با توجه به تنظیمات مسیریابی پیش فرض انجام شده، ابتدا مسیر http://localhost:4200/dashboard بارگذاری شدهاست.
ایجاد ماژولهای جدید به همراه تنظیمات مسیریابی آنها
در قسمت قبل با نحوهی ایجاد ماژولهای جدید توسط Angular CLI آشنا شدیم:
> ng g module admin
> ng g m admin --routing
سپس داخل این ماژول یک کامپوننت جدید را به نام admin ایجاد میکنیم:
> ng g c admin
برای مثال اگر نیاز به ایجاد کامپوننت دیگری به نام emails داخل این پوشه بود، باید به نحو ذیل عمل کرد:
> ng g c admin/email installing component create src\app\admin\email\email.component.css create src\app\admin\email\email.component.html create src\app\admin\email\email.component.spec.ts create src\app\admin\email\email.component.ts update src\app\admin\admin.module.ts
@NgModule({ imports: [ CommonModule, AdminRoutingModule ], declarations: [AdminComponent, EmailComponent] }) export class AdminModule { }
تا اینجا ماژول جدید admin را ایجاد کردهایم؛ اما برنامهی اصلی از آن اطلاعی ندارد. به همین جهت به فایل src\app\app.module.ts مراجعه کرده و این ماژول جدید را به آن معرفی میکنیم:
import { AdminModule } from './admin/admin.module'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule, AdminModule, AppRoutingModule ],
در ادامه برای تعریف مسیریابیهای این ماژول جدید، به فایل src\app\admin\admin-routing.module.ts آن مراجعه کرده و ثابت routes آنرا مقدار دهی میکنیم:
import { AdminComponent } from './admin.component'; import { EmailComponent } from './email/email.component'; const routes: Routes = [ { path: 'admin', component: AdminComponent, children:[ { path:'', component:EmailComponent }, { path:'email', component:EmailComponent } ] } ];
سپس به فایل app\admin\admin.component.html نیز مراجعه کرده و router-outlet آنرا به آن اضافه میکنیم:
<p> admin works! </p> <router-outlet></router-outlet>
یک نکته: امکان تولید route guards نیز توسط Angular CLI برای محافظت از مسیریابی خاصی وجود دارد. برای این منظور میتوان دستور ذیل را صادر کرد:
>ng g guard auth
در ادامهی بحث ترکیب کامپوننتها، پس از نمایش لیستی از کامپوننتهای شمارشگر و مقدار دهی عدد آغازین آنها، به همراه مدیریت حذف هر ردیف در قسمت قبل، اکنون میخواهیم دکمهای را اضافه کنیم تا تمام شمارشگرها را به حالت اول خودشان بازگرداند. برای این منظور دکمهی Reset را به ابتدای المانهای کامپوننت Counters اضافه میکنیم:
سپس متد رویدادگردان handleReset آنرا به صورت زیر با تنظیم مقدار value هر counter به صفر و بازگشت آن و در نهایت به روز رسانی state کامپوننت با این آرایهی جدید، پیاده سازی میکنیم:
اکنون پس از ذخیره سازی فایل counters.jsx و بارگذاری مجدد برنامه در مرورگر، هرچقدر بر روی دکمهی Reset کلیک کنیم ... اتفاقی رخ نمیدهد! حتی اگر به افزونهی React developer tools نیز مراجعه کنیم، مشاهده خواهیم کرد که عمل تنظیم value به صفر، در تک تک کامپوننتهای شمارشگر، به درستی صورت گرفتهاست؛ اما تغییرات به DOM اصلی منعکس نشدهاند:
البته اگر به همین تصویر دقت کنید، هنوز مقدار count، در state آن 4 است. علت اینجا است که هر کدام از Counterها دارای local state خاص خودشان هستند و در آنها، مقدار count به صورت زیر مقدار دهی شدهاست که در آن تغییرات بعدی این this.props.value، متصل به count نیست و count، فقط یکبار مقدار دهی میشود:
این قطعهی از کد، تنها زمانی اجرا میشود که یک وهله از کلاس کامپوننت Counter، در حال ایجاد است. به همین جهت زمانیکه صفحه برای بار اول بارگذاری میشود، مقدار آغازین count به درستی دریافت میشود. اما با کلیک بر روی دکمهی Reset، هرچند مقدار value هر شیء counter تعریف شدهی در کامپوننت والد تغییر میکند، اما local state کامپوننتهای فرزند به روز رسانی نمیشوند و مقدار جدید value را دریافت نمیکنند. برای رفع یک چنین مشکلی نیاز است یک مرجع مشخص را برای مقدار دهی stateهای کامپوننتهای فرزند ایجاد کنیم.
حذف Local state
اکنون میخواهیم در کامپوننت Counter، قسمت local state آنرا به طور کامل حذف کرده و تنها از this.props جهت دریافت اطلاعاتی که نیاز دارد، استفاده کنیم. به این نوع کامپوننتها، «Controlled component» نیز میگویند. یک کامپوننت کنترل شده دارای local state خاص خودش نیست و تمام دادههای دریافتی را از طریق this.props دریافت میکند و هر زمانیکه قرار است دادهای تغییر کند، رخدادی را به والد خود صادر میکند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل میشود.
برای پیاده سازی این مفهوم، ابتدا خاصیت state کامپوننت Counter را حذف میکنیم. سپس تمام ارجاعات به this.state را در این کامپوننت یافته و آنها را تغییر میدهیم. اولین ارجاع، در متد handleIncrement به صورت this.state.count تعریف شدهاست:
از این جهت که دیگر دارای local state نیستیم، داشتن متد this.setState در اینجا بیمفهوم است. در یک کامپوننت کنترل شده، هر زمانیکه قرار است دادهای ویرایش شود، این کامپوننت باید رخدادی را صادر کرده و از والد خود درخواست تغییر اطلاعات را ارائه دهد؛ شبیه به this.props.onDelete ای که در قسمت قبل کامل کردیم. بنابراین کل متد handleIncrement را نیز حذف میکنیم. اینبار رخداد onClick، سبب بروز رخداد onIncrement در والد خود خواهد شد:
همچنین دو متد دیگری که ارجاعی را به this.state داشتند، به صورت زیر جهت استفادهی از this.props.counter.value، به روز رسانی میشوند:
تا اینجا به صورت کامل local state این کامپوننت حذف و با this.props جایگزین شده و در نتیجه تحت کنترل کامپوننت والد آن قرار میگیرد.
در ادامه به کامپوننت Counters مراجعه کرده و متد رویدادگردانی را جهت پاسخگویی به رخداد onIncrement رسیدهی از کامپوننتهای فرزند، تعریف میکنیم:
سپس ارجاعی از این متد را به ویژگی onIncrement تعریف شدهی در المان Counter، متصل میکنیم:
اکنون هر زمانیکه بر روی دکمهی Increment کلیک شود، this.props.onIncrement آن، سبب فراخوانی متد handleIncrement والد خود خواهد شد.
پیاده سازی کامل متد handleIncrement اینبار به صورت زیر است:
همانطور که در قسمتهای قبل نیز عنوان شد، در React نباید مقدار state را به صورت مستقیم ویرایش کرد؛ مانند مراجعهی مستقیم به this.state.counters[index] و سپس تغییر خاصیت value آن. بنابراین باید یک clone از آرایهی counters و سپس یک clone از شیء counter رسیدهی از کامپوننت فرزند را ایجاد کنیم تا این cloneها دیگر ارجاعی را به اشیاء اصلی ساخته شدهی از روی آنها نداشته باشند (مهمترین خاصیت یک clone) تا اگر خاصیت و مقداری را در آنها تغییر دادیم، دیگر به شیء اصلی که از روی آنها clone شدهاند، منعکس نشوند. در اینجا از spread operator برای ایجاد این cloneها استفاده شدهاست. اکنون مقادیر خواص این cloneها را تغییر میدهیم و درنهایت این counters جدید را که خودش نیز یک clone است، به متد this.setState جهت به روز رسانی UI و همچنین state کامپوننت، ارسال میکنیم.
تا اینجا اگر برنامه را ذخیره کرده و منتظر به روز رسانی آن در مرورگر شویم، با کلیک بر روی Reset، تمام کامپوننتها با هر وضعیتی که پیشتر داشته باشند، به حالت اول خود باز میگردند:
همگام سازی چندین کامپوننت با هم زمانیکه رابطهی والد و فرزندی بین آنها وجود ندارد
در ادامه میخواهیم یک منوی راهبری (یا همان NavBar در بوت استرپ) را به بالای صفحه اضافه کنیم و در آن جمع کل تعداد Counterهای رندر شده را نمایش دهیم؛ مانند نمایش تعداد آیتمهای انتخاب شدهی توسط یک کاربر، در یک سبد خرید. برای پیاده سازی آن، درخت کامپوننتهای React را مطابق شکل فوق تغییر میدهیم. یعنی مجددا کامپوننت App را در به عنوان کامپوننت ریشهای انتخاب کرده که سایر کامپوننتها از آن مشتق میشوند و همچنین کامپوننت مجزای NavBar را نیز اضافه خواهیم کرد.
برای این منظور به index.js مراجعه کرده و مجددا کامپوننت App را که غیرفعال کرده بودیم و بجای آن Counters را نمایش میدادیم، اضافه میکنیم:
سپس کامپوننت جدید NavBar را توسط فایل جدید src\components\navbar.jsx اضافه میکنیم تا منوی راهبری سایت را نمایش دهد:
اکنون به App.js مراجعه کرده و متد render آنرا جهت نمایش درخت کامپوننتهایی که مشاهده کردید، تکمیل میکنیم:
ابتدا کامپوننت NavBar در بالای صفحه رندر میشود و سپس کامپوننت Counters در میانهی صفحه. چون در اینجا چندین المان قرار است رندر شوند، از React.Fragment برای محصور کردن آنها استفاده کردهایم.
تا اینجا اگر برنامه را ذخیره کنیم تا در مرورگر بارگذاری مجدد شود، چنین شکلی حاصل شدهاست:
اکنون میخواهیم تعداد کامپوننتهای شمارشگر را در navbar نمایش دهیم. پیشتر state کامپوننت Counters را توسط props، به کامپوننتهای Counter رندر شدهی توسط آن انتقال دادیم. استفادهی از این ویژگی به دلیل وجود رابطهی والد و فرزندی بین این کامپوننتها میسر شد. اما همانطور که در تصویر درخت کامپوننتهای جدید تشکیل شده مشاهده میکنید، رابطهی والد و فرزندی بین دو کامپوننت Counters و NavBar وجود ندارد. بنابراین اکنون این سؤال مطرح میشود که چگونه باید تعداد کل شمارشگرهای کامپوننت Counters را به کامپوننت NavBar، برای نمایش آنها انتقال داد؟ در یک چنین حالتهایی که رابطهی والد و فرزندی بین کامپوننتها وجود ندارد و میخواهیم آنها را همگام سازی کنیم و دادههایی را بین آنها به اشتراک بگذاریم، باید state را به یک سطح بالاتر انتقال داد. یعنی در این مثال باید state کامپوننت Counters را به والد آن که اکنون کامپوننت App است، منتقل کرد. پس از آن چون هر دو کامپوننت NavBar و Counters، از کامپوننت App مشتق میشوند، اکنون میتوان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت.
انتقال state به یک سطح بالاتر
برای انتقال state به یک سطح بالاتر، به کامپوننت Counters مراجعه کرده و خاصیت state آنرا به همراه تمامی متدهایی که آنرا تغییر میدهند و از آن استفاده میکنند، انتخاب و cut میکنیم. سپس به کامپوننت App مراجعه کرده و آنها را در اینجا paste میکنیم. یعنی خاصیت state و متدهای handleDelete، handleReset و handleIncrement را از کامپوننت Counters به کامپوننت App منتقل میکنیم. این مرحلهی اول است. سپس نیاز است به کامپوننت Counters مراجعه کرده و ارجاعات به state و متدهای یاد شده را توسط props اصلاح میکنیم. برای این منظور ابتدا باید این props را در کامپوننت App مقدار دهی کنیم تا بتوانیم آنها را در کامپوننت Counters بخوانیم؛ یعنی متد render کامپوننت App، تمام این خواص و متدها را باید به صورت ویژگیهایی به تعریف المان Counters اضافه کند تا خاصیت props آن بتواند به آنها دسترسی داشته باشد:
پس از این تعاریف میتوانیم به کامپوننت Counters بازگشته و ارجاعات فوق را توسط خاصیت props، در متد render آن اصلاح کنیم:
در اینجا سه رویدادگردان و یک خاصیت counters، از طریق خاصیت props والد کامپوننت Counter که اکنون کامپوننت App است، خوانده میشوند.
پس از این نقل و انتقالات، اکنون میتوانیم تعداد counters را در NavBar نمایش دهیم. برای این منظور ابتدا در کامپوننت App، به همان روشی که ویژگی counters={this.state.counters} را به تعریف المان Counters اضافه کردیم، شبیه به همین کار را برای کامپوننت NavBar نیز میتوانیم انجام دهیم تا از طریق خاصیت props آن قابل دسترسی شود و یا حتی میتوان به صورت زیر، تنها جمع کل را به آن کامپوننت ارسال کرد:
سپس در کامپوننت NavBar، عدد totalCounters فوق را که به تعداد کامپوننتهایی که مقدار value آنها بیشتر از صفر است، اشاره میکند، از طریق خاصیت props خوانده و نمایش میدهیم:
که با ذخیره کردن این فایل و بارگذاری مجدد برنامه در مرورگر، به خروجی زیر خواهیم رسید:
کامپوننتهای بدون حالت تابعی
اگر به کدهای کامپوننت NavBar دقت کنیم، تنها یک تک متد render در آن ذکر شدهاست و تمام اطلاعات مورد نیاز آن نیز از طریق props تامین میشود و دارای state و یا هیچ رویدادگردانی نیست. یک چنین کامپوننتی را میتوان به یک «Stateless Functional Component» تبدیل کرد؛ کامپوننتهای بدون حالت تابعی. در اینجا بجای اینکه از یک کلاس برای تعریف کامپوننت استفاده شود، میتوان از یک function استفاده کرد (به همین جهت به آن functional میگویند). احتمالا نمونهی آنرا با کامپوننت App پیشفرض قالب create-react-app نیز مشاهده کردهاید که در آن فقط یک ()function App وجود دارد. البته در کدهای فوق چون نیاز به ذکر state، در کامپوننت App وجود داشت، آنرا از حالت تابعی، به حالت کلاس استاندارد کامپوننت، تبدیل کردیم.
اگر بخواهیم کامپوننت بدون حالت NavBar را نیز تابعی کنیم، میتوان به صورت زیر عمل کرد:
برای اینکار قسمت return متد render کامپوننت را cut کرده و به داخل تابع NavBar منتقل میکنیم. بدنهی این تابع را هم میتوان توسط میانبر sfc که مخفف Stateless Functional Component است، در VSCode تولید کرد.
پیشتر در کامپوننت NavBar از شیء this استفاده شده بود. این روش تنها با کلاسهای استاندارد کامپوننت کار میکند. در اینجا باید props را به عنوان پارامتر متد دریافت (همانند مثال فوق) و سپس از آن استفاده کرد.
البته لازم به ذکر است که انتخاب بین «کامپوننتهای بدون حالت تابعی» و یک کامپوننت معمولی تعریف شدهی توسط کلاسها، صرفا یک انتخاب شخصی است.
یک نکته: امکان Destructuring Arguments نیز در اینجا وجود دارد. یعنی بجای اینکه یکبار props را به عنوان پارامتر دریافت کرد و سپس توسط آن به خاصیت totalCounters دسترسی یافت، میتوان نوشت:
در این حالت شیء props دریافت شده توسط ویژگی Objects Destructuring، به totalCounters تجزیه میشود و سپس میتوان تنها از همین متغیر دریافتی، به صورت {totalCounters} در کدها استفاده کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-08.zip
<button onClick={this.handleReset} className="btn btn-primary btn-sm m-2" > Reset </button>
handleReset = () => { const counters = this.state.counters.map(counter => { counter.value = 0; return counter; }); this.setState({ counters }); // = this.setState({ counters: counters }); };
اکنون پس از ذخیره سازی فایل counters.jsx و بارگذاری مجدد برنامه در مرورگر، هرچقدر بر روی دکمهی Reset کلیک کنیم ... اتفاقی رخ نمیدهد! حتی اگر به افزونهی React developer tools نیز مراجعه کنیم، مشاهده خواهیم کرد که عمل تنظیم value به صفر، در تک تک کامپوننتهای شمارشگر، به درستی صورت گرفتهاست؛ اما تغییرات به DOM اصلی منعکس نشدهاند:
البته اگر به همین تصویر دقت کنید، هنوز مقدار count، در state آن 4 است. علت اینجا است که هر کدام از Counterها دارای local state خاص خودشان هستند و در آنها، مقدار count به صورت زیر مقدار دهی شدهاست که در آن تغییرات بعدی این this.props.value، متصل به count نیست و count، فقط یکبار مقدار دهی میشود:
class Counter extends Component { state = { count: this.props.counter.value };
حذف Local state
اکنون میخواهیم در کامپوننت Counter، قسمت local state آنرا به طور کامل حذف کرده و تنها از this.props جهت دریافت اطلاعاتی که نیاز دارد، استفاده کنیم. به این نوع کامپوننتها، «Controlled component» نیز میگویند. یک کامپوننت کنترل شده دارای local state خاص خودش نیست و تمام دادههای دریافتی را از طریق this.props دریافت میکند و هر زمانیکه قرار است دادهای تغییر کند، رخدادی را به والد خود صادر میکند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل میشود.
برای پیاده سازی این مفهوم، ابتدا خاصیت state کامپوننت Counter را حذف میکنیم. سپس تمام ارجاعات به this.state را در این کامپوننت یافته و آنها را تغییر میدهیم. اولین ارجاع، در متد handleIncrement به صورت this.state.count تعریف شدهاست:
handleIncrement = () => { this.setState({ count: this.state.count + 1 }); };
<button onClick={() => this.props.onIncrement(this.props.counter)} className="btn btn-secondary btn-sm" > Increment </button>
getBadgeClasses() { let classes = "badge m-2 badge-"; classes += this.props.counter.value === 0 ? "warning" : "primary"; return classes; } formatCount() { const { value } = this.props.counter; // Object Destructuring return value === 0 ? "Zero" : value; }
در ادامه به کامپوننت Counters مراجعه کرده و متد رویدادگردانی را جهت پاسخگویی به رخداد onIncrement رسیدهی از کامپوننتهای فرزند، تعریف میکنیم:
handleIncrement = counter => { console.log("handleIncrement", counter); };
<Counter key={counter.id} counter={counter} onDelete={this.handleDelete} onIncrement={this.handleIncrement} />
پیاده سازی کامل متد handleIncrement اینبار به صورت زیر است:
handleIncrement = counter => { console.log("handleIncrement", counter); const counters = [...this.state.counters]; // cloning an array const index = counters.indexOf(counter); counters[index] = { ...counter }; // cloning an object counters[index].value++; console.log("this.state.counters", this.state.counters[index]); this.setState({ counters }); };
تا اینجا اگر برنامه را ذخیره کرده و منتظر به روز رسانی آن در مرورگر شویم، با کلیک بر روی Reset، تمام کامپوننتها با هر وضعیتی که پیشتر داشته باشند، به حالت اول خود باز میگردند:
همگام سازی چندین کامپوننت با هم زمانیکه رابطهی والد و فرزندی بین آنها وجود ندارد
در ادامه میخواهیم یک منوی راهبری (یا همان NavBar در بوت استرپ) را به بالای صفحه اضافه کنیم و در آن جمع کل تعداد Counterهای رندر شده را نمایش دهیم؛ مانند نمایش تعداد آیتمهای انتخاب شدهی توسط یک کاربر، در یک سبد خرید. برای پیاده سازی آن، درخت کامپوننتهای React را مطابق شکل فوق تغییر میدهیم. یعنی مجددا کامپوننت App را در به عنوان کامپوننت ریشهای انتخاب کرده که سایر کامپوننتها از آن مشتق میشوند و همچنین کامپوننت مجزای NavBar را نیز اضافه خواهیم کرد.
برای این منظور به index.js مراجعه کرده و مجددا کامپوننت App را که غیرفعال کرده بودیم و بجای آن Counters را نمایش میدادیم، اضافه میکنیم:
import App from "./App"; ReactDOM.render(<App />, document.getElementById("root"));
سپس کامپوننت جدید NavBar را توسط فایل جدید src\components\navbar.jsx اضافه میکنیم تا منوی راهبری سایت را نمایش دهد:
import React, { Component } from "react"; class NavBar extends Component { render() { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar </a> </nav> ); } } export default NavBar;
اکنون به App.js مراجعه کرده و متد render آنرا جهت نمایش درخت کامپوننتهایی که مشاهده کردید، تکمیل میکنیم:
import "./App.css"; import React from "react"; import Counters from "./components/counters"; import NavBar from "./components/navbar"; function App() { return ( <React.Fragment> <NavBar /> <main className="container"> <Counters /> </main> </React.Fragment> ); } export default App;
تا اینجا اگر برنامه را ذخیره کنیم تا در مرورگر بارگذاری مجدد شود، چنین شکلی حاصل شدهاست:
اکنون میخواهیم تعداد کامپوننتهای شمارشگر را در navbar نمایش دهیم. پیشتر state کامپوننت Counters را توسط props، به کامپوننتهای Counter رندر شدهی توسط آن انتقال دادیم. استفادهی از این ویژگی به دلیل وجود رابطهی والد و فرزندی بین این کامپوننتها میسر شد. اما همانطور که در تصویر درخت کامپوننتهای جدید تشکیل شده مشاهده میکنید، رابطهی والد و فرزندی بین دو کامپوننت Counters و NavBar وجود ندارد. بنابراین اکنون این سؤال مطرح میشود که چگونه باید تعداد کل شمارشگرهای کامپوننت Counters را به کامپوننت NavBar، برای نمایش آنها انتقال داد؟ در یک چنین حالتهایی که رابطهی والد و فرزندی بین کامپوننتها وجود ندارد و میخواهیم آنها را همگام سازی کنیم و دادههایی را بین آنها به اشتراک بگذاریم، باید state را به یک سطح بالاتر انتقال داد. یعنی در این مثال باید state کامپوننت Counters را به والد آن که اکنون کامپوننت App است، منتقل کرد. پس از آن چون هر دو کامپوننت NavBar و Counters، از کامپوننت App مشتق میشوند، اکنون میتوان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت.
انتقال state به یک سطح بالاتر
برای انتقال state به یک سطح بالاتر، به کامپوننت Counters مراجعه کرده و خاصیت state آنرا به همراه تمامی متدهایی که آنرا تغییر میدهند و از آن استفاده میکنند، انتخاب و cut میکنیم. سپس به کامپوننت App مراجعه کرده و آنها را در اینجا paste میکنیم. یعنی خاصیت state و متدهای handleDelete، handleReset و handleIncrement را از کامپوننت Counters به کامپوننت App منتقل میکنیم. این مرحلهی اول است. سپس نیاز است به کامپوننت Counters مراجعه کرده و ارجاعات به state و متدهای یاد شده را توسط props اصلاح میکنیم. برای این منظور ابتدا باید این props را در کامپوننت App مقدار دهی کنیم تا بتوانیم آنها را در کامپوننت Counters بخوانیم؛ یعنی متد render کامپوننت App، تمام این خواص و متدها را باید به صورت ویژگیهایی به تعریف المان Counters اضافه کند تا خاصیت props آن بتواند به آنها دسترسی داشته باشد:
render() { return ( <React.Fragment> <NavBar /> <main className="container"> <Counters counters={this.state.counters} onReset={this.handleReset} onIncrement={this.handleIncrement} onDelete={this.handleDelete} /> </main> </React.Fragment> ); }
پس از این تعاریف میتوانیم به کامپوننت Counters بازگشته و ارجاعات فوق را توسط خاصیت props، در متد render آن اصلاح کنیم:
render() { return ( <div> <button onClick={this.props.onReset} className="btn btn-primary btn-sm m-2" > Reset </button> {this.props.counters.map(counter => ( <Counter key={counter.id} counter={counter} onDelete={this.props.onDelete} onIncrement={this.props.onIncrement} /> ))} </div> ); }
پس از این نقل و انتقالات، اکنون میتوانیم تعداد counters را در NavBar نمایش دهیم. برای این منظور ابتدا در کامپوننت App، به همان روشی که ویژگی counters={this.state.counters} را به تعریف المان Counters اضافه کردیم، شبیه به همین کار را برای کامپوننت NavBar نیز میتوانیم انجام دهیم تا از طریق خاصیت props آن قابل دسترسی شود و یا حتی میتوان به صورت زیر، تنها جمع کل را به آن کامپوننت ارسال کرد:
<NavBar totalCounters={this.state.counters.filter(c => c.value > 0).length} />
سپس در کامپوننت NavBar، عدد totalCounters فوق را که به تعداد کامپوننتهایی که مقدار value آنها بیشتر از صفر است، اشاره میکند، از طریق خاصیت props خوانده و نمایش میدهیم:
class NavBar extends Component { render() { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar{" "} <span className="badge badge-pill badge-secondary"> {this.props.totalCounters} </span> </a> </nav> ); } }
کامپوننتهای بدون حالت تابعی
اگر به کدهای کامپوننت NavBar دقت کنیم، تنها یک تک متد render در آن ذکر شدهاست و تمام اطلاعات مورد نیاز آن نیز از طریق props تامین میشود و دارای state و یا هیچ رویدادگردانی نیست. یک چنین کامپوننتی را میتوان به یک «Stateless Functional Component» تبدیل کرد؛ کامپوننتهای بدون حالت تابعی. در اینجا بجای اینکه از یک کلاس برای تعریف کامپوننت استفاده شود، میتوان از یک function استفاده کرد (به همین جهت به آن functional میگویند). احتمالا نمونهی آنرا با کامپوننت App پیشفرض قالب create-react-app نیز مشاهده کردهاید که در آن فقط یک ()function App وجود دارد. البته در کدهای فوق چون نیاز به ذکر state، در کامپوننت App وجود داشت، آنرا از حالت تابعی، به حالت کلاس استاندارد کامپوننت، تبدیل کردیم.
اگر بخواهیم کامپوننت بدون حالت NavBar را نیز تابعی کنیم، میتوان به صورت زیر عمل کرد:
import React from "react"; // Stateless Functional Component const NavBar = props => { return ( <nav className="navbar navbar-light bg-light"> <a className="navbar-brand" href="#"> Navbar{" "} <span className="badge badge-pill badge-secondary"> {props.totalCounters} </span> </a> </nav> ); }; export default NavBar;
پیشتر در کامپوننت NavBar از شیء this استفاده شده بود. این روش تنها با کلاسهای استاندارد کامپوننت کار میکند. در اینجا باید props را به عنوان پارامتر متد دریافت (همانند مثال فوق) و سپس از آن استفاده کرد.
البته لازم به ذکر است که انتخاب بین «کامپوننتهای بدون حالت تابعی» و یک کامپوننت معمولی تعریف شدهی توسط کلاسها، صرفا یک انتخاب شخصی است.
یک نکته: امکان Destructuring Arguments نیز در اینجا وجود دارد. یعنی بجای اینکه یکبار props را به عنوان پارامتر دریافت کرد و سپس توسط آن به خاصیت totalCounters دسترسی یافت، میتوان نوشت:
const NavBar = ({ totalCounters }) => {
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-08.zip
بوت استرپ، به همراه کامپوننتهایی برای پیاده سازی اعمال متداول طرحبندی صفحات است؛ مانند jumbotron ،media ،table و card.
کامپوننت jumbotron
از Jumbotron برای نمایش متنی مشخص در بالای یک صفحه، استفاده میشود. دو روش استفادهی از آن در بوت استرپ 4 وجود دارند:
- داخل container:
با این خروجی:
در اینجا با اعمال کلاس jumbotron، متن header، داخل یک قاب با گوشههای گرد قرار میگیرد و مشخصتر نمایش داده خواهد شد. همچنین با mt-4، فاصلهای را بین آن و بالای صفحه ایجاد کردهایم.
- خارج از container:
با این خروجی:
اگر میخواهیم این قاب، تمام عرض صفحه را پر کند و همچمنین لبههای گرد آن نیز حذف شوند، میتوان از کلاس jumbotron-fluid استفاده کرد و آنرا خارج از container قرار داد. سپس برای اینکه متن داخل آن با container زیر آن تراز شود، میتوان یک container را در اینجا داخل jumbotron تعریف کرد.
کنترل ظاهر جداول، در بوت استرپ 4
بوت استرپ 4 به همراه تعدادی کلاس ویژه است که برای بهبود ظاهر المان استاندارد جدول، ارائه شدهاند. آنها را در طی مثالهایی بررسی خواهیم کرد.
برای رسیدن به چنین تصویری، تغییرات زیر را بر روی یک جدول استاندارد HTML اعمال کردهایم:
- کلاس table، کلاس پایه اعمال شیوهنامههای بوت استرپ 4 به المان جدول است که سبب خواهد شد آیتمهای آن با فاصلهی بهتری نسبت به یکدیگر ظاهر شوند. با استفاده از کلاس table-dark میتوان یک قالب مشکی را به جدول اعمال کرد.
- کلاس table-striped سبب میشود تا ردیفها، یک در میان با رنگی متمایز نمایش داده شوند.
- با افزودن table-hover، رنگ ردیفهای جدول با عبور اشارهگر ماوس از روی آنها تغییر میکند.
- کلاس table-bordered کار نمایش قاب جدول را انجام میدهد.
- کلاس table-responsive سبب میشود تا در اندازههای کوچک صفحه، یک اسکرول بار افقی برای نمایش آیتمهای جدول ظاهر شود و یا میتوان از کلاس table-sm نیز استفاده کرد تا padding تعریف شدهی در جدول، کاهش یابند. این کلاس، قابلیت پذیرش break-pointها را نیز دارد؛ مانند table-responsive-md.
- کلاسهای thead-light و یا thead-dark که بر روی تگ thead قرار میگیرند، رنگ پس زمینهی هدر جدول را مشخص میکنند.
- برای تغییر رنگ پس زمینه و متن یک ردیف میتوان از کلاسهای bg-color و text-color استفاده کرد:
- برای تغییر رنگ سلولهای جدول از کلاسهای table-color استفاده میکنیم:
فرمولهای رنگهای قابل اعمال به ردیفها، سلولها و متون جداول بوت استرپ 4 را در تصویر ذیل مشاهده میکنید:
کامپوننت جدید card در بوت استرپ 4
پنلهای بوت استرپ 3 حذف و بجای آن کامپوننت جدیدی به نام card در نگارش 4 آن ارائه شدهاست که با افزودن کلاس آن به یک div، بلافاصله قابی با گوشههای گرد به آن اضافه میشود.
- برای اینکه عناصر داخل card با فاصلهی مناسبی از لبههای آن قرار گیرند و همچنین شیوهنامههای قسمتهای مختلف آن به درستی اعمال شوند، نیاز است محتوای section ای که با کلاس card مشخص شده (تعیین container)، داخل یک div با کلاس card-body قرار گیرد. در اینجا امکان تعریف card-header و card-footer نیز وجود دارد.
- سپس یک card میتواند دارای تصویری واکنشگرا باشد که عرض card را پوشش میدهد. این تصویر با کلاس card-img مشخص میشود.
در اینجا امکان تعریف card-img-top و card-img-bottom نیز وجود دارند. این موارد تصویر card را در بالا و یا پایین آن، بدون padding، نمایش میدهند. اگر میخواهید متنی را بر روی این تصویر نمایش دهید، از کلاس card-img-overlay استفاده کنید. در این حالتها باید تصویر را خارج از card-body قرار دهید.
- عنوان و زیرعنوان یک card، توسط کلاسهای card-title و card-subtitle تعیین میشوند.
- متن داخل آنرا با کلاس card-text مشخص میکنیم.
- لینکهای ذیل آن نیز توسط کلاس card-link در طی یک ردیف نمایش داده میشوند.
امکان تعیین رنگ پس زمینه، حاشیه و متن یک card نیز وجود دارند:
با این خروجی:
و فرمول کلی رنگهای آن نیز به صورت زیر میباشد:
میتوان برای یک card، هدر و فوتر نیز تعریف کرد:
در اینجا همان card قبلی را مشاهده میکنید که عناوین آن به card-header و لینکهای ذیل آن به card-footer منتقل شدهاند:
برای تعریف یک list-group در داخل یک card، به صورت زیر عمل میکنیم:
ابتدا list-group را به خارج از card-body منتقل میکنیم. سپس برای حذف حاشیهی آن و همچنین گوشههای گرد آن، جهت یکی شدن با قاب card، کلاس list-group-flush را به آن اضافه میکنیم:
تعیین نحوهی چیدمان cards در بوت استرپ 4
اگر چندین card در یک صفحه تعریف شدهاند، برای تعیین نحوهی قرارگیری آنها در کنار یکدیگر میتوان یا از سیستم طرحبندی متداول بوت استرپ استفاده کرده و یا امکان تعریف گروهی از آنها نیز وجود دارد. برای اینکار کافی است یک div با کلاس card-group را تعریف و سپس تمام cards را داخل آن قرار دهیم:
که سبب خواهد شد تمام cards در کنار یکدیگر بدون فاصلهای نمایش داده شوند. اگر بجای آن از کلاس card-deck استفاده شود، فاصلهای بین cards قرار میگیرد.
اگر از کلاس card-columns استفاده کنیم، تمام cards را به صورت خودکار در ستونها و ردیفها، قرار میدهد که بعضی از آنها بلندتر و بعضی دیگر کوتاهتر هستند (نوعی نمایش کاشیکاری شدهاست):
ولی در کل اگر نیاز به کنترل بیشتری دارید، از همان روش متداول تعریف ردیفها و ستونهای سیستم طرحبندی بوت استرپ استفاده کنید.
المان media در بوت استرپ 4
برای نمایش متداول متن و تصویر که قرار است تصویر، در یک ستون و متن، در ستونی دیگر باشد، بوت استرپ 4 به همراه کلاس media است که بر اساس Flexbox بازنویسی شدهاست.
با این خروجی:
ابتدا توسط کلاس media یک container را تعریف میکنیم. سپس تصویر، یک ستون و media-body ستون دیگر را تشکیل میدهد.
با استفاده از d-flex، المان تصویر را به یک Flexbox container تبدیل کرده و با استفاده از کلاس align-self-center، آنرا در میانهی ستون قرار میدهیم. همچنین در اینجا توسط mr-3، فاصلهی آنرا با متن ستون کناری تنظیم کردهایم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: Bootstrap4_09.zip
کامپوننت jumbotron
از Jumbotron برای نمایش متنی مشخص در بالای یک صفحه، استفاده میشود. دو روش استفادهی از آن در بوت استرپ 4 وجود دارند:
- داخل container:
<div class="container"> <header class="jumbotron mt-4"> <div class="display-2 mb-4">Our Mission</div> <p class="lead">Wisdom Pet Medicine strives to blend the best in traditional and alternative medicine in the diagnosis and treatment of companion animals including dogs, cats, birds, reptiles, rodents, and fish. We apply the wisdom garnered in the centuries old tradition of veterinary medicine, to find the safest treatments and cures.</p> </header>
در اینجا با اعمال کلاس jumbotron، متن header، داخل یک قاب با گوشههای گرد قرار میگیرد و مشخصتر نمایش داده خواهد شد. همچنین با mt-4، فاصلهای را بین آن و بالای صفحه ایجاد کردهایم.
- خارج از container:
<header class="jumbotron jumbotron-fluid"> <div class="container"> <div class="display-2 mb-4">Our Mission</div> <p class="lead">Wisdom Pet Medicine strives to blend the best in traditional and alternative medicine in the diagnosis and treatment of companion animals including dogs, cats, birds, reptiles, rodents, and fish. We apply the wisdom garnered in the centuries old tradition of veterinary medicine, to find the safest treatments and cures.</p> </div> </header>
اگر میخواهیم این قاب، تمام عرض صفحه را پر کند و همچمنین لبههای گرد آن نیز حذف شوند، میتوان از کلاس jumbotron-fluid استفاده کرد و آنرا خارج از container قرار داد. سپس برای اینکه متن داخل آن با container زیر آن تراز شود، میتوان یک container را در اینجا داخل jumbotron تعریف کرد.
کنترل ظاهر جداول، در بوت استرپ 4
بوت استرپ 4 به همراه تعدادی کلاس ویژه است که برای بهبود ظاهر المان استاندارد جدول، ارائه شدهاند. آنها را در طی مثالهایی بررسی خواهیم کرد.
برای رسیدن به چنین تصویری، تغییرات زیر را بر روی یک جدول استاندارد HTML اعمال کردهایم:
<table class="table table-striped table-hover table-bordered table-responsive"> <thead class="thead-light">
- کلاس table-striped سبب میشود تا ردیفها، یک در میان با رنگی متمایز نمایش داده شوند.
- با افزودن table-hover، رنگ ردیفهای جدول با عبور اشارهگر ماوس از روی آنها تغییر میکند.
- کلاس table-bordered کار نمایش قاب جدول را انجام میدهد.
- کلاس table-responsive سبب میشود تا در اندازههای کوچک صفحه، یک اسکرول بار افقی برای نمایش آیتمهای جدول ظاهر شود و یا میتوان از کلاس table-sm نیز استفاده کرد تا padding تعریف شدهی در جدول، کاهش یابند. این کلاس، قابلیت پذیرش break-pointها را نیز دارد؛ مانند table-responsive-md.
- کلاسهای thead-light و یا thead-dark که بر روی تگ thead قرار میگیرند، رنگ پس زمینهی هدر جدول را مشخص میکنند.
- برای تغییر رنگ پس زمینه و متن یک ردیف میتوان از کلاسهای bg-color و text-color استفاده کرد:
<tr class="bg-danger text-light">
<td class="table-success">$100.00 </td>
کامپوننت جدید card در بوت استرپ 4
پنلهای بوت استرپ 3 حذف و بجای آن کامپوننت جدیدی به نام card در نگارش 4 آن ارائه شدهاست که با افزودن کلاس آن به یک div، بلافاصله قابی با گوشههای گرد به آن اضافه میشود.
<section class="card mb-5" id="drwinthrop"> <div class="card-body"> <img class="card-img img-fluid" src="images/testimonial-mcphersons.jpg" alt="Doctor Winthrop Photo"> <h2 class="card-title">Dr. Stanley Winthrop</h2> <h5 class="card-subtitle">Behaviorist</h5> <p class="card-text">Dr. Winthrop is the guardian of Missy, a three-year old Llaso mix, who he adopted at the shelter. Dr. Winthrop is passionate about spay and neuter and pet adoption, and works tireless hours outside the clinic, performing free spay and neuter surgeries for the shelter.</p> <a class="card-link" href="#">About Me</a> <a class="card-link" href="#">My Pets</a> <a class="card-link" href="#">Client Slideshow</a> </div> </section>
- سپس یک card میتواند دارای تصویری واکنشگرا باشد که عرض card را پوشش میدهد. این تصویر با کلاس card-img مشخص میشود.
در اینجا امکان تعریف card-img-top و card-img-bottom نیز وجود دارند. این موارد تصویر card را در بالا و یا پایین آن، بدون padding، نمایش میدهند. اگر میخواهید متنی را بر روی این تصویر نمایش دهید، از کلاس card-img-overlay استفاده کنید. در این حالتها باید تصویر را خارج از card-body قرار دهید.
- عنوان و زیرعنوان یک card، توسط کلاسهای card-title و card-subtitle تعیین میشوند.
- متن داخل آنرا با کلاس card-text مشخص میکنیم.
- لینکهای ذیل آن نیز توسط کلاس card-link در طی یک ردیف نمایش داده میشوند.
امکان تعیین رنگ پس زمینه، حاشیه و متن یک card نیز وجود دارند:
<section class="card mb-5 bg-primary text-light border-warning" id="drchase">
و فرمول کلی رنگهای آن نیز به صورت زیر میباشد:
میتوان برای یک card، هدر و فوتر نیز تعریف کرد:
<section class="card mb-5" id="drsanders"> <div class="card-header"> <h2 class="card-title">Dr. Kenneth Sanders</h2> <h5 class="card-subtitle">Nutritionist</h5> </div> <div class="card-body"> <img class="card-img img-fluid" src="images/testimonial-mcphersons.jpg" alt="Doctor Sanders Photo"> <p class="card-text">Leroy walked into Dr. Sanders front door when she was moving into a new house. After searching for weeks for Leroy's guardians, she decided to make Leroy a part of her pet family, and now has three cats.</p> </div> <div class="card-footer"> <a class="card-link" href="#">About Me</a> <a class="card-link" href="#">My Pets</a> <a class="card-link" href="#">Client Slideshow</a> </div> </section>
برای تعریف یک list-group در داخل یک card، به صورت زیر عمل میکنیم:
<section class="card mb-5" id="drwong"> <div class="card-body"> <img class="card-img img-fluid" src="images/testimonial-mcphersons.jpg" alt="Doctor Wong Photo"> <h2 class="card-title">Dr. Olivia Wong</h2> <h5 class="card-subtitle">Preventive Care</h5> <p class="card-text">Dr. Wong is a cancer survivor who was fortunate enough to get to spend time with a therapy dog during her recovery. She became passionate about therapy animals, and has started her own foundation to train and provide education to patients in recovery. Now she gets her own dose of daily therapy from her husky, Lilla.</p> </div> <div class="list-group list-group-flush"> <a class="list-group-item" href="#">About Me</a> <a class="list-group-item" href="#">My Pets</a> <a class="list-group-item" href="#"> Client Slideshow </a> </div> </section>
تعیین نحوهی چیدمان cards در بوت استرپ 4
اگر چندین card در یک صفحه تعریف شدهاند، برای تعیین نحوهی قرارگیری آنها در کنار یکدیگر میتوان یا از سیستم طرحبندی متداول بوت استرپ استفاده کرده و یا امکان تعریف گروهی از آنها نیز وجود دارد. برای اینکار کافی است یک div با کلاس card-group را تعریف و سپس تمام cards را داخل آن قرار دهیم:
<div class="container"> <div class="card-group">
اگر از کلاس card-columns استفاده کنیم، تمام cards را به صورت خودکار در ستونها و ردیفها، قرار میدهد که بعضی از آنها بلندتر و بعضی دیگر کوتاهتر هستند (نوعی نمایش کاشیکاری شدهاست):
ولی در کل اگر نیاز به کنترل بیشتری دارید، از همان روش متداول تعریف ردیفها و ستونهای سیستم طرحبندی بوت استرپ استفاده کنید.
المان media در بوت استرپ 4
برای نمایش متداول متن و تصویر که قرار است تصویر، در یک ستون و متن، در ستونی دیگر باشد، بوت استرپ 4 به همراه کلاس media است که بر اساس Flexbox بازنویسی شدهاست.
<body> <div class="container"> <section class="media mb-5" id="drwinthrop"> <img class="d-flex align-self-center img-fluid rounded mr-3" style="width:30%" src="images/testimonial-mcphersons.jpg" alt="Doctor Winthrop Photo"> <div class="media-body"> <h2>Dr. Stanley Winthrop</h2> <h5>Behaviorist</h5> <p>Dr. Winthrop is the guardian of Missy, a three-year old Llaso mix, who he adopted at the shelter. Dr. Winthrop is passionate about spay and neuter and pet adoption, and works tireless hours outside the clinic, performing free spay and neuter surgeries for the shelter.</p> </section> </div> </body>
ابتدا توسط کلاس media یک container را تعریف میکنیم. سپس تصویر، یک ستون و media-body ستون دیگر را تشکیل میدهد.
با استفاده از d-flex، المان تصویر را به یک Flexbox container تبدیل کرده و با استفاده از کلاس align-self-center، آنرا در میانهی ستون قرار میدهیم. همچنین در اینجا توسط mr-3، فاصلهی آنرا با متن ستون کناری تنظیم کردهایم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: Bootstrap4_09.zip
یک نکتهی تکمیلی
روشی که در سری ابتدایی Angular مطرح شدهاست، مبتنی بر سیستم مدیریت ماژولهای system.js هست. اما در نهایت روش توصیه شدهی توسط تیم Angular استفاده از Angular CLI است که مبتنی بر webpack است. این روش بسیار سادهتر (کار با ابزاری استاندارد)، ساختیافتهتر (به همراه تنظیماتی مبتنی بر best practices)، بهینهتر (به همراه بهینه سازیهای بسیاری جهت کاهش حجم نهایی و کاهش تعداد فایلهای تولیدی) و پیشرفتهتر از روش system.js هست و توضیحات تکمیلی آن در مطلب « Angular CLI - قسمت پنجم - ساخت و توزیع برنامه» ارائه شدهاند. این روشی است که برای ارائهی نهایی از آن استفاده میشود و در مطالبی مانند «یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017» و «سفارشی سازی صفحهی اول برنامههای Angular CLI توسط ASP.NET Core» از آنها استفاده شدهاست.
روشی که در سری ابتدایی Angular مطرح شدهاست، مبتنی بر سیستم مدیریت ماژولهای system.js هست. اما در نهایت روش توصیه شدهی توسط تیم Angular استفاده از Angular CLI است که مبتنی بر webpack است. این روش بسیار سادهتر (کار با ابزاری استاندارد)، ساختیافتهتر (به همراه تنظیماتی مبتنی بر best practices)، بهینهتر (به همراه بهینه سازیهای بسیاری جهت کاهش حجم نهایی و کاهش تعداد فایلهای تولیدی) و پیشرفتهتر از روش system.js هست و توضیحات تکمیلی آن در مطلب « Angular CLI - قسمت پنجم - ساخت و توزیع برنامه» ارائه شدهاند. این روشی است که برای ارائهی نهایی از آن استفاده میشود و در مطالبی مانند «یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017» و «سفارشی سازی صفحهی اول برنامههای Angular CLI توسط ASP.NET Core» از آنها استفاده شدهاست.
نظرات اشتراکها
ایجاد برنامههای دسکتاپ با Vue.js
- همان فایل FileContentResult را باید بازگشت دهید: « تغییرات متدهای بازگشت فایلها به سمت کلاینت در ASP.NET Core »
نظرات مطالب
آشنایی با فریمورک الکترون Electron
نه الزاما. برای نمونه پروژهی « Electron.NET » برنامههای ASP.NET Core شما را تبدیل به برنامههای دسکتاپ چندسکویی میکند. در اینجا Electron صرفا یک «دربرگیرنده» خواهد بود. نمونهی دیگر آن تبدیل برنامههای Angular به برنامههای دسکتاپ چندسکویی است: « electron-angular-native »