الدليل العملي السريع لتعلّم React: أهم المفاهيم والخطافات مع أمثلة واقعية

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

إذا كنت تبدأ رحلتك مع مكتبة React أو تريد مراجعة أهم مفاهيمها بسرعة وبأسلوب عملي، فهذا الدليل صُمّم ليكون مرجعاً مركزاً يغطي الأساسيات والموضوعات المتقدمة التي يحتاجها المطور في المشاريع الحقيقية.

ستجد هنا شرحاً مبسطاً ومدعوماً بأمثلة واقعية يساعدك على فهم كيفية كتابة واجهات تفاعلية قابلة لإعادة الاستخدام، وكيفية إدارة الحالة وتحسين الأداء والتعامل مع البيانات والأحداث داخل تطبيقات React.

ملخص بصري شامل لأهم مفاهيم React وخطافات Hooks مع أمثلة عملية للمطورين

محتويات الدليل

  • أساسيات React
  • JSX والعناصر
  • المكونات وProps
  • القوائم وKeys
  • الأحداث والتفاعل مع المستخدم
  • الخطافات الأساسية مثل useState وuseEffect وuseRef
  • تحسين الأداء باستخدام React.memo وuseCallback وuseMemo
  • Context وuseContext
  • useReducer وكتابة الخطافات المخصصة
  • قواعد استخدام Hooks

ما هي React ولماذا يعتمد عليها المطورون؟

React مكتبة JavaScript شهيرة لبناء واجهات المستخدم، وتتميز بفكرة تقسيم الواجهة إلى مكونات صغيرة مستقلة يمكن إعادة استخدامها بسهولة. هذا الأسلوب يجعل الشيفرة أكثر تنظيماً، ويُسهّل تطوير التطبيقات الكبيرة وصيانتها مع مرور الوقت.

الميزة الأهم في React أنها تمنحك طريقة واضحة لإدارة البيانات داخل الواجهة، ثم إعادة تحديث العناصر المتأثرة فقط عند تغيّر الحالة، بدلاً من إعادة بناء الصفحة بالكامل.

أساسيات React

فهم بنية JSX

تعتمد تطبيقات React غالباً على صيغة JSX، وهي أسلوب كتابة يشبه HTML لكنه يعمل داخل JavaScript. هذا يجعل بناء الواجهات أكثر وضوحاً وسهولة في القراءة.

/* JSX lets us write UI in a syntax similar to HTML */
<div>Hello React!</div>

رغم أن JSX يبدو مثل HTML، فإنه ليس مفهوماً مباشرة من المتصفح. لذلك يتم تحويله أثناء بناء المشروع إلى استدعاءات مثل React.createElement() باستخدام أدوات مثل Babel.

<div>Hello React!</div>

React.createElement('div', null, 'Hello React!');

كيف يتحول JSX إلى JavaScript؟

const greeting = <div>Hello React!</div>;

"use strict";
const greeting2 = React.createElement("div", null, "Hello React!");

هذه الفكرة مهمة لأنها تفسر لماذا توجد اختلافات بسيطة بين كتابة JSX وكتابة HTML التقليدي.

أهم الفروقات بين JSX وHTML

  • استخدام className بدلاً من class.
  • إغلاق العناصر الذاتية مثل input وimg بهذه الصورة: <input />.
  • كتابة خصائص الأنماط بصيغة camelCase.
<div id="header">
  <h1 className="title">Hello React!</h1>
</div>

<input type="email" />

إضافة الأنماط داخل العناصر

يمكنك تمرير الأنماط إلى الخاصية style على شكل كائن JavaScript، وليس كسلسلة نصية كما في HTML.

<h1 style={{ color: 'blue', fontSize: 22, padding: '0.5em 1em' }}>
  Hello React!
</h1>

استخدام التعبيرات داخل JSX

من أقوى مزايا JSX أنه يسمح بإدراج القيم والتعبيرات عبر الأقواس المعقوفة {}.

const year = 2021;
const greeting = <div>Hello React in {year}</div>;
const goodbye = <div>Goodbye previous year: {year - 1}</div>;

العناصر المتداخلة والتعليقات

const greeting = (
  <div>
    {/* This is a single line comment */}
    <h1>Hello!</h1>
    <p>Welcome to React</p>
  </div>
);

عرض التطبيق باستخدام ReactDOM.render()

لكي يظهر تطبيقك داخل الصفحة، تحتاج عادةً إلى:

  1. استيراد React وReactDOM.
  2. إنشاء عنصر جذري أو مكوّن رئيسي.
  3. ربطه بعنصر داخل الصفحة مثل root.
import React from "react";
import ReactDOM from "react-dom";

const App = <h1>Hello React!</h1>;

ReactDOM.render(App, document.getElementById("root"));

المكونات وخصائص Props

ما هي المكونات في React؟

المكونات هي وحدات مستقلة تعيد عناصر JSX. وهي حجر الأساس في بناء تطبيقات React. توجد طريقتان شائعتان لكتابتها: المكونات الدالية والمكونات الصفّية، لكن الاعتماد الأكبر اليوم يكون على المكونات الدالية.

function Header() {
  return <h1>Hello React</h1>;
}

const HeaderArrow = () => <h1>Hello React</h1>;
class Header extends React.Component {
  render() {
    return <h1>Hello React</h1>;
  }
}

تشغيل المكوّن داخل التطبيق

const Header = () => <h1>Hello React</h1>;

ReactDOM.render(<Header />, document.getElementById("root"));

إعادة استخدام المكونات

تسمح لك المكونات بإعادة استعمال نفس البنية في أكثر من صفحة أو جزء من التطبيق، وهو ما يقلل التكرار ويجعل التطوير أسرع وأكثر اتساقاً.

function IndexPage() {
  return (
    <div>
      <Header />
      <Hero />
      <Footer />
    </div>
  );
}

function AboutPage() {
  return (
    <div>
      <Header />
      <About />
      <Testimonials />
      <Footer />
    </div>
  );
}

تمرير البيانات عبر Props

Props هي الطريقة القياسية لتمرير البيانات من مكوّن أب إلى مكوّن ابن.

const username = "John";

ReactDOM.render(<Header username={username} />, document.getElementById("root"));

function Header(props) {
  return <h1>Hello {props.username}</h1>;
}

من المهم جداً عدم تعديل props مباشرة داخل المكوّن، لأنها تُعامل كبيانات للقراءة فقط.

function Header(props) {
  props.username = "Doug"; // wrong
  return <h1>Hello {props.username}</h1>;
}

الخاصية children

تُستخدم children عندما تريد أن يغلّف مكوّنٌ ما مكوّناتٍ أخرى بداخله، مثل مكوّنات التخطيط العام.

function Layout(props) {
  return <div className="container">{props.children}</div>;
}

function IndexPage() {
  return (
    <Layout>
      <Header />
      <Hero />
      <Footer />
    </Layout>
  );
}

العرض الشرطي داخل المكونات

function Header() {
  const isAuthenticated = checkAuth();

  if (isAuthenticated) {
    return <AuthenticatedApp />;
  }

  return <UnAuthenticatedApp />;
}
function Header() {
  const isAuthenticated = checkAuth();

  return (
    <nav>
      {isAuthenticated || <Logo />}
      {isAuthenticated ? <AuthenticatedApp /> : <LoginScreen />}
      {isAuthenticated && <Footer />}
    </nav>
  );
}

استخدام Fragments

تُفيد Fragments عندما تريد إرجاع أكثر من عنصر دون إضافة عنصر زائد إلى شجرة DOM.

function Header() {
  const isAuthenticated = checkAuth();

  return (
    <nav>
      <Logo />
      {isAuthenticated ? (
        <>
          <AuthenticatedApp />
          <Footer />
        </>
      ) : (
        <Login />
      )}
    </nav>
  );
}

القوائم وKeys

تحويل المصفوفات إلى عناصر

تعتمد React غالباً على الدالة .map() لتحويل البيانات إلى عناصر مرئية.

const people = ["John", "Bob", "Fred"];
const peopleList = people.map(person => <p>{person}</p>);
function App() {
  const people = ['John', 'Bob', 'Fred'];

  return (
    <ul>
      {people.map(person => <Person name={person} />)}
    </ul>
  );
}

function Person({ name }) {
  return <p>This person's name is: {name}</p>;
}

لماذا نحتاج إلى key؟

تحتاج كل عنصر داخل قائمة إلى قيمة key فريدة تساعد React على تتبّع العناصر وتحديثها بكفاءة عند تغير البيانات.

function App() {
  const people = [
    { id: 'Ksy7py', name: 'John' },
    { id: '6eAdl9', name: 'Bob' },
    { id: '8fTr91', name: 'Fred' },
  ];

  return (
    <ul>
      {people.map(person => (
        <Person key={person.id} name={person.name} />
      ))}
    </ul>
  );
}

عند عدم توفر معرف فريد، يمكن استخدام الفهرس index بحذر:

{people.map((person, i) => <Person key={i} name={person} />)}

التعامل مع الأحداث في React

التعامل مع الأحداث في React يشبه HTML من حيث الفكرة، لكنه يختلف في طريقة الكتابة. على سبيل المثال، نستخدم onClick بدلاً من onclick، ونمرر مرجع الدالة داخل أقواس معقوفة.

function handleToggleTheme() {
  // code to toggle app theme
}

<button onClick={handleToggleTheme}>Toggle Theme</button>

أهم الأحداث التي يجب معرفتها

  • onClick للتعامل مع النقر.
  • onChange لتغيّر قيم الحقول.
  • onSubmit لإرسال النماذج.
function App() {
  function handleInputChange(event) {
    const inputText = event.target.value;
    const inputName = event.target.name;
  }

  function handleClick(event) {
    console.log('clicked!');
    const eventType = event.type;
    const eventTarget = event.target;
  }

  function handleSubmit(event) {
    event.preventDefault();
    const formElements = event.target.elements;
    const inputValue = event.target.elements.emailAddress.value;
  }

  return (
    <form onSubmit={handleSubmit}>
      <input id="emailAddress" type="email" name="email" onChange={handleInputChange} />
      <button onClick={handleClick}>Submit</button>
    </form>
  );
}

الخطافات الأساسية في React

إدارة الحالة باستخدام useState

يُعد useState من أهم الخطافات في React، لأنه يتيح لك تخزين قيم قابلة للتغيير داخل المكونات الدالية.

import React, { useState } from "react";

function App() {
  const [language] = useState("javascript");
  return <div>I am learning {language}</div>;
}

يمكنك أيضاً الحصول على دالة تحديث مرتبطة بالحالة:

function App() {
  const [language, setLanguage] = React.useState("javascript");

  return (
    <div>
      <button onClick={() => setLanguage("python")}>Learn Python</button>
      <p>I am now learning {language}</p>
    </div>
  );
}

إدارة أكثر من قيمة حالة

function App() {
  const [language, setLanguage] = React.useState("python");
  const [yearsExperience, setYearsExperience] = React.useState(0);

  return (
    <div>
      <button onClick={() => setLanguage("javascript")}>Change language to JS</button>
      <input
        type="number"
        value={yearsExperience}
        onChange={event => setYearsExperience(event.target.value)}
      />
      <p>I am now learning {language}</p>
      <p>I have {yearsExperience} years of experience</p>
    </div>
  );
}

تحديث الكائنات داخل الحالة

function App() {
  const [developer, setDeveloper] = React.useState({
    language: "",
    yearsExperience: 0
  });

  function handleChangeYearsExperience(event) {
    const years = event.target.value;
    setDeveloper({ ...developer, yearsExperience: years });
  }

  return (
    <div>
      <button
        onClick={() =>
          setDeveloper({ language: "javascript", yearsExperience: 0 })
        }
      >
        Change language to JS
      </button>
      <input
        type="number"
        value={developer.yearsExperience}
        onChange={handleChangeYearsExperience}
      />
      <p>I am now learning {developer.language}</p>
      <p>I have {developer.yearsExperience} years of experience</p>
    </div>
  );
}

عندما تعتمد الحالة الجديدة على السابقة

function App() {
  const [developer, setDeveloper] = React.useState({
    language: "",
    yearsExperience: 0,
    isEmployed: false
  });

  function handleToggleEmployment() {
    setDeveloper(prevState => {
      return {
        ...prevState,
        isEmployed: !prevState.isEmployed
      };
    });
  }

  return (
    <button onClick={handleToggleEmployment}>
      Toggle Employment Status
    </button>
  );
}

التأثيرات الجانبية باستخدام useEffect

يُستخدم useEffect لتنفيذ المهام الجانبية مثل جلب البيانات من واجهة برمجية، أو تعديل DOM، أو الاشتراك في أحداث خارجية.

import React, { useState, useEffect } from 'react';

function App() {
  const [colorIndex, setColorIndex] = useState(0);
  const colors = ["blue", "green", "red", "orange"];

  useEffect(() => {
    document.body.style.backgroundColor = colors[colorIndex];
  });

  function handleChangeColor() {
    const nextIndex = colorIndex + 1 === colors.length ? 0 : colorIndex + 1;
    setColorIndex(nextIndex);
  }

  return <button onClick={handleChangeColor}>Change background color</button>;
}

مصفوفة الاعتمادات

يمكنك التحكم في وقت تشغيل التأثير عبر مصفوفة الاعتمادات، وهي الوسيلة الأهم لمنع التشغيل غير الضروري.

useEffect(() => {
  document.body.style.backgroundColor = colors[colorIndex];
}, [colorIndex]);

تنظيف التأثيرات

عند إنشاء مستمع أحداث أو اشتراك خارجي، يجب تنظيفه عند إزالة المكوّن لتفادي المشكلات وتسرب الذاكرة.

function MouseTracker() {
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  React.useEffect(() => {
    window.addEventListener("mousemove", handleMouseMove);

    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, []);

  function handleMouseMove(event) {
    setMousePosition({ x: event.pageX, y: event.pageY });
  }

  return (
    <div>
      <h1>The current mouse position is:</h1>
      <p>X: {mousePosition.x}, Y: {mousePosition.y}</p>
    </div>
  );
}

جلب البيانات من API

const endpoint = "https://api.github.com/users/reedbarger";

function App() {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    fetch(endpoint)
      .then(response => response.json())
      .then(data => setUser(data));
  }, []);
}
function App() {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    getUser();
  }, []);

  async function getUser() {
    const response = await fetch(endpoint);
    const data = await response.json();
    setUser(data);
  }
}

المراجع باستخدام useRef

يتيح لك useRef إنشاء مرجع مباشر إلى عنصر أو قيمة لا يؤدي تغييرها إلى إعادة التصيير. من أشهر الاستخدامات: التركيز على حقل إدخال أو تعديل خصائصه برمجياً.

function App() {
  const [query, setQuery] = React.useState("react hooks");
  const searchInput = useRef(null);

  function handleClearSearch() {
    searchInput.current.value = "";
    searchInput.current.focus();
  }

  return (
    <form>
      <input
        type="text"
        onChange={event => setQuery(event.target.value)}
        ref={searchInput}
      />
      <button type="submit">Search</button>
      <button type="button" onClick={handleClearSearch}>Clear</button>
    </form>
  );
}

تحسين الأداء في React

منع إعادة التصيير غير الضرورية عبر React.memo

يساعد React.memo في حفظ نتيجة المكوّن ومنع إعادة تصييره إذا لم تتغير خصائصه. هذا مفيد جداً في القوائم أو المكونات الثقيلة.

function App() {
  const [skill, setSkill] = React.useState('');
  const [skills, setSkills] = React.useState(['HTML', 'CSS', 'JavaScript']);

  function handleChangeInput(event) {
    setSkill(event.target.value);
  }

  function handleAddSkill() {
    setSkills(skills.concat(skill));
  }

  return (
    <>
      <input onChange={handleChangeInput} />
      <button onClick={handleAddSkill}>Add Skill</button>
      <SkillList skills={skills} />
    </>
  );
}

const SkillList = React.memo(({ skills }) => {
  console.log('rerendering');
  return (
    <ul>
      {skills.map((skill, i) => <li key={i}>{skill}</li>)}
    </ul>
  );
});

تثبيت الدوال باستخدام useCallback

عندما تمرر دالة من مكوّن أب إلى مكوّن ابن، قد تتسبب إعادة إنشاء هذه الدالة في كل تصيير بكسر فائدة React.memo. هنا يأتي دور useCallback.

function App() {
  const [skill, setSkill] = React.useState('');
  const [skills, setSkills] = React.useState(['HTML', 'CSS', 'JavaScript']);

  function handleChangeInput(event) {
    setSkill(event.target.value);
  }

  function handleAddSkill() {
    setSkills(skills.concat(skill));
  }

  const handleRemoveSkill = React.useCallback((skill) => {
    setSkills(skills.filter(s => s !== skill));
  }, [skills]);

  return (
    <>
      <input onChange={handleChangeInput} />
      <button onClick={handleAddSkill}>Add Skill</button>
      <SkillList skills={skills} handleRemoveSkill={handleRemoveSkill} />
    </>
  );
}

const SkillList = React.memo(({ skills, handleRemoveSkill }) => {
  console.log('rerendering');
  return (
    <ul>
      {skills.map(skill => (
        <li key={skill} onClick={() => handleRemoveSkill(skill)}>
          {skill}
        </li>
      ))}
    </ul>
  );
});

تخزين النتائج المحسوبة عبر useMemo

إذا كانت لديك عملية حسابية مكلفة مثل فلترة قائمة كبيرة، فإن useMemo يساعدك على حفظ النتيجة وإعادة حسابها فقط عند تغيّر المدخلات.

function SearchSkills({ skills }) {
  const [searchTerm, setSearchTerm] = React.useState('');

  const searchResults = React.useMemo(() => {
    return skills.filter(s => s.includes(searchTerm));
  }, [skills, searchTerm]);

  function handleSearchInput(event) {
    setSearchTerm(event.target.value);
  }

  return (
    <>
      <input onChange={handleSearchInput} />
      <ul>
        {searchResults.map((result, i) => (
          <li key={i}>{result}</li>
        ))}
      </ul>
    </>
  );
}

المفاهيم المتقدمة في React

حل مشكلة تمرير Props المتكرر باستخدام Context

عندما تحتاج إلى تمرير البيانات عبر عدة مستويات من المكونات، قد تقع في مشكلة تُعرف باسم props drilling. هنا يفيد Context.

function App() {
  const [user] = React.useState({ name: "Fred" });
  return <Main user={user} />;
}

const Main = ({ user }) => (
  <>
    <Header user={user} />
    <div>Main app content...</div>
  </>
);

const Header = ({ user }) => <header>Welcome, {user.name}!</header>;

وباستخدام Context:

const UserContext = React.createContext();

function App() {
  const [user] = React.useState({ name: "Fred" });

  return (
    <UserContext.Provider value={user}>
      <Main />
    </UserContext.Provider>
  );
}

const Main = () => (
  <>
    <Header />
    <div>Main app content</div>
  </>
);

const Header = () => (
  <UserContext.Consumer>
    {user => <header>Welcome, {user.name}!</header>}
  </UserContext.Consumer>
);

استخدام useContext

function Header() {
  const user = React.useContext(UserContext);
  return <header>Welcome, {user.name}!</header>;
}

إدارة الحالة المعقدة بواسطة useReducer

عندما تصبح تحديثات الحالة أكثر تعقيداً، يكون useReducer خياراً منظماً ومرناً، خاصة مع الحالات التي تعتمد على أنواع متعددة من الإجراءات.

const initialState = { username: "", isAuth: false };

function reducer(state, action) {
  switch (action.type) {
    case "LOGIN":
      return { username: action.payload.username, isAuth: true };
    case "SIGNOUT":
      return { username: "", isAuth: false };
    default:
      return state;
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  function handleLogin() {
    dispatch({ type: "LOGIN", payload: { username: "Ted" } });
  }

  function handleSignout() {
    dispatch({ type: "SIGNOUT" });
  }

  return (
    <>
      Current user: {state.username}, isAuthenticated: {String(state.isAuth)}
      <button onClick={handleLogin}>Login</button>
      <button onClick={handleSignout}>Signout</button>
    </>
  );
}

كتابة Custom Hooks

تسمح الخطافات المخصصة بإعادة استخدام السلوك البرمجي بين المكونات، تماماً كما تسمح المكونات بإعادة استخدام بنية الواجهة.

import React from "react";

export default function useWindowSize() {
  const isSSR = typeof window !== "undefined";

  const [windowSize, setWindowSize] = React.useState({
    width: isSSR ? 1200 : window.innerWidth,
    height: isSSR ? 800 : window.innerHeight,
  });

  function changeWindowSize() {
    setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight,
    });
  }

  React.useEffect(() => {
    window.addEventListener("resize", changeWindowSize);
    return () => {
      window.removeEventListener("resize", changeWindowSize);
    };
  }, []);

  return windowSize;
}
import React from "react";
import useWindowSize from "../utils/useWindowSize";

function Header() {
  const { width } = useWindowSize();

  return (
    <div>
      {width > 500 && <>Greater than 500px!</>}
      <p>I'm always visible</p>
    </div>
  );
}

قواعد استخدام Hooks

حتى تعمل Hooks بشكل صحيح، يجب الالتزام بقواعد أساسية لا ينبغي تجاوزها:

  • استخدم Hooks فقط داخل المكونات الدالية أو داخل Custom Hooks.
  • استدعِ Hooks في أعلى المكوّن، وليس داخل الشروط أو الحلقات أو الدوال المتداخلة.

نصائح عملية لتعلّم React بسرعة

  • ابدأ بفهم JSX والمكونات قبل الانتقال إلى الخطافات المتقدمة.
  • طبّق كل مفهوم في مشروع صغير بدل الاكتفاء بالقراءة النظرية.
  • فرّق جيداً بين props وstate لأن هذا من أهم أسس الفهم.
  • استخدم useEffect بحذر، ولا تضف أي اعتماد دون فهم تأثيره.
  • لا تلجأ إلى تحسين الأداء مبكراً إلا عند وجود سبب فعلي مثل بطء واضح أو إعادة تصيير زائدة.

دورة تعليمية متقدمة لتطوير تطبيقات React مع أمثلة عملية وشرح احترافي

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

تمثل React خياراً قوياً لبناء واجهات حديثة مرنة وقابلة للتوسع. وإذا أتقنت مفاهيم مثل JSX والمكونات وprops وstate، ثم انتقلت إلى Hooks مثل useState وuseEffect وuseContext، فستمتلك أساساً متيناً لتطوير تطبيقات احترافية. أما تحسين الأداء باستخدام React.memo وuseCallback وuseMemo، فهو مرحلة ترفع جودة التطبيق عندما تُستخدم في مواضعها الصحيحة، لا لمجرد الإضافة. باختصار: الفهم الجيد للتدفق المنطقي للبيانات داخل React أهم من حفظ الدوال، لأنه ما يصنع الفارق الحقيقي في المشاريع الواقعية.

اترك تعليقاً

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