6. پروژه اول : ساخت تایمر (اپیزود دو)

بازم سلام :) میدونم که همین امروز پست روز پنجم رو گذاشتم و در عرض چند ساعت رسیدم به روز ششم. نگران نباشید با فیلم های کریستوفر نولان روبرو نیستید ، فقط اینکه یک هفته ای مرخصی گرفتم. بله درست حدس زدی من از اون آدمایی هستم که مرخصی میگیرم و می شینم درس میخونم ، به جای سفر رفتن و....

خب بریم سر اصل مطلب.

ما پروژه رو به 4 فاز تقسیم کردیم. فاز اول ساخت یک تایمر ساده که با اجرا شدن برنامه شروع به شمردن میکنه بود. این چیزی هست که توی روزهای قبل نوشتیمش و کدش رو یادمون هست:

import React,{useState,useEffect} from &quotreact&quot
export function Counter( ){
const initialState = 0;
const [ counter , setCounter ] = useState (initialState);
useEffect( ( ) => { setTimeout(() => {setCounter(counter+1)}, 1000); } , [counter] )
      return (
                   <div>
                    <p>{counter}</p>
                    </div>
                    );  } //20

کد شماره 20 چیزی هست که توی روز 5 ام نوشته بودیم و در اون از یک UseEffect برای به روزرسانی State استفاده کردیم. برنامه رو اجرا کنید ، اگر موردی نداشت وارد فاز بعدی می شیم.

فاز دو: اضافه کردن دکمه Reset

قصد داریم با زدن دکمه ریست ، مقدار counter صفر بشه. خب این بخش هم خیلی راحتته ، کافیه یک دکمه تعریف کنیم و با استفاده از تابع SetCounter مقدار صفر رو به counter بدیم. به این صورت انجام میدیم: (فقط بخش return رو میذارم ، باقی کد مثل کد 20 هست.

return (
<div>
<p> {counter} </p>
<button  = { (  ) => setCounter ( initialState ) } > Reset </button>
</div> ); //21

بعد از اجرا کردن می بینیم که نشد آنچه باید می شد. با کلیک کردن روی ریست ، ترتیب نمایش اعداد قاطی میشه و اعداد پرت نشون میده. علت چیه؟

علت اینه که شما در هندلر میای مقدار counter رو تغییر میدیم. از طرفی توی هوک UseEffect گفتی هر موقع کانتر تغییر کرد بیا هوک رو اجرا کن و بازم از طرفی قرار بود این اجرای هوک فقط ثانیه ای یک بار رخ بده! حالا یه عامل دیگه فارق از زمان پیدا شده و داره هوک رو اجرا میکنه ! خب انگار به اون سادگی هم که فکر میکردیم نیست این فاز دوم :)))

راه حلی که من به ذهنم رسیده اینه : ( شاید شما راه حل های بهتری به ذهنتون برسه ، تمرین جالبی میتونه باشه ، قبل از نگاه کردن به کد من یکم درباره ش فکر کنید)

من اومدم یک State دیگه به اسم clockPulse تعریف کردم که هر یک ثانیه مقدارش زیاد میشه( و هیچ چیز دیگه ای هم روش تاثیر نمیذاره) و تغییر مقدار این State رو به عنوان موقعیت خاص هوک UseEffect تعریف کردم. چون مطمئن هستم که هر یک ثانیه اجرا میشه و هیچ چیز دیگه ای روش تاثیر نداره. عملیات خاص هوک همچنان همون تغییر مقدار counter هست. این کار چه فایده ای داره؟

با اینکار وقتی ما ریست رو میزنیم و مقدار state رو تغییر میدیم دیگه موجب اجرای ناخواسته ی هوک نمیشه. کد شبیه همچین چیزی خواهد شد:

export function Counter(){
const initialState =0;
const [ counter , setCounter ] = useState (initialState);
const [ clockPulse , setclockPulse] = useState (0);
            useEffect( (  ) => { setCounter ( counter+1 ) ; }  ,  [ clockPulse ] );
             setTimeout ( ( ) => { setclockPulse ( clockPulse + 1 ) }, 1000);
return (
             <div>
            <p> { counter } </p>
            <button ={ ( ) => setCounter(initialState) } > Reset </button>
            </div> ); } //22

فاز سه و چهار : اضافه کردن دکمه Stop و Start

خب الان که خوب فکرشو کردم دیدم فاز سه و چهار عین همدیگه اند. تایمر رو که نوشتیم و تونستیم مقدارش رو هم ریست کنیم. حالا چطور زمان رو متوقف کنیم؟ ( بازم رفتیم سراغ کریستوفر نولان :)

من نظرم اینه که یک متغییر تعریف کنم به اسم timmerEnable و داخل هوک UseEffect فقط وقتی کانتر رو اضافه کنم که تایمر فعال بشه. کلید های استاپ و استارت هم فقط مقدار timmerEnable رو تغییر میده به این صورت:

var timmerEnable = 1;
export function Counter(){
         const initialState =0;
         const [ counter , setCounter ] = useState (initialState);
         const [ clockPulse , setclockPulse] = useState (0);
useEffect( ( ) => { 
                 if (timmerEnable == 1)
                                    {
                                       setCounter(counter+1); 
                                     }
                               }   , [clockPulse]);
setTimeout(( ) => {setclockPulse(clockPulse+1)}, 1000);
return (
                          <div> 
                          <p> {counter} </p>
                          <button ={ ( ) => setCounter(initialState)}>Reset</button>
                          <button ={( ) => timmerEnable = 0}> Stop </button>
                         <button ={( ) => timmerEnable = 1}> Start </button>
                         </div> ); } //23

رواله؟

دوتا نکته: شما می تونستید به جای اینکه if رو داخل هوک بذارید بیاید و تابع setTimeout رو توی همچین if ای قرار بدید. اما کار جذابی که ما تو فاز قبل کردیم این بود که متغییر زمان رو از متغییر شمارنده جدا کردیم. پس حالا هم بهتره با متغییر زمان مثل خود زمان رفتار کنیم. زمانی که هیچ وقت متوقف نمیشه.

** این بخش توضیح اضافه است ، اگر پیچیده شد براتون رد بشید ازش:
البته شاید اینجا اهمیتش رو درک نکنید ، من کاری که کردم اینجا رو الگو برداری کردم از مسایلی که توی مهندسی یاد گرفتیم. ببینید توی کامپیوترها یک سیگنال پالس ساعت وجود داره که مثل نبض میزنه ( یا مثل یک طبل بزرگ با ریتم ثابت) . این سیگنال فرکانس ثابت هست که میاد همه چیز رو مرتب میکنه و اصلا خبر نداره کیا دارن از سیگنالش استفاده میکنن و شایدم کسی نکنه اصلا ولی ایشون با ریتم ثابت طبل خودشو میزنه. منم سیگنال clockpulse رو از همچین چیزی الگو برداری کردم. فردا روز شاید شما توی برنامتون از این پالس ده جای دیگه هم استفاده کرده بودید ، پس معقول تر اینه که این پالس همیشه فعال باشه و timmerEnable رو داخل همون هوک چک کنیم.

نکته بعد اینکه ، توجه داشته باشید که متغییر timmerEnable باید به صورت Global تعریف بشه وگرنه اگر داخل بدنه ی کامپوننت تعریف بشه با هر بار Render دوباره مقدارش ریست میشه و عملکرد مورد انتظار ما رو نخواهد داشت( چون این متغییر فقط باید توسط کلیدهای استاپ و استارت تغییر بشه)

خب کار ما به عنوان برنامه نویس تمام شد ، همه جنبه ها هم در نظر گرفتیم و تاماااام. این هم خروجی برنامه:

:|

:|

واقعا اینو میشه منتشر کرد؟ میشه فروختش یا دست کاربر داد؟ رسیدیم به همون بحث برنامه نویس و توسعه دهنده. برنامه ما به درستی کار میده ، اما زشته :) توی اپیزود آخر ، برنامه رو آماده ی انتشار می کنیم.