ارتقاء به HTTP Client در Angular 4.3
مبانی TypeScript؛ ماژولها
import {Book} from "./testmd";
استفاده از چندین Context در EF 6 Code first
Anti CSRF module for ASP.NET
حتما تا به حال در وب سایتهای زیادی قسمت هایی را دیده اید که چیدمان عناصر آن به شکل زیر است:
این گونه چیدمان را حتما در منوی Start ویندوز 8 بارها دیدهاید! عناصر تشکیل دهندهی این شکل از چیدمان، میتوانند یک سری عکس باشند که تشکیل یک گالری عکس را دادهاند و یا یک سری div که محتوای پستهای یک وبلاگ را در خود جای دادهاند. چیزی که این شکل از چیدمان عناصر را نسبت به چیدمانهای معمول متمایز میکند این است که طول و عرض هر یک از این عناصر با یکدیگر متفاوت است و هدف از این گونه چیدمان آن است که این عناصر در فضایی که به آنها اختصاص داده شده است، به صورت بهینه قرار گیرند تا کمترین فضا هدر رود.
برای اعمال این شکل از چیدمان در دنیای وب افزونههای زیادی بر فراز کتاب خانهی jQuery تدارک دیده شده است که از جمله مطرحترین آنها میتوان به افزونه های Isotope ، Masonry و Gridster اشاره کرد.
افزونهی Isotope مزایایی را برای من در پی داشت و این افزونه را برای انجام کارهای خود، مناسب دیدم. نکتهی مهم اینجا است که هدف من بررسی Isotope نیست، چرا که اگر به وب سایت آن مراجعه کنید، با کوهی از مستندات مواجه میشوید که چگونه از آن در وب سایتهای معمولی استفاده کنید.
در این مقاله قصد من این است که نشان دهم چگونه از افزونهی Isotope در AngularJS استفاده کنیم؛ چگونه چیدمان آن را راست به چپ کنیم و چگونه آن را با محیطهای واکنش گرا (Responsive) سازگار کنیم.
فرض کنید در یک وب سایت قصد داریم اطلاعات یک سری مطلب خبری را از سرور، به فرمت JSON دریافت کرده و نمایش دهیم. در AngularJS شیوهی کار بدین صورت است که اطلاعاتی که به فرمت JSON هستند را با استفاده از directive ایی به نام ng-repeat پیمایش کرده و آنها را نمایش دهیم. حال اگر بخواهیم چیدمان مطالب را با استفاده از Isotope تغییر دهیم، میبینیم که هیچ چیزی نمایش داده نمیشود. دلیل آن بر میگردد به مراحل کامپایل کردن AngularJS و نامشخص بودن زمان اعمال چیدمان Isotope به عناصر است.
در AngularJS هنگامیکه با دستکاری DOM سر و کار پیدا میکنیم، معمولا باید به سراغ Directiveها رفت و یک Directive سفارشی برای کار با Isotope تعریف کرد تا با مکانیزمهای Angular سازگار باشد. خوشبختانه Directive Isotope برای Angular موجود میباشد. نکتهی مهم این است که این Directive برای نگارش 1 افزونهی Isotope نوشته شده است. البته با نگارش 2 هم کار میکند که من برای انجام کار خود نسخهی 1 را ترجیح دادم استفاده کنم.
نکتهی بعدی که باید رعایت شود این است که چیدمان عناصر باید از راست به چپ شوند. خوشبختانه این کار در نسخهی 1 Isotope با تغییر کوچکی در سورس Isotope و تغییر یک تابع انجام میشود. گویا نسخهی دوم امکان پیش فرضی را برای این کار دارد، اما نتوانستم آن را به خوبی پیاده سازی کنم و به همین دلیل ترجیح دادم از همان نسخهی اول استفاده کنم.
برای اینکه در هنگام جابه جا شدن عناصر، انیمیشنها نیز از راست به چپ انجام شوند، باید cssهای زیر را نیز اعمال نمود:
.isotope .isotope-item { -webkit-transition-property: right, top, -webkit-transform, opacity; -moz-transition-property: right, top, -moz-transform, opacity; -ms-transition-property: right, top, -ms-transform, opacity; -o-transition-property: right, top, -o-transform, opacity; transition-property: right, top, transform, opacity; }
Responsive بودن این عناصر مسئلهی دیگری است که باید حل گردد. امروزه اکثر فریم ورکهای مطرح css، واکنشگرا نیز هستند و برای پشتیبانی از سایزهای متفاوت صفحه نمایش، تدابیری در نظر گرفتهاند. اساس کار واکنش گرا بودن این فریم ورکها در تعیین ابعاد عناصر، بیان ابعاد به صورت درصدی است. مثلا فلان عرض div برابر 50% باشد بدین معناست که همیشه عرض این div نصف عرض عنصر والد آن باشد.
متاسفانه Isotope میانهی چندانی با این ابعاد درصدی ندارد و باید عرض عناصر به صورت دقیق و بر حسب پیکسل بیان شود. البته نسخهی جدید آن و یا حتی پلاگین هایی برای کار با ابعاد درصدی نیز تدارک دیده شده است که به شخصه به نتیجهی با کیفیتی نرسیدم.
@media (min-width: 768px) and (max-width: 980px) { .card { width: 320px; } } @media (min-width: 980px) and (max-width: 1200px) { .card { width: 260px; } } @media (min-width: 1200px) { .card { width: 340px; } }
app.directive('imageOnload', function () { return { restrict: 'A', link: function (scope, element, attrs) { element.bind('load', function () { scope.$emit('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items }); } }; });
$(window).resize(function () { $timeout(function myfunction() { $scope.$broadcast('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items },1000); });
Cookie - قسمت دوم
کوکی در جاوا اسکریپت
document.cookie = "name=value; expires=date; domain=theDomain; path=thePath; secure";
document.cookie = 'myCookie=myValue; max-age=432000; domain=d.com; path=/test';
myCookie=myValue
document.cookie = 'mySecondCookie=mySecondValue; path=/'
myCookie=myValue; mySecondCookie=mySecondValue
function setCookie(data, value) { if (typeof data === "string") { data = { name: data, value: value }; }; if (!data.name) throw "Cookie's name can not be null."; var cookie = escape(data.name) + "=" + escape(data.value); var expDate = null; if (data.expDays) { expDate = new Date(); expDate.setDate(expDate.getDate() + data.expDays); } else if (data.expYear && data.expMonth && data.expDay) { expDate = new Date(data.expYear, data.expMonth, data.expDay); } else if (data.expires) { expDate = data.expires; } else if (data.maxAge) { expDate = new Date(); expDate.setSeconds(expDate.getSeconds() + data.maxAge); } if (expDate != null) cookie += "; expires=" + expDate.toGMTString(); if (data.domain) cookie += "; domain=" + escape(data.domain); if (data.path) cookie += "; path=" + escape(data.path); if (data.secure) cookie += "; secure"; document.cookie = cookie; return document.cookie; }
setCookie('cookie1', 'Value1'); setCookie({name:'cookie1', value:'Value1'}); setCookie({name:'cookie2', value:'Value2', expDays:10}); setCookie({name:'cookie3', value:'Value3', expires:new Date()}); setCookie({name:'cookie4', value:'Value4', expYear:2013, expMonth:0, expDay:13}); setCookie({name:'cookie3', value:'Value3', maxAge:365*24*60*60}); setCookie({name:'cookie5', value:'Value5', domain:'d.net', path:'/'}); setCookie({name:'cookie6', value:'Value6', secure:true}); setCookie({name:'cookie7', value:'Value7', expDays:100, domain:'dd.com', path:'/employee', secure:true});
function delCookie(data) { if (typeof data === "string") { data = { name: data }; }; data.expDays = -1; return setCookie(data); }
delCookie('myCookie'); delCookie({ name: 'myCookie', domain: 'd.com', path: '/test' });
function getCookie(name) { var cookies = document.cookie.split(";"); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i].split("="); if (cookie[0].trim() == escape(name)) { return unescape(cookie[1].trim()); } } return null; }
String.prototype.trim = function () { return this.replace(/^\s+|\s+$/g, ""); };
String.prototype.trimStart = function () { return this.replace(/^\s+/, ""); }; String.prototype.trimEnd = function () { return this.replace(/\s+$/, ""); };
cookie.shift(1); return unescape(cookie.join('=').trim());
function getCookie(name) { var foundCookies = []; var cookies = document.cookie.split(";"); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i].split("="); if (cookie[0].trim() == escape(name) && cookie.length >= 2) { cookie.shift(1); foundCookies.push(unescape(cookie.join('=').trim())); } } return foundCookies.length > 1 ? foundCookies : foundCookies.length == 1 ? foundCookies[0] : null; }
- Layout Rounding :
این مثال تاثیر استفاده از UseLayoutRounding را شرح میدهد. اگر شما در این مثال به آرامی اندازه پنجره را تغییر دهید، تفاوت دو شیء رسم شده را کاملا درک خواهید کرد.
<Page x:Class="LayoutRounding.Lines" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Lines" Name="linesPage" > <StackPanel Width="150" Margin="7" Orientation="Horizontal"> <!-- Single pixel line with layout rounding turned OFF.--> <Rectangle UseLayoutRounding="False" Width="45.5" Margin="10" Height="1" Fill="Red"/> <!-- Single pixel line with layout rounding turned ON.--> <Rectangle UseLayoutRounding="True" Width="45.5" Margin="10" Height="1" Fill="Red"/> </StackPanel> <!-- Background Grid --> <Page.Background> <DrawingBrush Viewport="0,0,10,10" ViewportUnits="Absolute" TileMode="Tile"> <DrawingBrush.Drawing> <DrawingGroup> <GeometryDrawing Brush="White"> <GeometryDrawing.Geometry> <RectangleGeometry Rect="0,0,1,1" /> </GeometryDrawing.Geometry> </GeometryDrawing> <GeometryDrawing Geometry="M0,0 L1,0 1,0.1, 0,0.1Z " Brush="#CCCCFF" /> <GeometryDrawing Geometry="M0,0 L0,1 0.1,1, 0.1,0Z" Brush="#CCCCFF" /> </DrawingGroup> </DrawingBrush.Drawing> </DrawingBrush> </Page.Background> </Page>
برای توضیحات بیشتر در مورد Layout Rounding میتوانید به لینک درج شده مراجعه کنید.
- Cached Composition :
<Window x:Class="BitmapCacheBrushDemo.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="300" Width="300" > <Window.Resources> <RichTextBox x:Key="cachedRichTextBox" > <RichTextBox.CacheMode> <BitmapCache EnableClearType="True" RenderAtScale="1" SnapsToDevicePixels="True" /> </RichTextBox.CacheMode> </RichTextBox> <BitmapCacheBrush x:Key="cachedRichTextBoxBrush" Target="{StaticResource cachedRichTextBox}"> <BitmapCacheBrush.BitmapCache> <BitmapCache EnableClearType="False" RenderAtScale="0.4" SnapsToDevicePixels="False" /> </BitmapCacheBrush.BitmapCache> </BitmapCacheBrush> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button1" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button2" Grid.Column="1" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button3" Grid.Column="2" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button4" Grid.Column="3" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button5" Grid.Column="4" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button6" Grid.Row="1" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="1" Name="button7" Grid.Row="1" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="2" Name="button8" Grid.Row="1" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="3" Name="button9" Grid.Row="1" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="4" Name="button10" Grid.Row="1" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button11" Grid.Row="2" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="1" Name="button12" Grid.Row="2" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="2" Name="button13" Grid.Row="2" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="3" Name="button14" Grid.Row="2" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="4" Name="button15" Grid.Row="2" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button16" Grid.Row="3" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="1" Name="button17" Grid.Row="3" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="2" Name="button18" Grid.Row="3" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="3" Name="button19" Grid.Row="3" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="4" Name="button20" Grid.Row="3" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button21" Grid.Row="4" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="1" Name="button22" Grid.Row="4" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="2" Name="button23" Grid.Row="4" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="3" Name="button24" Grid.Row="4" FontWeight="Bold" /> <Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="4" Name="button25" Grid.Row="4" FontWeight="Bold" /> </Grid> </Window>
- Pixel Shader 3 Support :
- Easing Functions :
عنوان بخش دوم Graphics and Rendering میباشد که در ادامه آشنا خواهیم شد.
ایجاد یک برنامهی خالی React برای آزمایش توابع Redux
در اینجا برای بررسی توابع Redux، یک پروژهی جدید React را ایجاد میکنیم:
> npm i -g create-react-app > create-react-app state-management-redux-mobx > cd state-management-redux-mobx > npm start
> npm install --save redux
معرفی سریع توابع Redux
Redux، کتابخانهی کوچکی است و تنها از 5 تابع تشکیل شدهاست:
applyMiddleware: function() bindActionCreators: function() combineReducers: function() compose: function() createStore: function()
بررسی تابع compose با یک مثال
پس از ایجاد پروژهی React و افزودن کتابخانهی Redux به آن، به فایل src\index.js این پروژه مراجعه کرده و محتویات آنرا با قطعه کد ذیل، تعویض میکنیم:
import { compose } from "redux"; const makeLouder = text => text.toUpperCase(); const repeatThreeTimes = text => text.repeat(3); const embolden = text => text.bold(); const makeLouderAndRepeatThreeTimesAndEmbolden = compose( embolden, repeatThreeTimes, makeLouder ); console.log(makeLouderAndRepeatThreeTimesAndEmbolden("Hello"));
- سپس سه تابع ساده را برای ضخیم کردن، تکرار و با حروف بزرگ نمایش دادن یک متن ورودی، تعریف کردهایم.
- اکنون با استفاده از متد compose کتابخانهی redux، این سه متد را به صورت ترکیبی، بر روی متن ورودی Hello، اعمال کردهایم.
- در آخر اگر برای مشاهدهی نتیجهی اجرای console.log بر روی آن، به کنسول توسعه دهندگان مرورگر مراجعه کنیم، به خروجی زیر خواهیم رسید:
<b>HELLOHELLOHELLO</b>
بررسی تابع createStore با یک مثال
Store در Redux، جائی است که در آن state برنامه ذخیره میشود. تابع createStore که کار ایجاد store را انجام میدهد، یک پارامتر را دریافت میکند و آنهم تابع reducer است که در قسمت قبل معرفی شد و در سادهترین حالت آن، به نحو زیر با یک متد خالی، قابل فراخوانی است:
import { createStore } from "redux"; createStore(() => {});
import { createStore } from "redux"; const reducer = (state, action) => { return state; }; const store = createStore(reducer); console.log(store);
در شیء store، چهار متد dispatch, subscribe, getState, replaceReducer مشخص هستند:
- کارکرد متد replaceReducer مشخص است؛ یک reducer جدید را به آن میدهیم و reducer قبلی را جایگزین میکند.
- متد dispatch آن مرتبط است به مبحث dispatch actions که در قسمت قبل به آن پرداختیم. هدف آن تغییر state، بر اساس یک action رسیدهاست.
- متد getState، وضعیت فعلی state را باز میگرداند.
- متد subscribe با هر تغییری در state، سبب بروز رخدادی میشود. یکی از کاربردهای آن میتواند به روز رسانی UI، بر اساس تغییرات state باشد. برای مثال کتابخانهی دیگری به نام react redux، دقیقا همین کار را به کمک متد subscribe، انجام میدهد. در این قسمت، هدف ما بررسی پشت صحنهی کتابخانههایی مانند react redux است که چه متدهایی را محصور کردهاند و دقیقا چگونه کار میکنند.
در مثال زیر، مقدار ابتدایی پارامتر state متد reducer را به یک شیء که دارای خاصیت value و مقدار 1 است، تنظیم کردهایم:
import { createStore } from "redux"; const reducer = (state = { value: 1 }, action) => { return state; }; const store = createStore(reducer); console.log(store.getState());
در کامپوننتهای React، این نوع خواص به صورت props ارسال میشوند. کل state در Redux ذخیره شده و سپس قابل دسترسی و خواندن خواهد شد.
بررسی متد dispatch یک store با مثال
در اینجا مثالی را از کاربرد متد dispatch ملاحظه میکنید که یک شیء را میپذیرد:
import { compose, createStore } from "redux"; const reducer = (state = { value: 1 }, action) => { console.log("Something happened!", action); return state; }; const store = createStore(reducer); console.log(store.getState()); store.dispatch({ type: "ADD" });
فرمت شیءای که به متد dispatch ارسال میشود، حتما باید به همراه خاصیت type باشد؛ در غیر اینصورت با صدور استثنائی، این نکته را گوشزد میکند. مقدار آن نیز یک رشتهاست که بیانگر وقوع رخدادی در برنامه میباشد؛ برای مثال کاربر درخواست دریافت اطلاعاتی را کردهاست و یا کاربر از سیستم خارج شدهاست و امثال آن.
خروجی قطعه کد فوق، به صورت زیر است:
با اجرای برنامه، یک رخداد درونی از نوع redux/INITo.2.g.b.y.i@@ صادر شدهاست که هدف آن آغاز و مقدار دهی اولیهی store است. پس از آن زمانیکه متد store.dispatch فراخوانی شدهاست، مجددا console.log داخل متد reducer فراخوانی شدهاست که اینبار به همراه type ای است که ما مشخص کردهایم.
در حالت کلی، اینکه شیء ارسالی توسط dispatch را چگونه طراحی میکنید، اختیاری است؛ اما الگوی پیشنهادی در این زمینه، redux standard actions نام دارد و به صورت زیر است که هدف از آن، یکدست شدن طراحی و پیاده سازی برنامه است:
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
- سپس به خاصیت payload، تمام دادههای مرتبط با آن اکشن، مانند نتیجهی بازگشتی از یک API، به صورت یک شیء، انتساب داده میشود.
- خاصیت meta، مرتبط با متادیتا و اطلاعات اضافی است که عموما استفاده نمیشود.
اکنون که طراحی شیء ارسالی به پارامتر action متد reducer مشخص شد، میتوان بر اساس خاصیت type آن، یک switch را طراحی کرد و نسبت به نوعهای مختلف رسیده، واکنش نشان داد:
import { createStore } from "redux"; const reducer = (state = { value: 1 }, action) => { console.log("Something happened!", action); if (action.type === "ADD") { const value = state.value; const amount = action.payload.amount; state.value = value + amount; } return state; }; const store = createStore(reducer); const firstState = store.getState(); console.log(firstState); store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} }); const secondState = store.getState(); console.log(secondState); console.log("secondState === firstState", secondState === firstState);
یک روش حل این مشکل، حذف سطر state.value = value + amount و جایگزینی آن با یک return است که شیء state جدیدی را بازگشت میدهد:
return { value: value + amount };
بررسی متد subscribe یک store با مثال
در ادامه به store همان مثال فوق، متد subscribe را نیز اضافه میکنیم و سپس دوبار، کار store.dispatch را انجام خواهیم داد:
const store = createStore(reducer); const unsubscribe = store.subscribe(() => console.log(store.getState())); store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} }); store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} }); unsubscribe();
- خروجی مستقیم متد store.subscribe نیز یک متد است که unsubscribe نام دارد و میتوان در پایان کار، برای جلوگیری از نشتیهای حافظه، آنرا فراخوانی کرد.
خروجی حاصل از console.log منتسب به متد store.subscribe، با دوبار فراخوانی متد store.dispatch پس از آن، به صورت زیر است:
{value: 3} {value: 5}
بررسی تابع combineReducers با یک مثال
اگر قرار باشد در متد reducer، صدها if action.type را تعریف کرد ... پس از مدتی از کنترل خارج میشود و غیرقابل نگهداری خواهد شد. تابع combineReducers به همین جهت طراحی شدهاست تا چندین متد مجزای reducer را با هم ترکیب کند. بنابراین میتوان در برنامه صدها متد کوچک reducer را که قابلیت توسعه و نگهداری بالایی را دارند، پیاده سازی کرد و سپس با استفاده از تابع combineReducers، آنها را یکی کرده و به متد createStore ارسال کرد. نکتهی مهم اینجا است که هرچند اینبار میتوان تعداد زیادی reducer را طراحی کرد، اما توسط تابع combineReducers، مقدار action ارسالی، از تمام این reducerها عبور داده خواهد شد. به این ترتیب اگر نیاز بود میتوان به یک action، در چندین متد مختلف reducer گوش فرا داد و بر اساس آن تصمیم گیری کرد. بنابراین بهتر است هر reducer تعریف شده در صورتیکه action.type آن با action رسیده تطابق نداشته باشد، همان state قبلی را بازگشت دهد تا بتوان زنجیرهی reducerها را تشکیل داد و بهتر مدیریت کرد.
برای نمونه اگر متد reducer فعلی را به calculatorReducer تغییر نام دهیم، روش معرفی آن توسط متد combineReducers به متد createStore به صورت زیر است:
import { combineReducers, createStore } from "redux"; // ... const calculatorReducer = (state = { value: 1 }, action) => { // ... }; const store = createStore( combineReducers({ calculator: calculatorReducer }) );
بررسی تابع bindActionCreators با یک مثال
فرض کنید میخواهیم متد dispatch را چندین بار فراخوانی کنیم:
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} }); store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
const createAddAction = amount => { return { type: "ADD", payload: { amount // = amount: amount }, meta: {} }; };
store.dispatch(createAddAction(2)); store.dispatch(createAddAction(2));
import { bindActionCreators, combineReducers, compose, createStore } from "redux"; // ... const dispatchAdd = bindActionCreators(createAddAction, store.dispatch); dispatchAdd(2); dispatchAdd(2);
میانافزارها (Middlewares) در Redux
پس از اینکه یک اکشن، به سمت reducer ارسال شد و پیش از رسیدن آن به reducer، میتوان کدهای دیگری را نیز اجرا کرد. برای مثال چون این توابع خالص هستند، نمیتوان اعمالی را داخل آنها اجرا کرد که به همراه اثرات جانبی مانند کار با یک API خارجی باشند. با استفاده از میانافزارها در این بین میتوان کدهایی را که با دنیای خارج تعامل دارند نیز اجرا کرد.
یک میانافزار در Redux، همانند سایر قسمتهای آن، تنها یک تابع سادهی جاوا اسکریپتی است:
const logger = ({ getState }) => { return next => action => { console.log( 'MIDDLEWARE', getState(), action ); const value = next(action); console.log({value}); return value; } }
در پایان کار میانافزار باید متد next آنرا فراخوانی کرد تا بتوان میانافزارهای متعددی را زنجیروار اجرا کرد.
در آخر برای معرفی آن به یک store میتوان از تابع applyMiddleware استفاده کرد:
import { applyMiddleware, bindActionCreators, combineReducers, compose, createStore } from "redux"; // ... const store = createStore( combineReducers({ calculator: calculatorReducer }), applyMiddleware(logger) );
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: state-management-redux-mobx-part02.zip