امروز می خواهیم ببینیم که کامپوننت های stateful چطور کار می کنند؟ و ما چه موقع و چرا باید از state ها در کامپوننت مان استفاده کنیم.
در هفته اول، هر آنچه که برای شروع برنامه نویسی ری اکت نیاز بود را یاد گرفتیم. با jsxها کار کردیم، اولین کامپوننت مان را ساختیم، رابطه والد-فرزند را توضیح دادیم و کار با پروپرتی ها (Props) در ری اکت را فرا گرفتیم. اما یک قابلیت خیلی مهم و کاربردی باقی مانده که در این درس می خواهیم راجع به آن صحبت کنیم و آن کار با وضعیت یا state ها است.
ری اکت به دلایلی به ما اجازه تغییر this.props
را بر روی کامپوننت ها نمی دهد فرض کنید اگر یک پروپرتی title به کامپوننت Header پاس دهیم و کامپوننت header بتواند آن را تغییر دهد، از کجا بفهمیم پروپرتی title در کامپوننت header چه مقداری دارد؟
نکته مهمی که باید بدانید این است که در کل تغییر دادن متغیری که کامپوننت والد به کامپوننت فرزند ارسال می کند، کار خوبی نیست. با این حال، گاهی اوقات یک کامپوننت نیاز دارد که وضعیت خودش را تغییر دهد. برای مثال یک برچسب flag active
تنظیم کنیم تا اگر یک کامپوننت فرزند نیاز داشت بتواند زمان را در کامپوننت ساعت انتخاب یا بروزرسانی کند.
هر چند پیشنهاد می شود تا حد امکان از props استفاده کنیم، اما گاهی اوقات نیاز داریم که وضعیت یک کامپوننت را نگه داریم.
state ها تنها در کامپوننت اصلی و کامپوننت های فرزند آن قابل دسترس هستند. همانند روشی که ما با آن یک props را به یک کامپوننت ارسال می کردیم، stateها می توانند توسط this.state
در یک کامپوننت قابل دسترس باشند. هر زمان که یک وضعیت تغییر کرد (که اینکار توسط تابع this.setState
انجام می شود)، کامپوننت رندر خواهد شد. برای مثال فرض کنید یک کامپوننت ساعت داریم که زمان جاری را نشان می دهد.
هر چند که این یک کامپوننت ساده است، اما برای اینکه بتواند زمان جاری را نشان دهد، نیاز به نگهداری یک وضعیت خواهد داشت. بدون استفاده از state می توانیم یک timer تنظیم کرده و کل کامپوننت ری اکت را رندر کنیم، اما کامپوننت های دیگر روی صفحه ممکن است نیاز به رندر نداشته باشند، در نتیجه باعث ایجاد سربار اضافی در برنامه شده و سرعت برنامه پایین خواهد آمد.
در عوض می توانیم یک تایمر برای فراخوانی تابع renderدر داخل کامپوننت تنظیم کرده و تنها وضعیت داخلی این کامپوننت را تغییر دهیم.
حال ساخت این کامپوننت را شروع کرده و نام آن را clock می گذاریم. قبل از اینکه stateها را ایجاد کنیم باید ابتدا متد render
را در کامپوننت بوجود بیاوریم.
سپس مقدار زمان جاری را گرفته و در متغیرهای زیر می ریزیم، در نهایت بررسی می کنیم که اگر مقدار ساعت از 12 کمتر بود، am در غیر اینصورت pm را به انتهای زمان جاری اضافه کند. مطابق زیر:
class Clock extends React.Component { render() { const currentTime = new Date(), hours = currentTime.getHours(), minutes = currentTime.getMinutes(), seconds = currentTime.getSeconds(), ampm = hours >= 12 ? 'pm' : 'am'; return ( <div className="clock"> { hours == 0 ? 12 : (hours > 12) ? hours - 12 : hours }:{ minutes > 9 ? minutes : `0${minutes}` }:{ seconds > 9 ? seconds : `0${seconds}` } {ampm} </div> ) } }
به عنوان یک راه حل جایگزین می توانیم از کد زیر برای مدیریت فاصله اضافی بین زمان درکامپوننت ساعت استفاده کنیم.
("00" + minutes).slice(-2)
اما ما پیشنهاد می کنیم که از روش قبلی استفاده کنید.
اگر یک کامپوننت clock را رندر کنیم، تنها یکبار زمان رندر خواهد شد و در دفعات بعد کامپوننت خودش را بروزرسانی می کند. در حال حاضر این ساعت کار خاصی انجام نمی دهد. همچنین برای تبدیل نمایش استاتیک زمان در کامپوننت clock به یک زمان دینامیک، باید بتوانیم به طریقی درهر ثانیه زمان را بروزرسانی کنیم.
برای انجام اینکار، باید مقدار زمان فعلی را در یک state نگهداری کنیم.
پس در ابتدا باید یک مقدار اولیه به state مان نسبت دهیم.
در ES6 با تنظیم یک مقدار برای this.state
که در متد constructor
انجام می دهیم، وضعیت اولیه کامپوننت ها را مشخص می کنیم.
constructor(props) { super(props); this.state = this.getTime(); }
در خط اول متد سازنده باید همیشه super(props)
را فراخوانی کند. اگر اینکار را نکنید، کامپوننت به درستی کار نخواهد کرد و یک خطا دریافت می کنید.
در واقع با دستور super props می خواهیم بگوییم که منظور ما از props همان چیزی است که برای ارسال داده بکار گرفته می شود و super باعث می شود که این دستور از کامپوننت مادر (یعنی کلاس Component) ارث بری کند.
حال که this.state
را در کامپوننت clock تعریف کردیم، می توانیم توسط this.state
یک ارجاع به آن در تابع render
داشته باشیم.
برای اینکار کدهای زیر را در تابع render
برای دریافت مقادیر this.state
قرار دهید.
class Clock extends React.Component { // ... render() { const {hours, minutes, seconds, ampm} = this.state; return ( <div className="clock"> { hours === 0 ? 12 : (hours > 12) ? hours - 12 : hours }:{ minutes > 9 ? minutes : `0${minutes}` }:{ seconds > 9 ? seconds : `0${seconds}` } {ampm} </div> ) } }
به جای اینکه مستقیم با مقدار داده ها کار کنید، می توانید state کامپوننت ها را بروزرسانی کرده و در حقیقت تابع render را از بخش مدیریت داده ها جدا کنیم.
همچنین برای بروزرسانی وضعیت از یک تابع به نام setState
استفاده می کنیم. این تابع باعث رندر شدن کامپوننت خواهد شد.
نکته: ما باید متد setState
را روی مقدار this
کامپوننت مان فراخوانی کنیم.
در کامپوننت clock از متد setTimeout
برای ساخت یک تایمر به منظور بروزرسانی this.state
در هر 1000 میلی ثانیه استفاده می کنیم.
class Clock extends React.Component { // ... constructor(props) { super(props); this.state = this.getTime(); } // ... setTimer() { clearTimeout(this.timeout); this.timeout = setTimeout(this.updateClock.bind(this), 1000); } // ... updateClock() { this.setState(this.getTime, this.setTimer); } // ... }
در داخل تابع updateClock
وضعیت یا state را با یک زمان جدید بروزرسانی می کنیم.
class Clock extends React.Component { // ... updateClock() { this.setState(this.getTime, this.setTimer); } // ... }
بعد از قرار دادن کامپوننت در صفحه، هر یک ثانیه (یا 1000 میلی ثانیه) یکبار مقدار زمان جاری را بروزرسانی می شود، با این حال دوباره به وضعیت قبل خود بر نمی گردد. در قدم بعد باید تابع setTimer
را در انتهای متد فراخوانی کنیم.
class Clock extends React.Component { // ... updateClock() { const currentTime = new Date(); this.setState({ currentTime: currentTime }) this.setTimer(); } // ... }
حال امکان دارد خود کامپوننت نسبت به متد Timeout آهسته تر رندر شود که اینکار باعث بروز خطا و رندر غیرضروری می شود.
به جای فراخوانی متد setTimer
، بعد از this.setState
می توانیم یک آرگومان دوم به متد this.setState
ارسال کرده تا مطمئن شویم که متد setTimer
بعد از این که state بروزرسانی شد، فراخوانی می شود.
class Clock extends React.Component { // ... updateClock() { const currentTime = new Date(); this.setState({ currentTime: currentTime }, this.setTimer); } // ... }
چند نکته که باید به یاد داشته باشید:
setState
را با یک آرگومان آبجکتی فراخوانی کنیم، توسط this.state
می توانیم به آبجکت ها دسترسی داشته باشیم و سپس کامپوننت را رندر کنیم.render
از آن استفاده می کنیم را نگه داریم. همان طور که در مثال بالا دیدید ما hours (ساعت)، minutes (دقیقه) و seconds (ثانیه) را در state مان ذخیره کردیم. ذخیره کردن آبجکت یا دستورات محاسباتی در stateیی که قصد نداریم در تابع render
از آن استفاده کنیم، کارخوبی نیست، چون اینکار باعث رندر غیرضروری کامپوننت می شود و اتلاف چرخه cpu خواهد شد.همان طور که در ابتدای این بخش گفتیم، تا حد امکان از props استفاده کنید، چون نه تنها از نظر کارایی اینکار بهتر است، بلکه تست کردن کامپوننت های stateful دشوارتر است.
در این درس ما کامپوننت مان را به حالت stateful در آوردیم و یاد گرفتیم که چطور در مواقع لزوم یک کامپوننت stateful را مدیریت کنیم. در درس بعدی به بحث درباره چرخه حیات یک کامپوننت و نحوه تعامل با آن در یک صفحه می پردازیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.