1359 words
7 minutes
Virtual DOM in React

إزاي React بتخلي مواقعك سريعة: القصة الكاملة للـ Virtual DOM 🚀#

مقدمة#

كلنا بنحب المواقع السريعة والـ smooth، صح؟ النهاردة هنتكلم عن السر اللي بيخلي المواقع المشهورة سريعة كده. السر في الموضوع ده كله موجود في حاجة اسمها DOM.

يعني إيه DOM أصلاً؟#

الـ DOM (Document Object Model) ده زي شجرة بتمثل الصفحة بتاعتك. كل <div>، كل <p>، كل عنصر في الصفحة بيبقى branch في الشجرة دي.

المشكلة القديمة 🤔#

زمان، وإحنا بنستخدم JavaScript العادي، كنا بنستخدم حاجات زي:

document.getElementById('myElement')
document.removeChild()

المشكلة إن الطريقة دي كانت بتخلي المواقع بطيئة، خصوصاً لما الموقع يكبر ويبقى فيه عناصر كتير.

إزاي React حلت المشكلة؟ 💡#

React جابت فكرة جامدة اسمها Virtual DOM. تخيل إنه نسخة خفيفة من الـ DOM الحقيقي، بتتخزن في الـ memory.

الفكرة بتشتغل إزاي؟#

  1. لما بتعمل تغيير في الـ state
  2. React بتعمل نسخة جديدة من الـ Virtual DOM
  3. بتقارن النسخة القديمة بالجديدة (Diffing)
  4. بتحدث بس الحاجات اللي اتغيرت في الـ DOM الحقيقي

الـ Diffing Algorithm: القلب النابض ⚡#

React عندها algorithm ذكي جداً بيشوف إيه اللي اتغير بالظبط. خلينا ناخد أمثلة:

مثال 1: لو غيرنا نوع العنصر#

// قبل التغيير
<div>
  <MyComponent />
</div>

// بعد التغيير
<span>
  <MyComponent />
</span>

هنا React هتعمل rebuild للـ tree كله عشان الـ root element اتغير.

مثال 2: لو غيرنا attribute#

// قبل
<span id="before" />

// بعد
<span id="after" />

هنا React هتغير بس الـ id.

سر الـ Keys في React 🔑#

الـ Keys مهمة جداً في الـ lists. بتساعد React تعرف مين اتغير ومين لأ:

<ul>
  <li key="1">محمد</li>
  <li key="2">أحمد</li>
  <li key="3">علي</li>
</ul>

التعامل المباشر مع الـ DOM باستخدام Refs 🎯#

ساعات بنحتاج نتعامل مع الـ DOM مباشرة (زي لما نعمل focus على input). React وفرت لنا useRef:

function MyComponent() {
  const myRef = useRef(null);

  const handleClick = () => {
    myRef.current.focus();
  };

  return (
    <input ref={myRef} type="text" />
  );
}

نصائح مهمة للأداء الأفضل 🚀#

  1. خلي الـ Keys ثابتة: متستخدمش index كـ key في الـ lists
  2. قلل الـ DOM Updates: حاول تجمع التغييرات مع بعض
  3. استخدم React.memo: للـ components اللي مش بتتغير كتير

شرح React Fiber & Diffing بالتفصيل 🚀#

١. ايه هو Fiber؟#

Fiber هو إعادة تصميم كاملة للCore Architecture بتاع React. الهدف الأساسي منه هو تحسين الأداء وتقسيم عملية الRendering لـchunks.

المكونات الأساسية للFiber:#

// مثال لهيكل الFiber Node
interface FiberNode {
  // نوع العنصر
  type: any,             
  // Reference للـDOM
  stateNode: any,        
  // الابن الأول
  child: Fiber|null,     
  // العنصر التالي
  sibling: Fiber|null,   
  // العنصر الأب
  return: Fiber|null,    
  // الProps
  pendingProps: any,     
  // الProps الحالية
  memoizedProps: any,    
  // الState
  memoizedState: any     
}

٢. ازاي بيشتغل؟#

أ. عملية الReconciliation:#

// مثال لمكون React بسيط
function WelcomeCard({ name }) {
  const [isExpanded, setIsExpanded] = useState(false);
  
  return (
    <div className="card">
      <h1>أهلا {name}</h1>
      {isExpanded && (
        <p>معلومات إضافية</p>
      )}
      <button onClick={() => setIsExpanded(!isExpanded)}>
        {isExpanded ? 'أقل' : 'المزيد'}
      </button>
    </div>
  );
}

ب. خطوات الDiffing:#

  1. مقارنة النوع:
// كود توضيحي لعملية المقارنة
function compareElements(oldElement, newElement) {
  // نفس النوع - هنعمل update بس
  if (oldElement.type === newElement.type) {
    updateProperties(oldElement, newElement.props);
    return;
  }
  
  // نوع مختلف - هنشيل القديم ونحط الجديد
  replaceElement(oldElement, newElement);
}
  1. مقارنة الProps:
// مثال لمقارنة الProps
function updateProperties(element, newProps) {
  const oldProps = element.props;
  
  // شيل الprops القديمة
  for (let key in oldProps) {
    if (!(key in newProps)) {
      // احذف الprop
      element[key] = null;
    }
  }
  
  // حط الprops الجديدة
  for (let key in newProps) {
    if (oldProps[key] !== newProps[key]) {
      // تحديث الprop
      element[key] = newProps[key];
    }
  }
}

٣. Priority Levels#

React Fiber بيقسم الشغل لـpriority levels مختلفة:

const PriorityLevels = {
  // للتحديثات العاجلة زي الanimation
  ImmediatePriority: 1,
  // للتفاعلات المباشرة زي الclick
  UserBlockingPriority: 2,
  // للتحديثات العادية
  NormalPriority: 3,
  // للتحديثات اللي ممكن تتأخر
  LowPriority: 4,
  // للتحديثات اللي ممكن تتعمل في أي وقت
  IdlePriority: 5
};

٤. مثال عملي للفرق#

قبل Fiber:#

// كل التحديثات كانت بتتعمل مرة واحدة
class OldComponent extends React.Component {
  componentWillUpdate() {
    // كل التحديثات في نفس الوقت
    this.heavyOperation();
    this.updateUI();
    this.loadData();
  }
}

بعد Fiber:#

// التحديثات بتتقسم على مراحل
function NewComponent() {
  useEffect(() => {
    // تحديثات عالية الأولوية
    requestAnimationFrame(() => {
      updateUI();
    });
    
    // تحديثات عادية
    setTimeout(() => {
      heavyOperation();
    }, 0);
    
    // تحديثات منخفضة الأولوية
    requestIdleCallback(() => {
      loadData();
    });
  }, []);
}

شرح الـ Virtual DOM بالتفصيل 🔍#

١. الهيكل الأساسي للـ Virtual DOM#

أولاً، Virtual DOM عبارة عن تمثيل للـ DOM الحقيقي في الميموري:

// مثال لهيكل بسيط للـ Virtual DOM node
class VirtualNode {
  constructor(type, props, children) {
    this.type = type;         // نوع العنصر (div, span, p, etc.)
    this.props = props;       // الخصائص
    this.children = children; // العناصر الفرعية
    this.key = props.key;     // مفتاح خاص للمقارنة
  }
}

// مثال لإنشاء Virtual DOM
function createElement(type, props, ...children) {
  return new VirtualNode(
    type,
    props || {},
    children.flat()
  );
}

٢. عملية الـ Rendering#

أ. إنشاء Virtual DOM الأولي:#

// مثال لمكون React
function App() {
  return (
    <div className="app">
      <h1>مرحباً</h1>
      <p>هذا مثال</p>
    </div>
  );
}

// يتحول داخلياً إلى:
const vdom = createElement('div', { className: 'app' }, [
  createElement('h1', {}, ['مرحباً']),
  createElement('p', {}, ['هذا مثال'])
]);

ب. إنشاء DOM الحقيقي:#

function createDOMElement(vnode) {
  // إذا كان نص
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode);
  }

  // إنشاء العنصر
  const element = document.createElement(vnode.type);
  
  // إضافة الخصائص
  Object.entries(vnode.props).forEach(([name, value]) => {
    if (name !== 'children') {
      element.setAttribute(name, value);
    }
  });
  
  // إضافة العناصر الفرعية بشكل متكرر
  vnode.children.forEach(child => {
    element.appendChild(createDOMElement(child));
  });
  
  return element;
}

٣. عملية الـ Diffing (المقارنة)#

أ. المقارنة الأساسية:#

function diff(oldVNode, newVNode) {
  // إذا كان أحدهما فارغ
  if (!oldVNode) {
    return { type: 'CREATE', node: newVNode };
  }

  if (!newVNode) {
    return { type: 'REMOVE' };
  }

  if (changed(oldVNode, newVNode)) {
    return { type: 'REPLACE', node: newVNode };
  }

  if (oldVNode.type === newVNode.type) {
    const patches = diffChildren(oldVNode.children, newVNode.children);
    return { type: 'UPDATE', patches, props: diffProps(oldVNode.props, newVNode.props) };
  }
}

ب. مقارنة الـ Props:#

function diffProps(oldProps, newProps) {
  const patches = [];

  // إضافة/تحديث الخصائص
  Object.entries(newProps).forEach(([key, value]) => {
    if (oldProps[key] !== value) {
      patches.push({ type: 'SET_PROP', key, value });
    }
  });

  // حذف الخصائص القديمة
  Object.keys(oldProps).forEach(key => {
    if (!(key in newProps)) {
      patches.push({ type: 'REMOVE_PROP', key });
    }
  });

  return patches;
}

ج. مقارنة العناصر الفرعية:#

function diffChildren(oldChildren, newChildren) {
  const patches = [];
  const maxLength = Math.max(oldChildren.length, newChildren.length);

  for (let i = 0; i < maxLength; i++) {
    patches[i] = diff(oldChildren[i], newChildren[i]);
  }

  return patches;
}

٤. عملية التحديث (Patching)#

function patch(dom, patches) {
  if (!patches) return dom;

  switch (patches.type) {
    case 'CREATE': {
      return createDOMElement(patches.node);
    }
    
    case 'REMOVE': {
      dom.parentNode.removeChild(dom);
      return undefined;
    }
    
    case 'REPLACE': {
      const newDOM = createDOMElement(patches.node);
      dom.parentNode.replaceChild(newDOM, dom);
      return newDOM;
    }
    
    case 'UPDATE': {
      // تحديث الخصائص
      patches.props.forEach(propPatch => {
        patchProp(dom, propPatch);
      });
      
      // تحديث العناصر الفرعية
      patches.patches.forEach((childPatch, i) => {
        patch(dom.childNodes[i], childPatch);
      });
      
      return dom;
    }
  }
}

٥. مثال عملي كامل#

// مكون React
function TodoList({ items }) {
  return (
    <ul className="todo-list">
      {items.map(item => (
        <li key={item.id}>
          {item.text}
        </li>
      ))}
    </ul>
  );
}

// خطوات التحديث
const oldVDOM = createElement('ul', { className: 'todo-list' }, [
  createElement('li', { key: 1 }, ['اشرب قهوة']),
  createElement('li', { key: 2 }, ['اكتب كود'])
]);

const newVDOM = createElement('ul', { className: 'todo-list' }, [
  createElement('li', { key: 1 }, ['اشرب قهوة']),
  createElement('li', { key: 2 }, ['اكتب كود']),
  createElement('li', { key: 3 }, ['نام كويس']) // عنصر جديد
]);

// ١. حساب الفروق
const patches = diff(oldVDOM, newVDOM);

// ٢. تطبيق التحديثات
const dom = document.querySelector('.todo-list');
patch(dom, patches);

٦. تحسينات الأداء#

أ. استخدام Keys:#

function diffChildren(oldChildren, newChildren) {
  const patches = [];
  const keyed = {};
  
  // تجميع العناصر القديمة بالـ key
  oldChildren.forEach(child => {
    if (child.key) {
      keyed[child.key] = child;
    }
  });
  
  // مقارنة باستخدام الـ keys
  newChildren.forEach((newChild, i) => {
    if (newChild.key) {
      const oldChild = keyed[newChild.key];
      if (oldChild) {
        patches[i] = diff(oldChild, newChild);
      } else {
        patches[i] = { type: 'CREATE', node: newChild };
      }
    }
  });
  
  return patches;
}

ب. تجنب المقارنات غير الضرورية:#

function shouldComponentUpdate(oldProps, newProps) {
  // مقارنة سريعة للـ props
  return !shallowEqual(oldProps, newProps);
}

function shallowEqual(obj1, obj2) {
  if (obj1 === obj2) return true;
  
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  
  if (keys1.length !== keys2.length) return false;
  
  return keys1.every(key => obj1[key] === obj2[key]);
}

٧. الخلاصة والنصائح#

  1. دايماً استخدم key للـ lists:
function List({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.text}</li> // مهم جداً
      ))}
    </ul>
  );
}
  1. تجنب التغييرات غير الضرورية:
// مش كويس
function BadComponent() {
  return <div style={{ width: '100%' }} />;
}

// كويس
const styles = { width: '100%' };
function GoodComponent() {
  return <div style={styles} />;
}
  1. استخدم React.memo للمكونات الثابتة:
const PureComponent = React.memo(function({ text }) {
  return <div>{text}</div>;
});

الخلاصة#

  • Fiber بيخلي React أذكى في التعامل مع التحديثات
  • بيقدر يقسم الشغل ويديله أولويات
  • بيحسن أداء التطبيق خصوصاً في الحاجات المعقدة
  • مع Concurrent Mode, بنقدر نتحكم أكتر في الRendering

Join our whatsapp group here
My Channel here