برپایی پروژهی ایجاد اولین کامپوننت React
در اینجا برای بررسی مقدماتی کامپوننتها، یک پروژهی جدید React را ایجاد میکنیم.
> create-react-app sample-04
> cd sample-04
> npm start
اکنون در ادامه اولین کاری را که انجام میدهیم، نصب
توئیتر بوت استرپ 4 است تا بتوانیم توسط امکانات آن، ظاهر بهتری را برای برنامهی تهیه شده تدارک ببینیم. برای این منظور پس از باز کردن پوشهی اصلی برنامه توسط VSCode، دکمههای `+ctrl را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
این دستور علاوه بر نصب بوت استرپ 4.3.1 (آخرین نگارش موجود در زمان نگارش این مطلب)، به دلیل ذکر سوئیچ save، مدخل آنرا نیز به فایل package.json برنامه اضافه میکند.
پس از اجرای این دستور، ممکن است پیامهای اخطاری مانند «requires a peer of jquery@1.9.1 - 3 but none is installed» را نیز مشاهده کنید که مهم نیستند. چون در اینجا صرفا از امکانات CSS ای بوت استرپ استفاده خواهیم کرد و کاری با jQuery نداریم. محل نصب آن نیز پوشهی node_modules\bootstrap برنامه است.
سپس برای افزودن فایل bootstrap.css به پروژهی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام میدهد، مورد استفاده قرار میگیرد.
ایجاد اولین کامپوننت React
در پوشهی src برنامه، پوشهی جدیدی را به نام components ایجاد میکنیم و تمام کامپوننتهای خود را در آن قرار خواهیم داد. سپس داخل این پوشه، یک فایل جدید و خالی را به نام counter.jsx ایجاد میکنیم. پسوند این فایل jsx است و نام فایلهای کامپوننتها را نیز camel case وارد میکنیم؛ یعنی اولین حرف اولین واژهی وارد شده، با حروف کوچک و تمام واژههای پس از آن با حروف بزرگ شروع خواهند شد مانند coolApp. مزیت استفادهی از پسوند jsx نسبت به js، فراهم شدن امکانات مخصوص React در VSCode است.
در ابتدای فایل counter.jsx، نیاز است وابستگیهای React را import کنیم. اگر از
قسمت اول بخاطر داشته باشید، «
simple react snippets» را نیز در VSCode نصب کردیم. به کمک آن میتواند این نوع importها را سادهتر وارد کرد. برای این منظور imrc را تایپ کرده و سپس دکمهی tab را فشار دهید. به این ترتیب یک سطر زیر به صورت خودکار تولید میشود:
import React, { Component } from 'react';
پس از این سطر، cc را تایپ کرده و سپس دکمهی tab را فشار دهید تا ساختار کلاس یک کامپوننت React تولید شود. همان لحظهای که این ساختار تولید میشود، اگر دقت کنید دو کرسر در صفحه ظاهر شدهاند که با تایپ نام کامپوننت، نام کلاس و نام export آنرا تکمیل میکنند. نام این کامپوننت را Counter که با حروف بزرگ شروع میشود، وارد میکنیم. اکنون کدهای آن را به نحو زیر ویرایش میکنیم:
import React, { Component } from "react";
class Counter extends Component {
render() {
return <h1>Hello world!</h1>;
}
}
export default Counter;
مفهوم ساختار یک چنین کلاس و export ای را در
قسمت قبل با معرفی کلاسها، ارث بری، ماژولها و همچنین exportهای آنها بررسی کردیم. البته در قسمت قبل، export default class را مشاهده کردید و در اینجا بجای آن، سطر آخر این ماژول به export default ختم شدهاست که روش دیگری برای تعریف این export است.
خروجی متد render در اینجا، یک رشتهی معمولی نیست؛ بلکه یک عبارت jsx است که در قسمت اول معرفی شد. این عبارت در نهایت توسط کامپایلر Babel به معادل React.createElement ترجمه میشود. به همین جهت نیاز است تا import React را در ابتدای این ماژول درج کرد؛ هرچند به ظاهر به صورت مستقیم از آن استفاده نمیکنیم.
تا اینجا این کامپوننت در UI برنامه نمایش داده نمیشود. به همین جهت به فایل index.js مراجعه کرده و آنرا به صورت زیر تغییر میدهیم:
- ابتدا نیاز است تا شیء Counter را در اینجا import کنیم و چون خروجی پیشفرض است، نیازی به ذکر {} برای معرفی آن نیست:
import Counter from "./components/counter";
- سپس در سطر ReactDOM.render، بجای رندر کامپوننت App، کامپوننت Counter را ذکر میکنیم:
ReactDOM.render(<Counter />, document.getElementById("root"));
اکنون برنامه هر زمانیکه به المان جدید Counter برسد، بجای آن به متد render کامپوننت متناظر مراجعه کرده و خروجی آنرا رندر میکند. پس از این تغییر اگر به مرورگر مراجعه کنید، خروجی hello world را مشاهده خواهید کرد.
درج چند عنصر در عبارات JSX
میخواهیم در کامپوننت Counter، یک دکمه را نیز نمایش دهیم. برای انجام اینکار، به نحو زیر عمل میکنیم:
render() {
return <h1>Hello world!</h1><button>Increment</button>;
}
در این حالت هم در VSCode و هم در کنسول توسعه دهندگان مرورگر، خطای «JSX expressions must have one parent element» ظاهر میشود.
عبارات JSX در نهایت باید تبدیل به متد React.createElement شوند. اولین پارامتر این متد، نوع المانی است که قرار است ایجاد شود که در اینجا h1 است. اما در اینجا دو المان را داریم. در این حالت Babel نمیداند که چگونه باید یک چنین عبارتی را به React.createElement ترجمه کند. یک راه حل این است که کل این عبارت را داخل یک div قرار داد:
render() {
return (
<div>
<h1>Hello world!</h1>
<button>Increment</button>
</div>
);
در اینجا فرمت چند سطری return، توسط افزونهی
Prettier که در قسمت اول معرفی شد، پس از ذخیرهی فایل، به صورت خودکار اعمال شدهاست. همچنین اگر دقت کنید یک () جدید را نیز مشاهده میکنید. علت آن مقابله با automatic semicolon insertion است (درج ; خودکار). در جاوا اسکریپت اگر یک return را داشته باشید و پس از آن در همان سطر، چیزی درج نشده باشد، یک سمیکالن را به صورت خودکار درج/تفسیر میکند. به این ترتیب عبارت JSX چند سطری درج شدهی در سطرهای بعد از return، دیده نخواهد شد؛ یعنی چیزی شبیه به عبارات زیر تفسیر میشود:
برای رفع این مشکل باید دقیقا جلوی واژهی کلیدی return، یک پرانتز را باز کرد و آنرا پس از خاتمهی عبارت JSX، بست (که البته افزونهی Prettier اینکار را به صورت خودکار برای شما انجام میدهد):
نکته 1: بدیهی است زمانیکه المانهای درج شده را درون یک div محصور کردیم، به همین نحو نیز در DOM اصلی ظاهر خواهند شد. اگر علاقمند نیستید که این div در خروجی نهایی رندر شود، میتوان بجای آن از React.Fragment استفاده کرد که هیچ نوع المان اضافهتری را در DOM بجای آن درج نمیکند:
return (
<React.Fragment>
<h1>Hello world!</h1>
<button>Increment</button>
</React.Fragment>
);
نکته 2: در VSCode برای ویرایش همزمان ابتدا و انتهای یک تگ (برای مثال ویرایش همزمان عبارت div در اینجا و تبدیل آن به React.Fragment در دو قسمت)، عبارت آن تگ را انتخاب کرده و سپس دکمههای ctrl+d را فشار دهید تا بتوانید همزمان هر دو عبارت انتخاب شده را با هم ویرایش کنید. به اینکار multi-cursor editing میگویند.
نمایش پویای اطلاعات در عبارات JSX
در ادامه بجای نمایش عبارت ثابت «Hello world»، میخواهیم آنرا به صورت پویا تنظیم کنیم. برای این منظور یک خاصیت جدید را در کلاس جاری، به نام state تعریف کرده و آنرا با یک شیء، مقدار دهی میکنیم. state، یک خاصیت ویژه در کامپوننتهای React است و بیانگر دادههایی است که آن کامپوننت نیاز دارد. این دادهها میتوانند یک key/value ساده باشند و یا حتی value تعریف شده نیز میتواند یک شیء پیچیده باشد.
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};
render() {
return (
<React.Fragment>
<span>{this.state.count}</span>
<button>Increment</button>
</React.Fragment>
);
}
}
export default Counter;
در اینجا خاصیت state را با شیءای که حاوی key/value مساوی count با مقدار صفر است، مقدار دهی کردهایم. سپس برای نمایش این اطلاعات در عبارت JSX، از یک {} استفاده میشود. داخل {}ها میتوان هر نوع عبارت مجاز جاوا اسکریپتی را درج کرد. برای مثال با this شروع میکنیم که بیانگر اشارهگری به وهلهای از شیء جاری است. سپس میتوان توسط آن به خاصیت state و سپس کلید count شیء منتسب به آن دسترسی یافت. به این ترتیب عدد صفر، در کنار دکمهای با برچسب Increment، در مرورگر ظاهر خواهد شد.
همانطور که عنوان شد در بین {}ها میتوان هر نوع عبارت مجاز جاوا اسکریپتی را ذکر کرد و عبارت چیزی است که مقداری را بازگشت میدهد. بنابراین عبارتی مانند {2+2} را نیز میتوان در اینجا بکار برد و یا حتی در اینجا میتوان متدی را فراخوانی کرد که مقداری را بازگشت میدهد:
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};
render() {
return (
<React.Fragment>
<span>{this.formatCount()}</span>
<button>Increment</button>
</React.Fragment>
);
}
formatCount() {
const { count } = this.state; // Object Destructuring
return count === 0 ? "Zero" : count;
}
}
export default Counter;
در این مثال میخواهیم اگر مقدار count مساوی صفر بود، بجای عدد صفر، واژهی Zero را نمایش دهد. به همین جهت این منطق را به یک متد مانند formatCount منتقل کرده و سپس آنرا به صورت {()this.formatCount}، فراخوانی کرده و نمایش میدهیم.
در متد formatCount حتی میتوان عبارات JSX را نیز بجای یک رشتهی ساده، بازگشت داد:
formatCount() {
const { count } = this.state; // Object Destructuring
return count === 0 ? <h1>Zero</h1> : count;
}
مقدار دهی ویژگیهای عناصر در عبارات JSX
فرض کنید یک المان img را به عبارت JSX کلاس Counter اضافه کردهایم. اکنون میخواهیم ویژگی src آنرا مقدار دهی کنیم. در اینجا هر چیزی که بین "" قرار گیرد، به صورت یک رشتهی ثابت پردازش میشود. برای تنظیم آن به یک متغیر، ابتدا خاصیت state را به صورت زیر جهت درج imageUrl، ویرایش میکنیم:
state = {
count: 0,
imageUrl: "/logo192.png"
};
پس از آن عبارت مقدار خاصیت this.state.imageUrl را توسط یک {}، به ویژگی src تصویر نسبت میدهیم:
render() {
return (
<React.Fragment>
<img src={this.state.imageUrl} alt="" />
<span>{this.formatCount()}</span>
<button>Increment</button>
</React.Fragment>
);
}
مقدار دهی class و style المانها، نسبت به مقدار دهی attributes که مشاهده کردید، اندکی متفاوت است؛ از این جهت که در نهایت یک عبارت JSX توسط کامپایلر Babel به معادل جاوا اسکریپتی آن ترجمه میشود و اگر در اینجا به عنوان مثال از ویژگی class استفاده شود، چون نام class، یک نام و واژهی کلیدی از پیش معین شدهی جاوا اسکریپتی است، امکان استفادهی از آن در اینجا وجود ندارد. به همین جهت در React برای تنظیم ویژگی class عناصر، از className استفاده میشود:
return (
<React.Fragment>
<img src={this.state.imageUrl} alt="" />
<span className="badge badge-primary m-2">{this.formatCount()}</span>
<button className="btn btn-secondary btn-sm">Increment</button>
</React.Fragment>
);
در اینجا اعمال یک سری از کلاسهای بوت استرپ را که در ابتدای مطلب به پروژه اضافه کردیم، به ویژگیهای className المانهای span و button مشاهده میکنید.
تا اینجا اگر فایل کامپوننت Counter را ذخیره کنید، خروجی ذیل در مرورگر ظاهر خواهد شد:
روش مقدار دهی ویژگی style نیز متفاوت است. در اینجا React انتظار دارد تا شیءای را که به صورت زیر تشکیل میشود:
styles = {
fontSize: 50,
fontWeight: "bold"
};
به صورت {this.styles} به ویژگی style انتساب دهیم:
return (
<React.Fragment>
<img src={this.state.imageUrl} alt="" />
<span style={this.styles} className="badge badge-primary m-2">
{this.formatCount()}
</span>
<button className="btn btn-secondary btn-sm">Increment</button>
</React.Fragment>
);
نحوهی تشکیل خاصیت styles، بر اساس ذکر خواص CSS، به صورت خواصی camel-case است؛ مانند fontSize. در اینجا عدد 50 توسط react به صورت خودکار به 50px تبدیل میشود.
اعمال این styles نمونه، یک چنین خروجی را به همراه خواهد داشت:
مزیت تعریف شیء styles به صورت یک خاصیت در کلاس، امکان استفادهی مجدد از آن در سایر المانها است. اگر چنین چیزی مدنظر شما نیست، میتوان این شیء را به صورت inline هم تعریف کرد:
<button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
در اینجا، ابتدا یک {} درج میشود تا بیانگر نمایش دریافت یک عبارت معتبر جاوا اسکریپتی باشد. سپس داخل آن یک {} دیگر نیز قرار گرفتهاست که بیانگر تعریف یک شیء جاوا اسکریپتی است و در این حالت باید با نحوهی تشکیل عناصر شیء style مورد نظر React که به صورت caml-case هستند، تطابق داشته باشد.
مقدار دهی پویای ویژگی className عناصر در عبارات JSX
در ادامه میخواهیم اگر مقدار count مساوی صفر بود، span ای که هم اکنون با یک badge آبی (با کلاس badge-primary) نمایش داده میشود، زرد رنگ (با کلاس badge-warning) شود و در غیراینصورت آبی رنگ. بنابراین میخواهیم بر اساس مقدر count، مقدار کلاسهای انتسابی به className را به صورت پویا تغییر دهیم و این الگویی است که در برنامههای واقعی بسیار استفاده میشود:
render() {
let classes = "badge m-2 badge-";
classes += this.state.count === 0 ? "warning" : "primary";
return (
<React.Fragment>
<img src={this.state.imageUrl} alt="" />
<span style={this.styles} className={classes}>
{this.formatCount()}
</span>
<button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
Increment
</button>
</React.Fragment>
);
برای این منظور متغیر classes را تعریف کردهایم که در ابتدا با مقادیری که ثابت هستند، مقدار دهی شدهاند. سپس بر اساس مقدار this.state.count، مقدار مشخص warning و یا primary به این رشته افزوده میشود. در آخر هم از این متغیر به صورت className={classes} استفاده شدهاست؛ با این خروجی:
البته باید دقت داشت که میتوان منطق تشکیل متغیر classes را به یک متد، جهت خلوت سازی متد render نیز منتقل کرد. برای این کار، دو سطر مرتبط با متغیر classes را در VSCode انتخاب کنید. سپس یک آیکن لامپ مانند ظاهر میشود که با کلیک بر روی آن، منوی extract to method نیز قابل انتخاب است:
render() {
let classes = this.getBadgeClasses();
// ...
}
getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += this.state.count === 0 ? "warning" : "primary";
return classes;
}
البته در اینجا میتوان متغیر classes را نیز حذف کرد و مستقیما متد getBadgeClasses را مورد استفاده قرار داد:
<span style={this.styles} className={this.getBadgeClasses()}>