React یک کتابخانه مبتنی بر کامپوننت می باشد که توسط فیسبوک و با استفاده از جاوااسکریپت توسعه یافته و ساختن رابط های کاربری را آسان تر کرده است. در این کتابخانه می توانید برای امکاناتی که در اپلیکیشن خود لازم دارید view های ساده ای را طراحی و از آن استفاده کنید. همچنین زمانی که داده های شما در UI تغییر می کند React به شکل موثری آن ها را اداره و به روز می کند.
React مبتنی بر کامپوننت است، و این بدین معنی است که که می توانیم کامپوننت های مختلف را به صورت کاملا مجزا تعریف کنیم و آن ها را برای ساختن UI های پیچیده تر استفاده کنیم. کامپوننت ها ترکیبی از HTML و Javascript هستند که منطق مورد نیاز قسمت های مختلف یک UI را پوشش می دهند. هر اپلیکیشن React حداقل دارای یک کامپوننت می باشد.
در ادامه مطلب همراه من باشید.
در ابتدا یک صفحه Html ایجاد می کنیم و کد زیر را در آن قرار می دهیم.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Add React in One Minute</title>
</head>
<body>
<!-- We will put our React component inside this div. -->
<div id="root"></div>
</body>
</html>
برای اضافه کردن React به صفحه وب می توان به صورت زیر و با استفاده از تگ script کتابخانه را به صفحه اضافه کرد:
<!-- ... other HTML ... -->
<!-- Load React. -->
<script src="React/react-development.js>"</script>
<script src="React/react-dom-development.js"></script>
<script src="React/babel-core-5.8.34.js"></script>
<!-- Load our React component. -->
<script type="text/babel" src="example.js"></script>
</body>
توجه داشته باشید که تگ های script را قبل از بسته شدن تگ <body> قرار دادیم.
دو تگ <script> اول React را به صفحه اضافه می کند، تگ <script> سوم مربوط به JSX و کامپایلر Babel می باشد و تگ چهارم فایلی که شامل کدهای ماست را به صفحه اضافه می کند.
نکته:
از این لینک می توانید فایل های مربوط به این قسمت از آموزش را دریافت کنید.
در کنار فایل Html یک فایل جاوااسکریپت با نام example.js ایجاد می کنیم و کد زیر را در آن قرار می دهیم.
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
این کد یک تگ <h1> با متن Hello, world! در صفحه، نمایش می دهد.
قبل از ادامه آموزش، مروری بر JSX خواهیم کرد که یک ابزار کارآمد در کنار React برای توسعه UI می باشد.
معرفی JSX
JSX یک syntax extension برای جاوااسکریپت است که در کنار React برای طراحی UI استفاده می شود که ما در قسمت های بعد از آن برای render کردن عناصر DOM استفاده می کنیم.
به بیان ساده تر با استفاده از JSX می توانیم همانطوری که در یک فایل HTML از ساختار HTML/XML (همانند ساختار درختی DOM) استفاده می کنیم در فایل های جاوااسکریپت هم بدون هیچ مشکلی و به سادگی از همان ساختار استفاده کنیم و کامپایلر Babel این کدها (کدهای JSX) را به کد جاوااسکریپت ترجمه می کند. بر خلاف گذشته که کدهای Javascript را در فایل های HTML قرار می دادیم، حالا و با استفاده از JSX می توانیم کدهای HTML را در فایل Javascript قرار دهیم.
در ادامه با مباحث اساسی JSX که مورد نیاز ما است آشنا می شویم.
به عنوان مثال با استفاده از JSX می توانیم کد JSX/Javascript زیر را بنویسیم:
var nav = (
<ul id="nav">
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Clients</a></li>
<li><a href="#">Contact Us</a></li>
</ul>
);
و کامپایلر Babel آن را به کد زیر تبددیل می کند:
var nav = React.createElement(
"ul",
{ id: "nav" },
React.createElement(
"li",
null,
React.createElement(
"a",
{ href: "#" },
"Home"
)
),
React.createElement(
"li",
null,
React.createElement(
"a",
{ href: "#" },
"About"
)
),
React.createElement(
"li",
null,
React.createElement(
"a",
{ href: "#" },
"Clients"
)
),
React.createElement(
"li",
null,
React.createElement(
"a",
{ href: "#" },
"Contact Us"
)
)
);
یا تعریف متغیر زیر را در نظر بگیرید:
const element = <h1>Hello, world!</h1>;
همانطور که syntax کد را مشاهده می کنید نه به صورت یک رشته است و نه به صورت تگ HTML.
چرا JSX؟
React این واقعیت را پذیرفته که منطق render و منطق UI در هم آمیخته است. اینکه چطور رویداد ها مدیریت می شوند، چطور state ها تغییر می کنند و داده ها چطور برای نمایش آماده می شوند مسائلی از این دست هستند که فقط با تکیه بر منطق render یا منطق UI قابل انجام نیست.
کامپوننت های React به ما این امکان را می دهند که به جای جدا کردن کدهای HTML و منطق UI، هر دو تکنولوژی را به صورت واحد در اختیار داشته باشیم.
برای کار با React هیچ اجباری به استفاده از JSX نیست اما اکثر توسعه دهندگان استفاده از آن را در زمان طراحی UI بسیار کارآمد می دانند. همچنین با استفاده از JSX، پیغام خطاهای کارآمد تری را در اختیار خواهیم داشت.
استفاده از JSX
در مثال پایین ما یک متغیر به نام name تعریف کردیم و در خط بعد با استفاده از JSX از این متغیر که در بین دو آکولاد قرار گرفته است استفاده کردیم.
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
شما می توانید هر نوع عبارت جاوااسکریپتی را بین دو آکولاد در JSX استفده کنید. برای مثال: 2+2، user.firstName یا formatName(user) نمونه ای از عبارت جاوااسکریپتی هستند که می توانید استفاده کنید.
در مثال پایین، نتیجه فراخوانی یک تابع جاوااسکریپت به نام formatName(user) را داخل یک تگ <h1> قرار دادیم.
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
در مثال بالا می توانستیم کدهای JSX را در یک خط بنویسیم اما برای خوانایی بیشتر به صورت چند خطی نوشتیم البته باید توجه داشته باشیم که آن را بین پرانتز قرار دادیم تا از درج سیمی کالن به صورت خودکار در انتهای هر خط جلوگیری کنیم.
عبارات JSX به توابع و اشیا جاوااسکریپت تبدیل می شوند و این بدین معنی است که ما این امکان را خواهیم داشت که از JSX در:
- حلقه for یا if استفاده کنیم
- آنها را به متغیر نسبت دهیم
- به عنوان آرگومان تابع استفاده کنیم
- و یا آن را از یک تابع return کنیم
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
مشخص کردن Attribute در JSX
برای مشخص کردن یک Attribute با استفاده از رشته به صورت زیر عمل می کنیم:
const element = <div tabIndex="0"></div>;
و برای مشخص کردن Attribute با استفاده از یک عبارت جاوااسکریپت به صورت زیر:
const element = <img src={user.avatarUrl}></img>;
بابد به این نکته توجه داشته باشیم که زمانی که یک عبارت جاوااسکریپت را به عنوان مقدار Attribute در نظر می گیریم نباید از کوتیشن استفاده کنیم. زمانی که از یک مقدار نوع رشته به عنوان مقدار Attribute استفاده می کنیم باید از کوتیشن (برای مقادیر رشته ای) و زمانی که از یک عبارت جاوااسکریپت در مقدار Attribute استفاده می کنیم باید از آکولاد (برای عبارات) استفاده کنیم و نمی توانیم هم زمان از هر دوی آنها استفاده کنیم.
نکته
از آنجا که JSX بیشتر شبیه به جاوااسکریپت است تا HTML، React از قرار داد نامگذاری مشخصه به صورت camelCase به جای نام مشخصه های HTML استفاده می کند. به عنوان مثال مشخصه class در JSX به صورت className استفاده می شود و tabindex به صورت tabIndex.
تعریف فرزند با استفاده از JSX
در JSX اگر یک تگ خالی باشد و فرزندی نداشته باشد باید با استفاده از /> بسته شود. به مثال زیر دقت کنید:
const element = <img src={user.avatarUrl} />;
و اگر تگی دارای فرزند باشد به صورت زیر عمل می کنیم:
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
JSX جلوگیری از حملات Injection
زمانی که از ورودی های کاربر مانند نمونه زیر استفاده می کنیم:
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
Reactبه صورت پیش فرض همه مقادریر JSX را قبل از render کردن آنها escape می کند. هر چیزی قبل از render شدن به رشته تبدیل می شود و این به جلوگیری از حملات XSS کمک می کند.
Object های JSX
Babel کدهای JSX را به فراخوانی های React.createElement کامپایل می کند.
از اینرو دو مثال زیر با یکدیگر برابر هستند:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
متد React.createElement() کدهای ما را بررسی می کند تا خطایی در آن وجود نداشته باشد و یک شی به شکل زیر ایجاد می کند.
// Note: this structure is simplified
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
چنین اشیایی React elements خوانده می شود و می توان این برداشت را از آن داشت که توصیف آن چیزی است که می خواهیم در صفحه مشاهده کنیم. React این اشیا را می خواند و از آنها برای ساخت DOM و به روز رسانی آن استفاده می کند.
Render کردن عناصر
Element ها کوچکترین قسمت اپلیکیشن های React محسوب می شوند.
یک element شرح آن چیزی است که می خواهیم در صفحه، نمایش دهیم.
const element = <h1>Hello, world</h1>;
در ادامه ابتدا یک تگ div در صفحه HTML تعریف می کنیم و id آن را root قرار می دهیم.
<div id="root"></div>
این تگ div را گره DOM ریشه می خوانیم زیرا هر چیزی در آن با استفاده از React مدیریت می شود. اپلیکیشن هایی که فقط با React ایجاد می شوند معمولا دارای یک گره ریشه هستند اما اگر ما React را در یک برنامه import کنیم می توانیم چندین گره root داشته باشیم.
برای render کردن عنصر React در گره root که آن را بالاتر تعریف کردیم، عنصر React و گره با شناسه root را به عنوان آرگومان به تابع ReactDOM.render()
ارسال می کنیم.
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
کد بالا Hello world! را در صفحه، نمایش می دهد.
به روز رسانی Element رندر شده
Element های React تغییر ناپذیر هستند. زمانی که یک element را ایجاد می کنیم، نمی توانیم فرزندانش یا attribute های آن را تغییر دهیم. یک element مانند یک فریم در یک فیلم می باشد که UI را در یک نقطه مشخص از زمان نمایندگی می کند. در حال حاضر و با آموخته های ما تنها راه برای بروز رسانی UI ساخت element جدید و فرستادن آن به ReactDOM.render() می باشد .
به عنوان مثال ساعت زیر را در نظر بگیرید:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
این کد هر ثانیه و با استفاده از متد setInterval()، ReactDOM.render() را فراخوانی می کند.
React فقط تغییراتی را که ضروری است اعمال می کند
در مثال بالا React عنصر مورد نظر و فرزندانش را با خود عنصر، قبل از فراخوانی render مقایسه می کند و تنها تغییرات ضروری را برای اعمال تغییرات انجام می دهد. در مثال قبلی می توانیم از صفحه inspect بگیریم و این ویژیگی React را مشاهده کنیم.
با اینکه ما یک element تعریف کردیم که در آن کل UI تعریف شده است تنها متن گره ای که محتوای آن تغییر کرده است توسط React به روز رسانی می شود.
Component و Prop
کامپوننت ها این امکان را به ما می دهند که UI را به قسمت های مستقلی تقسیم کنیم که قابلیت استفاده مجدد دارند. کامپوننت ها مانند توابع جاوااسکریپت، ورودی های مورد نظر را که props خوانده می شود، دریافت می کنند و یک عنصر React را بر می گردانند که در واقع این عنصر آن چیزیست که می خواهیم در صفحه نمایش مشاهده کنیم.
کامپوننت به صورت کلاس و تابع
ساده ترین راه برای ایجاد کامپوننت تعریف یک تابع جاوااسکریپت است.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
این تابع یک React Component در نظر گرفته می شود زیرا دارای آرگومان props است و یک عنصر React را بر می گرداند. چنین کامپوننت هایی “function components” در نظر گرفته می شوند در واقع آنها توابع جاوااسکریپت هستند.
همچنین می توانیم از کلاس های ES6 برای تعریف کامپوننت در React استفاده کنیم.
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
دو کامپوننت بالا در React تفاوتی با یکدیگر ندارند.
رندر کردن یک کامپوننت
تا اینجا فقط با عناصر React که تگ های DOM را نمایش می دادند آشنا شدیم.
const element = <div />;
در مثال پایین مشاهده می کنید که چطور می توان از کامپوننت های که تعریف کردیم استفاده کنیم.
const element = <Welcome name="Sara" />;
React زمانی که با یک عنصر کامپوننت که توسط کاربر تعریف شده است مواجه می شود، attribute های JSX را به صورت یک object (که همان props است) به کامپوننت ارسال می کند.
برای مثال این کد “Hello Sara” را در صفحه، نمایش می دهد:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
در مثال بالا:
- ReactDOM.render را با عنصر </Welcome name= "Sara"> فراخوانی می کنیم.
-
React کامپوننت Welcome را با {name: ‘Sara’} فراخوانی می کند.
-
کامپوننت Welcome یک عنصر <h1>Hello,Sara</h1> را به عنوان نتیجه بر می گرداند.
-
ReactDOM، صفحه را برای مطابقت با <h1>Hello,Sara</h1> بروز رسانی می کند.
نکته:
در نامگذاری کامپوننت ها همیشه حرف اول را به صورت بزرگ در نظر می گیریم. React، کامپوننت هایی را که با حرف کوچک شروع شده اند به عنوان تگ DOM در نظر می گیرد. به عنوان مثال <div/> یک تگ div در HTML در نظر گرفته می شود اما <Welcome/> یک کامپوننت است که البته باید در همان Scope تعریف شده باشد.
ترکیب کامپوننت ها
کامپوننت ها می توانند در خروجی های خود از کامپوننت های دیگر استفاده کنند. این مورد به ما این امکان را می دهد تا از یک کامپوننت برای هر موردی مانند button، form یا dialog استفاده کنیم.
در مثال پایین ما یک کامپوننت به نام App تعریف کردیم که Welcome را چندین مرتبه render می کند:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
تقسیم یک کامپوننت بزرگ به کامپوننت های کوچکتر
برای مثال کامپوننت Comment را در نظر بگیرید:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
این کامپوننت author را به صورت یک object، text را به صورت string و date را به صورت تاریخ در props دریافت می کند و یک Comment را در شبکه اجتماعی توصیف می کند. این کامپوننت می تواند به چندین کامپوننت کوچکتر تقسیم شود که هر یک قابلیت استفاده مجدد را دارد.
در ابتدا می توانیم یک کامپوننت به نام Avatar ایجاد کنیم:
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
در کد بالا و در props، نام عمومی تر user را به جای author در نظر گرفتیم زیرا این کامپوننت می تواند در هر کامپوننت دیگری مانند Comment رندر شود.در نام گذاری prop ها این موضوع را در نظر داشته باشید که نام ها را با توجه به همان کامپوننت در نظر بگیرید و نه Context که در آن استفاده می شود.
تا اینجای کار توانستیم کامپوننت Comment را مقداری ساده تر کنیم.
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<Avatar user={props.author} />
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
در قدم بعدی می توانیم کامپوننت UserInfo را که یک Avatar را در کنار نام کاربر render می کند در نظر بگیریم:
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
که باعث می شود تا کامپوننت Comment ما ساده تر شود.
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
تقسیم یک کامپوننت به کامپوننت های کوچکتر ممکن است، کاری اضافه به نظر برسد اما می توانیم یک مجموعه از کامپوننت هایی که دارای قابلیت استفاده مجدد هستند را در اختیار داشته باشیم و از آن ها در اپلیکیشن های بزرگتر استفاده کنیم. به عنوان مثال می توان در دیگر اپلیکیشن ها از کامپوننت هایی مانند Button، Avatar و Panel یا کامپوننت های پیچیده تر نظیر App، FeedStroy یا Comment استفاده کنیم.
Props ها فقط خواندی هستند
زمانی که یک کامپوننت چه به صورت تابعی و چه به صورت کلاس تعریف شود هیچگاه نمی تواند prop های خودش را ویرایش کند.
تابع sum زیر را در نظر بگیرید:
function sum(a, b) {
return a + b;
}
چنین توابعی اصطلاحا pure خوانده می شود زیرا نمی توانند ورودی هایی که برای آنها در نظر گرفته شده است را تغییر دهند، و همیشه نتیایج مشابه را برای ورودی های مشابه بر می گردانند.
علاوه بر این توابعی مانند توابع زیر که به شکل نادرستی ورودی های خودشان را تغییر می دهند در آینده مشکل ساز خواهند بود.
function withdraw(account, amount) {
account.total -= amount;
}
React دارای انعطاف پذیری خوبی است اما یک قاعده سختگیرانه دارد:
همه کامپوننت های React باید همانند توابع pure با props ها برخورد کنند. هر چند که UI اپلیکیشن ها پویاست و در طول زمان تغییر می کنند.
در React مفهوم دیگری به نام state وجود دارد که به کامپوننت های React این امکان را می دهد تا در طول زمان خروجی های خودشان را در پاسخ به فعالیت های کاربر، پاسخ های شبکه و مواردی از این دست بدون نقض این قاعده تغییر دهند.
State و LifeCycle در React
مثال ساعت را که، بالاتر بررسی کردیم در نظر بگیرید. ما از ReactDOM.render() برای تغییر خروجی render شده استفاده کردیم.
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
در این قسمت بررسی می کنیم که چطور می توانیم کامپوننتی به نام Clock ایجاد کنیم که به درستی دارای قابلیت استفاده مجدد باشد و خود کامپوننت دارای تایمر باشد و هر ثانیه به صورت خودکار خودش را بروز کند که در اینصورت دیگر نیازی به setInterval برای بروزرسانی خودش نداشته باشد.
کامپوننت Clock را تعریف می کنیم و آن را در تابع tick به صورت زیر فراخوانی می کنیم:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
در مثال بالا Clock باید timer خودش را داشته باشد و UI را هر ثانیه بروزر سانی کند. به صورت ایده آل ما باید کد را به صورت زیر بنویسیم و کامپوننت Clock به صورت خودکار بروزرسانی شود.
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
برای پیاده سازی این امکان ما نیاز به اضافه کردن state به کامپوننت Clock داریم.
State شبیه به prop است اما خصوصی است و کاملا توسط خود کامپوننت کنترل می شود.
زمانی که کامپوننت ها را به صورت کلاس تعریف می کنیم می توانیم از ویژگی های بیشتری استفاده کنیم و state یکی از همین ویژیگی هاست.
تبدیل یک کامپوننت تابعی به کلاس
شما می توانید در 5 قدم یک Function component را به Class component تبدیل کنید:
- یک کلاس ES6 هم نام با تابع ایجاد می کنیم که از React.Component، با استفاده از extend ارث بری می کند.
- یک متد خالی به نام render به کلاس اضافه می کنیم.
- کد های موجود در تابع را به متد render اضافه می کنیم.
- prop های موجود در کدهای متد render را با this.props جایگزین می کنیم.
- تابع مربوط به کامپوننت را حذف می کنیم.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
حالا کامپوننت Clock به صورت کلاس تعریف شده است.
متد render هر زمان که update رخ دهد فراخوانی می شود، اما هر زمان که ما کامپوننت Clock را در یک گره مشابه DOM رندر می کنیم فقط یک نمونه از کلاس Clock استفاده می شود هر چند که کلاس ها امکانات بیشتری مانند: local state و متدهای life cycle.
اضافه کردن Local State به کلاس
در این قسمت ما در سه مرحله داده های لازم را از props به state منتقل می کنیم.
1- جایگزین کردن this.state.date به جای this.props.date در متد render.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
2- اضافه کردن class constructor برای نمونه سازی اولیه this.state
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
توجه داشته باشید که باید props را به صورت زیر به base constructor بفرستیم:
constructor(props) {
super(props);
this.state = {date: new Date()};
}
Class Component ها همیشه باید base constructor را با props فراخوانی کنند.
3- حذف date از عنصر <Clock />:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
بعدا کد تایمر را به کلاس اضافه خواهیم کرد اما تا الان کد ما باید بصورت زیر باشد:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
اضافه کردن متدهای Lifecycle به Class
در اپلیکیشن هایی که تعداد زیادی کامپوننت دارند، آزاد کردن منابع تخصیص یافته به آنها زمانی که حذف می شوند اهمیت بالایی دارد.
ما می خواهیم زمانی که Clock در DOM رندر شد یک تایمر داخل کامپوننت شروع به کار کند. که این کار در React، Mounting نامیده می شود. و هر زمان که عنصر ایجاد شده توسط Clock از DOM حذف شد این تایمر هم حذف شود که به این کار هم در React، Unmounting می گویند.
ما می توانیم در کامپوننت ها متدهای خاصی را برای زمانی که Mount و Unmount می شود تعریف کنیم.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
این متدها lifecycle method نام دارند.
متد componentDidMount() بعد از اینکه خروجی کامپوننت در DOM رندر شد، اجرا می شود که مکان خوبی برای شروع به کار تایمر می باشد.
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
توجه داشته باشید که چطور شناسه تایمر را در this ذخیره کردیم.
در حالی که this.props توسط React تنظیم می شود و مقادیر this.state دارای یک مفهوم خاص است، شما می توانید فیلدهای مورد نظر خود را که در جریان داده جایگاه مشخصی ندارند به صورت دستی به کلاس اضافه کنید مانند شناسه timer.
در متد componentWillUnmout() تایمر را clear می کنیم.
componentWillUnmount() {
clearInterval(this.timerID);
}
در آخر باید متد tick() را که کامپوننت را هر ثانیه فراخوانی می کند پیاده سازی کنیم. این تابع از متد this.setState() برای بروز رسانی state کامپوننت استفاده می کند.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
حالا دوباره به صورت خلاصه، متد های موجود در کامپوننت Clock را بررسی می کنیم:
1- زمانی که <Clock /> به تابع ReactDOM.render() ارسال می شود، React، constructor کامپوننت Clock را فراخوانی می کند. از آنجا که Clock به زمان جاری، برای اینکه آن را نمایش دهد نیاز دارد، this.state را با یک object که شامل زمان جاری است نمونه سازی اولیه می کنیم که البته مقدار state بعدا بروز رسانی خواهد شد.
2- سپس React متد render کامپوننت Clock را فراخوانی می کند. React از طریق این متد آن چیزی که باید در صفحه، نمایش دهد را ایجاد می کند. پس از این React، DOM را برای تطابق با خروجی متد render کامپوننت Clock تطبیق می دهد.
3- زمانی که خروجی کامپوننت در DOM وارد می شود، React متد lifecycle، componentDidMount() را فراخوانی می کند. داخل این متد، کامپوننت Clock هر ثانیه متد tick() را اجرا می کند.
4- مرورگر هر ثانیه متد tick() را فراخوانی می کند که در داخل این متد کامپوننت Clock، با فراخوانی متد setState() با یک object که شامل تاریخ جاری می شود UI را بروزرسانی می کند. این نکته را باید در نظر داشته باشیم که زمانی که متد setState فراخوانی می شود، React پس از تغییر state متد render را فرا خوانی می کند تا تغییرات را در UI اعمال کند. این بار this.state.date در متد render متفاوت است و خروجی متد render شامل زمان بروز شده است. React، DOM را طبق تغییرات بروزرسانی می کند.
5- اگر کامپوننت Clock از DOM حذف شود، React متد componentWillUnmount را که یک متد lifecycle است را فراخوانی می کند و در این متد تایمر متوقف می شود.
استفاده صحیح از State
سه نکته مهم در ارتباط با :setState()
1- به صورت مستقیم State را ویرایش نکنید
به طور مثال در کد پایین، کامپوننت رندر مجدد نخواهد شد:
// Wrong
this.state.comment = 'Hello';
و باید به جای آن از setState استفاده کنیم.
// Correct
this.setState({comment: 'Hello'});
تنها جایی که باید از this.state استفاده کنیم داخل constructor است.
2- بروزرسانی State ممکن است به صورت نا همگام باشد
ممکن است React چندین فراخوانی setState را برای حفظ کارایی، تنها یکبار اجرا کند. به همین علت در محاسبه state بعدی باید این نکته را در نظر داشته باشید که ممکن است مقادیر هنوز بروز نشده باشد.
برای مثال ممکن است اجرای این کد در بروز رسانی counter موفقیت آمیز نباشد.
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
برای حل این مشکل می توان از شکل دوم تابع setState که یک تابع را به جای یک شی دریافت می کند استفاده کرد. تابعی که به عنوان آرگومان برای متد setState در نظر می گیریم، state قبلی را را به عنوان اولین آرگومان و props فعلی را به عنوان دومین آرگومان دریافت می کند.
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
در مثال بالا ما از arrow function به جای regular function استفاده کردیم ولی می توانید از regular function ها هم به صورت زیر استفاده کنید:
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
3- بروزرسانی های State ادغام می شوند
زمانی که setState را فراخوانی می کنیم، React، object که به state جاری ارائه کردیم را ادغام می کند.
به طور مثال ممکن است state شما شامل چندین مقدار مستقل مانند نمونه زیر باشد:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
ما می توانیم آنها را به صورت مستقل با فراخوانی های متعدد setState() بروز رسانی کنیم:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
بنابراین this.setState({comments})، this.state.posts را تغییر نمی دهد اما this.state.comments را به صورت کامل جایگزین می کند.
هیچ یک از کلاس های والد یا فرزند نمی دانند که یک کامپوننت به صورت stateful است یا stateless به همین دلیل است که معمولا state ها local یا encapsulated خوانده می شوند. state از هیچ کامپوننت دیگری به غیر از کامپوننتی که در آن تعریف شده است و مقادیرش در آن set شده قابل دسترسی نیست.
همچنین کاپوننت ها از اینکه کامپوننت های دیکر به صورت class component تعریف شده اند یا function component اطلاعی ندارند.
یک کامپوننت ممکن است state خود را به عنوان props به فرزندانش ارسال کند:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
همچنین می تواند state را به کامپوننت های تعریف شده توسط کاربر ارسال کند:
<FormattedDate date={this.state.date} />
کامپوننت FormattedDate داده را در props خود دریافت می کند و هیچ اطلاعی از اینکه این داده از state کامپوننت Clock آمده است یا از props کامپوننت Clock ندارد.
این جریان داده معمولا “top-down” یا “unidirectional” خوانده می شود. هر state همیشه متعلق به یک کامپوننت خاص است، و هر داده یا UI که از آن حاصل می شود فقط می تواند برکامپوننت هایی که در سطح پایینتر آنها هستند تاثیر بگذارد.
اگر یک کامپوننت را آبشاری از prop ها در نظر بگیریم، هر state کامپوننت مانند یک منبع جریان آب اضافی به آن می پیوندد.
برای نشان دادن این موضوع که همه کامپوننت ها از یک دیگر جدا هستند، می توانیم یک کامپوننت App ایجاد کنیم که سه کامپوننت <Clock> را به صورت زیر رندر می کند.
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
هر Clock دارای تایمر مختص به خود می باشد. در اپلیکیشن های React تفاوتی ندارد که یک کامپوننت به صورت statefull است یا stateless و در هر صورت پیاده سازی یک کامپوننت در نظر گرفته می شود. می توانیم از یک کامپوننت stateless در یک کامپوننت statefull و بالعکس استفاده کنیم.
منبع: reactjs.org
React | Javascript |