یک Flex Container متداول به این صورت کار میکند:
این کلاسها که موارد داخل پرانتز آنها اختیاری است، المان را تبدیل به یک المان Flexbox میکنند. حالت نمایشی پیشفرض آنها block است؛ اما اگر نیاز بود میتوان آنها را تبدیل به in-line نیز کرد. بنابراین سادهترین Flex Container را میتوان با افزودن کلاس d-flex ایجاد کرد.
بر روی یک Flex Container میتوان کلاسهای تعیین جهت را نیز تعریف کرد:
به این ترتیب میتوان آیتمها را به صورت ردیفها و یا ستونهایی، نمایش داد. مقدار row در اینجا به صورت پیشفرض اعمال میشود. بنابراین ذکر کلاس خالی flex به معنای قرار دادن المانها در طی چندین ردیف در صفحه است. در اینجا استفادهی از reverse، نمایش المانها را از راست به چپ میسر میکند.
در یک Flex Container امکان تعیین ترتیب عناصر نیز وجود دارد:
این مورد را در مطلب «طرحبندی صفحات وب با بوت استرپ 4 - قسمت دوم » بررسی کردیم. کلاس order را علاوه بر ستونها، بر روی هر دربرگیرندهای که دارای کلاس d-flex است نیز میتوان اعمال کرد.
همچنین امکان تنظیم فواصل بین آیتمها نیز در یک Flex Container پیش بینی شدهاست:
برای مثال استفادهی از مقدار تراز center، روش بسیار مناسبی برای قرار دادن عناصر، در میانهی افقی صفحه است. این مورد را نیز در قسمت قبل بررسی کردیم.
میتوان نحوهی Wrap المانها را بر اساس فضای خالی در یک Flex Container به صورت زیر تنظیم کرد:
که در اینجا دو مقدار wrap و nowrap قابل تنظیم است. در حالت wrap، اگر آیتمها با اندازهی خودشان در ردیف جاری جا نشدند، به سطر یا سطرهای بعدی منتقل خواهند شد. حالت پیشفرض nowrap است.
برای تغییر تراز عمودی المانها در یک Flex Container از کلاس align-content استفاده میشود:
این مورد را نیز در قسمت قبل بررسی کردیم و همانند کار با ستونها میباشد.
یک مثال: بررسی ویژگیهای یک Flex Container
<head> <style> .item { background: #f0ad4e; text-align: center; width: 150px; height: 30px; border: 1px solid white; } </style> </head> <body> <div class="container bg-danger"> <div class="bg-info" style="height:100vh"> <div class="item">Exotic</div> <div class="item">Grooming</div> <div class="item">Health</div> <div class="item">Nutrition</div> <div class="item">Pests</div> <div class="item">Vaccinations</div> </div> </div> </body>
در ابتدا کلاس d-flex را به div داخل container اضافه میکنیم:
<div class="bg-info d-flex" style="height:100vh">
و اگر جهت این Flex Container را به صورت صریح مشخص کنیم:
<div class="bg-info d-flex flex-column" style="height:100vh">
و یا اگر بخواهیم آیتمها را از راست به چپ به صورت یک ردیف نمایش دهیم میتوان از flex-row-reverse استفاده کرد:
<div class="bg-info d-flex flex-row-reverse" style="height:100vh">
و اگر بجای row در این حالت column را مقدار دهی کنیم:
<div class="bg-info d-flex flex-sm-column-reverse" style="height:100vh">
آیتمها از پایین صفحه شروع خواهند شد. البته در این مثال break-point از نوع sm نیز ذکر شدهاست تا پس از گذر از این اندازهی صفحه، چنین اتفاقی رخ دهد.
و یا اگر بخواهیم آیتمها از راست به چپ در طی یک ردیف، پس از اندازهی صفحهی sm و همچنین در میانهی صفحه ظاهر شوند، میتوان از کلاس justify-content استفاده کرد:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center" style="height:100vh">
و اگر wrap را فعال کنیم:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-wrap" style="height:100vh">
اگر آیتمها با اندازهی اصلی خودشان، در ردیف جاری جا نشدند، به سطرهای بعدی منتقل خواهند شد.
اگر nowrap را فعال کنیم:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-nowrap" style="height:100vh">
و با فعالسازی align-content-start، تمام آیتمها را به سمت بالای صفحه هدایت میکند و align-content-end، آنها را از پایین صفحه شروع خواهد کرد:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-wrap align-content-start" style="height:100vh">
کنترل آیتمهای قرار گرفتهی درون یک Flex Container در بوت استرپ 4
علاوه بر امکان کنترل ویژگیهای یک Flex Container، اجزای قرار گرفتهی درون آنها را نیز میتوان کنترل کرد و اینکار توسط کلاس align-self میسر است:
این مورد نیز همانند توضیحات کلاس align-self اعمالی به ستونها است که در قسمت قبل بررسی کردیم.
به علاوه در اینجا امکان تعریف floating elements نیز مسیر است که شبیه به دسترسی به امکانات CSS در بوت استرپ است با امکان تنظیم break-points:
فرض کنید به تمام آیتمهای داخل Flex Container کلاس float-left را اضافه کردهایم. در این حالت Container قابلیت ردیابی اندازهی این آیتمها را از دست میدهد. به همین جهت با اعمال کلاس clearfix بوت استرپ به container، مجددا امکان ردیابی این آیتمها را پیدا میکند.
کلاسهای تعریف margin و padding در بوت استرپ 4
در بوت استرپ 4 کلاسهای ویژهای برای ایجاد margin و padding بین عناصر در نظر گرفته شدهاند که خلاصهی آنها فرمول زیر است:
ابتدا با تعریف یک خاصیت شروع میشود؛ مانند m یا p، برای کنترل margin و padding. سپس لبهای که باید به آن اعمال شود بدون فاصله و یا - ذکر میشود؛ مانند mt به معنای margin-top. در این فرمول x به معنای اعمال همزمان به چپ و راست است و y به معنای اعمال همزمان به بالا و پایین و اگر میخواهید آیتمهای کناری آیتم جاری را به دو طرف لبهها هدایت کنید از mx-auto استفاده کنید.
در اینجا امکان اعمال یک break-point اختیاری نیز وجود دارد. در آخر اندازه ذکر میشود که بین 0 تا 5 متغیر است.
یک مثال: اعمال کلاسهای padding و margin بوت استرپ 4
<head> <style> .item { background: #f0ad4e; text-align: center; border: 1px solid white; } </style> </head> <body> <div class="container bg-danger"> <div class="bg-info d-flex"> <div class="item">Exotic</div> <div class="item">Grooming</div> <div class="item bg-danger ml-3 my-sm-3 pb-3 pt-5">Health</div> <div class="item">Nutrition</div> <div class="item">Pests</div> <div class="item">Vaccinations</div> </div> </div> </body>
نمایش و مخفی سازی عناصر در بوت استرپ 4
کلاس invisible سبب میشود تا المانی در صفحه نمایش داده نشود، اما این المان همچنان فضای اختصاصی خود را خواهد داشت. کلاس visible به معنای نمایان بودن المانی تنها برای screen readers است (دستگاههای کمکی معلولها).
اما روش اصلی نمایش و یا مخفی سازی عناصر در بوت استرپ 4، استفاده از خاصیت display است:
برای مثال با انتساب کلاس d-sm-none به المانی، میتوان سبب مخفی شدن آن پس از گذر از sm شد.
امکان تعیین اندازهی عناصر در بوت استرپ 4
بوت استرپ 4 تعدادی کلاس ویژه را برای تعیین اندازهی عناصر نیز افزودهاست:
در اینجا w=width، h=height، mw=max-height و mh=max-height است با مقادیر 25، 50، 75 و 100 و مقدار پیشفرض آن 100 است (یعنی پوشاندن کل container).
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: Bootstrap4_06.zip
بهوسیلهی order میتوان ترتیب قرارگیری آیتمهای Grid را مشخص کرد. فرض کنید یک ردیف سه ستون داریم و میخواهیم زمانیکه این آیتمها در مروگر نمایش داده میشوند، ترتیب قرارگیری آنها متناسب با نیاز ما باشد که با استفاده از دستور order این کار صورت میپذیرد.
نکته: این کار برای بحث seo مورد استفاده قرار میگیرد.
در پایین با یک مثال، چگونگی انجام اینکار شرح داده شدهاست:<div id="app"> <v-app id="inspire"> <v-container fluid> <v-flex xs4 order-md2> <v-card dark tile flat color="red lighten-1"> <v-card-text>#1</v-card-text> </v-card> </v-flex> <v-flex xs4 order-md3> <v-card dark tile flat color="red lighten-2"> <v-card-text>#2</v-card-text> </v-card> </v-flex> <v-flex xs4 order-md1> <v-card dark tile flat color="red darken-1"> <v-card-text>#3</v-card-text> </v-card> </v-flex> </v-layout> <v-layout row wrap> <v-flex xs12 sm6 md3 order-md4 order-sm2> <v-card dark tile flat color="red darken-2"> <v-card-text>#1</v-card-text> </v-card> </v-flex> <v-flex xs12 sm6 md3 order-md3 order-sm1> <v-card dark tile flat color="deep-orange lighten-1"> <v-card-text>#2</v-card-text> </v-card> </v-flex> <v-flex xs12 sm6 md3 order-md2 order-sm4> <v-card dark tile flat color="deep-orange darken-3"> <v-card-text>#3</v-card-text> </v-card> </v-flex> <v-flex xs12 sm6 md3 order-md1 order-sm3> <v-card dark tile flat color="deep-orange"> <v-card-text>#4</v-card-text> </v-card> </v-flex> </v-layout> </v-container> </v-app> </div>
<div id="app"> <v-app id="inspire"> <v-container fluid grid-list-xl> <v-layout row justify-space-between> <v-flex xs2> <v-card dark color="primary"> <v-card-text>one</v-card-text> </v-card> </v-flex> <v-flex xs2> <v-card dark color="secondary"> <v-card-text>two</v-card-text> </v-card> </v-flex> <v-flex xs2> <v-card dark color="accent"> <v-card-text>three</v-card-text> </v-card> </v-flex> </v-layout> <v-layout row justify-space-around> <v-flex xs2> <v-card dark color="primary"> <v-card-text>one</v-card-text> </v-card> </v-flex> <v-flex xs2> <v-card dark color="secondary"> <v-card-text>two</v-card-text> </v-card> </v-flex> <v-flex xs2> <v-card dark color="accent"> <v-card-text>three</v-card-text> </v-card> </v-flex> </v-layout> <v-layout row justify-center> <v-flex xs2> <v-card dark color="primary"> <v-card-text>one</v-card-text> </v-card> </v-flex> <v-flex xs2> <v-card dark color="secondary"> <v-card-text>two</v-card-text> </v-card> </v-flex> <v-flex xs2> <v-card dark color="accent"> <v-card-text>three</v-card-text> </v-card> </v-flex> </v-layout> </v-container> </v-app> </div>
<div id="app"> <v-app id="inspire"> <v-container fluid> <v-layout row> <v-flex grow pa-1> <v-card dark color="green darken-3"> <v-card-text>#1 Im a Grow Flex</v-card-text> </v-card> </v-flex> <v-flex shrink pa-1> <v-card dark color="green darken-1"> <v-card-text>#2 Im a Shrink Flex</v-card-text> </v-card> </v-flex> </v-layout> <v-layout row> <v-flex grow pa-1> <v-card dark color="green darken-1"> <v-card-text>#1 Im a Grow Flex</v-card-text> </v-card> </v-flex> <v-flex shrink pa-1> <v-card dark color="green lighten-1"> <v-card-text>#2 Im a Shrink Flex</v-card-text> </v-card> </v-flex> <v-flex grow pa-1> <v-card dark color="green darken-4"> <v-card-text>#3 Im a Grow Flex</v-card-text> </v-card> </v-flex> </v-layout> <v-layout row> <v-flex shrink pa-1> <v-card dark color="green darken-3"> <v-card-text>#1 Im a Shrink Flex</v-card-text> </v-card> </v-flex> <v-flex grow pa-1> <v-card dark color="green"> <v-card-text>#2 Im a Grow Flex</v-card-text> </v-card> </v-flex> <v-flex shrink pa-1> <v-card dark color="green lighten-1"> <v-card-text>#3 Im a Shrink Flex</v-card-text> </v-card> </v-flex> </v-layout> </v-container> </v-app> </div>
<div id="app"> <v-app id="inspire"> <v-container fluid grid-list-md> <v-layout row wrap> <v-flex d-flex xs12 sm6 md4> <v-card color="purple" dark> <v-card-title primary>Lorem</v-card-title> <v-card-text>{{ lorem }}</v-card-text> </v-card> </v-flex> <v-flex d-flex xs12 sm6 md3> <v-layout row wrap> <v-flex d-flex> <v-card color="indigo" dark> <v-card-text>{{ lorem.slice(0, 70) }}</v-card-text> </v-card> </v-flex> <v-flex d-flex> <v-layout row wrap> <v-flex v-for="n in 2":key="n" d-flex xs12 > <v-card color="red lighten-2" dark > <v-card-text>{{ lorem.slice(0, 40) }}</v-card-text> </v-card> </v-flex> </v-layout> </v-flex> </v-layout> </v-flex> <v-flex d-flex xs12 sm6 md2 child-flex> <v-card color="green lighten-2" dark> <v-card-text>{{ lorem.slice(0, 90) }}</v-card-text> </v-card> </v-flex> <v-flex d-flex xs12 sm6 md3> <v-card color="blue lighten-2" dark> <v-card-text>{{ lorem.slice(0, 100) }}</v-card-text> </v-card> </v-flex> </v-layout> </v-container> </v-app> </div>
bcdedit /set hypervisorlaunchtype off
bcdedit /set hypervisorlaunchtype auto
در این مقاله با ادیتور VS Code کار میکنیم. بعد از نصب آن، از منوی Terminal، گزینهی New Terminal را کلیک کنید تا پنجرهی PowerShell نمایش داده شود؛ برای سرعت و دقت بیشتر در برنامههای vue.js ای. با دستور زیر vue cli را نصب میکنیم (فقط یک مرتبه و برای برنامههای بعدی vue.jsای، نیازی به اجرای این دستور نداریم):
npm install -g @vue/cli
جهت راه اندازی یک برنامهی پیش فرض Vue.js ای، کافیست دستور زیر را اجرا نماییم تا پکیجهای مورد نیاز، به همراه کانفیگ اولیه (Zero config) برای ما ایجاد شوند:
vue create movie-app
بعد از ایجاد برنامه در vs code، از طریق منوی File، گزینه Open Folder را کلیک کرده و پوشه برنامهای را که ایجاد کردیم، Select Folder میکنیم. ساختار اولیهی برنامهی ایجاد شده، به شکل زیر میباشد:
نیازمندیهای مثال جاری
A) برای گرفتن اطلاعات مورد نمایش در مثال جاری، از سایت omdbapi.com استفاده میکنیم که با دریافت یک api key آن بصورت رایگان، میتوانیم web serviceهای آن را Call نماییم.
B) از vuetify برای ui استفاده میکنیم که بصورت Material Design و دارای کامپوننتهای غنی میباشد؛ ضمن اینکه RTL را هم پشتیبانی میکند.
برای نصب آن در Terminal دستور زیر را اجرا میکنیم:
vue add vuetify
سپس جهت تست و صحت افزوده شدن و کانفیگ درست، با دستور زیر برنامه را اجرا میکنیم:
npm run serve
بعد از اجرای دستور فوق، روی گزینه زیر ctrl+click میکنیم تا نتیجه کار در مرورگر قابل رویت باشد:
نمایش صفحه زیر نشان دهندهی درستی انجام کار تا اینجا است:
نکته: جهت استفاده از امکان RTL کافیست در فایل vuetify.js واقع در پوشهی plugins، تغییرات زیر را انجام دهیم. در مثال جاری بدلیل اینکه اطلاعات انگلیسی میباشند، از نسخه LTR آن استفاده میکنیم؛ هر چند یکسری api فارسی نیز موجود میباشد که میتوان از آنها استفاده نمود.
import Vue from 'vue' import Vuetify from 'vuetify/lib' import 'vuetify/src/stylus/app.styl' Vue.use(Vuetify, { iconfont: 'md', rtl: true })
C) نصب vue-router : جهت انجام routeهای تودرتو ، مپ کردن کامپوننت ها با آدرسی مشخص، کار با پارامتر و HTML5 History API مورد استفاده قرار میگیرد. برای نصب آن، دستور زیر را اجرا میکنیم:
npm install vue-router
برای نوشتن routeهای مورد نیاز، یک فولدر را با نام router، در پوشه src برنامه ایجاد میکنیم و یک فایل جاوا اسکریپتی را در آن با نام index.js، میسازیم (این ساختار برای مدیریت بهتر پروژه میباشد):
درون فایل index.js، محتویات زیر را طبق مستندات آن قرار میدهیم:
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)
جهت استفاده از این router، نیاز است تا در نمونهی وهله سازی شدهی vue برنامه بکار گرفته شود. فایل main.js را باز کنید و خط زیر را در قسمت بالای برنامه وارد کنید:
import router from './router'
اکنون محتویات فایل main.js بشکل زیر میباشد:
import Vue from 'vue' import './plugins/vuetify' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ render: h => h(App), router }).$mount('#app')
D) نصب axios : برای انجام درخواستهای HTTP و عملیات ایجکس در vue.js ترجیحا بهتر است از axios که یک کتابخانهی محبوب میباشد و کار با آن ساده است، استفاده شود. برای نصب آن، دستور زیر را اجرا میکنیم:
npm install axios
E) نصب vuex : کتابخانهای جهت مدیریت حالت (state management) برای vue.js میباشد و مشابه آن Flux و Redux برای React میباشند. برای نصب، دستور زیر را اجرا میکنیم:
npm install vuex
برای بکارگیری آن یک فولدر را با نام store در پوشهی src برنامه ایجاد میکنیم و یک فایل جاوا اسکریپتی را در آن با نام index.js میسازیم (این ساختار برای مدیریت بهتر پروژه میباشد). درون فایل index.js، محتویات زیر را طبق مستندات آن و ^ قرار میدهیم.
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export const store = new Vuex.Store()
برای استفاده و کانفیگ آن، محتویات فایل main.js را بشکل زیر تغییر دهید:
import Vue from 'vue' import './plugins/vuetify' import App from './App.vue' import router from './router' import {store} from './store' Vue.config.productionTip = false new Vue({ render: h => h(App), store, router }).$mount('#app')
نکته: برای اجرای برنامه و دریافت پکیجهای مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید:
npm install
پی نوشت: برای داشتن نیوگت شخصی سایتهای نظیر Nuget Server و Myget ( به همراه پشتیبانی از مخازن npm و Bower ) هم این سرویس را ارائه میکنند. ولی باید هزینهی آن را پرداخت کنید. البته سایت GemFury مخازن رایگان مختلفی چون Nuget را نیز پشتیبانی میکند.
نصب بر روی یک سیستم شخصی یا لوکال
در اولین قدم، شما باید یک دایرکتوری را در سیستم خود درست کنید تا پکیجهای خود را داخل آن قرار دهید. پنجرهی Package manager Settings را باز کنید و در آن گزینهی Package Sources را انتخاب کنید. سپس در کادر باز شده، بر روی دکمهی افزودن، در بالا کلیک کنید تا در پایین کادر، از شما نام محل توزیع بسته و آدرس آن را بپرسد:
بعد از ورود اطلاعات، بر روی دکمهی Update کلیک کنید. از این پس این دایرکتوری، منبع پکیجهای شماست و برای دریافت پکیجها از این آدرس میتوانید از طریق منوی کشویی موجود در کنسول، پکیج جدید خودتان را انتخاب کنید:
اگر میخواهید میتوانید این دایرکتوری را به اشتراک بگذارید تا دیگر افراد حاضر در شبکهی محلی هم بتوانند آن را به عنوان منبع توزیع خود به سیستم اضافه کنند.
مرحلهی بعدی این است که از طریق ابزار خط فرمان نیوگت نسخه 3.3 پکیجهایتان را به دایرکتوری مربوطه انتقال دهید. نحوهی صدا زدن این دستور به شکل زیر است:
nuget init e:\nuget\ e:\nuget\test
nuget add GMap.Net.1.0.1.nupkg -source e:\nuget\test
ساخت منبع راه دور (اینترنت)
شما با استفاده از ویژوال استودیو و انجام چند عمل ساده میتوانید پکیجهای خودتان را مدیریت کنید. برای شروع، یک پروژهی تحت وب خام (Empty) را ایجاد کنید و در کنسول Nuget دستور زیر را وارد کنید:
Install-Package NuGet.Server
<appSettings> <!-- Set the value here to specify your custom packages folder. --> <add key="packagesPath" value="×\Packages" /> </appSettings>
(هر محلی که نصب کنید طبق الگوی مسیریابی، آدرس nuget را در انتها وارد کنید)
Dotnettips.info/nuget
در صورتی که قصد دارید مستقیما بستهای را به سمت سرور push کنید، از یک رمز عبور قدرتمند که آن را میتوانید در web.config، بخش apiKey وارد نمایید، استفاده کنید. اگر هم نمیخواهید، میتوانید در تگ RequiredApiKey در خصوصیت Value، مقدار false را وارد نمایید.
برای اینکار میتوانید از دستور زیر استفاده کنید:
nuget push GMap.Net.1.0.1.nupkg -source http://Dotenettips.info/nuget (ApiKey)
nuget setapikey -source https://www.dntips.ir/Nuget (ApiKey)
آشنایی با Flex Layout Box Model
برای طراحی ظاهر یک برنامهی وب نیاز است عناصر آنرا در مکانهای مختلفی از صفحه قرار داد که به آن Layout گفته میشود. برای این منظور عموما 4 روش ذیل مرسوم هستند:
1. Table
2. Float, position, clear
3. CSS Grids
4. FlexBox CSS
امروزه دیگر آنچنان روشهای 1 و 2 به صورت مستقیم مورد استفاده قرار نمیگیرند. CSS Grid روش نهایی طراحی Layout در آینده خواهد بود و در حال حاضر تعداد مرورگرهایی که از آن پشتیبانی میکنند، قابل توجه نیست؛ اما از FlexBox در IE 11، کروم 21 و فایرفاکس 22 به بعد پشتیبانی میشود.
FlexBox CSS، سیلان المانهای قرار گرفتهی در داخل آنرا سبب میشود. در اینجا یک container اصلی وجود دارد که در برگیرندهی المانها است. در تصویر فوق دو محور را مشاهده میکنید. محور افقی از چپ به راست ادامه پیدا میکند. محور عمودی نحوهی ارتباط عناصر را مشخص میکند.
اکنون این سؤال مطرح میشود که چه تفاوتی بین یک Grid و FlexBox CSS وجود دارد؟ در یک Grid طراحی دو بعدی سطرها و ستون وجود دارد. اما به FlexBox باید به صورت سیلان یک بعدی سلولها نگاه کرد. برای مثال عناصر قرار گرفتهی درون Container یا به صورت افقی درون آن گسترده شده و قرار میگیرند و یا به صورت عمودی.
نحوهی تفکر و کارکرد با FlexBox چگونه است؟
در اینجا باید به دو مفهوم دقت داشت:
الف) سیلان عناصر درون Container که میتواند افقی و یا عمودی باشد.
ب) اندازهی المانها که میتواند ثابت و یا نسبی باشد.
یک Container، جهت سیلان عناصر درون آنرا مشخص میکند. المانهای آن، اندازه، فاصلهی از یکدیگر و ترتیب قرارگیری را ارائه میدهند. یک flex container میتواند شامل چندین flex container تو در تو نیز باشد.
نحوهی سیلان عناصر در FlexBox چگونه است؟
برای نمونه طرحبندی متداول ذیل را درنظر بگیرید:
نحوه تفکر در مورد طراحی این طرحبندی، باید از بیرون به درون و از بالا به پایین (سیلان عمودی) باشد:
سپس نحوهی سیلان عناصر درون Containerهای تعریف شده را مشخص میکنیم. برای مثال اولین Container دارای سیلان افقی از چپ به راست خواهد بود که عنصر سوم آن به دلیل اندازههای مشخص دو عنصر قبلی، به سطر دوم منتقل شدهاست.
در ادامه به قسمت میانی میرسیم که آن نیز دارای سیلان افقی از چپ به راست است:
در اینجا نیز میتوان سه Container را متصور شد که وسطی دارای سیلان افقی از راست به چپ است و مواردی که بر اساس اندازهی آنها در یک سطر جا نشدهاند، به سطر بعدی منتقل خواهند شد:
و تمام این سیلانها و انتقال به سطرهای بعدی بر اساس اندازهی المانها صورت میگیرد:
البته در این تصویر یک ایراد هم وجود دارد. با توجه به اینکه در ناحیهی میانی سه Container تعریف خواهند شد. Container ایی که در میان آن قرار میگیرد، دارای سیلان خاص خودش است و اندازههای آن باید نسبت به این Container تعریف شوند و نه نسبت به کل ناحیهی میانی. یعنی بجای اینکه 50 درصد، 25، 25 و 50 درصد را داشته باشیم، اینها در اصل 100 درصد، 50 و 50 درصد و سپس 100 درصد هستند.
معرفی کتابخانهی Angular Flex Layout
برای کار با Flex CSS نیاز است:
- مقدار زیادی کد CSS نوشت.
- نیاز به درک عمیقی از Flex Box دارد.
- نیاز است با باگهای مرورگرها و تفاوتهای پیاده سازیهای آنها در مورد FlexBox آشنا بود.
- نیاز به Prefixing دارد.
- برای Angular طراحی نشدهاست.
جهت رفع این مشکلات و محدودیتها، تیم Angular کتابخانهای را به نام Angular Flex Layout مخصوص نگارشهای جدید Angular طراحی کردهاست. این کتابخانه مستقل از Angular Material است اما عموما به همراه آن استفاده میشود.
مزایای کار با کتابخانهی Angular flex layout
- پیاده سازی TypeScript ایی دارد. در اصل یک سری directives مخصوص Angular است که با TypeScript نوشته شدهاست.
- به صورت پویا و inline تمام CSSهای مورد نیاز را تولید و تزریق میکند.
- به همراه یک API استاتیک است و همچنین یک API واکنشگرا
- با Angular CLI نیز یکپارچه شدهاست.
نصب و تنظیم کتابخانهی Angular Flex layout
برای نصب این کتابخانه، در ریشهی پروژه دستور زیر را صادر کنید:
npm install @angular/flex-layout --save
import { FlexLayoutModule } from "@angular/flex-layout"; @NgModule({ imports: [ FlexLayoutModule ], exports: [ FlexLayoutModule ] }) export class SharedModule { }
کار با API استاتیک Angular Flex layout
API استاتیک Angular Flex layout شامل این مزایا و مشخصات است:
- به صورت یکسری دایرکتیو Angular طراحی شدهاست که به HTML قالب کامپوننتها اضافه میشود.
- از data binding پشتیبانی میکند.
- CSS نهایی را به صورت پویا و inline تولید و به صفحه تزریق میکند. Inline CSS تزریق شده به ویژگیهای styles هر المان تزریق میشوند و موارد مشابه را در صورت وجود بازنویسی میکنند.
- از تشخیص تغییرات پشتیبانی میکند.
- به همراه ویژگیهای fxHide و fxShow است.
- کارآیی مطلوبی دارد.
در اینجا برای تعریف container اصلی از دایرکتیوهای زیر استفاده میشود:
- fxLayout جهتهای flex را مشخص میکند.
<div fxLayout="row" fxLayout.xs="column"></div>
- fxLayoutWrap مشخص میکند که آیا المانها باید به سطر و یا ستون بعدی منتقل شوند یا خیر؟
<div fxLayoutWrap></div>
<div fxLayoutGap="10px"></div>
<div fxLayoutAlign="start stretch"></div>
چند مثال:
و یا حالت راست به چپ آن به صورت زیر است:
و برای تعریف آیتمهای قرار گرفتهی درون containers میتوان از دایرکتیوهای زیر استفاده کرد:
- fxflex برای تعیین اندازه و flex المانها
<div fxFlex="1 2 calc(15em + 20px)"></div>
fxFlex="grow shrink basis"
fxFlex="basis"
- shrink به این معنا است که اگر به اندازهی کافی فضا وجود نداشت، این المان نسبت به سایر المانهای دیگر تا چه اندازهای میتواند کوچک شود.
- basis به معنای اندازهی پیشفرض المان است.
در اینجا اندازهها برحسب پیکسل، درصد و یا calcs, em, cw, vh میتوانند تعیین شوند. همچنین یک سری نام مستعار مانند grow, initial, auto, none, nogrow, noshrink هم قابل استفاده هستند.
- fxflexorder برای تعیین ترتیب قرارگیری یک المان
<div fxFlexOrder="2"></div>
<div fxFlexOffset="20px"></div>
<div fxFlexAlign="center"></div>
<div fxFlexFill></div>
چند مثال:
در اینجا سه نمایشی را که در ذیل تعریف divها مشاهده میکنید بر اساس تغییر اندازهی صفحه حاصل شدهاند. چون آیتم دوم دارای مقدار grow مساوی 5 است، به همین جهت با تغییر اندازهی صفحه و دسترسی به مقدار فضای بیشتر، بزرگتر شدهاست.
یک مثال کامل
اگر علاقمند باشید تا توانمندیهای angular flex layout را در قالب یک مثال کامل مشاهده کنید، به آدرس زیر مراجعه نمائید:
https://tburleson-layouts-demos.firebaseapp.com/#/docs
در این مثال با تغییر گزینههای مختلف، کد معادل angular flex layout آن نیز تولید میشود.
همچنین wiki خود پروژه نیز به همراه مثالهای بیشتری است:
https://github.com/angular/flex-layout/wiki
کار با API واکنشگرای Angular Flex layout
در طراحی واکنشگرا، container و عناصر داخل آن بر اساس تغییرات اندازهی صفحه و یا اندازهی وسیلهی نمایشی، تغییر اندازه و همچنین موقعیت میدهند و این تغییرات بر اساس انطباق با viewport وسیلهی نمایشی صورت میگیرند. به همین جهت برای طراحی واکنشگرا نیاز به Flex CSS و همچنین Media Query است. نوشتن Medial Query و ترکیب آن با Flex CSS کار مشکلی است. به همین جهت Angular Flex layout به همراه یک API واکنشگرا نیز هست که در پشت صحنه Flex CSS را بر اساس طراحی متریال و Medial Queries مورد استفاده قرار میدهد.
اگر علاقمند هستید تا اندازههای واکنشگرای استاندارد متریال را ملاحظه کنید، میتوانید به آدرس زیر مراجعه نمائید (قسمت Breakpoint system آن):
https://material.io/design/layout/responsive-layout-grid.html#breakpoints
برای مثال هر اندازهای کمتر از 600px در گروه extra small قرار میگیرد (با مخفف xs). از 600px تا 1024px در بازهی small (با مخفف sm)، از 1024px تا 1440px در بازهی medium (با مخفف md) و از 1440px تا 1920px در بازهی large (با مخفف lg) و بیشتر از آن در بازهی xlrage قرار میگیرند (با مخفف xl). این اعداد و بازهها، پایهی طراحی API واکنشگرای Angular Flex layout هستند. به همین جهت نام این بازهها در این API به صورت مخفف xs, sm, md, lg, xl درنظر گرفته شدهاند و مورد استفاده قرار میگیرند. همچنین اگر اندازههای مدنظر از این بازهها کمتر باشند، میتوان از lt-sm, lt-md, lt-lg, lt-xl نیز استفاده کرد. در اینجا lt به معنای less than است و یا اگر بازههای مورد نیاز بیش از این اندازهها باشند میتوان با gt-xs, gt-sm, gt-md, gt-lg کار کرد. در اینجا gt به معنای greater than است.
به این مخففها «media query alias» هم گفته میشود و اکنون که لیست آنها مشخص است، تنها کافی است آنها را به API استاتیکی که پیشتر بررسی کردیم، اضافه کنیم. برای مثال:
fxLayout.sm = "..." fxLayoutAlign.md = "..." fxHide.gt-sm = "..."
معادل طراحی آن با API استاتیک Angular Flex Layout به صورت زیر است:
اکنون که این طرحبندی دسکتاپ را داریم، چگونه باید آنرا تبدیل به طرحبندی موبایل، مانند شکل زیر کنیم؟
برای اینکار ابتدا fxLayout.xs را به سطر میانی اضافه میکنیم تا هرگاه به این اندازه رسیدیم، بجای ردیف، تبدیل به ستون شود. سپس توسط fxFlexOrder.xs، در اندازهی xs، محل قرارگیری المانهای این ستون را هم مشخص میکنیم:
همانطور که ملاحظه میکنید کار کردن با این API بسیار سادهاست و نیازی به کارکردن مستقیم با Media Queries و یا برنامه نویسی مستقیم ندارد و تمام آن در قالب HTML یک کامپوننت قابل پیاده سازی است.
یک نکته: مثال کاملی که پیشتر در این بحث مطرح شد، به همراه مثال واکنشگرا نیز هست که برای مشاهدهی اثر آنها بهتر است اندازهی مرورگر را کوچک و بزرگ کنید.
مخفی کردن و یا نمایش قسمتی از صفحه بر اساس اندازهی آن
علاوه بر media query alias هایی که عنوان شد، امکان نمایش و یا مخفی سازی قسمتهای مختلف صفحه بر اساس اندازهی صفحهی نمایشی نیز هست:
<div fxShow fxHide.xs="false" fxHide.lg="true"></div>
تغییر اندازهی قسمتی از صفحه بر اساس اندازهی آن
در مثال زیر اگر اندازهی صفحه gt-sm باشد (بیشتر از small)، اندازهی این div به 100 درصد بجای 50 درصد حالتهای دیگر، تنظیم میشود:
<div fxFlex="50%" fxFlex.gt-sm="100%"></div>
حالتهای ویژهی طراحی واکنشگرا در Angular Flex Layout
در API واکنشگرای آن حالتهای ویژهی fxshow, fxhide, ngclass و ngstyle نیز درنظر گرفته شدهاند که امکان فعالسازی آنها در اندازههای مختلف صفحه مسیر است:
<div fxShow [fxShow.xs]="isVisibleOnMobile()"></div> <div fxHide [fxHide.gt-sm]="isVisibleOnDesktop()"></div> <div [ngClass.sm]="{'fxClass-sm': hasStyle}" ></div> <div [ngStyle.xs]="{color: 'blue'}"></div>
امکان کار با API واکنشگرا از طریق برنامه نویسی
برای این منظور میتوان از سرویس ObservableMedia مانند مثال زیر استفاده کرد:
در اینجا به فعالسازی یک بازهی خاص گوش فرا خواهیم داد. برای مثال اگر اندازهی صفحه xs بود، سبب بارگذاری محتوای خاص مرتبط با موبایل خواهیم شد.
برای مطالعهی بیشتر
dotnet new console -o MyFirstWasiApp cd MyFirstWasiApp dotnet add package Wasi.Sdk --prerelease dotnet build
wasmtime bin/Debug/net7.0/MyFirstWasiApp.wasm
FROM scratch COPY bin/Debug/net7.0/MyFirstWasiApp.wasm MyFirstWasiApp.wasm ENTRYPOINT [ "MyFirstWasiApp.wasm" ]
docker buildx build . --file=Dockerfile --tag=dotnet-webassembly --platform wasi/wasm32
docker run --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm32 dotnet-webassembly
دقت داشته باشید که این فیچر هنوز در مرحله Beta قرار دارد؛ و ممکن است در حین تهیه ایمیج با edge caseهای روبرو شوید به عنوان مثال من سعی کردم یک وبسرور ASP.NET Core (توسط Wasi.Sdk این امکان وجود دارد) را containerised کنم که در نهایت با خطای زیر مواجه شدم:
[error] instantiation failed: incompatible import type, Code: 0x61 Mismatched function type. Expected: FuncType {params{i32 , i32 , i32} returns{i32}} , Got: FuncType {params{i32 , i32} returns{i32}} When linking module: "wasi_snapshot_preview1" , function name: "sock_accept" At AST node: import description At AST node: import section At AST node: module
مدیریت پیشرفتهی حالت در React با Redux و Mobx - قسمت سوم - روش اتصال Redux به برنامههای React
نصب پیشنیازها
میتوان همانند قسمت قبل، تمام کارها را با کتابخانهی redux انجام داد و یا میتوان قسمت به روز رسانی UI آنرا و همچنین مدیریت state را به کتابخانهی ساده کنندهی دیگری به نام react-redux واگذار کرد. به همین جهت در ادامهی همان برنامهی قسمت قبل، دو کتابخانهی redux و همچنین react-redux را به همراه types آن نصب میکنیم (نصب types، سبب ارائهی intellisense بهتری در VSCode میشود؛ حتی اگر نخواهیم با TypeScript کار کنیم).
برای این منظور پس از باز کردن پوشهی اصلی برنامه توسط VSCode، دکمههای ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save redux react-redux > npm install --save-dev @types/react-redux
> npm install --save bootstrap
import "bootstrap/dist/css/bootstrap.css";
معرفی ساختار ابتدایی برنامه
برنامهای را که در این قسمت بررسی میکنیم، ساختار بسیار سادهای را داشته و به همراه دو دکمهی افزایش و کاهش مقدار یک شمارشگر است؛ به همراه دکمهی برای به حالت اول در آوردن آن. هدف اصلی دنبال شدهی در اینجا نیز نحوهی برپایی redux و همچنین react-redux و اتصال آنها به برنامهی React جاری است:
به همین جهت ابتدا کامپوننت جدید src\components\counter.jsx را به نحو زیر تشکیل میدهیم تا markup ابتدایی فوق را به همراه سه دکمه و یک span، برای نمایش مقدار شمارشگر، رندر کند:
import React, { Component } from "react"; class Counter extends Component { render() { return ( <section className="card mt-5"> <div className="card-body text-center"> <span className="badge m-2 badge-primary">0</span> </div> <div className="card-footer"> <div className="d-flex justify-content-center align-items-center"> <button className="btn btn-secondary btn-sm">+</button> <button className="btn btn-secondary btn-sm m-2">-</button> <button className="btn btn-danger btn-sm">Reset</button> </div> </div> </section> ); } } export default Counter;
import "./App.css"; import React from "react"; import Counter from "./components/counter"; function App() { return ( <main className="container"> <Counter /> </main> ); } export default App;
پوشه بندی مخصوص برنامههای مبتنی بر Redux
هدف ما در ادامه ایجاد یک store مخصوص redux است و سپس اتصال آن به کامپوننت شمارشگر برنامه. به همین جهت نیاز به 4 پوشهی جدید، برای مدیریت بهتر برنامه خواهیم داشت:
- پوشه constants: برای اینکه نام رشتهای نوع اکشنهای مختلف را بتوانیم در قسمتهای مختلف برنامه استفاده کنیم، بهتر است فایل جدید src\actions\index.js را ایجاد کرده و این ثوابت را داخل آن export کنیم.
- پوشهی actions: در فایل جدید src\actions\index.js، تمام متدهای ایجاد کنندهی شیء خاص action، که در قسمت قبل در مورد آن بحث شد، قرار میگیرند. نمونهی آن، متد createAddAction قسمت قبل است.
- پوشهی reducers: تمام توابع reducer برنامه را در فایلهای مجزایی در پوشهی reducers قرار میدهیم. سپس در فایل src\reducers\index.js با استفاده از متد combineReducer آنها را یکی کرده و به متد createStore ارسال میکنیم.
- پوشهی containers: این پوشه جائی است که کار فراخوانی متد connect کتابخانهی react-redux به ازای هر کامپوننت استفاده کنندهی از redux store، صورت میگیرد.
ایجاد نام نوع اکشن متناظر با دکمهی افزودن مقدار
میخواهیم با کلیک بر روی دکمهی +، مقدار شمارشگر افزایش یابد. به همین جهت نیاز به یک نام وجود دارد تا در تابع Reducer متناظر و قسمتهای دیگر برنامه، بتوان بر اساس آن، این اکشن خاص را شناسایی کرد و سپس عکس العمل نشان داد. به همین جهت فایل جدید src\constants\ActionTypes.js را ایجاد کرده و به صورت زیر تکمیل میکنیم:
export const Increment = "Increment";
ایجاد متد Action Creator
در قسمت قبل مشاهده کردیم که شیء ارسالی به یک reducer از طریق dispatch یک action خاص، دارای فرمت ویژهی زیر است:
{ type: "ADD", payload: { amount // = amount: amount }, meta: {} }
import * as types from "../constants/ActionTypes"; export const incrementValue = () => ({ type: types.Increment });
ایجاد تابع reducer مخصوص افزودن مقدار
ابتدا فایل جدید src\reducers\counter.js را با محتوای زیر ایجاد میکنیم:
import * as types from "../constants/ActionTypes"; const initialState = { count: 0 }; export default function counterReducer(state = initialState, action) { if (action.type === types.Increment) { return { count: state.count + 1 }; } return state; }
- سپس میخواهیم رویداد کلیک بر روی دکمه + را مدیریت کنیم. به همین جهت نیاز به یک اکشن جدید به نام Increment داریم که توسط مقدار ثابت رشتهای types.Increment، از فایل مجزای src\constants\ActionTypes.js، تامین میشود.
- پس از مشخص کردن نوع action ای که قرار است مدیریت شود و همچنین ایجاد متدی برای تولید شیء حاوی اطلاعات آن که در فایل src\actions\index.js قرار دارد، اکنون میتوان متد reducer را که state و action را دریافت میکند و سپس state جدیدی را بر اساس action.type دریافتی و در صورت نیاز بازگشت میدهد، ایجاد کرد. این متد بررسی میکند که آیا action.type رسیده همان ثابت Increment است؟ اگر بله، بجای تغییر مستقیم state.count، یک شیء جدید را بازگشت میدهد. البته روش صحیحتر اینکار را در قسمت اول این سری با معرفی روشهایی برای کپی اشیاء و آرایهها، بررسی کردیم. در اینجا جهت سادگی بیشتر، یک شیء کاملا جدید را دستی ایجاد میکنیم. در آخر اگر action.type رسیده قابل پردازش نبود، همان state ابتدایی دریافتی را بازگشت میدهیم تا در صورت وجود چندین reducer تعریف شدهی در سیستم، زنجیرهی آنها قابل پردازش باشد. این مورد را در قسمت قبل، ذیل عنوان «بررسی تابع combineReducers با یک مثال» بیشتر بررسی کردیم.
پس از ایجاد reducer اختصاصی عمل افزودن مقدار شمارشگر، فایل جدید src\reducers\index.js را نیز با محتوای زیر ایجاد میکنیم:
import { combineReducers } from "redux"; import counterReducer from "./counter"; const rootReducer = combineReducers({ counterReducer }); export default rootReducer;
ایجاد store مخصوص Redux
تا اینجا رسیدیم به یک rootReducer متشکل از تمام reducerهای سفارشی برنامه. اکنون بر اساس آن در فایل src\index.js، یک store جدید را ایجاد میکنیم:
import { createStore } from "redux"; import reducer from "./reducers"; //... const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); //...
نکته 2: در اینجا روش فعالسازی افزونهی redux-devtools را نیز ملاحظه میکنید. ابتدا بررسی میشود که آیا متد ویژهی فراخوانی این افزونه وجود دارد یا خیر؟ اگر بله، فراخوانی میشود. بدون این پارامتر دوم، افزونهی redex dev tools، هیچ خروجی را نمایش نخواهد داد.
اتصال React به Redux
کتابخانهی react-redux تنها به همراه دو شیء مهم connect و Provider است. شیء Provider آن شبیه به Context API خود React است و هدف آن، ارسال ارجاعی از store ایجاد شده، به برنامهی React است. پس از ایجاد store در فایل src\index.js، اکنون نوبت به اتصال آن به برنامهی React ای جاری است. به همین جهت در بالاترین سطح برنامه، ابتدا شیء کامپوننت App را با شیء Provider محصور میکنیم:
import { Provider } from "react-redux"; import { createStore } from "redux"; import reducer from "./reducers"; // ... const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") );
تامین state کامپوننت شمارشگر از طریق props
همانطور که عنوان شد، کامپوننت Counter به همراه state نیست و ما قصد نداریم در آن از state خود React استفاده کنیم؛ البته فلسفهی آنرا در قسمت اول این سری بررسی کردیم و همچنین اگر کامپوننتی نیاز به اشتراک گذاری اطلاعات خودش را با لایههای زیرین یا بالاتر از خود ندارد، شاید اصلا نیازی به Redux نداشته باشد و همان state استاندارد React برای آن کافی است. بنابراین میتوان برنامهای را داشت که ترکیبی از state استاندارد React، در کامپوننتهای متکی به خود و Redux، در کامپوننتهایی که باید اطلاعاتی را با هم به اشتراک بگذارند، باشد. برای مثال، کامپوننت مثال جاری، واقعا نیازی را به Redux، برای مدیریت حالت خود، ندارد؛ هدف ما در اینجا بررسی نحوهی برقراری ارتباطات یک سیستم مبتنی بر Redux، در برنامههای React است.
بنابراین در اینجا و کامپوننتی که قرار است از Redux برای مدیریت حالت خود استفاده کند، هر اطلاعاتی که به آن از طریق react-redux store وارد میشود، از طریق props به آن ارسال خواهد شد. برای مثال در اینجا مقدار count، از طریق props خوانده میشود و همچنین امکان ارسال action ای خاص به متد reducer تعریف شده نیز باید تعریف شود. بنابراین در ادامه نیاز داریم تا یک کامپوننت React را به redux store متصل کنیم. برای این منظور فایل جدید src\containers\Counter.js را با محتوای زیر ایجاد میکنیم:
import { connect } from "react-redux"; import { incrementValue } from "../actions"; import Counter from "../components/counter"; const mapStateToProps = state => { return state; }; const mapDispatchToProps = dispatch => { return { increment() { dispatch(incrementValue()); } }; }; export default connect(mapStateToProps, mapDispatchToProps)(Counter);
زمانیکه در مورد store در redux صحبت میشود، داخل آن یک شیء بزرگ state قرار گرفتهاست که حاوی کل state برنامهاست. اما شاید هر کامپوننت به تمام آن نیازی نداشته باشد. برای مثال شاید کامپوننت شمارشگر، اهمیتی به اطلاعات خطاهای سیستم و یا کاربر وارد شدهی به سیستم که در شیء کلی state موجود در store وجود دارند، ندهد. به همین جهت متد mapStateToProps، کل state برنامه را دریافت کرده و به ما اجازه میدهد تا تنها اطلاعاتی را که از آن نیاز داریم، به صورت props دریافت کنیم. به این ترتیب از رندر مجدد این کامپوننت نیز جلوگیری خواهد شد؛ چون این کامپوننت دیگر وابستهی به تغییرات سایر اجزای کل state برنامه، نخواهد بود و اگر آنها تغییر کردند، این کامپوننت رندر مجدد نخواهد شد.
بنابراین میتوان متد mapStateToProps را به صورت کلی زیر نیز تعریف کرد:
const mapStateToProps = (state) => { return state };
یک نکته: اگر کامپوننتی نیاز به تامین state خود را از طریق props نداشت و فقط کارش صدور رخدادها است، میتوان پارامتر اول متد connect را نال وارد کرد.
پارامتر dispatch متد mapDispatchToProps، به متد store.dispatch اشاره میکند. بنابراین توسط آن امکان ارسال actions را میسر کرده و میتوان state را توسط reducerهای تعریف شده، تغییر داد که در نتیجهی آن props جدیدی به کامپوننت منتقل میشوند. این تابع نیز یک شیء را باز میگرداند. این شیء را فعلا با یک متد دلخواه مقدار دهی میکنیم که توسط پارامتر dispatch رسیدهی به آن، متد action creator تعریف شدهی در فایل src\actions\index.js را به نام incrementValue، فراخوانی میکند؛ دقیقا عملی شبیه به فراخوانی store.dispatch(createAddAction(2)) در قسمت قبل که از آن برای ارسال یک اکشن، به reducer متناظری استفاده شد.
یک نکته: اگر کامپوننتی کار صدور رخدادها را انجام نمیدهد، میتوان پارامتر دوم متد connect را بطور کامل حذف کرد و قید نکرد.
استفاده از کامپوننت جدید خروجی متد connect، جهت تامین props کامپوننت شمارشگر
در انتهای فایل src\components\counter.jsx، چنین سطری درج شدهاست:
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import "./App.css"; import React from "react"; import CounterContainer from "./containers/Counter"; function App() { return ( <main className="container"> <CounterContainer /> </main> ); } export default App;
اکنون کامپوننت شمارشگر src\components\counter.jsx، دو شیء را از طریق props دریافت میکند؛ یکی کل state است که خاصیت count داخل آن قرار دارد و از طریق mapStateToProps تامین میشود. دیگری متد increment ای است که در متد mapDispatchToProps تعریف کردیم و کار صدور رخدادی را به reducer متناظر، انجام میدهد. به همین جهت تغییرات ذیل را در کامپوننت Counter اعمال میکنیم:
import React, { Component } from "react"; class Counter extends Component { render() { console.log("props", this.props); const { counterReducer: { count }, increment } = this.props; return ( <section className="card mt-5"> <div className="card-body text-center"> <span className="badge m-2 badge-primary">{count}</span> </div> <div className="card-footer"> <div className="d-flex justify-content-center align-items-center"> <button className="btn btn-secondary btn-sm" onClick={increment}> + </button> <button className="btn btn-secondary btn-sm m-2">-</button> <button className="btn btn-danger btn-sm">Reset</button> </div> </div> </section> ); } } export default Counter;
به همین جهت، خاصیت تو در توی this.props.counterReducer.count و همچنین اشارهگر به متد increment، توسط Object Destructuring به صورت زیر از this.props دریافتی، تجزیه شدهاند:
const { counterReducer: { count }, increment } = this.props;
جزئیات کار با Redux store را نیز میتوان در افزونهی redux dev tools مشاهده کرد:
این افزونه در نوار ابزار پایین آن، امکان export کل state و سپس import و بازیابی آنرا نیز به همراه دارد.
دریافت props از طریق کامپوننت دربرگیرنده و ارسال آن به کامپوننت اصلی
فرض کنید نیاز باشد تا اطلاعاتی را به صورت متداول React از طریق props، به کامپوننت دربرگیرندهی کامپوننت شمارشگر ارسال کرد:
function App() { const prop1 = 123 return ( <main className="container"> <CounterContainer prop1={prop1} /> </main> ); }
const mapStateToProps = (state, ownProps) => { console.log("mapStateToProps", { state, ownProps }); return state; };
پیاده سازی دکمهی کاهش مقدار شمارشگر
پس از آشنایی با روش کلی برقراری اتصالات سیستم react-redux، پیاده سازی دکمهی کاهش مقدار شمارشگر بسیار سادهاست و شامل مراحل زیر است:
1) ایجاد نام نوع اکشن متناظر با دکمهی کاهش مقدار
به فایل src\constants\ActionTypes.js، نوع جدید کاهشی را اضافه میکنیم:
export const Decrement = "Decrement";
در فایل src\actions\index.js، متد ایجاد کنندهی شیء اکشن ارسالی به reducer متناظری را تعریف میکنیم تا بتوان بر اساس نوع آن در reducer کاهشی، منطق کاهش را پیاده سازی کرد:
export const decrementValue = () => ({ type: types.Decrement });
اکنون در فایل src\reducers\counter.js، بر اساس نوع شیء رسیده، تصمیم به کاهش یا افزایش مقدار موجود در state گرفته میشود:
export default function counterReducer(state = initialState, action) { // ... if (action.type === types.Decrement) { return { count: state.count - 1 }; } return state; }
در ادامه نیاز است بتوان اکشن کاهش را به این reducer ارسال کرد. به همین جهت به کامپوننت دربرگیرندهی کامپوننت شمارشگر در فایل src\containers\Counter.js مراجعه کرده و به شیء خروجی متد mapDispatchToProps، متد کاهش را اضافه میکنیم:
import { decrementValue, incrementValue } from "../actions"; // ... const mapDispatchToProps = dispatch => { return { // ... decrement() { dispatch(decrementValue()); } }; };
در آخر به فایل src\components\counter.jsx مراجعه کرده و اشارهگر به متد decrement را از طریق this.props دریافت میکنیم:
const { // ... decrement } = this.props;
<button className="btn btn-secondary btn-sm m-2" onClick={decrement} > - </button>
به عنوان تمرین، پیاده سازی دکمهی Reset را نیز انجام دهید که جزئیات آن بسیار شبیه به دو مثال قبلی افزودن و کاهش مقدار شمارشگر است.
بهبود کیفیت کدهای کامپوننت دربرگیرندهی کامپوننت Counter
متد mapDispatchToProps فایل src\containers\Counter.js اکنون چنین شکلی را پیدا کردهاست:
const mapDispatchToProps = dispatch => { return { increment() { dispatch(incrementValue()); }, decrement() { dispatch(decrementValue()); } }; };
import { bindActionCreators } from "redux"; // ... const mapDispatchToProps = dispatch => { return bindActionCreators( { incrementValue, decrementValue }, dispatch ); };
به همین جهت نیاز است در متد رندر کامپوننت src\components\counter.jsx، نامهایی را که به متدهای action creator اشاره میکنند، به صورت زیر تغییر داد:
const { counterReducer: { count }, incrementValue, decrementValue } = this.props;
روش دوم: در نگارشهای اخیر react-redux میتوان متد mapDispatchToProps را به صورت زیر نیز خلاصه و تعریف کرد که بسیار سادهتر است:
const mapDispatchToProps = { incrementValue, decrementValue };
همچنین بجای بازگشت کل state در متد mapStateToProps، میتوان تنها خواص مدنظر را بازگشت داد:
const mapStateToProps = state => { //return state; return { count: state.counterReducer.count }; };
بنابراین باید در متد رندر کامپوننت شمارشگر، خاصیت count را به صورت معمولی دریافت کرد:
const { //counterReducer: { count }, count, incrementValue, decrementValue } = this.props;
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: state-management-redux-mobx-part03.zip