مطالب
آشنایی بیشتر با AngularJS Directive

در مطلب آشنایی با Directive‌ها در AngularJS با نحوه‌ی ایجاد Directive آشنا شدیم. هدف از این مطلب، آشنایی بیشتر با Directive در AngularJS است؛ یکی از بهترین فریم ورک‌های جاوااسکریپتی، با قابلیت ایجاد کتابخانه‌هایی از کامپوننت‌ها که می‌توانند به HTML اضافه شوند .

کتابخانه‌های جاوااسکریپتی زیادی وجود دارند. به عنوان مثال Bootstrap یکی از محبوب‌ترین "front-end framework" ها است که امکان تغییر در ظاهر المنت‌ها را فراهم می‌کند و شامل تعدادی کامپوننت جاوااسکریپتی نیز می‌باشد. مشکل کار، در هنگام استفاده از کامپوننت ها است. شخصی که در حال توسعه‌ی HTML است باید در کد جاوااسکریپتی خود از  jQuery استفاده کند و بعنوان مثال یک Popover  را فعال یا غیر فعال کند و این، یک فرآیند خسته کننده و مستعد خطا است. 


یک مثال ساده از Directives AngularJS و بررسی آن

var m = angular.module("myApp");
 
myApp.directive("myDir", function() {
  return {
  restrict: "E",   
  scope: {     
   name: "@",   
   amount: "=",  
   save: "&"    
  },
  template:    
   "<div>" +
   "  {{name}}: <input ng-model='amount' />" +
   "  <button ng-click='save()'>Save</button>" +
   "</div>",
  replace: true,   
  transclude: false, 
  controller: [ "$scope", function ($scope) { …  }],
  link: function (scope, element, attrs, controller) {…}
  }
});

به الگوی نامگذاری directive دقت کنید. پیشوند my شبیه به یک namespace است. بنابراین اگر یک Application از دایرکتیوهای قرار گرفته در Module ‌های متفاوت استفاده کند، به راحتی می‌توان محل تعریف یک directive را تشخیص داد. این نام می‌تواند نشان دهنده‌ی این باشد که این directive را خودتان توسعه داده‌اید یا از یک directive توسعه داده شده توسط شخص دیگری در حال استفاده هستید. به هر حال این نحوه‌ی نام گذاری یک اجبار نیست و به عنوان یک پیشنهاد است.

سازنده directive یک شیء را با تعدادی خاصیت باز می‌گرداند که تمامی آنها در سایت AngularJS توضیح داده شده‌اند. در اینجا قصد داریم تا توضیحی مختصر در مورد کاری که این خصوصیات انجام می‌دهند داشته باشیم.

· restrict : تشخیص می‌دهد که آیا directive در HTML استفاده خواهد شد. گزینه‌های قابل استفاده ‘A’ ،  ‘E’ ، ‘C’ برای attribute ، element ، class و یا comment است . پیش فرض ‘A’ برای attribute است. اما ما بیشتر علاقه به استفاده از ویژگی element برای ایجاد المنت‌های UI داریم.

· scope : ایجاد یک scope ایزوله که متعلق به directive است و موجب ایزوله شدن آن از scope صدا زننده directive می‌شود. متغیرهای scope پدر از طریق خصوصیات تگ directive ارسال می‌شوند. این ایزوله کردن زمانی کاربردی است که در حال ایجاد کامپوننت هایی با قابلیت استفاده مجدد هستیم، که نباید متکی به scope پدر باشند. شیء scope در directive نام و نوع متغیرهای scope را تعیین می‌کنند. در مثال بالا سه متغیر برای scope تعریف شده است:

-   name: "@" (by value, one-way) : علامت @ مشخص می‌کند که مقدار متغیر ارسال می‌شود. Directive یک رشته را دریافت می‌کند که شامل مقدار ارسال شده از scope پدر می‌باشد. Directive می‌تواند از آن استفاده کند، اما نمی‌تواند مقدار آن را در scope پدر تغییر دهد.

-   amount: "=" (by reference, two-way) : علامت = مشخص می‌کند این متغیر با ارجاع ارسال می‌شود. Directive یک ارجاع به مقدار متغیر در scope اصلی دریافت می‌کند. مقدار می‌تواند هر نوع داده ای، شامل یک شیء complex یا یک آرایه باشد. Directive می‌تواند مقدار را در scope پدر تغییر دهد. این نوع متغیر، زمانیکه نیاز باشد directive مقدار را در scope پدر تغییر دهد، استفاده می‌شود.

-   save: "&" (expression) : علامت & مشخص می‌کند این متغیر یک expression را که در scope پدر اجرا می‌شود، نگهداری می‌کند. اکنون directive قابلیت انجام کارهایی فراتر از تغییر یک مقدار را دارد. به عنوان مثال می‌توان یک تابع را از scope پدر فراخوانی و نتیجه‌ی اجرا را دریافت کرد.

· template :  الگوی رشته ای که جایگزین المنت تعریف شده می‌شود. فرآیند جایگزینی تمامی خصوصیات را از المنت قدیمی به المنت جدید انتقال می‌دهد. به نحوه استفاده از متغیر‌های تعریف شده در scope ایزوله دقت کنید. این مورد به شما امکان تعریف directive های macro-style را می‌دهد که نیاز به کد اضافه‌ای، ندارند. اگرچه در بیشتر موارد الگو  یک تگ ساده <div> است که از کد‌های link که در زیر توضیح داده شده است استفاده می‌کند.

· replace : تعیین می‌کند که آیا الگوی directive باید جایگزین المنت شود. مقدار پیش فرض false است.

· transclude : تعیین کننده این است که محتوای directive باید در المنت کپی شود یا خیر. در مثال زیر المنت tab شامل المنت‌های HTML دیگر است پس transclude برابر true است.  

<body ng-app="components">
  <h3>BootStrap Tab Component</h3>
    <tabs>
       <pane title="First Tab">
          <div>This is the content of the first tab.</div>
       </pane>
       <pane title="Second Tab">
          <div>This is the content of the second tab.</div>
       </pane>
    </tabs>
</body>
 

· link : این تابع بیشتر منطق directive را شامل می‌شود. Link وظیفه دستکاری DOM ، ایجاد event listener ‌ها و... را دارد. تابع Link پارامترهای زیر را دریافت می‌کند:

-   scope : ارجاع به scope ایزوله شده directive دارد.

-   element : ارجاع به المنت‌های DOM که directive را تعریف کرده اند. تابع link معمولا برای دستکاری المنت از jQuery استفاده می‌کند. (یا از Angular's jqLite در صورتی که jQuery بارگذاری نشده باشد)

-   controller : در مواقعی که از دایرکتیو‌های تو در تو استفاده می‌شود کاربرد دارد. این پارامتر یک directive فرزند با ارجاعی به پدر را فراهم می‌کند، بنابراین موجب ارتباط  directive ‌ها می‌شود.

به عنوان مثال،  این directive  که پیاده سازی bootstrap tab را انجام داده است، می‌توانید مشاهده نمایید.

موفق باشید

اشتراک‌ها
انتشار Node.js Tools در GitHub
With the start of a new season of Game of Thrones, folks are sitting on the edge of their seats waiting to see what’s coming. Unlike the Khaleesi who seems to be going nowhere fast, Node.js Tools for Visual Studio (NTVS) has made some moves recently. Over the last month, we released Node.js Tools 1.0 for Visual Studio, joined the vibrant open source community on GitHub , and created VM images so anyone can quickly get started with NTVS.
انتشار Node.js Tools در GitHub
مطالب
تنظیمات مورد نیاز جهت شروع به کار با C# 9.0
ویژگی‌های جدید C# 9.0، به همراه NET 5. ارائه می‌شوند. بنابراین جهت راه اندازی پروژه‌ای که قرار است بر این مبنا تهیه شود، نیاز است مراحل زیر را طی کنید:
- آخرین نگارش NET 5 SDK. را از اینجا دریافت و نصب کنید (حتما SDK باشد و نه runtime).
- اگر می‌خواهید با ویژوال استودیو کار کنید، نیاز است حداقل نگارش 16.7 یا بالاتر را نصب کرده باشید. به همین جهت پیش از ادامه‌ی بحث، از منوی Help، گزینه‌ی Check For Updates را انتخاب کرده و حتما آخرین به روز رسانی‌های موجود را نصب کنید. بنابراین برای کار با C# 9.0 توسط VS، حتما باید آخرین نگارش 2019 آن، به همراه تمام به روز رسانی‌های ممکن بر روی سیستم شما نصب باشند؛ در غیراینصورت امکان کار با آن‌را حداقل توسط VS نخواهید داشت.
- در زمان نگارش این مطلب چون هنوز نگارش نهایی NET 5 SDK. ارائه نشده‌است، نیاز است به منوی Tools در ویژوال استودیو مراجعه کرده و با انتخاب «Preview Features» آن، گزینه‌ی «Use previews of the .NET Core SDK» را فعال کنید. پس از آن، یکبار هم نیاز است VS را بسته و راه اندازی مجدد نمائید.

پس از نصب پیشنیازهای لازم، اکنون فایل csproj را به صورت زیر ویرایش کنید:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <LangVersion>9.0</LangVersion>
  </PropertyGroup>
</Project>
در اینجا TargetFramework حتما باید net5.0 باشد و اگر هنوز نگارش نهایی SDK آن در دسترس نیست، تنظیم گزینه‌ی LangVersion هم ضروری است. پس از ارائه‌ی نگارش نهایی SDK، می‌توان ویژگی LangVersion را حذف کرد؛ چون به صورت خودکار به آخرین نگارش موجود تنظیم می‌شود.


تنظیمات مورد نیاز C# 9.0 در پروژه‌های کتابخانه‌ای

در این نوع پروژه‌ها، علاوه بر نصب پیشنیازهای یاد شده، نیاز است TargetFramework را به حداقل netstandard2.1 تنظیم کرد (و یا حتی net5.0 هم در اینجا کار می‌کند):
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>9</LangVersion>


تنظیمات مورد نیاز C# 9.0 در VSCode

برای کار با VSCode، تنها کافی است آخرین نگارش SDK و آخرین نگارش افزونه‌ی #C آن‌را نصب کنید و TargetFramework و LangVersion را همانطور که عنوان شد، تنظیم نمائید. این روش ساده‌ترین و کم حجم‌ترین روش کار با C# 9.0 است.


تنظیمات مورد نیاز C# 9.0 در Rider

اگر می‌خواهید برای کار با C# 9.0 از محصولات Jetbrains استفاده کنید، نیاز است حداقل نگارش 2020.3 آن‌ها را نصب کنید که در این زمان هنوز در مرحله‌ی پیش‌نمایش (ReSharper 2020.3 EAP or Rider 2020.3 EAP) به سر می‌برند.


من از چه روشی استفاده می‌کنم؟!

VS کامل بر روی سیستم من نصب نیست؛ هیچ نگارشی از آن! عمده‌ی کارهای من توسط VS Code و افزونه‌ی #C آن انجام می‌شوند و هر از چندگاهی، یکبار هم توسط Rider چون به همراه ReSharper توکار است، کنترل کیفیت می‌شوند (در حد بررسی گزارش‌های ReSharper آن). البته افزونه‌ی Roslynator برای VS Code هم موجود است و بسیاری از قابلیت‌های ReSharper را در VSCode نیز مهیا می‌کند.
اشتراک‌ها
اعتبارسنجی JSON با JSON-Schema

When you’re dealing with complex and structured data, you need to determine whether the data is valid or not. JSON-Schema is the standard of JSON documents that describes the structure and the requirements of your JSON data. In this two-part series, you’ll learn how to use JSON-Schema to validate data. 

اعتبارسنجی JSON با JSON-Schema
مسیرراه‌ها
کتابخانه ReactJS
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت سوم - روش اتصال Redux به برنامه‌های React
پس از بررسی ساختار کتابخانه‌ی 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
به علاوه در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.


معرفی ساختار ابتدایی برنامه

برنامه‌ای را که در این قسمت بررسی می‌کنیم، ساختار بسیار ساده‌ای را داشته و به همراه دو دکمه‌ی افزایش و کاهش مقدار یک شمارشگر است؛ به همراه دکمه‌ی برای به حالت اول در آوردن آن. هدف اصلی دنبال شده‌ی در اینجا نیز نحوه‌ی برپایی 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;
سپس المان آن‌را جهت نمایش در برنامه، به فایل src\App.js اضافه می‌کنیم:
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: {}
}
به همین جهت برای نظم بخشیدن به تعریف این نوع اشیاء و یک‌دست سازی آن‌ها، فایل جدید src\actions\index.js را ایجاد کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
import * as types from "../constants/ActionTypes";

export const incrementValue = () => ({ type: types.Increment });
همانطور که ملاحظه می‌کنید در این متد، فعلا فقط نام رشته‌ای نوع این اکشن، بیشتر مدنظر است تا بر اساس action.type رسیده در reducer متناظر با آن، عملی رخ دهد. بنابراین فقط قسمت type آن‌را مقدار دهی کرده‌ایم. مقدار ثابت رشته‌ای types.Increment نیز از فایل مجزای src\constants\ActionTypes.js که پیشتر تعریف کردیم، تامین شده‌است.


ایجاد تابع 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;
}
- اگر دقت کرده باشید، کامپوننت شمارشگر این قسمت، دارای state نیست و همچنین نمی‌خواهیم هم که دارای state باشد؛ چون قرار است توسط redux مدیریت شود. به همین جهت state اولیه را به صورت initialState که محتوای یک شیء با خاصیت count با مقدار اولیه‌ی صفر است، خارج از کلاس کامپوننت، ایجاد کرده‌ایم.
- سپس می‌خواهیم رویداد کلیک بر روی دکمه + را مدیریت کنیم. به همین جهت نیاز به یک اکشن جدید به نام 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;
کار این فایل، مدیریت مرکزی تمام reducerهای سفارشی تعریف شده‌ی در برنامه‌است. لیست آن‌ها را به متد combineReducers ارسال کرده و در نهایت یک 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__()
);

//...
نکته 1: چون شیء rootReducer در فایل src\reducers\index.js واقع شده‌است، دیگر در حین import، نیازی به ذکر نام فایل index آن نیست.
نکته 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")
);
کامپوننت Provider، از طریق props خود نیاز به دریافت store تعریف شده را دارد. به این ترتیب هر کامپوننتی که در درخت کامپوننت‌های App قرار می‌گیرد، می‌تواند با redux store کار کند.


تامین 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);
ابتدا متد connect را از react-redux دریافت می‌کنیم. connect خود متدی است که منتظر یک کامپوننت React است؛ مانند Counter. همچنین به عنوان پارامتر، توابعی را دریافت می‌کند که اطلاعات redux store را به کامپوننت، نگاشت می‌کنند؛ مانند props و actions. در اینجا دو تابع نگاشت state به props و همچنین dispatch به props را ملاحظه می‌کنید (توابع mapStateToProps و mapDispatchToProps)؛ هرچند الزامی نیست، ولی بهتر است از همین روش نامگذاری استفاده شود.

زمانیکه در مورد store در redux صحبت می‌شود، داخل آن یک شیء بزرگ state قرار گرفته‌است که حاوی کل state برنامه‌است. اما شاید هر کامپوننت به تمام آن نیازی نداشته باشد. برای مثال شاید کامپوننت شمارشگر، اهمیتی به اطلاعات خطاهای سیستم و یا کاربر وارد شده‌ی به سیستم که در شیء کلی state موجود در store وجود دارند، ندهد. به همین جهت متد mapStateToProps، کل state برنامه را دریافت کرده و به ما اجازه می‌دهد تا تنها اطلاعاتی را که از آن نیاز داریم، به صورت props دریافت کنیم. به این ترتیب از رندر مجدد این کامپوننت نیز جلوگیری خواهد شد؛ چون این کامپوننت دیگر وابسته‌ی به تغییرات سایر اجزای کل state برنامه، نخواهد بود و اگر آن‌ها تغییر کردند، این کامپوننت رندر مجدد نخواهد شد.
بنابراین می‌توان متد mapStateToProps را به صورت کلی زیر نیز تعریف کرد:
const mapStateToProps = (state) => { return state };
هرچند این روش در مثال ما بدون مشکل کار می‌کند، اما چون کل state را دریافت می‌کند، مشکل رندر مجدد کامپوننت را به ازای هر تغییری در 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);
این شیء حاصل، به خودی خود، سبب بروز تغییری در کامپوننت شمارشگر نمی‌شود. بلکه یک کامپوننت دربرگیرنده‌ی کامپوننت Counter را ایجاد می‌کند (به همین جهت آن‌را در پوشه‌ی containers یا دربرگیرنده‌ها قرار دادیم). بنابراین برای استفاده‌ی از آن، به کامپوننت src\App.js مراجعه کرده و جائیکه المان کامپوننت Counter قبلی درج شده، آن‌را به صورت زیر تغییر می‌دهیم:
import "./App.css";

import React from "react";

import CounterContainer from "./containers/Counter";

function App() {
  return (
    <main className="container">
      <CounterContainer />
    </main>
  );
}

export default App;
ابتدا کامپوننت جدید CounterContainer را که تبادل اطلاعات بین کامپوننت Counter اصلی و state و action مخزن redux را برقرار می‌کند، import کرده و سپس المان جدید آن‌را جایگزین المان کامپوننت شمارشگر اصلی می‌کنیم.

اکنون کامپوننت شمارشگر 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.pros این کامپوننت که اکنون توسط دربرگیرنده‌ی آن ارائه می‌شود، به صورت زیر است:


به همین جهت، خاصیت تو در توی this.props.counterReducer.count و همچنین اشاره‌گر به متد increment، توسط Object Destructuring به صورت زیر از this.props دریافتی، تجزیه شده‌اند:
    const {
      counterReducer: { count },
      increment
    } = this.props;
سپس مقدار count، توسط span نمایش داده و همچنین دکمه +  را به صورت onClick={increment} تکمیل کرده‌ایم تا با کلیک بر روی آن، متد increment که در حقیقت معادل فراخوانی store.dispatch(incrementValue()) است، اجرا شود. حاصل آن، افزایش مقدار شمارشگر است:


جزئیات کار با Redux store را نیز می‌توان در افزونه‌ی redux dev tools مشاهده کرد:


این افزونه در نوار ابزار پایین آن، امکان export کل state و سپس import و بازیابی آن‌را نیز به همراه دارد.


دریافت props از طریق کامپوننت دربرگیرنده و ارسال آن به کامپوننت اصلی

فرض کنید نیاز باشد تا اطلاعاتی را به صورت متداول React از طریق props، به کامپوننت دربرگیرنده‌ی کامپوننت شمارشگر ارسال کرد:
function App() {
  const prop1 = 123
  return (
    <main className="container">
      <CounterContainer prop1={prop1} />
    </main>
  );
}
برای دسترسی به آن، پارامتر دومی به متد mapStateToProps به نام ownProps اضافه می‌شود که حاوی props ارسالی به کامپوننت container است:
const mapStateToProps = (state, ownProps) => {
  console.log("mapStateToProps", { state, ownProps });
  return state;
};
در این حالت اگر نیاز به انتقال آن به کامپوننت اصلی بود، می‌توان شیء بازگشت داده شده‌ی از mapStateToProps را به همراه یک سری خواص سفارشی دریافتی از ownProps، تعریف کرد.


پیاده سازی دکمه‌ی کاهش مقدار شمارشگر

پس از آشنایی با روش کلی برقراری اتصالات سیستم react-redux، پیاده سازی دکمه‌ی کاهش مقدار شمارشگر بسیار ساده‌است و شامل مراحل زیر است:
1)  ایجاد نام نوع اکشن متناظر با دکمه‌ی کاهش مقدار
به فایل src\constants\ActionTypes.js، نوع جدید کاهشی را اضافه می‌کنیم:
export const Decrement = "Decrement";
2) ایجاد متد Action Creator
در فایل src\actions\index.js، متد ایجاد کننده‌ی شیء اکشن ارسالی به reducer متناظری را تعریف می‌کنیم تا بتوان بر اساس نوع آن در reducer کاهشی، منطق کاهش را پیاده سازی کرد:
export const decrementValue = () => ({ type: types.Decrement });
3) ایجاد تابع reducer مخصوص کاهش مقدار
اکنون در فایل src\reducers\counter.js، بر اساس نوع شیء رسیده، تصمیم به کاهش یا افزایش مقدار موجود در state گرفته می‌شود:
export default function counterReducer(state = initialState, action) {

  // ...

  if (action.type === types.Decrement) {
    return {
      count: state.count - 1
    };
  }

  return state;
}
4) تامین state کامپوننت شمارشگر از طریق props
در ادامه نیاز است بتوان اکشن کاهش را به این reducer ارسال کرد. به همین جهت به کامپوننت دربرگیرنده‌ی کامپوننت شمارشگر در فایل src\containers\Counter.js مراجعه کرده و به شیء خروجی متد mapDispatchToProps، متد کاهش را اضافه می‌کنیم:
import { decrementValue, incrementValue } from "../actions";
// ...

const mapDispatchToProps = dispatch => {
  return {
    // ...
    decrement() {
      dispatch(decrementValue());
    }
  };
};
5) استفاده از نتایج دریافتی از props
در آخر به فایل src\components\counter.jsx مراجعه کرده و اشاره‌گر به متد decrement را از طریق this.props دریافت می‌کنیم:
const {
      // ...
      decrement
    } = this.props;
 سپس آن‌را به onClick دکمه‌ی کاهش، انتساب خواهیم داد:
<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());
    }
  };
};
می‌توان با استفاده از تابع bindActionCreators که در قسمت قبل در مورد آن بحث شد، تعریف آن‌را به صورت زیر خلاصه کرد:
import { bindActionCreators } from "redux";

// ...

const mapDispatchToProps = dispatch => {
  return bindActionCreators(
    {
      incrementValue,
      decrementValue
    },
    dispatch
  );
};
با استفاده از تابع bindActionCreators کتابخانه‌ی redux، می‌توان تمام action creators واقع در فایل src\actions\index.js را به صورت یک شیء به آن ارسال کرد و پارامتر دوم آن‌را نیز به store.dispatch یا در اینجا به همان dispatch دریافتی توسط پارامتر dispatch متد mapDispatchToProps، تنظیم کرد. البته در این حالت props دریافتی در کامپوننت شمارشگر به صورت زیر تغییر می‌کنند:


به همین جهت نیاز است در متد رندر کامپوننت src\components\counter.jsx، نام‌هایی را که به متدهای action creator اشاره می‌کنند، به صورت زیر تغییر داد:
const {
      counterReducer: { count },
      incrementValue,
      decrementValue
    } = this.props;
و همچنین نام‌های منتسب به onClickها را نیز بر این اساس، اصلاح کرد.

روش دوم: در نگارش‌های اخیر react-redux می‌توان متد mapDispatchToProps را به صورت زیر نیز خلاصه و تعریف کرد که بسیار ساده‌تر است:
const mapDispatchToProps = {
  incrementValue,
  decrementValue
};
البته در این حالت نیز مابقی آن که شامل تغییر نام‌ها می‌شود، یکسان است.

همچنین بجای بازگشت کل state در متد mapStateToProps، می‌توان تنها خواص مدنظر را بازگشت داد:
const mapStateToProps = state => {
  //return state;
  return {
    count: state.counterReducer.count
  };
};
در این حالت props ارسالی به کامپوننت یک چنین شکلی را پیدا می‌کنند:


بنابراین باید در متد رندر کامپوننت شمارشگر، خاصیت count را به صورت معمولی دریافت کرد:
const {
      //counterReducer: { count },
      count,
      incrementValue,
      decrementValue
    } = this.props;

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: state-management-redux-mobx-part03.zip
مطالب
React 16x - قسمت 25 - ارتباط با سرور - بخش 4 - یک تمرین
همان مثال backend قسمت 22 را با افزودن وب سرویس‌هایی برای قسمت‌های نمایش لیست فیلم‌ها، ژانرها و سایر صفحات اضافه شده‌ی به برنامه‌ی تکمیل شده‌ی تا قسمت 21، توسعه می‌دهیم. کدهای کامل آن، به علت شباهت و یکی بودن نکات آن با مطلب 22، در اینجا تکرار نخواهند شد و می‌توانید کل پروژه‌ی آن‌را از پیوست انتهای بحث دریافت کنید. سپس فایل dotnet_run.bat آن‌را اجرا کنید تا در آدرس https://localhost:5001 قابل دسترسی شود.


افزودن سرویس httpService.js به برنامه

تا این قسمت، تمام اطلاعات نمایش داده شده‌ی در لیست فیلم‌ها، از سرویس درون حافظه‌ای src\services\fakeMovieService.js و لیست ژانرها از سرویس src\services\fakeGenreService.js، تامین می‌شوند. اکنون در ادامه می‌خواهیم این سرویس‌ها را با سرویس backend یاد شده، جایگزین کنیم تا این برنامه، اطلاعات خودش را از سرور دریافت کند. به همین جهت قبل از هر کاری، سرویس عمومی src\services\httpService.js را که در قسمت قبل توسعه دادیم، به برنامه‌ی نمایش لیست فیلم‌ها نیز اضافه می‌کنیم (فایل آن‌را از پروژه‌ی قبلی کپی کرده و در اینجا paste می‌کنیم)، تا بتوانیم از امکانات آن در اینجا نیز استفاده کنیم. فایل httpService.js، دارای وابستگی‌های خارجی react-toastify و axios است. به همین جهت برای افزودن آن‌ها مراحل زیر را طی می‌کنیم:
- نصب کتابخانه‌های react-toastify و axios از طریق خط فرمان (با فشردن دکمه‌های ctrl+back-tick در VSCode):
> npm i axios --save
> npm i react-toastify --save
سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />


دریافت اطلاعات لیست نمایش ژانرها از سرویس backend

با فراخوانی آدرس https://localhost:5001/api/Genres، می‌توان لیست ژانرهای سینمایی تعریف شده‌ی در سرویس‌های backend را مشاهده کرد. اکنون قصد داریم از این اطلاعات، در برنامه استفاده کنیم. به همین جهت به فایل src\components\movies.jsx مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
چون نمی‌خواهیم تغییراتی بسیار اساسی را در اینجا اعمال کنیم، قدم به قدم عمل کرده و سرویس قبلی fakeGenreService.js را با یک سرویس جدید که اطلاعات خودش را از سرور دریافت می‌کند، جایگزین می‌کنیم. بنابراین ابتدا فایل جدید src\services\genreService.js را ایجاد می‌کنیم. سپس آن‌را طوری تکمیل خواهیم کرد که اینترفیس آن، با اینترفیس fakeGenreService قبلی یکی باشد:
import { apiUrl } from "../config.json";
import http from "./httpService";

export function getGenres() {
  return http.get(apiUrl + "/genres");
}
همچنین در اینجا import وابستگی config.json را نیز مشاهده می‌کنید که در قسمت قبل در مورد آن توضیح دادیم. به همین جهت برای تمیزتر شدن قسمت‌های مختلف برنامه، فایل config.json را در مسیر src\config.json ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
apiUrl به ریشه‌ی URLهای ارائه شده‌ی توسط backend service ما، اشاره می‌کند.

پس از تکمیل سرویس جدید src\services\genreService.js، به فایل src\components\movies.jsx بازگشته و سطر قبلی
import { getGenres } from "../services/fakeGenreService";
را با سطر جدید زیر، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، خطای زیر را مشاهده خواهید کرد:
Uncaught TypeError: Object is not a function or its return value is not iterable
علت اینجا است که سرویس قبلی fakeGenreService، دارای متد export شده‌ای به نام getGenres بود که یک آرایه‌ی معمولی را بازگشت می‌داد. اکنون این سرویس جدید نیز همان ساختار را دارد، اما اینبار یک Promise را بازگشت می‌دهد. به همین جهت متد componentDidMount را به صورت زیر اصلاح می‌کنیم:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ _id: "", name: "All Genres" }, ...data];
    this.setState({ movies: getMovies(), genres });
  }
متد getGenres باید await شود تا نتیجه‌ی آن توسط خاصیت data شیء بازگشتی از آن، قابل دسترسی شود. با این تغییر، نیاز است این متد را نیز به صورت async معرفی کرد.


دریافت اطلاعات لیست فیلم‌ها از سرویس backend

پس از دریافت لیست ژانرهای سینمایی از سرور، اکنون نوبت به جایگزینی src\services\fakeMovieService.js با یک نمونه‌ی متصل به backend است. به همین جهت ابتدا فایل جدید src\services\movieService.js را ایجاد کرده و سپس آن‌را به صورت زیر تکمیل می‌کنیم:
import { apiUrl } from "../config.json";
import http from "./httpService";

const apiEndpoint = apiUrl + "/movies";

function movieUrl(id) {
  return `${apiEndpoint}/${id}`;
}

export function getMovies() {
  return http.get(apiEndpoint);
}

export function getMovie(movieId) {
  return http.get(movieUrl(movieId));
}

export function saveMovie(movie) {
  if (movie.id) {
    return http.put(movieUrl(movie.id), movie);
  }

  return http.post(apiEndpoint, movie);
}

export function deleteMovie(movieId) {
  return http.delete(movieUrl(movieId));
}
سپس شروع به اصلاح کامپوننت movies می‌کنیم.
ابتدا دو متد دریافت لیست فیلم‌ها و حذف یک فیلم را که در این کامپوننت استفاده شده‌اند، import می‌کنیم:
import { getMovies, deleteMovie } from "../services/movieService";
بعد متد getMovies پیشین، که یک آرایه را بازگشت می‌داد، توسط متد جدیدی که یک Promise را بازگشت می‌دهد، جایگزین می‌شود:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ id: "", name: "All Genres" }, ...data];

    const { data: movies } = await getMovies();
    this.setState({ movies, genres });
  }
همچنین مدیریت حذف رکوردها را نیز به صورت زیر با پیاده سازی «به‌روز رسانی خوشبینانه UI» که در قسمت قبل در مورد آن بیشتر بحث شد، تغییر می‌دهیم. در این حالت فرض بر این است که به احتمال زیاد،  await deleteMovie با موفقیت به پایان می‌رسد. بنابراین بهتر است UI را ابتدا به روز رسانی کنیم تا کاربر حس کار کردن با یک برنامه‌ی سریع را داشته باشد:
  handleDelete = async movie => {
    const originalMovies = this.state.movies;

    const movies = originalMovies.filter(m => m.id !== movie.id);
    this.setState({ movies });

    try {
      await deleteMovie(movie.id);
    } catch (ex) {
      if (ex.response && ex.response.status === 404) {
        console.log(ex);
        toast.error("This movie has already been deleted.");
      }

      this.setState({ movies: originalMovies }); //undo changes
    }
  };
ابتدا ارجاعی را از state قبلی ذخیره می‌کنیم تا در صوت بروز خطایی در سطر await deleteMovie، بتوانیم مجددا state را به حالت اول آن بازگردانیم. به همین منظور پیاده سازی «به‌روز رسانی خوشبینانه UI»، حتما نیاز به درج صریح try/catch را دارد. برای نمایش خطاهای ویژه‌ی 404 نیز از یک toast استفاده شده که نیاز به import زیر را دارد:
import { toast } from "react-toastify";
سایر خطاهای رخ داده، توسط interceptor درج شده‌ی در سرویس http، به صورت خودکار پردازش می‌شوند.


اتصال فرم ثبت و ویرایش یک فیلم به backend server

تا اینجا اگر برنامه را اجرا کنیم، با کلیک بر روی لینک هر فیلم نمایش داده شده‌ی در صفحه، به صفحه‌ی not-found هدایت می‌شویم. برای رفع این مشکل، به فایل src\components\movieForm.jsx مراجعه کرده و ابتدا
import { getGenres } from "../services/fakeGenreService";
import { getMovie, saveMovie } from "../services/fakeMovieService";
قبلی را با نمونه‌ها‌ی جدیدی که با سرور کار می‌کنند، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
import { getMovie, saveMovie } from "../services/movieService";
سپس ارجاعات به این سه متد import شده را با await، همراه کرده و متد اصلی را به صورت async معرفی می‌کنیم:
  async componentDidMount() {
    const { data: genres } = await getGenres();
    this.setState({ genres });

    const movieId = this.props.match.params.id;
    if (movieId === "new") return;

    const { data: movie } = await getMovie(movieId);
    if (!movie) return this.props.history.replace("/not-found");

    this.setState({ data: this.mapToViewModel(movie) });
  }
البته می‌توان جهت بهبود کیفیت کدها، از متد componentDidMount، دو متد با مسئولیت‌های مجزای دریافت ژانرها (populateGenres) و سپس نمایش فرم اطلاعات فیلم (populateMovie) را استخراج کرد:
  async populateGenres() {
    const { data: genres } = await getGenres();
    this.setState({ genres });
  }

  async populateMovie() {
    try {
      const movieId = this.props.match.params.id;
      if (movieId === "new") return;

      const { data: movie } = await getMovie(movieId);
      this.setState({ data: this.mapToViewModel(movie) });
    } catch (ex) {
      if (ex.response && ex.response.status === 404)
        this.props.history.replace("/not-found");
    }
  }

  async componentDidMount() {
    await this.populateGenres();
    await this.populateMovie();
  }
در متد populateMovie، اگر movieId اشتباهی وارد شود و یا کلا عملیات دریافت اطلاعات، با شکست مواجه شود، کاربر را به صفحه‌ی not-found هدایت می‌کنیم. یعنی وجود try/catch در اینجا ضروری است. چون اگر movieId اشتباهی وارد شود، اینبار دیگر خطوط بعدی اجرا نمی‌شوند و در همان سطر await getMovie، یک استثناء صادر شده و کار خاتمه پیدا می‌کند. بنابراین نیاز داریم بتوانیم این استثنای احتمالی را مدیریت کرده و کاربر را به صفحه‌ی not-found هدایت کنیم.
پیشتر زمانیکه متد getMovie، یک شیء ساده را از fake service، بازگشت می‌داد، چنین مشکلی را نداشتیم؛ به همین جهت در سطر بعدی آن، هدایت کاربر در صورت نال بودن نتیجه، با یک return صورت می‌گرفت. اما اینجا بجای نال، یک استثناء را ممکن است دریافت کنیم.

مرحله‌ی آخر اصلاح این فرم، اتصال قسمت ثبت اطلاعات آن است که با قرار دادن یک await، پیش از متد saveMovie و async کردن متد آن، انجام می‌شود:
  doSubmit = async () => {
    await saveMovie(this.state.data);

    this.props.history.push("/movies");
  };


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-25-backend.zip و sample-25-frontend.zip
اشتراک‌ها
تعریف محرمانگی اطلاعات

The need to maintain information privacy is applicable to collected personal information, such as medical records, financial data, criminal records, political records, business related information or website data 

تعریف محرمانگی اطلاعات