إزاي تبقى أفضل في React Code Reusability الجزء الأول

تم التحديث: ٢٧ مارس ٢٠٢٦

How to be Better in React Code Reusability Part1

ملخص

تعد الخطافات المخصصة (Custom hooks) هي الآلية الأساسية لإعادة الاستخدام في 2026 React. قم باستخراج المنطق المرتبط بالحالة (stateful logic) إلى خطافات، واستخدم تركيب المكونات (component composition) مع children والفتحات (slots) لتخطيطات مرنة، واستفد من مكتبات واجهة المستخدم بدون واجهة رسومية (headless UI) مثل Radix UI للحصول على مكونات سهلة الوصول وغير منسقة. يتيح لك Storybook 9 توثيق واختبار هذه الأنماط بصريًا، مع اختبارات المكونات المدمجة وفحوصات إمكانية الوصول.

تعد إعادة استخدام الكود أحد الوعود الأساسية لـ React، ولكن الحديث عنها أسهل من تحقيقها. يمكنك نسخ ولصق JSX، أو إنشاء مكونات مغلفة (wrapper components)، أو تغليف كل شيء في مكونات عالية الرتبة (higher-order components) — ولكن لا يوجد أي من هذه الأساليب يتوسع حقًا. لقد تطور React إلى ما هو أبعد من تلك الأنماط.

في عام 2026، تعني إعادة الاستخدام في React التفكير في استخراج المنطق من خلال الخطافات المخصصة، والمرونة البصرية من خلال التركيب، والتحكم السلوكي من خلال مكتبات headless UI. يركز هذا الدليل على الجزء الأول: التقنيات الأساسية التي تحتاجها لكتابة كود React يتبع مبدأ DRY (لا تكرر نفسك) ويكون قابلاً للصيانة والاختبار.

الخطافات المخصصة: آلية إعادة الاستخدام الأساسية

الخطافات المخصصة (Custom hooks) هي دوال JavaScript تستخدم خطافات React داخليًا. فهي تغلف المنطق المرتبط بالحالة بحيث يمكنك إعادة استخدامه عبر المكونات دون تكرار الكود.

لماذا تتفوق الخطافات المخصصة على الأنماط الأخرى:

  • لا تلوث التسلسل الهرمي للمكونات (على عكس HOCs)
  • لا يوجد "تنقيب في الخصائص" (prop drilling) (على عكس render props)
  • لا يوجد عبء إضافي للمكونات المغلفة
  • سهولة دمج خطافات متعددة في مكون واحد
  • سهولة الاختبار بشكل منفصل

بناء أول خطاف مخصص لك

لنقم باستخراج منطق التعامل مع النماذج (forms) إلى خطاف قابل لإعادة الاستخدام:

// useForm.js
import { useState } from 'React';

export function useForm(initialValues, onSubmit) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsSubmitting(true);
    try {
      await onSubmit(values);
    } catch (error) {
      setErrors({ submit: error.message });
    } finally {
      setIsSubmitting(false);
    }
  };

  return {
    values,
    errors,
    isSubmitting,
    handleChange,
    handleSubmit,
  };
}

الآن يمكن لأي مكون استخدام هذا:

import { useForm } from './useForm';

export function LoginForm() {
  const { values, handleChange, handleSubmit, isSubmitting } = useForm(
    { email: '', password: '' },
    async (data) => {
      const response = await fetch('/API/login', {
        method: 'POST',
        body: JSON.stringify(data)
      });
      if (!response.ok) throw new Error('Login failed');
      return response.json();
    }
  );

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        value={values.email}
        onChange={handleChange}
        type="email"
      />
      <input
        name="password"
        value={values.password}
        onChange={handleChange}
        type="password"
      />
      <button disabled={isSubmitting}>Login</button>
    </form>
  );
}

أنماط الخطافات المخصصة الشائعة

useAsync — لجلب البيانات:

function useAsync(asyncFunction, immediate = true) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(immediate);

  const execute = useCallback(async () => {
    setLoading(true);
    try {
      const result = await asyncFunction();
      setData(result);
      setError(null);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [asyncFunction]);

  useEffect(() => {
    if (immediate) execute();
  }, [execute, immediate]);

  return { data, error, loading, execute ;
}

useLocalStorage — للحالة المستمرة:

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

تركيب المكونات مع children والفتحات (Slots)

يتيح لك التركيب (Composition) بناء واجهات مستخدم مرنة وقابلة لإعادة الاستخدام دون انفجار في عدد الخصائص (props). بدلاً من إنشاء متغيرات لكل حالة استخدام، قم ببناء لبنات بناء قابلة للتركيب.

نمط children

// مكون Card قابل لإعادة الاستخدام — لا يعرف ما يحتويه
export function Card({ children, title }) {
  return (
    <div className="border rounded-lg p-4">
      {title && <h2 className="font-bold mb-2">{title}</h2>}
      {children}
    </div>
  );
}

// إعادة الاستخدام لأي شيء
export function UserCard({ user }) {
  return (
    <Card title={user.name}>
      <p>{user.email}</p>
      <button>Follow</button>
    </Card>
  );
}

نمط الفتحات (متقدم)

عندما تحتاج إلى مزيد من التحكم في الموضع، استخدم الفتحات المسماة (named slots):

// LayoutComponent مع نقاط إدخال متعددة
export function PageLayout({ header, sidebar, main, footer }) {
  return (
    <div className="grid grid-cols-12 gap-4">
      <header className="col-span-12">{header}</header>
      <aside className="col-span-3">{sidebar}</aside>
      <main className="col-span-9">{main}</main>
      <footer className="col-span-12">{footer}</footer>
    </div>
  );
}

// الاستخدام
<PageLayout
  header={<Header />}
  sidebar={<Navigation />}
  main={<PostList />}
  footer={<Footer />}
/>

هذا أكثر مرونة من بناء مكون ضخم يحتوي على عشرات الخصائص الاختيارية.

مكتبات Headless UI: Radix و Ark UI و React Aria

توفر مكتبات Headless UI مكونات غير منسقة وسهلة الوصول. تحصل على السلوك وإمكانية الوصول مجانًا وتقوم بتنسيقها بالطريقة التي تريدها.

مثال على Radix UI

import * as Dialog from '@radix-ui/React-dialog';
import * as Select from '@radix-ui/React-select';

export function UserSettingsDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger asChild>
        <button>Open Settings</button>
      </Dialog.Trigger>

      <Dialog.Portal>
        <Dialog.Overlay />
        <Dialog.Content>
          <Dialog.Title>User Settings</Dialog.Title>

          <Select.Root>
            <Select.Trigger>
              <Select.Value placeholder="Select language" />
            </Select.Trigger>
            <Select.Content>
              <Select.Item value="en">English</Select.Item>
              <Select.Item value="ar">Arabic</Select.Item>
            </Select.Content>
          </Select.Root>

          <Dialog.Close asChild>
            <button>Save</button>
          </Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

يتعامل Radix مع:

  • التنقل عبر لوحة المفاتيح (Tab، Escape، مفاتيح الأسهم)
  • إدارة التركيز (Focus management)
  • سمات ARIA
  • اكتشاف النقر في الخارج
  • خطافات الرسوم المتحركة

أنت توفر التنسيق عبر CSS-in-JS أو Tailwind أو CSS modules.

React Aria (من Adobe)

يوفر React Aria خطافات لبناء مكونات سهلة الوصول:

import { useButton } from 'React-aria';
import { useToggleState } from 'React-stately';

function MyButton(props) {
  let state = useToggleState(props);
  let ref = useRef(null);
  let { buttonProps } = useButton(
    {
      onPress: () => state.toggle(),
      ...props,
    },
    ref
  );

return (
    <button
      ref={ref}
      {...buttonProps}
      style={{ background: state.isSelected ? 'blue' : 'gray' }}
    >
      {props.children}
    </button>
  );
}

تعد Ark UI، التي يصونها فريق Chakra UI، خيارًا آخر لـ headless UI في نفس الفئة — مكوناتها غير منسقة ومبنية فوق آلات الحالة Zag.js، مع نفس التكافؤ بين React/Solid/Vue/Svelte كـ API واحد. إذا كنت تستخدم بالفعل نظام Chakra البيئي، فإن Ark تمنحك الأساس غير المنسق مع الحفاظ على التوافق مع طبقة Chakra المنسقة في الأعلى.

Storybook 9 لتوثيق المكونات

يقوم Storybook بعزل وتوثيق مكوناتك. إنه لا يقدر بثمن لبناء مكتبة مكونات واختبار قابلية إعادة الاستخدام.

الإعداد الأساسي

// Button.stories.ts
import type { Meta, StoryObj } from '@storybook/React';
import { Button } from './Button';

const meta = {
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    onClick: { action: 'clicked' },
  },
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: {
    children: 'Click me',
    variant: 'primary',
  },
};

export const Secondary: Story = {
  args: {
    children: 'Secondary button',
    variant: 'secondary',
  },
};

export const Disabled: Story = {
  args: {
    children
نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.