إزاي تكون أفضل في React إعادة استخدام الكود الجزء الأول

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

How to be Better in React Code Reusability Part1

ملخص

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

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

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

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

الخطافات المخصصة هي دوال 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)

يسمح لك التركيب ببناء واجهة مستخدم مرنة وقابلة لإعادة الاستخدام دون تضخم الـ props. بدلاً من إنشاء متغيرات لكل حالة استخدام، قم ببناء لبنات بناء قابلة للتركيب.

نمط children

// Reusable Card component — doesn't know what it contains
export function Card({ children, title }) {
  return (
    <div className="border rounded-lg p-4">
      {title && <h2 className="font-bold mb-2">{title}</h2>}
      {children}
    </div>
  );
}

// Reuse for anything
export function UserCard({ user }) {
  return (
    <Card title={user.name}>
      <p>{user.email}</p>
      <button>Follow</button>
    </Card>
  );
}

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

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

// LayoutComponent with multiple insertion points
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>
  );
}

// Usage
<PageLayout
  header={<Header />}
  sidebar={<Navigation />}
  main={<PostList />}
  footer={<Footer />}
/>

هذا أكثر مرونة من بناء مكون متجانس (monolithic) بعشرات الـ props الاختيارية.

مكتبات 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
  • اكتشاف النقر في الخارج
  • خطافات الرسوم المتحركة (Animation hooks)

أنت توفر التنسيق عبر 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) بين فوائد كليهما: مكونات مبنية مسبقًا مع مرونة headless.

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

يقوم 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' 
  
<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: 'Disabled'
    disabled: true
  
;

قم بتشغيل npm run storybook لرؤية جميع المتغيرات بشكل تفاعلي. يمكنك اختبار إمكانية الوصول، والسلوك المتجاوب، وتغييرات الحالة دون لمس تطبيقك.

نشرة أسبوعية مجانية

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

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

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