دليل شامل: تحديد الأنواع (Types) في React Hooks باستخدام TypeScript

دقائق القراءة: 6

يُعد TypeScript إضافة قوية للغة JavaScript، حيث يُمكّنك من التحقق من أنواع البيانات في شيفرتك البرمجية، مما يُسهم في بناء تطبيقات أكثر قوة ووضوحاً وسهولة في الصيانة. في هذا الدليل الشامل، سنستعرض كيفية تحديد وتعيين الأنواع (Types) بدقة على أشهر خطاطيف React (React Hooks) مثل useState، useRef، useContext، useReducer، useMemo، و useCallback، لضمان كتابة كود نظيف وخالٍ من الأخطاء.

تحديد الأنواع لخطاف useState

يُعد الخطاف useState حجر الزاوية في إدارة الحالة (State Management) داخل مكونات React الوظيفية (Functional Components). إنه بمثابة البديل الحديث والمكافئ لـ this.state الذي كان يُستخدم في مكونات الفئات (Class Components).

import * as React from "react";

export const App: React.FC = () => {
  const [counter, setCounter] = React.useState<number>(0)

  return (
    <div className="App">
      <h1>Result: { counter }</h1>
      <button onClick={() => setCounter(counter + 1)}>+</button>
      <button onClick={() => setCounter(counter - 1)}>-</button>
    </div>
  );
}

لتحديد الأنواع لخطاف useState، يجب تمرير نوع الحالة المتوقعة داخل الأقواس المعقوفة (<>) مباشرة بعد استدعاء الخطاف. على سبيل المثال، إذا كانت حالتك ستكون رقماً، يمكنك تحديدها كـ <number>. وفي حال عدم وجود قيمة ابتدائية للحالة، أو إذا كانت الحالة قد تحمل أكثر من نوع واحد، يمكنك استخدام أنواع الاتحاد (Union Types) مثل <number | null> للسماح للحالة بأن تكون إما رقماً أو null.

تحديد الأنواع لخطاف useRef

يُرجع الخطاف useRef كائناً مرجعياً قابلاً للتعديل (Mutable Ref Object) يسمح لك بالوصول المباشر إلى عناصر نموذج كائن المستند (DOM Elements) أو الاحتفاظ بقيم قابلة للتغيير بين دورات إعادة التصيير (Re-renders) دون التسبب في إعادة تصيير المكون.

import * as React from "react";

export const App: React.FC = () => {
  const myRef = React.useRef<HTMLElement | null>(null)

  return (
    <main className="App" ref={myRef}>
      <h1>My title</h1>
    </main>
  );
}

كما هو الحال مع useState، يتم تحديد الأنواع لخطاف useRef بنفس الطريقة؛ بتمرير النوع المتوقع داخل الأقواس المعقوفة (<>). في المثال أعلاه، حددنا أن المرجع يمكن أن يكون إما عنصراً من نوع HTMLElement أو null، وذلك باستخدام نوع الاتحاد <HTMLElement | null>، وهو أمر مفيد عند التعامل مع عناصر DOM التي قد لا تكون موجودة فوراً عند التحميل.

تحديد الأنواع لخطاف useContext

يُمكنك الخطاف useContext من الوصول إلى السياق (Context) المُعرف مسبقاً واستهلاكه داخل أي مكون في تطبيق React، مما يُسهل تمرير البيانات عبر شجرة المكونات دون الحاجة إلى تمريرها يدوياً كـ props في كل مستوى.

import * as React from "react";

interface IArticle {
  id: number
  title: string
}

const ArticleContext = React.createContext<IArticle[] | []>([]);

const ArticleProvider: React.FC<React.ReactNode> = ({ children }) => {
  const [articles, setArticles] = React.useState<IArticle[] | []>([
    { id: 1, title: "post 1" },
    { id: 2, title: "post 2" }
  ]);

  return (
    <ArticleContext.Provider value={{ articles }}>
      {children}
    </ArticleContext.Provider>
  );
}

const ShowArticles: React.FC = () => {
  const { articles } = React.useContext<IArticle[]>(ArticleContext);

  return (
    <div>
      {articles.map((article: IArticle) => (
        <p key={article.id}>{article.title}</p>
      ))}
    </div>
  );
};

export const App: React.FC = () => {
  return (
    <ArticleProvider>
      <h1>My title</h1>
      <ShowArticles />
    </ArticleProvider>
  );
}

في هذا المثال، نبدأ بتعريف الواجهة IArticle، التي تُحدد بنية الكائنات التي سيتعامل معها السياق. بعد ذلك، نستخدم هذه الواجهة مع الدالة React.createContext() لإنشاء سياق جديد، ونقوم بتهيئته بمصفوفة فارغة []. تجدر الإشارة إلى أنه يمكنك أيضاً استخدام null كقيمة ابتدائية إذا كان ذلك يناسب حالتك. بعد إعداد السياق، نتمكن من إدارة حالته وتعيين النوع المناسب لخطاف useContext، حيث يتوقع أن تكون القيمة المستهلكة عبارة عن مصفوفة من النوع IArticle.

تحديد الأنواع لخطاف useReducer

يُقدم الخطاف useReducer حلاً قوياً لإدارة الحالات الأكثر تعقيداً في تطبيقات React. يُعد بديلاً لـ useState في بعض السيناريوهات، خاصةً عندما تتضمن منطق تحديث الحالة عمليات معقدة أو عندما تعتمد الحالة الجديدة على الحالة السابقة بطريقة محددة، مع الأخذ في الاعتبار أن لكل منهما حالات استخدام مختلفة.

import * as React from "react";

enum ActionType {
  INCREMENT_COUNTER = "INCREMENT_COUNTER",
  DECREMENT_COUNTER = "DECREMENT_COUNTER"
}

interface IReducer {
  type: ActionType;
  count: number;
}

interface ICounter {
  result: number;
}

const initialState: ICounter = { result: 0 };
const countValue: number = 1;

const reducer: React.Reducer<ICounter, IReducer> = (state, action) => {
  switch (action.type) {
    case ActionType.INCREMENT_COUNTER:
      return { result: state.result + action.count };
    case ActionType.DECREMENT_COUNTER:
      return { result: state.result - action.count };
    default:
      return state;
  }
};

export default function App() {
  const [state, dispatch] = React.useReducer<React.Reducer<ICounter, IReducer>>(
    reducer,
    initialState
  );

  return (
    <div className="App">
      <h1>Result: {state.result}</h1>
      <button
        onClick={() => dispatch({ type: ActionType.INCREMENT_COUNTER, count: countValue })}
      >
        +
      </button>
      <button
        onClick={() => dispatch({ type: ActionType.DECREMENT_COUNTER, count: countValue })}
      >
        -
      </button>
    </div>
  );
}

في هذا المثال، نبدأ بتحديد أنواع الإجراءات (Action Types) التي ستُستخدم للتحكم في العداد. بعد ذلك، نُعرف واجهتين: IReducer لتحديد بنية كائن الإجراء، و ICounter لتحديد بنية كائن الحالة. تتوقع دالة الـ reducer استلام state من النوع ICounter و action من النوع IReducer. يقوم خطاف useReducer باستقبال دالة الـ reducer والحالة الأولية (initialState) كمعاملات، ويُرجع مصفوفة تحتوي على عنصرين: الحالة الحالية (state) ودالة الإرسال (dispatch) التي تُستخدم لتشغيل الإجراءات. لتحديد الأنواع للقيم التي يُرجعها useReducer، يتم تمرير نوع دالة الـ reducer الكاملة (React.Reducer<ICounter, IReducer>) داخل الأقواس المعقوفة (<>) عند استدعاء الخطاف. بهذا الإعداد، يصبح العداد جاهزاً للزيادة أو النقصان بشكل آمن ومُحدد الأنواع.

تحديد الأنواع لخطاف useMemo

يُستخدم الخطاف useMemo لتحسين الأداء عن طريق تخزين (Memoize) نتيجة دالة معينة. يقوم بإعادة القيمة المخزنة مؤقتاً فقط إذا لم تتغير المدخلات التابعة لها، مما يمنع إعادة حساب القيم المكلفة في كل مرة يتم فيها إعادة تصيير المكون.

const memoizedValue = React.useMemo<string>(() => {
  computeExpensiveValue(a, b)
}, [a, b])

لتحديد الأنواع لخطاف useMemo، ببساطة قم بتمرير نوع البيانات التي تتوقع أن تُرجعها الدالة المخزنة مؤقتاً داخل الأقواس المعقوفة (<>). في المثال الموضح، يتوقع الخطاف أن تكون القيمة المُعادة من نوع string.

تحديد الأنواع لخطاف useCallback

يُستخدم الخطاف useCallback لتخزين (Memoize) دالة معينة نفسها، وليس نتيجتها. هذا يمنع إعادة إنشاء الدالة في كل مرة يُعاد فيها تصيير المكون، مما يُسهم في تحسين الأداء، خاصةً عند تمرير هذه الدوال كـ props إلى مكونات فرعية تعتمد على مرجع الدالة لتجنب إعادة التصيير غير الضرورية.

type CallbackType = (...args: string[]) => void

const memoizedCallback = React.useCallback<CallbackType>(() => {
  doSomething(a, b);
}, [a, b]);

في هذا المثال، نقوم بتعريف نوع مخصص باسم CallbackType، والذي يصف توقيع الدالة التي نرغب في تخزينها مؤقتاً. يُشير هذا النوع إلى أن الدالة تتوقع استقبال عدد متغير من المعاملات من نوع string، ولا تُعيد أي قيمة (void). بعد ذلك، نقوم بتعيين هذا النوع على خطاف useCallback. ميزة TypeScript هنا أنها ستُصدر تحذيراً أو خطأً (TypeScript will yell at you) إذا حاولت تمرير دالة بتوقيع غير متوافق مع CallbackType، أو إذا كانت مصفوفة التبعيات (Dependencies Array) غير صحيحة، مما يضمن سلامة الأنواع ويمنع الأخطاء المحتملة.

الخلاصة التقنية

يُقدم دمج TypeScript مع خطاطيف React قفزة نوعية في تطوير تطبيقات الويب، حيث يُمكن المطورين من بناء شيفرة برمجية أكثر موثوقية وقابلية للصيانة. من خلال تحديد الأنواع بدقة لخطاطيف مثل useState و useContext و useReducer، نضمن اكتشاف الأخطاء المحتملة في مرحلة التطوير بدلاً من وقت التشغيل، مما يُقلل من الأخطاء البرمجية ويُحسن من تجربة المطور. إن فهم كيفية تطبيق الأنواع على هذه الخطاطيف ليس مجرد ممارسة جيدة، بل هو ضرورة في المشاريع الكبيرة والمعقدة لضمان الوضوح، وتحسين قابلية القراءة، وتسهيل التعاون بين أعضاء الفريق. هذا الدليل يُعد نقطة انطلاق ممتازة لأي مطور يسعى لرفع مستوى جودة كوده باستخدام قوة TypeScript في بيئة React.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *