مقدمه

در درس قبل، ما رابط کاربری تایمر خود را با JSX طراحی کردیم. اکنون زمان آن است که به این UI جان ببخشیم و منطق اصلی شمارش زمان را پیاده‌سازی کنیم. برای این کار، ما باید از یک «عارضه جانبی» (side effect) استفاده کنیم، زیرا نیاز به تعامل با یک API مرورگر، یعنی setInterval، داریم. همانطور که در فصل ششم یاد گرفتیم، ابزار اصلی ما برای مدیریت عوارض جانبی، هوک useEffect است.

پیاده‌سازی منطق شمارش با useEffect

ما می‌خواهیم که تایمر تنها زمانی شروع به شمارش کند که وضعیت آن فعال باشد (یعنی isActive برابر با true باشد). بنابراین، افکت ما باید به متغیر state مربوط به isActive وابسته باشد.

استفاده از setInterval و clearInterval

تابع setInterval در جاوااسکریپت یک تابع را در فواصل زمانی مشخصی به صورت مکرر اجرا می‌کند. این تابع یک ID برای تایمر ایجاد شده برمی‌گرداند که می‌توانیم از آن برای متوقف کردن تایمر با استفاده از clearInterval استفاده کنیم. این پاک‌سازی برای جلوگیری از نشت حافظه (memory leak) ضروری است.

Copy Icon JAVASCRIPT (React)
import React, { useState, useEffect } from 'react';

const Timer = () => {
  const [isActive, setIsActive] = useState(false);
  const [time, setTime] = useState(0);

  useEffect(() => {
    let interval = null;

    if (isActive) {
      interval = setInterval(() => {
        setTime((time) => time + 1);
      }, 1000);
    } else {
      clearInterval(interval);
    }

    return () => clearInterval(interval);
  }, [isActive]);

  // ... return JSX with buttons and time display ...
};

بیایید این کد را به دقت بررسی کنیم. ما یک هوک useEffect تعریف کرده‌ایم که به متغیر state مربوط به isActive وابسته است. در داخل افکت، ما بررسی می‌کنیم که آیا تایمر باید فعال باشد یا خیر.

اگر isActive برابر با true باشد، ما یک setInterval راه‌اندازی می‌کنیم که هر ۱۰۰۰ میلی‌ثانیه (۱ ثانیه)، تابع setTime را فراخوانی می‌کند. توجه کنید که ما از «فرم تابع به‌روزرسان» (setTime(time => time + 1)) استفاده کرده‌ایم. این کار تضمین می‌کند که ما همیشه بر اساس آخرین مقدار state، آن را به‌روزرسانی می‌کنیم و از مشکلات مربوط به stale state در closureها جلوگیری می‌کند. اگر isActive برابر با false باشد، ما هر interval فعالی را پاک می‌کنیم. در نهایت، ما یک تابع پاک‌سازی (cleanup function) را برمی‌گردانیم که تضمین می‌کند با هر بار اجرای مجدد افکت یا هنگام unmount شدن کامپوننت، تایمر قبلی حتماً پاک شود.

اتصال منطق به دکمه‌ها

اکنون که منطق اصلی تایمر را داریم، باید آن را به دکمه‌های UI خود متصل کنیم. ما توابع event handler برای شروع، توقف و ریست کردن تایمر خواهیم ساخت.

Copy Icon JAVASCRIPT (React)
// Inside the Timer component
const handleStart = () => {
  setIsActive(true);
};

const handleStop = () => {
  setIsActive(false);
};

const handleReset = () => {
  setIsActive(false);
  setTime(0);
};

// In the return JSX
<div className="buttons">
  <button onClick={handleStart}>Start</button>
  <button onClick={handleStop}>Stop</button>
  <button onClick={handleReset}>Reset</button>
</div>

تابع handleStart با true کردن isActive، باعث اجرای افکت و شروع setInterval می‌شود. تابع handleStop با false کردن isActive، باعث اجرای مجدد افکت و پاک شدن interval می‌شود. تابع handleReset نیز تایمر را متوقف کرده و زمان را به صفر برمی‌گرداند.

در این درس، با ترکیب هوک‌های useState و useEffect، منطق اصلی یک تایمر کارا را پیاده‌سازی کردیم. ما یاد گرفتیم که چگونه با استفاده از setInterval یک عارضه جانبی زمان‌بندی شده ایجاد کنیم و چگونه با استفاده از تابع پاک‌سازی useEffect، از نشت حافظه و رفتارهای ناخواسته جلوگیری کنیم.

اکنون تایمر ما کار می‌کند، اما می‌توانیم آن را بهتر کنیم. در درس پایانی این فصل، به «توقف، ریست و پاک‌سازی تایمر» با جزئیات بیشتری خواهیم پرداخت و کد خود را برای مدیریت بهتر وضعیت‌های مختلف، بازسازی خواهیم کرد.