مقدمه

در درس قبل، ما منطق اصلی شمارش زمان را با استفاده از useEffect و setInterval پیاده‌سازی کردیم. در این درس، ما با تکمیل منطق دکمه‌ها و بازسازی (refactor) کد برای مدیریت بهتر منابع، پروژه تایمر خود را به پایان خواهیم رساند.

بازسازی کد با useRef

در حال حاضر، ما ID تایمر setInterval را در یک متغیر محلی در داخل useEffect ذخیره می‌کنیم. این روش کار می‌کند، اما یک راه بهتر و اصولی‌تر، استفاده از هوک useRef برای نگهداری این `ID` است.

چرا از useRef استفاده کنیم؟

useRef به ما یک "جعبه" قابل تغییر (.current) می‌دهد که مقدار آن در طول تمام رندرهای کامپوننت پایدار باقی می‌ماند و تغییر آن باعث رندر مجدد نمی‌شود. این ویژگی، useRef را به ابزاری ایده‌آل برای نگهداری مقادیری تبدیل می‌کند که به چرخه رندر UI وابسته نیستند، مانند ID تایمرها یا ارجاع به عناصر DOM.

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

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

  useEffect(() => {
    if (isActive) {
      countRef.current = setInterval(() => {
        setTime((time) => time + 1);
      }, 1000);
    }

    return () => clearInterval(countRef.current);
  }, [isActive]);
  
  // ... handler functions and JSX ...
};

در این نسخه، ما یک ref به نام countRef ایجاد کرده‌ایم. در داخل useEffect، ما ID تایمر را به countRef.current اختصاص می‌دهیم. تابع پاک‌سازی نیز با خواندن countRef.current تایمر صحیح را پاک می‌کند. این روش خواناتر است و از بروز باگ‌های احتمالی مربوط به closureها جلوگیری می‌کند.

پیاده‌سازی کامل Event Handlerها

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

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

const handleStop = () => {
  setIsActive(false);
  clearInterval(countRef.current);
};

const handleReset = () => {
  setIsActive(false);
  clearInterval(countRef.current);
  setTime(0);
};

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

تابع handleStart تنها isActive را true می‌کند و useEffect مسئول راه‌اندازی interval خواهد بود. تابع handleStop، علاوه بر false کردن isActive، به صورت صریح نیز interval را پاک می‌کند تا توقف آنی باشد. تابع handleReset نیز همین کار را انجام داده و time را به صفر برمی‌گرداند.

ما همچنین اتریبیوت disabled را به دکمه‌های Start و Stop اضافه کرده‌ایم تا از کلیک‌های نابجا توسط کاربر (مثلاً کلیک روی Start زمانی که تایمر در حال اجراست) جلوگیری کنیم.

در این درس، با بازسازی کد و تکمیل منطق event handlerها، پروژه تایمر خود را به یک کامپوننت کامل و قوی تبدیل کردیم. ما با استفاده از useRef، مدیریت منابع خارجی مانند ID تایمر را بهینه کردیم و با غیرفعال کردن دکمه‌ها، تجربه کاربری بهتری را فراهم نمودیم.

با این درس، فصل «پروژه: ساخت تایمر» به پایان می‌رسد. شما اکنون می‌توانید کامپوننت‌های پیچیده‌تری بسازید که با APIهای زمانی مرورگر در تعامل هستند و چرخه حیات آنها را به درستی مدیریت می‌کنند. در فصل پایانی این دوره، به سراغ بزرگترین پروژه خود، «پروژه: مدیریت کارها (Task Manager)» خواهیم رفت و تمام مهارت‌های خود را برای ساخت یک اپلیکیشن CRUD کامل به کار خواهیم بست.